Compare commits

...

358 Commits

Author SHA1 Message Date
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
Andy Miller
5435ee60d8 Merge branch 'release/1.4.6' 2018-06-20 19:38:39 +01:00
Andy Miller
761d79272c Prepare for release 2018-06-20 19:37:15 +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
d72eca7fb5 Force Rockettheme/Toolbox to version 1.3.* in Grav 1.4 series 2018-06-20 11:11:46 +03:00
Matias Griese
ca9dba1372 Move wrongly placed items in changelog to the latest release 2018-06-20 10:57:57 +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
Andy Miller
34cc3781d6 Merge branch 'release/1.4.6' 2018-06-19 18:42:55 +01:00
Andy Miller
4eb986643c Merge tag '1.4.6' into develop
Release v1.4.6
2018-06-19 18:42:55 +01:00
Andy Miller
290e5be534 Prepare for release 2018-06-19 18:42:40 +01:00
Andy Miller
aea26f4db9 Updated changelog 2018-06-19 18:38:48 +01:00
Matias Griese
bf5e742a7f Revert Medium object cloning for now (has unexpected side effects) [#2045] 2018-06-15 09:51:42 +03: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
bd21b7f966 Fixed image resource memory deallocation (part 2) [#2045] 2018-06-15 09:17:50 +03:00
Matias Griese
021fbb8ecd Merge remote-tracking branch 'origin/develop' into develop 2018-06-15 09:03:57 +03:00
Matias Griese
6d7e9ba107 Fixed image resource memory deallocation [#2045] 2018-06-15 09:03:51 +03:00
Matias Griese
bbfbdec483 Added setting to disable sessions from the site [#2013] 2018-06-14 19:23:35 +03:00
Andy Miller
62a32ab5c5 vendor updates 2018-06-14 16:00:31 +01:00
Matias Griese
2e3a64fcef Fixed incorrect routing with setup.php based base [#1892] 2018-06-14 14:55:08 +03:00
Matias Griese
ecdbff68d8 Fixed crash on IIS (Windows) with open_basedir in effect [#2053] 2018-06-14 14:31:29 +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
Jean-Baptiste Alleaume
036fc2d2af Modular template extension follows the master page extension (#2044) 2018-06-08 22:00:07 +00:00
Hydraner
ab58cca3f7 Add getter methods for original and action to the Page object. (#2005) 2018-06-08 21:37:44 +00:00
Jeremy Bouquain
f883820c6a Handle multibyte strings in truncateLetters (#2007) 2018-06-08 21:37:06 +00:00
Lauri Nurmi
9053f9ab44 Use @example.com in default email addresses. (#2020)
It could be a bad idea to use default email addresses that could really
exist. Instead, use example.com, which RFC2606 defines as one of the
domains reserved for examples.
2018-06-08 21:36:38 +00:00
Felipe Fonseca
bd7706a38e pointing fastcgi_pass path to match php7.0-fpm (on debian stretch) (#2021) 2018-06-08 21:36:11 +00:00
Tuukka Norri
da7a93527d Fix image memory use (#2045)
* Add a method for freeing the image file, free each derivative after it has been created

* Save the scaled derivative on disk before freeing it, store its path
2018-06-08 21:33:40 +00: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
Andy Miller
3b4296c7a4 fix for URI’s with no authority // eg mailto:blah@blah.com 2018-05-31 17:48:48 -06:00
Andy Miller
8e065e1109 Fix classes on non-http based protocol links #2034 2018-05-31 14:28:48 -06:00
Andy Miller
93f3fa9685 Updated changelog 2018-05-31 14:08:01 -06:00
Andy Miller
27a9390ec7 Re-added SSL off-loading that was lost with Grav v1.4.0 merge #1888 2018-05-31 14:05:33 -06: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
Ante Drnasin
9eded2ef39 Update robots.txt (#2043)
When using "Fetch and Render as Google" in Google Search console it will report "partial" rendering due to the blocked images in /user/images directory which is blocked because of the Disallow /user/ rule. Proposing this small change as it improves google rendering of the page.
2018-05-30 11:47:21 -06:00
Andy Miller
636bc97d29 handle errors.display properly admin#1452 2018-05-29 09:26:52 -06: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
03a0c42795 Merge branch 'release/1.4.5' 2018-05-15 12:55:39 -06:00
Andy Miller
1976471982 Merge tag '1.4.5' into develop
Release v1.4.5
2018-05-15 12:55:39 -06:00
Andy Miller
1426a7ec95 prepare for release 2018-05-15 12:55:27 -06:00
Andy Miller
2a759eed74 updated changelog 2018-05-15 11:46:54 -06:00
Andy Miller
8980b78220 Revert "Add MIME type to video <source> tag (#1992)"
This reverts commit e60fd82400.
2018-05-15 11:45:36 -06:00
Andy Miller
12b0a839e7 Merge branch 'develop' of github.com:getgrav/grav into develop 2018-05-15 11:43:42 -06:00
Andy Miller
b4d570fd21 updated changelog 2018-05-15 11:43:37 -06:00
Christian Weiske
e60fd82400 Add MIME type to video <source> tag (#1992) 2018-05-15 11:39:50 -06:00
Davide Liessi
a1abcfd067 Move fix for #1114 to Truncator::truncateLetters (#2004)
* Move fix for #1114 to Truncator::truncateLetters

The original fix provided by #1125 in Utils::truncateHtml compared the
summary size with the full HTML string length.
Thus, the string was still being truncated (and the HTML rewritten) even
when only the HTML string length, and not the text length, exceeded the
summary size.

* Add fix for #1114 also to Truncator::truncateWords
2018-05-15 11:38:48 -06:00
Christian Weiske
7f90ad8474 Do not crash when generating URLs with arrays as parameters (#2018)
When using a default image derivatives (#1979) setting like

  images:
    defaults:
      derivatives: [300,600]

then Grav crashes when a video gets embedded.
The following error occurs:

  rawurlencode() expects parameter 1 to be string, array given
  system/src/Grav/Common/Page/Medium/Medium.php:521

This patch encodes array URL parameters correctly.
2018-05-15 11:37:18 -06:00
Andy Miller
e1d52181a3 updated changelog 2018-05-15 11:36:20 -06:00
Matias Griese
d4494cb502 Fixed users getting logged out after Grav upgrade 2018-05-15 16:26:11 +03: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
Andy Miller
2f17b3fa7d Revert "Add special handling for User authenticated and authorized properties"
This reverts commit 8e0e3e8718.
2018-05-14 18:25:19 -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
afe72d0783 Merge remote-tracking branch 'origin/develop' into develop 2018-05-14 21:24:03 +03:00
Matias Griese
8e0e3e8718 Add special handling for User authenticated and authorized properties 2018-05-14 21:23:56 +03:00
Matias Griese
5ab956a8ec Fixed Route::withQueryParam() to accept array values 2018-05-14 12:30:30 +03:00
Andy Miller
a17554c951 Merge branch 'release/1.4.4' 2018-05-11 16:07:38 -06:00
Andy Miller
30ff986603 Merge tag '1.4.4' into develop
Releaase v1.4.4
2018-05-11 16:07:38 -06:00
Andy Miller
18f46d902d Prepare for release 2018-05-11 16:07:27 -06:00
Andy Miller
3948ed5618 updated changelog 2018-05-11 16:06:54 -06:00
Matias Griese
e6dd91f698 Added authorized support (2FA)
(cherry picked from commit 2c82e15)
2018-05-10 19:58:39 +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
Dmitry Yakovlev
40b475ecb9 Fix hardcoded string (#1993)
Fix hard-coded string "Modular Setup" to improve internationalization. Needs new string PLUGIN_ADMIN.MODULAR_SETUP in Admin plugin language file.
2018-04-28 11:29:28 -06:00
Gabriel Caruso
4ab0a601ad Use array_key_exists instead of in_array + array_keys (#1991) 2018-04-28 11:25:41 -06:00
Gabriel Caruso
40ba5e9106 Use dedicated PHPUnit assertions (#1990)
Signed-off-by: Gabriel Caruso <carusogabriel34@gmail.com>
2018-04-28 11:24:43 -06:00
Christian Weiske
d4ec2a25d4 Add default configuration for images (#1979)
This patch allows configuring image processing instructions
for all images.
A use case is generating a fixed set of sizes (derivatives) for
all images, without having to specify them in the content markdown.

Example configuration in user/config/system.yaml:

images:
  defaults:
    derivatives: "[300,600,1200]"
2018-04-28 11:24:14 -06: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
Andy Miller
027dbcf6fa Added Uri::post() and Uri::getConentType() 2018-04-27 15:40:03 -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
Andy Miller
692aff3c89 SORT_REGULAR for dates #1910 2018-04-18 18:37:34 -06:00
Andy Miller
3091a14223 Improve support for regex redirects with query and params #1983 2018-04-18 15:02:53 -06:00
Andy Miller
a49f3d460e allow type to be passed 2018-04-18 10:43:27 -06:00
Andy Miller
b6e0f8b75a Added a Medium::thumbnailExists() method 2018-04-17 23:29:23 -06:00
Andy Miller
ac8a1191f8 Fix for custom_base_url not routing properly 2018-04-17 19:04:16 -06: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
33cfa17899 Fix couple of deprecated extend@s 2018-04-14 13:00:40 +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
Andy Miller
4db2b75699 Merge tag '1.4.3' into develop
Release v1.4.3
2018-04-12 15:54:26 -06:00
Andy Miller
c64590af16 Merge branch 'release/1.4.3' 2018-04-12 15:54:25 -06:00
Andy Miller
3607124e55 Prepare for release 2018-04-12 15:54:16 -06:00
Andy Miller
e7405a13fc Revert "Test fixes for session/parsedown changes, jquery update, quark default theme"
This reverts commit cad10b6095.

# Conflicts:
#	CHANGELOG.md
2018-04-12 15:28:53 -06:00
Andy Miller
46f16ce4db Note about updated vendor libs 2018-04-12 15:26:26 -06:00
Andy Miller
d3a4466d9b Updated changelog 2018-04-12 15:25:29 -06:00
Andy Miller
46d682f889 Revert "Fix for audio/video parsedown #1924"
This reverts commit 33cfa5e104.

# Conflicts:
#	CHANGELOG.md
2018-04-12 15:25:19 -06:00
Matias Griese
2b17bf70de Merge branch 'develop' of https://github.com/getgrav/grav into feature/session 2018-04-10 09:47:59 +03:00
Andy Miller
8e019b7958 Updated changelog 2018-04-09 14:38:46 -06:00
Andy Miller
47037e3f5e moved sortArrayByKey logic into Utils:: 2018-04-09 14:35:44 -06:00
Andy Miller
1ec653268d Parsedown 1.8 breaks things, need to fix when final is released 2018-04-09 12:00:07 -06:00
Andy Miller
3ee140e77f Revert "Temporarily using parsedown dev-master to address HTML encoding issues #1921"
This reverts commit ff2df04a58.
2018-04-09 11:58:52 -06:00
Rex Kelly
cb490a1762 Add overriding of setup.php config file, via constants (#1945)
I'm working on a custom install of Grav, where being able to dynamically swap setup files was required. I've modified the $file assignment to permit overriding it's value via a defined constant, "GRAV_SETUP_PATH".

And I thought that might be useful to others, so I'm submitting a pull request.
2018-04-09 11:35:49 -06:00
Raul E Watson
6d8ba5ed4d Update CHANGELOG.md (#1958)
Fixed small typos in changelog
2018-04-09 11:34:56 -06:00
michielStaes
7020130511 Readme.md was missing info regarding the 'composer test-windows' command (#1970) 2018-04-09 11:34:36 -06:00
Andy Miller
ff2df04a58 Temporarily using parsedown dev-master to address HTML encoding issues #1921 2018-04-02 18:04:40 -06:00
Djamil Legato
3248b97997 Reverted zip changes while adding extra details about unzip failure 2018-03-29 13:07:11 -07:00
Djamil Legato
3c26d831fd Refactored Installer (zip) change to maintain consistency in the errorCode 2018-03-29 12:15:34 -07:00
Andy Miller
f4e584cda1 Fix for bad ZipArchive references 2018-03-24 13:49:56 -06:00
Andy Miller
df7e9b9c1f Merge tag '1.4.2' into develop
Release v1.4.2
2018-03-21 11:49:38 -06:00
Andy Miller
1108d063ef Merge branch 'release/1.4.2' 2018-03-21 11:49:37 -06:00
Andy Miller
a6306aae05 Prepare for release 2018-03-21 11:49:28 -06:00
Andy Miller
fdf79caf71 updated changelog 2018-03-21 11:19:47 -06: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
Matias Griese
df185621ad Composer update (Add missing PSR-7 requirements) 2018-03-21 11:29:26 +02:00
Andy Miller
301429d992 Merge branch 'develop' of github.com:getgrav/grav into develop 2018-03-20 10:33:32 -06:00
Andy Miller
07db8a2f9d Updated composer binary 2018-03-20 10:33:26 -06:00
Matias Griese
3a207843c7 Merge branch 'develop' of https://github.com/getgrav/grav into feature/1.4 2018-03-20 09:22:19 +02:00
Andy Miller
8f1639c10b case for case sensitive metadata grav-admin-plugin#1370 2018-03-18 21:37:17 -06:00
Andy Miller
b515a5add0 updated changelog 2018-03-18 12:57:40 -06:00
Andy Miller
33cfa5e104 Fix for audio/video parsedown #1924 2018-03-18 12:55:20 -06:00
Mujib Azizi
83b85e2cac Re-add .gitkeep to keep the backup folder. (#1913) 2018-03-18 11:59:19 -06:00
Mathieu Santostefano
861eb43efa Remove duplicate entry (#1916) 2018-03-18 11:58:40 -06:00
Al-Demon
a20d6d7230 Update LanguageCodes.php (#1917)
Add Bosnian LanguageCode
2018-03-18 11:57:16 -06:00
Andy Miller
40e7ee79b4 Added more detailed error handling (#1922) 2018-03-18 11:56:59 -06:00
Paul Massendari
dce97221c5 Small typo in default page (#1923) 2018-03-16 12:37:08 -07:00
Matias Griese
fea02736c5 Changelog: fixed typo 2018-03-15 10:45:37 +02:00
Andy Miller
d58ad3749d Fixed a test 2018-03-13 16:17:46 -06:00
Andy Miller
727f759b41 Added some twig stuff 2018-03-13 16:10:12 -06:00
Andy Miller
f42d59409b updaetd clean command with some minor changes 2018-03-11 16:28:50 -06:00
Andy Miller
4adaead4ec Merge branch 'release/1.4.1' 2018-03-11 16:13:03 -06:00
Andy Miller
8dbe248df1 Merge tag '1.4.1' into develop
Release v1.4.1
2018-03-11 16:13:03 -06:00
Andy Miller
0e26422613 Prepare for release 2018-03-11 16:12:55 -06:00
Matias Griese
234555b208 Fixed session timing out because of session cookie was not being sent 2018-03-11 21:52:49 +02:00
Andy Miller
3cfc3f1cbe Merge tag '1.4.0' into develop
Release v1.4.0
2018-03-09 13:03:06 -07:00
Andy Miller
9b149307ef Merge branch 'release/1.4.0' 2018-03-09 13:03:05 -07:00
Andy Miller
4c1d1dd5d0 Prepare for release 2018-03-09 13:02:50 -07:00
Andy Miller
cad10b6095 Test fixes for session/parsedown changes, jquery update, quark default theme 2018-03-09 12:45:27 -07:00
Andy Miller
7aa930ca55 Merge branch '1.4' into develop
# Conflicts:
#	system/src/Grav/Common/Twig/TwigExtension.php
#	system/src/Grav/Common/Uri.php
#	tests/unit/Grav/Common/AssetsTest.php
2018-03-09 12:43:08 -07:00
Matias Griese
7e1c6b6137 Do not initialize session in CLI and unit tests 2018-03-09 21:07:44 +02:00
Andy Miller
d008376169 Updated parsedown + toolbox 2018-03-08 17:08:20 -07:00
Andy Miller
eaac77881f Force clearing PHP clearstatcache and opcache-reset on Cache::clear() 2018-03-08 14:52:49 -07:00
Andy Miller
43ca0a2f58 vendor updates 2018-03-07 11:57:55 -07:00
Andy Miller
54cd7f85b8 theme_var() twig function now checks page header var first 2018-03-02 15:39:59 -06:00
Andy Miller
5ffe32ef58 Fix alt tag issue #1883 2018-03-01 15:30:51 -06:00
Andy Miller
b0ad83ebf7 support both alt and alt_text 2018-03-01 15:13:42 -06:00
mahagr
d5060a2012 Framework\Route::getRoute() return relative path only if offset isn't 0 2018-02-25 17:38:55 -06:00
mahagr
bf16e2e854 Framework\Route::getRoute() with parameters should return relative path 2018-02-25 17:37:23 -06:00
mahagr
ec78319993 Add Framework\Route::getRoute() parameters 2018-02-25 17:35:01 -06:00
mahagr
67b5649ee4 Add Framework\Route::getRouteParts() function 2018-02-25 17:29:29 -06:00
CSixtyFour
eca23a918a Fix for #1827 (#1888)
Modified Build scheme and Build port functions to take into account those in SSL offloading situations.
2018-02-24 11:00:26 -07:00
Andy Miller
5b291f8f54 Added Route overrides 2018-02-22 16:14:24 -07:00
Matias Griese
274c701907 Improved Object serialize()/unserialize() 2018-02-22 09:00:41 +02:00
Andy Miller
e533024b2e Added {% switch %} twig tag 2018-02-21 23:00:37 -07:00
Andy Miller
8750602f76 Added new {% markdown %} twig tag 2018-02-21 17:28:43 -07:00
Todd Coleman
51a9b9b7c7 Taxonomy Filtering - space characters (#1880)
Taxonomies with spaces were not recognized because they are encoded in the uri.
2018-02-21 10:43:20 -07:00
Matias Griese
42d8836569 Improved Session initialization (#1879) 2018-02-21 10:42:09 -07:00
Matias Griese
7cfc6fbdb8 Merge remote-tracking branch 'origin/1.4' into 1.4 2018-02-21 12:53:48 +02:00
Matias Griese
01886b6df9 Added better session checks, warn in debugbar if session messages cannot be used 2018-02-21 12:53:40 +02:00
Andy Miller
b7d4697db8 changelog updated 2018-02-20 17:30:17 -07:00
Matias Griese
742c6f9baa Fixed issue with UriFactory::parseQuery() 2018-02-20 12:00:17 +02:00
Matias Griese
c97da77153 Merge branch '1.4' of https://github.com/getgrav/grav into 1.4 2018-02-20 11:07:36 +02:00
Matias Griese
871848dc28 Added Grav\Framework\Route classes to allow route/link manipulation
Added `$grav['uri]->getCurrentRoute()` method to get `Grav\Framework\Route\Route` instance for the current URL
2018-02-20 11:07:28 +02:00
Andy Miller
84a0b5d1e1 cleanup with default value 2018-02-19 21:23:11 -07:00
Andy Miller
8e68317e7c Composer updates 2018-02-19 11:48:38 -07:00
Matias Griese
35f7a2a9de Added $grav['uri]->getCurrentUri() function to get Grav\Framework\Uri instance for the current URL. 2018-02-19 11:10:37 +02:00
Matias Griese
62dfa0e060 Reorder functions in Common\Uri class 2018-02-19 10:30:16 +02:00
Andy Miller
2e28461435 Updated changelog 2018-02-18 14:08:23 -07:00
Andy Miller
4259d01638 Fix bug with remote minimum PHP version determination 2018-02-18 14:07:35 -07:00
Matias Griese
39783df92d Fixed bug with absolute_urls=true 2018-02-17 14:35:02 +02:00
Matias Griese
d39253e36f Moved URI parts filtering functions to Framework\UriPartsFilter class 2018-02-17 14:28:26 +02:00
Matias Griese
82868037ec Some code cleanup on ParsedownGravTrait and Grav classes 2018-02-17 13:03:56 +02:00
Matias Griese
17ba58a5c2 Framework\Uri: Improve query parameter handling 2018-02-17 13:00:40 +02:00
Matias Griese
0a79788582 Improve Uri class by adding useful tests 2018-02-17 12:23:45 +02:00
Andy Miller
ccf0f9c74e Added support for php dependencies in GPM blueprints 2018-02-16 18:16:07 -07:00
Matias Griese
79e580a79d Added Grav\Framework\Uri classes extending PSR-7 HTTP message UriInterface implementation 2018-02-17 00:18:21 +02:00
Andy Miller
5b9a40de5f Prepare for release 2018-02-15 15:53:36 -07:00
Andy Miller
550468aefb Added checkbox-style support for switch field 2018-02-15 14:51:31 -07:00
Andy Miller
84a2a08d22 Fixed YAML blueprint valdiation/filtering 2018-02-14 22:29:41 -07:00
Matias Griese
ee8ea5af57 Clean up Utils class with minor fixes 2018-02-09 10:44:32 +02:00
Andy Miller
2f763809ac fixed twig evaluate function 2018-02-06 09:44:00 -07:00
Andy Miller
f91678df4a Fix for invalid template error in evaluate() 2018-02-06 09:42:13 -07:00
Andy Miller
4c654ef19c Stopped Chrome from auto-completing admin user profile form #1847 2018-02-05 13:07:51 -07:00
Andy Miller
637308d294 Fix for opengraph tags #1849 2018-02-05 11:59:14 -07:00
Andy Miller
e2ce65dc61 cast to array 2018-02-03 14:26:23 -07:00
Andy Miller
f6061e96e8 fix for assets change 2018-02-03 14:19:30 -07:00
Gabriel Caruso
8c0134eb64 Test against PHP 7.2 (#1835) 2018-02-03 14:05:48 -07:00
Mateusz Kutyba
18c6bd6e23 Remove the default type attribute from script tags (#1823) 2018-02-03 14:05:27 -07:00
Andy Miller
08be06c9bc Addd a new Medium:exists() method 2018-01-31 16:07:36 -07:00
Andy Miller
19ae66d0e9 updated changelog 2018-01-31 14:04:56 -07:00
Andy Miller
c3f84d8682 new read_file() twig function 2018-01-31 14:04:50 -07:00
Andy Miller
c721be8787 Added yaml encode/decode filters 2018-01-25 16:40:02 -07:00
Andy Miller
47746d3313 Added new Collection::toExtendedArray() 2018-01-25 16:37:21 -07:00
Andy Miller
103ac4b137 Improve collection filtering 2018-01-24 15:42:27 -07:00
Gabriel Caruso
f261173de2 Refactoring tests (#1779) 2018-01-19 11:15:20 -07:00
Miguel Piedrafita
9d2dea07ad Update license year (#1813) 2018-01-19 11:10:56 -07:00
Andy Miller
0695c29cf5 Bad quotes 2017-12-31 17:07:52 -08:00
Immanuel Bendahan
084a5c7fbe Bug fi (#1808) 2017-12-29 13:42:45 -07:00
128 changed files with 6786 additions and 1671 deletions

2
.gitignore vendored
View File

@@ -37,9 +37,9 @@ Thumbs.db
# phpstorm
.idea/*
user/config/security.yaml
tests/_output/*
tests/_support/_generated/*
tests/cache/*
tests/error.log
/system/templates/testing

View File

@@ -1,9 +1,9 @@
language: php
php:
- '5.5'
- '5.6'
- '7.0.21'
- '7.1'
- '7.2'
branches:
only:
- develop

View File

@@ -1,46 +1,204 @@
# v1.4.0-rc.1
## 01/22/2018
# 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
1. [](#improved)
* Manually re-added the improved SSL off-loading that was lost with Grav v1.4.0 merge [#1888](https://github.com/getgrav/grav/pull/1888)
* Handle multibyte strings in `truncateLetters()` [#2007](https://github.com/getgrav/grav/pull/2007)
* Updated robots.txt to include `/user/images/` folder [#2043](https://github.com/getgrav/grav/pull/2043)
* Add getter methods for original and action to the Page object [#2005](https://github.com/getgrav/grav/pull/2005)
* Modular template extension follows the master page extension [#2044](https://github.com/getgrav/grav/pull/2044)
* Vendor library updates
1. [](#bugfix)
* Handle `errors.display` system property better in admin plugin [admin#1452](https://github.com/getgrav/grav-plugin-admin/issues/1452)
* Fix classes on non-http based protocol links [#2034](https://github.com/getgrav/grav/issues/2034)
* Fixed crash on IIS (Windows) with open_basedir in effect [#2053](https://github.com/getgrav/grav/issues/2053)
* Fixed incorrect routing with setup.php based base [#1892](https://github.com/getgrav/grav/issues/1892)
* Fixed image resource memory deallocation [#2045](https://github.com/getgrav/grav/pull/2045)
* Fixed issue with Errors `display:` option not handling integers properly [admin#1452](https://github.com/getgrav/grav-plugin-admin/issues/1452)
# v1.4.5
## 05/15/2018
1. [](#bugfix)
* 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
1. [](#new)
* Added support for `Uri::post()` and `Uri::getConentType()`
* 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)
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
* Improve support for regex redirects with query and params [#1983](https://github.com/getgrav/grav/issues/1983)
* Changed collection-based date sorting to `SORT_REGULAR` for better server compatibility [#1910](https://github.com/getgrav/grav/issues/1910)
* Fix hardcoded string in modular blueprint [#1933](https://github.com/getgrav/grav/pull/1993)
# v1.4.3
## 04/12/2018
1. [](#new)
* moved Twig `sortArrayByKey` logic into `Utils::` class
1. [](#improved)
* Rolled back Parsedown library to stable `1.6.4` until a better solution for `1.8.0` compatibility can fe found
* Updated vendor libraries to latest versions
1. [](#bugfix)
* Fix for bad reference to `ZipArchive` in `GPM::Installer`
# v1.4.2
## 03/21/2018
1. [](#new)
* 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)
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
# v1.4.1
## 03/11/2018
1. [](#bugfix)
* Fixed session timing out because of session cookie was not being sent
# v1.4.0
## 03/09/2018
1. [](#new)
* Added `Grav\Framework\Uri` classes extending PSR-7 `HTTP message UriInterface` implementation
* Added `Grav\Framework\Route` classes to allow route/link manipulation
* Added `$grav['uri]->getCurrentUri()` method to get `Grav\Framework\Uri\Uri` instance for the current URL
* Added `$grav['uri]->getCurrentRoute()` method to get `Grav\Framework\Route\Route` instance for the current URL
* Added ability to have `php` version dependencies in GPM assets
* Added new `{% switch %}` twig tag for more elegant if statements
* Added new `{% markdown %}` twig tag
* Added **Route Overrides** to the default page blueprint
* Added new `Collection::toExtendedArray()` method that's particularly useful for Json output of data
* Added new `|yaml_encode` and `|yaml_decode` Twig filter to convert to and from YAML
* Added new `read_file()` Twig function to allow you to load and display a file in Twig (Supports streams and regular paths)
* Added a new `Medium::exists()` method to check for file existence
* Moved Twig `urlFunc()` to `Utils::url()` as its so darn handy
1. [](#improved)
* Made `modular` blueprint more flexible
* Code optimizations to `Utils` class [#1830](https://github.com/getgrav/grav/pull/1830)
* Objects: Add protected function `getElement()` to get serialized value for a single property
* `ObjectPropertyTrait`: Added protected functions `isPropertyLoaded()`, `offsetLoad()`, `offsetPrepare()` and `offsetSerialize()`
* `Grav\Framework\Cache`: Allow unlimited TTL
* Optimizations & refactoring to the test suite [#1779](https://github.com/getgrav/grav/pull/1779)
* Slight modification of Whoops error colors
* Updated vendor libs to latest
1. [](#bugfix)
* Date ordering should always be numeric [#1810](https://github.com/getgrav/grav/issues/1810)
# v1.4.0-beta.3
## 12/29/2017
1. [](#bugfix)
* Fix for base paths containing special characters [#1799](https://github.com/getgrav/grav/issues/1799)
* Fix for session cookies in paths containing special characters
* Fix for `vundefined` error for version numbers in GPM [form#222](https://github.com/getgrav/grav-plugin-form/issues/222)
1. [](#improved)
* Added new configuration option `system.session.initialize` to delay session initialization if needed by a plugin
# v1.4.0-beta.2
## 12/18/2017
1. [](#new)
* Transferred overall copyright from RocketTheme, LLC, to Trilby Media LLC
* Added `theme_var`, `header_var` and `body_class` Twig functions for themes
1. [](#improved)
* Updated vendor libraries to latest versions
* Removed constructor from `ObjectInterface`
1. [](#bugfix)
* Fixed `BadMethodCallException` thrown in GPM updates [#1784](https://github.com/getgrav/grav/issues/1784)
# v1.4.0-beta.1
## 12/11/2017
1. [](#new)
* Added `Grav\Framework\Cache` classes providing PSR-16 `Simple Cache` implementation
* Added `Grav\Framework\ContentBlock` classes for nested HTML blocks with CSS/JS assets
* Added `Grav\Framework\Object` classes for creating collections of objects
@@ -49,15 +207,38 @@
* Added `{% script %}` and `{% style %}` tags for Twig templates
* Deprecated GravTrait
1. [](#improved)
* Improved `Session` initialization
* Added ability to set a `theme_var()` option in page frontmatter
* Force clearing PHP `clearstatcache` and `opcache-reset` on `Cache::clear()`
* Better `Page.collection()` filtering support including ability to have non-published pages in collections
* Stopped Chrome from auto-completing admin user profile form [#1847](https://github.com/getgrav/grav/issues/1847)
* Support for empty `switch` field like a `checkbox`
* Made `modular` blueprint more flexible
* Code optimizations to `Utils` class [#1830](https://github.com/getgrav/grav/pull/1830)
* Objects: Add protected function `getElement()` to get serialized value for a single property
* `ObjectPropertyTrait`: Added protected functions `isPropertyLoaded()`, `offsetLoad()`, `offsetPrepare()` and `offsetSerialize()`
* `Grav\Framework\Cache`: Allow unlimited TTL
* Optimizations & refactoring to the test suite [#1779](https://github.com/getgrav/grav/pull/1779)
* Slight modification of Whoops error colors
* Added new configuration option `system.session.initialize` to delay session initialization if needed by a plugin
* Updated vendor libraries to latest versions
* Removed constructor from `ObjectInterface`
* Make it possible to include debug bar also into non-HTML responses
# v1.3.11
## mm/dd/2017
1. [](#improved)
* Updated vendor libraries to latest
* Updated built-in JQuery to latest 3.3.1
1. [](#bugfix)
* Fixed issue with image alt tag always getting empted out unless set in markdown
* Fixed issue with remote PHP version determination for Grav updates [#1883](https://github.com/getgrav/grav/issues/1883)
* Fixed issue with _illegal scheme offset_ in `Uri::convertUrl()` [page-inject#8](https://github.com/getgrav/grav-plugin-page-inject/issues/8)
* Properly validate YAML blueprint fields so admin can save as proper YAML now [addresses many issues]
* Fixed OpenGraph metatags so only Twitter uses `name=`, and all others use `property=` [#1849](https://github.com/getgrav/grav/issues/1849)
* Fixed an issue with `evaluate()` and `evaluate_twig()` Twig functions that throws invalid template error
* Fixed issue with `|sort_by_key` twig filter if the input was null or not an array
* Date ordering should always be numeric [#1810](https://github.com/getgrav/grav/issues/1810)
* Fix for base paths containing special characters [#1799](https://github.com/getgrav/grav/issues/1799)
* Fix for session cookies in paths containing special characters
* Fix for `vundefined` error for version numbers in GPM [form#222](https://github.com/getgrav/grav-plugin-form/issues/222)
* Fixed `BadMethodCallException` thrown in GPM updates [#1784](https://github.com/getgrav/grav/issues/1784)
* NOTE: Parsedown security release now escapes `&` to `&amp;` in Markdown links
# v1.3.10
## 12/06/2017

View File

@@ -18,7 +18,7 @@ The underlying architecture of Grav is designed to use well-established and _bes
# 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
@@ -183,7 +183,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.
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`

0
backup/.gitkeep Normal file
View File

Binary file not shown.

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

@@ -6,31 +6,33 @@
"homepage": "http://getgrav.org",
"license": "MIT",
"require": {
"roave/security-advisories": "dev-master",
"php": ">=5.5.9",
"php": ">=5.6.4",
"twig/twig": "~1.24",
"erusev/parsedown": "~1.6",
"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",
"filp/whoops": "~2.0",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "~1.3",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.4",
"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",
@@ -44,7 +46,7 @@
},
"config": {
"platform": {
"php": "5.5.9"
"php": "5.6.4"
}
},
"repositories": [

1035
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,9 +17,9 @@ 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>");
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

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

File diff suppressed because one or more lines are too long

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

@@ -835,6 +835,8 @@ form:
-1: PLUGIN_ADMIN.ERROR_SYSTEM
0: PLUGIN_ADMIN.ERROR_SIMPLE
1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE
validate:
type: int
errors.log:
@@ -994,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
@@ -1204,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
@@ -310,6 +313,31 @@ form:
toggleable: true
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
routes_only:
type: section
title: PLUGIN_ADMIN.ROUTE_OVERRIDES
underline: true
fields:
header.routes.default:
type: text
toggleable: true
label: PLUGIN_ADMIN.ROUTE_DEFAULT
header.routes.canonical:
type: text
toggleable: true
label: PLUGIN_ADMIN.ROUTE_CANONICAL
header.routes.aliases:
type: array
toggleable: true
value_only: true
size: large
label: PLUGIN_ADMIN.ROUTE_ALIASES
admin_only:
type: section
title: PLUGIN_ADMIN.ADMIN_SPECIFIC_OVERRIDES

View File

@@ -1,5 +1,5 @@
title: PLUGIN_ADMIN:EXTERNAL
@extends:
extends@:
type: default
context: blueprints://pages

View File

@@ -1,5 +1,5 @@
title: PLUGIN_ADMIN.MODULAR
'@extends': default
extends@: default
form:
fields:
@@ -13,7 +13,7 @@ form:
modular_title:
type: spacer
title: Modular Setup
title: PLUGIN_ADMIN.MODULAR_SETUP
header.content.items:
type: text

View File

@@ -40,6 +40,7 @@ form:
type: password
size: large
label: PLUGIN_ADMIN.PASSWORD
autocomplete: new-password
validate:
required: false
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
@@ -93,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

@@ -3,7 +3,7 @@ default_lang: en # Default language for site (potenti
author:
name: John Appleseed # Default author name
email: 'john@email.com' # Default author email
email: 'john@example.com' # Default author email
taxonomies: [category,tag] # Arbitrary list of taxonomy types

View File

@@ -25,7 +25,7 @@ home:
hide_in_urls: false # Hide the home route in URLs
pages:
theme: antimatter # Default theme (defaults to "antimatter" theme)
theme: quark # Default theme (defaults to "quark" theme)
order:
by: default # Order pages by "default", "alpha" or "date"
dir: asc # Default ordering direction, "asc" or "desc"
@@ -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.0-rc.1');
define('GRAV_TESTING', true);
define('GRAV_VERSION', '1.5.4');
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

@@ -617,7 +617,7 @@ class Assets
$inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
$attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes));
$attributes = $this->attributes($attributes);
$output = '';
$inline_js = '';

View File

@@ -429,6 +429,14 @@ class Cache extends Getters
$output[] = '';
}
// Clear stat cache
@clearstatcache();
// Clear opcache
if (function_exists('opcache_reset')) {
@opcache_reset();
}
return $output;
}

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

@@ -137,7 +137,8 @@ class Setup extends Data
// Pre-load setup.php which contains our initial configuration.
// Configuration may contain dynamic parts, which is why we need to always load it.
$file = GRAV_ROOT . '/setup.php';
// If "GRAVE_SETUP_PATH" has been defined, use it, otherwise use defaults.
$file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
$setup = is_file($file) ? (array) include $file : [];
// Add default streams defined in beginning of the class.
@@ -261,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
{
@@ -33,7 +32,7 @@ class Validation
$method = 'type'.strtr($type, '-', '_');
// If value isn't required, we will stop validation if empty value is given.
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || ($field['type'] === 'checkbox' && $value == false))) {
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))) {
return $messages;
}
@@ -41,11 +40,6 @@ class Validation
$field['type'] = 'text';
}
// If this is a YAML field, stop validation
if (isset($field['yaml']) && $field['yaml'] === true) {
return $messages;
}
// Get language class.
$language = Grav::instance()['language'];
@@ -54,6 +48,12 @@ class Validation
? $language->translate($field['validate']['message'])
: $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
// If this is a YAML field validate/filter as such
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'typeYaml';
}
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
} else {
@@ -100,15 +100,16 @@ class Validation
$field['type'] = 'text';
}
// If this is a YAML field, simply parse it and return the value.
if (isset($field['yaml']) && $field['yaml'] === true) {
return $value;
}
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
// If this is a YAML field validate/filter as such
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
if (!method_exists(__CLASS__, $method)) {
$method = 'filterText';
}
@@ -126,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;
}
@@ -639,23 +642,14 @@ class Validation
return (array) $value;
}
public static function typeYaml($value, $params)
{
try {
Yaml::parse($value);
return true;
} catch (ParseException $e) {
return false;
}
}
public static function filterYaml($value, $params)
{
try {
return (array) Yaml::parse($value);
} catch (ParseException $e) {
return null;
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

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

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

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;
}
@@ -719,8 +722,8 @@ class GPM extends Iterator
foreach ($packages as $package_name => $package) {
if (isset($package['dependencies'])) {
foreach ($package['dependencies'] as $dependency) {
if (is_array($dependency)) {
$dependency = array_keys($dependency)[0];
if (is_array($dependency) && isset($dependency['name'])) {
$dependency = $dependency['name'];
}
if ($dependency == $slug) {
@@ -835,6 +838,20 @@ class GPM extends Iterator
continue;
}
// Check PHP version
if ($dependency_slug == 'php') {
$current_php_version = phpversion();
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
$current_php_version) === 1
) {
//Needs a Grav update first
throw new \Exception("<red>One of the packages require PHP " . $dependencies['php'] . ". Please update PHP to resolve this");
} else {
unset($dependencies[$dependency_slug]);
continue;
}
}
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
if ($dependency_slug == 'grav') {
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
@@ -859,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
@@ -1062,9 +1081,9 @@ class GPM extends Iterator
} elseif ($version == '') {
return null;
} elseif ($this->versionFormatIsNextSignificantRelease($version)) {
return substr($version, 1);
return trim(substr($version, 1));
} elseif ($this->versionFormatIsEqualOrHigher($version)) {
return substr($version, 2);
return trim(substr($version, 2));
} else {
return $version;
}

View File

@@ -43,6 +43,11 @@ class Installer
*/
protected static $error = 0;
/**
* @var integer Zip Error Code
*/
protected static $error_zip = 0;
/**
* @var string Post install message
*/
@@ -190,10 +195,10 @@ class Installer
}
self::$error = self::ZIP_EXTRACT_ERROR;
self::$error_zip = $archive;
return false;
}
/**
* Instantiates and returns the package installer class
*
@@ -291,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);
}
@@ -460,7 +465,42 @@ class Installer
break;
case self::ZIP_EXTRACT_ERROR:
$msg = 'An error occurred while extracting the package';
$msg = 'Unable to extract the package. ';
if (self::$error_zip) {
switch(self::$error_zip) {
case \ZipArchive::ER_EXISTS:
$msg .= "File already exists.";
break;
case \ZipArchive::ER_INCONS:
$msg .= "Zip archive inconsistent.";
break;
case \ZipArchive::ER_MEMORY:
$msg .= "Malloc failure.";
break;
case \ZipArchive::ER_NOENT:
$msg .= "No such file.";
break;
case \ZipArchive::ER_NOZIP:
$msg .= "Not a zip archive.";
break;
case \ZipArchive::ER_OPEN:
$msg .= "Can't open file.";
break;
case \ZipArchive::ER_READ:
$msg .= "Read error.";
break;
case \ZipArchive::ER_SEEK:
$msg .= "Seek error.";
break;
}
}
break;
default:

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

@@ -18,6 +18,7 @@ class GravCore extends AbstractPackageCollection
private $version;
private $date;
private $min_php;
/**
* @param bool $refresh
@@ -37,6 +38,7 @@ class GravCore extends AbstractPackageCollection
$this->data = json_decode($this->raw, true);
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
if (isset($this->data['assets'])) {
foreach ((array)$this->data['assets'] as $slug => $data) {
@@ -92,6 +94,11 @@ class GravCore extends AbstractPackageCollection
return $this->date;
}
/**
* Determine if this version of Grav is eligible to be updated
*
* @return mixed
*/
public function isUpdatable()
{
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
@@ -107,6 +114,25 @@ class GravCore extends AbstractPackageCollection
return $this->version;
}
/**
* Returns the minimum PHP version
*
* @return null|string
*/
public function getMinPHPVersion()
{
// If non min set, assume current PHP version
if (is_null($this->min_php)) {
$this->min_php = phpversion();
}
return $this->min_php;
}
/**
* Is this installation symlinked?
*
* @return bool
*/
public function isSymlink()
{
return is_link(GRAV_ROOT . DS . 'index.php');

View File

@@ -24,6 +24,8 @@ class Upgrader
*/
private $remote;
private $min_php;
/**
* Creates a new GPM instance with Local and Remote packages available
*
@@ -89,17 +91,33 @@ class Upgrader
}
/**
* Make sure this meets minimum PHP requirements
*
* @return bool
*/
public function meetsRequirements()
{
if (version_compare(PHP_VERSION, GRAV_PHP_MIN, '<')) {
$current_php_version = phpversion();
if (version_compare($current_php_version, $this->minPHPVersion(), '<')) {
return false;
}
return true;
}
/**
* Get minimum PHP version from remote
*
* @return null
*/
public function minPHPVersion()
{
if (is_null($this->min_php)) {
$this->min_php = $this->remote->getMinPHPVersion();
}
return $this->min_php;
}
/**
* Checks if the currently installed Grav is upgradable to a newer version
*

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;
@@ -39,8 +38,7 @@ class Grav extends Container
'uri' => 'Grav\Common\Uri',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'cache' => 'Grav\Common\Cache',
'session' => 'Grav\Common\Session',
'Grav\Common\Service\MessagesServiceProvider',
'Grav\Common\Service\SessionServiceProvider',
'plugins' => 'Grav\Common\Plugins',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
@@ -206,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);
}
@@ -444,15 +439,16 @@ 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');
// Check whitelist first, then ensure extension is a valid media type
if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types)) {
return;
} elseif (!array_key_exists($uri_extension, $supported_types)) {
return;
if (!empty($fallback_types) && !\in_array($uri_extension, $fallback_types, true)) {
return false;
}
if (!array_key_exists($uri_extension, $supported_types)) {
return false;
}
$path_parts = pathinfo($path);

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);
@@ -172,10 +172,9 @@ class Excerpts
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
unset($url_parts['stream'], $url_parts['scheme']);
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
}
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
}
@@ -246,11 +245,12 @@ class Excerpts
// Process operations
$medium = static::processMediaActions($medium, $url_parts);
$element_excerpt = $excerpt['element']['attributes'];
$alt = isset($excerpt['element']['attributes']['alt']) ? $excerpt['element']['attributes']['alt'] : '';
$title = isset($excerpt['element']['attributes']['title']) ? $excerpt['element']['attributes']['title'] : '';
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
$id = isset($excerpt['element']['attributes']['id']) ? $excerpt['element']['attributes']['id'] : '';
$alt = isset($element_excerpt['alt']) ? $element_excerpt['alt'] : '';
$title = isset($element_excerpt['title']) ? $element_excerpt['title'] : '';
$class = isset($element_excerpt['class']) ? $element_excerpt['class'] : '';
$id = isset($element_excerpt['id']) ? $element_excerpt['id'] : '';
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
@@ -293,6 +293,15 @@ class Excerpts
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
}
$defaults = Grav::instance()['config']->get('system.images.defaults');
if (is_array($defaults) && count($defaults)) {
foreach ($defaults as $method => $params) {
$actions[] = [
'method' => $method,
'params' => $params,
];
}
}
// loop through actions for the image and call them
foreach ($actions as $action) {

View File

@@ -47,6 +47,7 @@ class Truncator {
// Iterate over words.
$words = new DOMWordsIterator($body);
$truncated = false;
foreach ($words as $word) {
// If we have exceeded the limit, we delete the remainder of the content.
@@ -70,12 +71,19 @@ class Truncator {
self::insertEllipsis($curNode, $ellipsis);
}
$truncated = true;
break;
}
}
return self::innerHTML($body);
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
} else {
return $html;
}
}
/**
@@ -98,24 +106,32 @@ class Truncator {
// Iterate over letters.
$letters = new DOMLettersIterator($body);
$truncated = false;
foreach ($letters as $letter) {
// If we have exceeded the limit, we want to delete the remainder of this document.
if ($letters->key() >= $limit) {
$currentText = $letters->currentTextPosition();
$currentText[0]->nodeValue = substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
self::removeProceedingNodes($currentText[0], $body);
if (!empty($ellipsis)) {
self::insertEllipsis($currentText[0], $ellipsis);
}
$truncated = true;
break;
}
}
return self::innerHTML($body);
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
} else {
return $html;
}
}
/**

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

@@ -174,23 +174,23 @@ class Language
// if languages set
if ($this->enabled()) {
// try setting from prefix of URL (/en/blah/blah)
// Try setting language from prefix of URL (/en/blah/blah).
if (preg_match($regex, $uri, $matches)) {
$this->lang_in_url = true;
$this->active = $matches[2];
$uri = preg_replace("/\\" . $matches[1] . "/", '', $uri, 1);
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
// store in session if different
if ($this->config->get('system.session.enabled', false)
// Store in session if language is different.
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
) {
$this->grav['session']->active_language = $this->active;
}
} else {
// try getting from session, else no active
if ($this->config->get('system.session.enabled', false) &&
$this->config->get('system.languages.session_store_active', true)) {
// Try getting language from the session, else no active.
if (isset($this->grav['session']) && $this->grav['session']->isStarted()
&& $this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
}
// if still null, try from http_accept_language header
@@ -203,8 +203,8 @@ class Language
}
}
// repeat if not found, try base language only - fixes Safari sending the language code always
// with a locale (e.g. it-it or fr-fr)
// Repeat if not found, try base language only - fixes Safari sending the language code always
// with a locale (e.g. it-it or fr-fr).
foreach ($preferred as $lang) {
$lang = substr($lang, 0, 2);
if ($this->validate($lang)) {

View File

@@ -22,6 +22,7 @@ class LanguageCodes
'bn-BD' => [ 'name' => 'Bengali (Bangladesh)', 'nativeName' => 'বাংলা (বাংলাদেশ)' ],
'bn-IN' => [ 'name' => 'Bengali (India)', 'nativeName' => 'বাংলা (ভারত)' ],
'br' => [ 'name' => 'Breton', 'nativeName' => 'Brezhoneg' ],
'bs' => [ 'name' => 'Bosnian', 'nativeName' => 'Bosanski' ],
'ca' => [ 'name' => 'Catalan', 'nativeName' => 'Català' ],
'ca-valencia'=> [ 'name' => 'Catalan (Valencian)', 'nativeName' => 'Català (valencià)' ], // not iso-639-1. a=l10n-drivers
'cs' => [ 'name' => 'Czech', 'nativeName' => 'Čeština' ],

View File

@@ -35,7 +35,7 @@ trait ParsedownGravTrait
$grav = Grav::instance();
$this->page = $page;
$this->BlockTypes['{'] [] = "TwigTag";
$this->BlockTypes['{'] [] = 'TwigTag';
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
if ($defaults === null) {
@@ -56,6 +56,9 @@ trait ParsedownGravTrait
*
* @param $type
* @param $tag
* @param bool $continuable
* @param bool $completable
* @param $index
*/
public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
{
@@ -67,7 +70,7 @@ trait ParsedownGravTrait
$block = &$this->BlockTypes[$type];
}
if (!isset($index)) {
if (null === $index) {
$block[] = $tag;
} else {
array_splice($block, $index, 0, [$tag]);
@@ -86,10 +89,11 @@ trait ParsedownGravTrait
*
* @param $type
* @param $tag
* @param $index
*/
public function addInlineType($type, $tag, $index = null)
{
if (!isset($index) || !isset($this->InlineTypes[$type])) {
if (null === $index || !isset($this->InlineTypes[$type])) {
$this->InlineTypes[$type] [] = $tag;
} else {
array_splice($this->InlineTypes[$type], $index, 0, [$tag]);
@@ -109,7 +113,7 @@ trait ParsedownGravTrait
*/
protected function isBlockContinuable($Type)
{
$continuable = in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
$continuable = \in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
return $continuable;
}
@@ -123,7 +127,7 @@ trait ParsedownGravTrait
*/
protected function isBlockCompletable($Type)
{
$completable = in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
$completable = \in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
return $completable;
}
@@ -157,32 +161,31 @@ trait ParsedownGravTrait
/**
* Ensure Twig tags are treated as block level items with no <p></p> tags
*
* @param array $line
* @return array|null
*/
protected function blockTwigTag($Line)
protected function blockTwigTag($line)
{
if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $Line['body'], $matches)) {
$Block = [
'markup' => $Line['body'],
];
return $Block;
if (preg_match('/(?:{{|{%|{#)(.*)(?:}}|%}|#})/', $line['body'], $matches)) {
return ['markup' => $line['body']];
}
return null;
}
protected function inlineSpecialCharacter($Excerpt)
protected function inlineSpecialCharacter($excerpt)
{
if ($Excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $Excerpt['text'])) {
if ($excerpt['text'][0] === '&' && !preg_match('/^&#?\w+;/', $excerpt['text'])) {
return [
'markup' => '&amp;',
'extent' => 1,
];
}
if (isset($this->special_chars[$Excerpt['text'][0]])) {
if (isset($this->special_chars[$excerpt['text'][0]])) {
return [
'markup' => '&' . $this->special_chars[$Excerpt['text'][0]] . ';',
'markup' => '&' . $this->special_chars[$excerpt['text'][0]] . ';',
'extent' => 1,
];
}
@@ -199,11 +202,11 @@ trait ParsedownGravTrait
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
return $excerpt;
} else {
$excerpt['type'] = 'image';
$excerpt = parent::inlineImage($excerpt);
}
$excerpt['type'] = 'image';
$excerpt = parent::inlineImage($excerpt);
// if this is an image process it
if (isset($excerpt['element']['attributes']['src'])) {
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
@@ -228,10 +231,10 @@ trait ParsedownGravTrait
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
return $excerpt;
} else {
$excerpt = parent::inlineLink($excerpt);
}
$excerpt = parent::inlineLink($excerpt);
// if this is a link
if (isset($excerpt['element']['attributes']['href'])) {
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
@@ -243,10 +246,10 @@ trait ParsedownGravTrait
// For extending this class via plugins
public function __call($method, $args)
{
if (isset($this->$method) === true) {
$func = $this->$method;
if (isset($this->{$method}) === true) {
$func = $this->{$method};
return call_user_func_array($func, $args);
return \call_user_func_array($func, $args);
}
return 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

@@ -610,4 +610,23 @@ class Collection extends Iterator
return $this;
}
/**
* Get the extended version of this Collection with each page keyed by route
*
* @return array
* @throws \Exception
*/
public function toExtendedArray()
{
$items = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page !== null) {
$items[$page->route()] = $page->toArray();
}
}
return $items;
}
}

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();
@@ -86,7 +88,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;
}

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.
@@ -44,10 +47,25 @@ abstract class AbstractMedia extends Getters
return $this->offsetGet($filename);
}
/**
* @param mixed $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
$object = parent::offsetGet($offset);
// It would be nice if previous image modification would not affect the later ones.
//$object = $object ? clone($object) : null;
return $object;
}
/**
* Get a list of all media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function all()
{
@@ -59,7 +77,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all image media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function images()
{
@@ -70,7 +88,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all video media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function videos()
{
@@ -81,7 +99,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all audio media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function audios()
{
@@ -92,7 +110,7 @@ abstract class AbstractMedia extends Getters
/**
* Get a list of all file media.
*
* @return array|Medium[]
* @return array|MediaObjectInterface[]
*/
public function files()
{
@@ -102,7 +120,7 @@ abstract class AbstractMedia extends Getters
/**
* @param string $name
* @param Medium $file
* @param MediaObjectInterface $file
*/
protected function add($name, $file)
{
@@ -130,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;
}
@@ -168,8 +192,8 @@ abstract class AbstractMedia extends Getters
$type = 'base';
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta' && $part != 'thumb') {
if (isset($extension)) {
if ($part !== 'meta' && $part !== 'thumb') {
if (null !== $extension) {
$name .= '.' . $extension;
}
$extension = $part;

View File

@@ -11,10 +11,16 @@ namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use Gregwar\Image\Exceptions\GenerationError;
use Gregwar\Image\Image;
use Gregwar\Image\Source;
use RocketTheme\Toolbox\Event\Event;
class ImageFile extends Image
{
public function __destruct()
{
$this->getAdapter()->deinit();
}
/**
* Clear previously applied operations
*/
@@ -30,15 +36,15 @@ class ImageFile extends Image
* @param int $quality the quality (for JPEG)
* @param bool $actual
*
* @return mixed|string
* @return string
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
{
if ($type == 'guess') {
if ($type === 'guess') {
$type = $this->guessType();
}
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
if (!$this->forceCache && !count($this->operations) && $type === $this->guessType()) {
return $this->getFilename($this->getFilePath());
}
@@ -60,8 +66,7 @@ class ImageFile extends Image
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
$cacheFile .= '.' . $type;
// If the files does not exists, save it
$image = $this;
@@ -76,7 +81,7 @@ class ImageFile extends Image
$generate = function ($target) use ($image, $type, $quality) {
$result = $image->save($target, $type, $quality);
if ($result != $target) {
if ($result !== $target) {
throw new GenerationError($result);
}
@@ -87,15 +92,19 @@ class ImageFile extends Image
try {
$perms = Grav::instance()['config']->get('system.images.cache_perms', '0755');
$perms = octdec($perms);
$file = $this->cache->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
$file = $this->getCacheSystem()->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
} catch (GenerationError $e) {
$file = $e->getNewFile();
}
// Nulling the resource
$this->getAdapter()->setSource(new Source\File($file));
$this->getAdapter()->deinit();
if ($actual) {
return $file;
} else {
return $this->getFilename($file);
}
return $this->getFilename($file);
}
}

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
{
@@ -103,6 +104,18 @@ class ImageMedium extends Medium
}
}
public function __destruct()
{
unset($this->image);
}
public function __clone()
{
$this->image = $this->image ? clone $this->image : null;
parent::__clone();
}
/**
* Add meta file for the medium.
*
@@ -152,14 +165,20 @@ 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);
$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);
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
}
if ($reset) {
@@ -226,13 +245,13 @@ class ImageMedium extends Medium
{
if ($this->get('prettyname')) {
return $this->get('prettyname');
} else {
$basename = $this->get('basename');
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
$basename = $matches[1];
}
return $basename;
}
$basename = $this->get('basename');
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
$basename = $matches[1];
}
return $basename;
}
/**
@@ -280,7 +299,7 @@ class ImageMedium extends Medium
// It's possible that MediumFactory::fromFile returns null if the
// original image file no longer exists and this class instance was
// retrieved from the page cache
if (isset($derivative)) {
if (null !== $derivative) {
$index = 2;
$alt_widths = array_keys($this->alternatives);
sort($alt_widths);
@@ -339,7 +358,6 @@ class ImageMedium extends Medium
if ($this->image) {
$this->image();
$this->image->clearOperations(); // Clear previously applied operations
$this->querystring('');
$this->filter();
$this->clearAlternatives();
@@ -432,7 +450,7 @@ class ImageMedium extends Medium
* Set or get sizes parameter for srcset media action
*
* @param string $sizes
* @return $this
* @return string
*/
public function sizes($sizes = null)
{
@@ -459,7 +477,7 @@ class ImageMedium extends Medium
*/
public function width($value = 'auto')
{
if (!$value || $value == 'auto')
if (!$value || $value === 'auto')
$this->attributes['width'] = $this->get('width');
else
$this->attributes['width'] = $value;
@@ -480,7 +498,7 @@ class ImageMedium extends Medium
*/
public function height($value = 'auto')
{
if (!$value || $value == 'auto')
if (!$value || $value === 'auto')
$this->attributes['height'] = $this->get('height');
else
$this->attributes['height'] = $value;
@@ -496,11 +514,11 @@ class ImageMedium extends Medium
*/
public function __call($method, $args)
{
if ($method == 'cropZoom') {
if ($method === 'cropZoom') {
$method = 'zoomCrop';
}
if (!in_array($method, self::$magic_actions)) {
if (!\in_array($method, self::$magic_actions, true)) {
return parent::__call($method, $args);
}
@@ -551,6 +569,9 @@ class ImageMedium extends Medium
// Use existing cache folder or if it doesn't exist, create it.
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
// Make sure we free previous image.
unset($this->image);
$this->image = ImageFile::open($file)
->setCacheDir($cacheDir)
->setActualCacheDir($cacheDir)
@@ -562,7 +583,7 @@ class ImageMedium extends Medium
/**
* Save the image with cache.
*
* @return mixed|string
* @return string
*/
protected function saveImage()
{
@@ -576,7 +597,7 @@ class ImageMedium extends Medium
return $this->result;
}
if ($this->get('debug') && !$this->debug_watermarked) {
if (!$this->debug_watermarked && $this->get('debug')) {
$ratio = $this->get('ratio');
if (!$ratio) {
$ratio = 1;
@@ -623,9 +644,9 @@ class ImageMedium extends Medium
}
return $max;
} else {
return $this;
}
return $this;
}
}

View File

@@ -12,8 +12,9 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Data\Data;
use Grav\Common\Data\Blueprint;
use Grav\Common\Media\Interfaces\MediaObjectInterface;
class Medium extends Data implements RenderableInterface
class Medium extends Data implements RenderableInterface, MediaObjectInterface
{
use ParsedownHtmlTrait;
@@ -72,6 +73,11 @@ class Medium extends Data implements RenderableInterface
$this->reset();
}
public function __clone()
{
// Allows future compatibility as parent::__clone() works.
}
/**
* Create a copy of this media object
*
@@ -79,7 +85,7 @@ class Medium extends Data implements RenderableInterface
*/
public function copy()
{
return clone($this);
return clone $this;
}
/**
@@ -92,6 +98,20 @@ class Medium extends Data implements RenderableInterface
return new Data($this->items);
}
/**
* Check if this medium exists or not
*
* @return bool
*/
public function exists()
{
$path = $this->get('filepath');
if (file_exists($path)) {
return true;
}
return false;
}
/**
* Returns an array containing just the metadata
*
@@ -179,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();
@@ -270,10 +295,14 @@ class Medium extends Data implements RenderableInterface
}
if (empty($attributes['alt'])) {
if (!empty($alt) || $alt === '') {
if (!empty($alt)) {
$attributes['alt'] = $alt;
} elseif (!empty($this->items['alt'])) {
$attributes['alt'] = $this->items['alt'];
} elseif (!empty($this->items['alt_text'])) {
$attributes['alt'] = $this->items['alt_text'];
} else {
$attributes['alt'] = '';
}
}
@@ -380,6 +409,22 @@ class Medium extends Data implements RenderableInterface
return $mode === 'thumbnail' ? ($this->getThumbnail() ? $this->getThumbnail()->reset() : null) : $this->reset();
}
/**
* Helper method to determine if this media item has a thumbnail or not
*
* @param string $type;
*
* @return bool
*/
public function thumbnailExists($type = 'page')
{
$thumbs = $this->get('thumbnails');
if (isset($thumbs[$type])) {
return true;
}
return false;
}
/**
* Switch thumbnail.
*
@@ -402,6 +447,7 @@ class Medium extends Data implements RenderableInterface
return $this;
}
/**
* Turn the current Medium into a Link
*
@@ -500,7 +546,12 @@ class Medium extends Data implements RenderableInterface
{
$qs = $method;
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
$qs .= '=' . implode(',', array_map(function ($a) { return rawurlencode($a); }, $args));
$qs .= '=' . implode(',', array_map(function ($a) {
if (is_array($a)) {
$a = '[' . implode(',', $a) . ']';
}
return rawurlencode($a);
}, $args));
}
if (!empty($qs)) {

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();
@@ -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) {
@@ -763,6 +764,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 +816,8 @@ class Page
*/
public function setRawContent($content)
{
$content = $content === null ? '': $content;
$this->content = $content;
}
@@ -1109,7 +1114,7 @@ class Page
*/
public function toYaml()
{
return Yaml::dump($this->toArray(), 10);
return Yaml::dump($this->toArray(), 20);
}
/**
@@ -1122,6 +1127,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 +1144,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)) : [];
}
/**
@@ -1499,6 +1522,8 @@ class Page
// Build an array of meta objects..
foreach ((array)$metadata as $key => $value) {
// Lowercase the key
$key = strtolower($key);
// If this is a property type metadata: "og", "twitter", "facebook" etc
// Backward compatibility for nested arrays in metas
if (is_array($value)) {
@@ -1525,12 +1550,13 @@ class Page
$separator = strpos($key, ':');
$hasSeparator = $separator && $separator < strlen($key) - 1;
$entry = [
'name' => $key,
'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
];
if ($hasSeparator) {
if ($hasSeparator && !Utils::startsWith($key, 'twitter')) {
$entry['property'] = $key;
} else {
$entry['name'] = $key;
}
$this->metadata[$key] = $entry;
@@ -1558,7 +1584,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;
}
@@ -1623,14 +1649,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 */
@@ -1639,41 +1670,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 !== '/') {
@@ -1787,7 +1802,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'])) {
@@ -2471,7 +2486,15 @@ class Page
return new Collection();
}
$collection = $this->evaluate($params['items']);
// See if require published filter is set and use that, if assume published=true
$only_published = true;
if (isset($params['filter']['published']) && $params['filter']['published']) {
$only_published = false;
} elseif (isset($params['filter']['non-published']) && $params['filter']['non-published']) {
$only_published = false;
}
$collection = $this->evaluate($params['items'], $only_published);
if (!$collection instanceof Collection) {
$collection = new Collection();
}
@@ -2486,7 +2509,7 @@ class Page
if ($process_taxonomy) {
foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
if ($uri->param($taxonomy)) {
if ($uri->param(rawurlencode($taxonomy))) {
$items = explode(',', $uri->param($taxonomy));
$collection->setParams(['taxonomies' => [$taxonomy => $items]]);
@@ -2510,25 +2533,60 @@ class Page
// If a filter or filters are set, filter the collection...
if (isset($params['filter'])) {
// remove any inclusive sets from filer:
$sets = ['published', 'visible', 'modular', 'routable'];
foreach ($sets as $type) {
if (isset($params['filter'][$type]) && isset($params['filter']['non-'.$type])) {
if ($params['filter'][$type] && $params['filter']['non-'.$type]) {
unset ($params['filter'][$type]);
unset ($params['filter']['non-'.$type]);
}
}
}
foreach ((array)$params['filter'] as $type => $filter) {
switch ($type) {
case 'published':
if ((bool) $filter) {
$collection->published();
}
break;
case 'non-published':
if ((bool) $filter) {
$collection->nonPublished();
}
break;
case 'visible':
$collection->visible($filter);
if ((bool) $filter) {
$collection->visible();
}
break;
case 'non-visible':
$collection->nonVisible($filter);
if ((bool) $filter) {
$collection->nonVisible();
}
break;
case 'modular':
$collection->modular($filter);
if ((bool) $filter) {
$collection->modular();
}
break;
case 'non-modular':
$collection->nonModular($filter);
if ((bool) $filter) {
$collection->nonModular();
}
break;
case 'routable':
$collection->routable($filter);
if ((bool) $filter) {
$collection->routable();
}
break;
case 'non-routable':
$collection->nonRoutable($filter);
if ((bool) $filter) {
$collection->nonRoutable();
}
break;
case 'type':
$collection->ofType($filter);
@@ -2589,11 +2647,11 @@ class Page
/**
* @param string|array $value
*
* @param bool $only_published
* @return mixed
* @internal
*/
public function evaluate($value)
public function evaluate($value, $only_published = true)
{
// Parse command.
if (is_string($value)) {
@@ -2662,7 +2720,7 @@ class Page
}
}
$results = $results->published();
break;
case 'page@':
@@ -2706,16 +2764,14 @@ class Page
$results = $page->children()->nonModular();
}
$results = $results->published();
break;
case 'root@':
case '@root':
if (!empty($parts) && $parts[0] === 'descendants') {
$results = $pages->all($pages->root())->nonModular()->published();
$results = $pages->all($pages->root())->nonModular();
} else {
$results = $pages->root()->children()->nonModular()->published();
$results = $pages->root()->children()->nonModular();
}
break;
@@ -2732,10 +2788,14 @@ class Page
if (!empty($parts)) {
$params = [implode('.', $parts) => $params];
}
$results = $taxonomy_map->findTaxonomy($params)->published();
$results = $taxonomy_map->findTaxonomy($params);
break;
}
if ($only_published) {
$results = $results->published();
}
return $results;
}
@@ -2907,4 +2967,26 @@ class Page
return $route;
}
}
/**
* Gets the Page Unmodified (original) version of the page.
*
* @return Page
* The original version of the page.
*/
public function getOriginal()
{
return $this->_original;
}
/**
* Gets the action.
*
* @return string
* The Action string.
*/
public function getAction()
{
return $this->_action;
}
}

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);
}
@@ -469,14 +491,13 @@ class Pages
if ($site_route) {
$page = $this->dispatch($site_route, $all);
} else {
// Try Regex style redirects
$uri = $this->grav['uri'];
$source_url = $route;
$extension = $uri->extension();
if (isset($extension) && !Utils::endsWith($uri->url(), $extension)) {
$source_url.= '.' . $extension;
}
/** @var Uri $uri */
$uri = $this->grav['uri'];
/** @var \Grav\Framework\Uri\Uri $source_url */
$source_url = $uri->uri(false);
// Try Regex style redirects
$site_redirects = $config->get("site.redirects");
if (is_array($site_redirects)) {
foreach ((array)$site_redirects as $pattern => $replace) {
@@ -1008,32 +1029,63 @@ class Pages
throw new \RuntimeException('Fatal error when creating page instances.');
}
$content_exists = false;
$pages_found = new \GlobIterator($directory . '/*' . CONTENT_EXT);
// Build regular expression for all the allowed page extensions.
$page_extensions = $language->getFallbackPageExtensions();
$regex = '/^[^\.]*(' . implode('|', array_map(
function ($str) {
return preg_quote($str, '/');
},
$page_extensions
)) . ')$/';
$folders = [];
$page_found = null;
$page_extension = '';
$last_modified = 0;
if ($pages_found && count($pages_found) > 0) {
$iterator = new \FilesystemIterator($directory);
/** @var \FilesystemIterator $file */
foreach ($iterator as $file) {
$filename = $file->getFilename();
$page_extensions = $language->getFallbackPageExtensions();
// Ignore all hidden files if set.
if ($this->ignore_hidden && $filename && $filename[0] === '.') {
continue;
}
foreach ($page_extensions as $extension) {
foreach ($pages_found as $found) {
if ($found->isDir()) {
continue;
}
$regex = '/^[^\.]*' . preg_quote($extension, '/') . '$/';
if (preg_match($regex, $found->getFilename())) {
$page_found = $found;
$page_extension = $extension;
break 2;
}
// Handle folders later.
if ($file->isDir()) {
// But ignore all folders in ignore list.
if (!\in_array($filename, $this->ignore_folders, true)) {
$folders[] = $file;
}
continue;
}
// Ignore all files in ignore list.
if (\in_array($filename, $this->ignore_files, true)) {
continue;
}
// Update last modified date to match the last updated file in the folder.
$modified = $file->getMTime();
if ($modified > $last_modified) {
$last_modified = $modified;
}
// Page is the one that matches to $page_extensions list with the lowest index number.
if (preg_match($regex, $filename, $matches, PREG_OFFSET_CAPTURE)) {
$ext = $matches[1][0];
if ($page_found === null || array_search($ext, $page_extensions, true) < array_search($page_extension, $page_extensions, true)) {
$page_found = $file;
$page_extension = $ext;
}
}
}
if ($parent && !empty($page_found)) {
$content_exists = false;
if ($parent && $page_found) {
$page->init($page_found, $page_extension);
$content_exists = true;
@@ -1043,48 +1095,31 @@ class Pages
}
}
// set current modified of page
$last_modified = $page->modified();
// Now handle all the folders under the page.
/** @var \FilesystemIterator $file */
foreach ($folders as $file) {
$filename = $file->getFilename();
$iterator = new \FilesystemIterator($directory);
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
$name = $file->getFilename();
// Ignore all hidden files if set.
if ($this->ignore_hidden && $name && $name[0] === '.') {
// if folder contains separator, continue
if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
continue;
}
if ($file->isFile()) {
// Update the last modified if it's newer than already found
if (!in_array($file->getBasename(), $this->ignore_files) && ($modified = $file->getMTime()) > $last_modified) {
$last_modified = $modified;
}
} elseif ($file->isDir() && !in_array($file->getFilename(), $this->ignore_folders)) {
if (!$page->path()) {
$page->path($file->getPath());
}
// if folder contains separator, continue
if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
continue;
}
$path = $directory . DS . $filename;
$child = $this->recurse($path, $page);
if (!$page->path()) {
$page->path($file->getPath());
}
if (Utils::startsWith($filename, '_')) {
$child->routable(false);
}
$path = $directory . DS . $name;
$child = $this->recurse($path, $page);
$this->children[$page->path()][$child->path()] = ['slug' => $child->slug()];
if (Utils::startsWith($name, '_')) {
$child->routable(false);
}
$this->children[$page->path()][$child->path()] = ['slug' => $child->slug()];
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
}
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
}
}
@@ -1203,19 +1238,19 @@ class Pages
break;
case 'date':
$list[$key] = $child->date();
$sort_flags = SORT_NUMERIC;
$sort_flags = SORT_REGULAR;
break;
case 'modified':
$list[$key] = $child->modified();
$sort_flags = SORT_NUMERIC;
$sort_flags = SORT_REGULAR;
break;
case 'publish_date':
$list[$key] = $child->publishDate();
$sort_flags = SORT_NUMERIC;
$sort_flags = SORT_REGULAR;
break;
case 'unpublish_date':
$list[$key] = $child->unpublishDate();
$sort_flags = SORT_NUMERIC;
$sort_flags = SORT_REGULAR;
break;
case 'slug':
$list[$key] = $child->slug();

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)) {
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->redirect(rtrim($path, '/'), 302);
}
$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

@@ -1,30 +0,0 @@
<?php
/**
* @package Grav.Common.Service
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Service;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\Session\Message;
class MessagesServiceProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
// Define session message service.
$container['messages'] = function ($c) {
$session = $c['session'];
if (!isset($session->messages)) {
$session->messages = new Message;
}
return $session->messages;
};
}
}

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

@@ -0,0 +1,112 @@
<?php
/**
* @package Grav.Common.Service
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\Inflector;
use Grav\Common\Session;
use Grav\Common\Uri;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\Session\Message;
class SessionServiceProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
// Define session service.
$container['session'] = function ($c) {
/** @var Config $config */
$config = $c['config'];
/** @var Uri $uri */
$uri = $c['uri'];
// Get session options.
$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;
if ($config->get('plugins.admin.enabled')) {
$base = '/' . trim($config->get('plugins.admin.route'), '/');
// Uri::route() is not processed yet, let's quickly get what we need.
$current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH));
// 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) {
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
$enabled = $is_admin = true;
}
}
// Fix for HUGE session timeouts.
if ($cookie_lifetime > 99999999999) {
$cookie_lifetime = 9999999999;
}
$inflector = new Inflector();
$session_name = $inflector->hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5(GRAV_ROOT), 0, 7);
if ($is_admin && $config->get('system.session.split', true)) {
$session_name .= '-admin';
}
// Define session service.
$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;
};
// Define session message service.
$container['messages'] = function ($c) {
if (!isset($c['session']) || !$c['session']->isStarted()) {
/** @var Debugger $debugger */
$debugger = $c['debugger'];
$debugger->addMessage('Inactive session: session messages may disappear', 'warming');
return new Message;
}
/** @var Session $session */
$session = $c['session'];
if (!isset($session->messages)) {
$session->messages = new Message;
}
return $session->messages;
};
}
}

View File

@@ -8,111 +8,123 @@
namespace Grav\Common;
use RocketTheme\Toolbox\Session\Session as BaseSession;
class Session extends BaseSession
class Session extends \Grav\Framework\Session\Session
{
protected $grav;
protected $session;
/** @var bool */
protected $autoStart = false;
/**
* Session constructor.
*
* @param Grav $grav
* @return \Grav\Framework\Session\Session
* @deprecated 1.5 Use getInstance() method instead
*/
public function __construct(Grav $grav)
public static function instance()
{
$this->grav = $grav;
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getInstance() method instead', E_USER_DEPRECATED);
return static::getInstance();
}
/**
* Session init
* Initialize session.
*
* Code in this function has been moved into SessionServiceProvider class.
*/
public function init()
{
/** @var Uri $uri */
$uri = $this->grav['uri'];
$config = $this->grav['config'];
$is_admin = false;
$base_url = $uri->rootUrl(false);
$session_timeout = $config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path');
if (!$session_path) {
$session_path = '/' . ltrim(Uri::filterPath($base_url), '/');
}
// Activate admin if we're inside the admin path.
if ($config->get('plugins.admin.enabled')) {
$route = $config->get('plugins.admin.route');
// Uri::route() is not processed yet, let's quickly get what we need
$current_route = str_replace(Uri::filterPath($base_url), '', parse_url($uri->url(true), PHP_URL_PATH));
$base = '/' . trim($route, '/');
if (substr($current_route, 0, strlen($base)) == $base || //handle no language specified
substr($current_route, 3, strlen($base)) == $base || //handle language (en)
substr($current_route, 6, strlen($base)) == $base) { //handle region specific language prefix (en-US)
$session_timeout = $config->get('plugins.admin.session.timeout', 1800);
$is_admin = true;
}
}
if ($config->get('system.session.enabled') || $is_admin) {
$domain = $uri->host();
if ($domain === 'localhost') {
$domain = '';
}
// Fix for HUGE session timeouts
if ($session_timeout > 99999999999) {
$session_timeout = 9999999999;
}
// Define session service.
parent::__construct($session_timeout, $session_path, $domain);
$secure = $config->get('system.session.secure', false);
$httponly = $config->get('system.session.httponly', true);
$unique_identifier = GRAV_ROOT;
$inflector = new Inflector();
$session_name = $inflector->hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5($unique_identifier), 0, 7);
$split_session = $config->get('system.session.split', true);
if ($is_admin && $split_session) {
$session_name .= '-admin';
}
$this->setName($session_name);
ini_set('session.cookie_secure', $secure);
ini_set('session.cookie_httponly', $httponly);
if ($this->autoStart) {
$this->start();
setcookie(session_name(), session_id(), $session_timeout ? time() + $session_timeout : 0, $session_path, $domain, $secure, $httponly);
$this->autoStart = false;
}
}
// Store something in session temporarily
/**
* @param bool $auto
* @return $this
*/
public function setAutoStart($auto)
{
$this->autoStart = (bool)$auto;
return $this;
}
/**
* Returns attributes.
*
* @return array Attributes
* @deprecated 1.5 Use getAll() method instead
*/
public function all()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getAll() method instead', E_USER_DEPRECATED);
return $this->getAll();
}
/**
* Checks if the session was started.
*
* @return Boolean
* @deprecated 1.5 Use isStarted() method instead
*/
public function started()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use isStarted() method instead', E_USER_DEPRECATED);
return $this->isStarted();
}
/**
* Store something in session temporarily.
*
* @param string $name
* @param mixed $object
* @return $this
*/
public function setFlashObject($name, $object)
{
$this->$name = serialize($object);
$this->{$name} = serialize($object);
return $this;
}
// Return object and remove it from session
/**
* Return object and remove it from session.
*
* @param string $name
* @return mixed
*/
public function getFlashObject($name)
{
$object = unserialize($this->$name);
$object = unserialize($this->{$name});
$this->$name = null;
$this->{$name} = null;
return $object;
}
// Store something in cookie temporarily
/**
* Store something in cookie temporarily.
*
* @param string $name
* @param mixed $object
* @param int $time
* @return $this
*/
public function setFlashCookieObject($name, $object, $time = 60)
{
setcookie($name, json_encode($object), time() + $time, '/');
return $this;
}
// Return object and remove it from the cookie
/**
* Return object and remove it from the cookie.
*
* @param string $name
* @return mixed|null
*/
public function getFlashCookieObject($name)
{
if (isset($_COOKIE[$name])) {
@@ -120,5 +132,7 @@ class Session extends BaseSession
setcookie($name, '', time() - 3600, '/');
return $object;
}
return null;
}
}

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

@@ -0,0 +1,35 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
{
public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
{
parent::__construct(['body' => $body], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler A Twig_Compiler instance
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('ob_start();' . PHP_EOL)
->subcompile($this->getNode('body'))
->write('$content = ob_get_clean();' . PHP_EOL)
->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL)
->write('$lines = explode("\n", $content);' . PHP_EOL)
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
->write('$content = join("\n", $content);' . PHP_EOL)
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($content);' . PHP_EOL);
}
}

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

@@ -0,0 +1,73 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeSwitch extends \Twig_Node
{
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);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler A Twig_Compiler instance
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('switch (')
->subcompile($this->getNode('value'))
->raw(") {\n")
->indent();
foreach ($this->getNode('cases') as $case) {
if (!$case->hasNode('body')) {
continue;
}
foreach ($case->getNode('values') as $value) {
$compiler
->write('case ')
->subcompile($value)
->raw(":\n");
}
$compiler
->write("{\n")
->indent()
->subcompile($case->getNode('body'))
->write("break;\n")
->outdent()
->write("}\n");
}
if ($this->hasNode('default') && $this->getNode('default') !== null) {
$compiler
->write("default:\n")
->write("{\n")
->indent()
->subcompile($this->getNode('default'))
->outdent()
->write("}\n");
}
$compiler
->outdent()
->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

@@ -0,0 +1,53 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\TokenParser;
use Grav\Common\Twig\Node\TwigNodeMarkdown;
/**
* Adds ability to inline markdown between tags.
*
* {% markdown %}
* This is **bold** and this _underlined_
*
* 1. This is a bullet list
* 2. This is another item in that same list
* {% endmarkdown %}
*/
class TwigTokenParserMarkdown extends \Twig_TokenParser
{
/**
* {@inheritdoc}
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new TwigNodeMarkdown($body, $lineno, $this->getTag());
}
/**
* Decide if current token marks end of Markdown block.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideMarkdownEnd(\Twig_Token $token)
{
return $token->test('endmarkdown');
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'markdown';
}
}

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

@@ -0,0 +1,125 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
* @origin https://gist.github.com/maxgalbu/9409182
*/
namespace Grav\Common\Twig\TokenParser;
use Grav\Common\Twig\Node\TwigNodeSwitch;
/**
* Adds ability use elegant switch instead of ungainly if statements
*
* {% switch type %}
* {% case 'foo' %}
* {{ my_data.foo }}
* {% case 'bar' %}
* {{ my_data.bar }}
* {% default %}
* {{ my_data.default }}
* {% endswitch %}
*/
class TwigTokenParserSwitch extends \Twig_TokenParser
{
/**
* {@inheritdoc}
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $this->parser->getExpressionParser()->parseExpression();
$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()) === '') {
$stream->next();
}
$stream->expect(\Twig_Token::BLOCK_START_TYPE);
$expressionParser = $this->parser->getExpressionParser();
$default = null;
$cases = [];
$end = false;
while (!$end) {
$next = $stream->next();
switch ($next->getValue()) {
case 'case':
$values = [];
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;
case 'default':
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
case 'endswitch':
$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);
}
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return new TwigNodeSwitch($name, new \Twig_Node($cases), $default, $lineno, $this->getTag());
}
/**
* Decide if current token marks switch logic.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideIfFork(\Twig_Token $token)
{
return $token->test(array('case', 'default', 'endswitch'));
}
/**
* Decide if current token marks end of swtich block.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideIfEnd(\Twig_Token $token)
{
return $token->test(array('endswitch'));
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'switch';
}
}

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

@@ -113,8 +113,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 +129,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 +140,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,7 +152,7 @@ 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')) {
@@ -159,7 +166,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 +245,9 @@ class Twig
// Process Modular Twig
if ($item->modularTwig()) {
$twig_vars['content'] = $content;
$template = $item->template() . TEMPLATE_EXT;
$extension = $item->templateFormat();
$extension = $extension ? ".{$extension}.twig" : TEMPLATE_EXT;
$template = $item->template() . $extension;
$output = $content = $local_twig->render($template, $twig_vars);
}
@@ -406,8 +415,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,13 +11,17 @@ 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;
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\Uri;
use Grav\Common\Helpers\Base32;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -61,20 +65,20 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']),
new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']),
new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']),
new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']),
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']),
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']),
new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']),
new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']),
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']),
new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']),
new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']),
new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
@@ -85,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']),
@@ -95,6 +96,20 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFilter('basename', 'basename'),
new \Twig_SimpleFilter('dirname', 'dirname'),
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']),
];
}
@@ -106,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'),
@@ -117,18 +132,16 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']),
new \Twig_SimpleFunction('print_r', 'print_r'),
new \Twig_SimpleFunction('http_response_code', 'http_response_code'),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_SimpleFunction('pathinfo', 'pathinfo'),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
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']),
@@ -140,7 +153,16 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
// Translations
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
];
}
@@ -153,6 +175,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
new TwigTokenParserTryCatch(),
new TwigTokenParserScript(),
new TwigTokenParserStyle(),
new TwigTokenParserMarkdown(),
new TwigTokenParserSwitch(),
];
}
@@ -181,10 +205,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
{
$email = '';
for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) {
$j = rand( 0, 1);
if ( $j == 0 ) {
$j = mt_rand( 0, 1);
if ( $j === 0 ) {
$email .= '&#' . ord( $str[$i] ) . ';';
} elseif ( $j == 1 ) {
} elseif ( $j === 1 ) {
$email .= $str[$i];
}
}
@@ -214,7 +238,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$random = array_slice($original, $offset);
shuffle($random);
$sizeOf = sizeof($original);
$sizeOf = count($original);
for ($x = 0; $x < $sizeOf; $x++) {
if ($x < $offset) {
$sorted[] = $original[$x];
@@ -229,9 +253,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* Returns the modulus of an integer
*
* @param int $number
* @param int $divider
* @param array $items array of items to select from to return
* @param string|int $number
* @param int $divider
* @param array $items array of items to select from to return
*
* @return int
*/
@@ -246,10 +270,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
if (is_array($items)) {
if (isset($items[$remainder])) {
return $items[$remainder];
} else {
}
return $items[0];
}
}
return $remainder;
}
@@ -279,20 +303,23 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
$inflector = $this->grav['inflector'];
if (in_array(
if (\in_array(
$action,
['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize']
['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
true
)) {
return $inflector->$action($data);
} elseif (in_array($action, ['pluralize', 'singularize'])) {
}
if (\in_array($action, ['pluralize', 'singularize'], true)) {
if ($count) {
return $inflector->$action($data, $count);
} else {
}
return $inflector->$action($data);
}
} else {
return $data;
}
}
/**
@@ -357,25 +384,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*
* @param array $input
* @param string $filter
* @param array|int $direction
* @param int $direction
* @param int $sort_flags
*
* @return string
* @return array
*/
public function sortByKeyFilter(array $input, $filter, $direction = SORT_ASC)
public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR)
{
$output = [];
if (!$input) {
return $output;
}
foreach ($input as $key => $row) {
$output[$key] = $row[$filter];
}
array_multisort($output, $direction, $input);
return $input;
return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags);
}
/**
@@ -387,7 +403,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function ksortFilter($array)
{
if (is_null($array)) {
if (null === $array) {
$array = [];
}
ksort($array);
@@ -429,7 +445,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*
* @return boolean
*/
public function nicetimeFilter($date, $long_strings = true)
public function nicetimeFunc($date, $long_strings = true)
{
if (empty($date)) {
return $this->grav['language']->translate('NICETIME.NO_DATE_PROVIDED', null, true);
@@ -512,9 +528,29 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
if ($now == $unix_date) {
return "{$tense}";
}
else {
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);
}
/**
@@ -537,7 +573,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* @param bool $block Block or Line processing
* @return mixed|string
*/
public function markdownFilter($string, $block = true)
public function markdownFunction($string, $block = true)
{
$page = $this->grav['page'];
$defaults = $this->config->get('system.pages.markdown');
@@ -589,12 +625,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function definedDefaultFilter($value, $default = null)
{
if (isset($value)) {
return $value;
} else {
return $default;
return null !== $value ? $value : $default;
}
}
/**
* @param $value
@@ -618,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
*/
@@ -678,53 +766,36 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
public function urlFunc($input, $domain = false)
{
return Utils::url($input, $domain);
}
}
/**
* This function will evaluate Twig $twig through the $environment, and return its results.
*
* @param \Twig_Environment $environment
* @param array $context
* @param string $twig
* @return mixed
*/
public function evaluateTwigFunc( \Twig_Environment $environment, $context, $twig ) {
$loader = $environment->getLoader( );
public function evaluateTwigFunc($context, $twig ) {
$parsed = $this->parseString( $environment, $context, $twig );
$loader = new \Twig_Loader_Filesystem('.');
$env = new \Twig_Environment($loader);
$environment->setLoader( $loader );
return $parsed;
$template = $env->createTemplate($twig);
return $template->render($context);
}
/**
* This function will evaluate a $string through the $environment, and return its results.
*
* @param \Twig_Environment $environment
* @param $context
* @param $string
* @return mixed
*/
public function evaluateStringFunc(\Twig_Environment $environment, $context, $string )
public function evaluateStringFunc($context, $string )
{
$parsed = $this->evaluateTwigFunc($environment, $context, "{{ $string }}");
return $parsed;
return $this->evaluateTwigFunc($context, "{{ $string }}");
}
/**
* Sets the parser for the environment to Twig_Loader_String, and parsed the string $string.
*
* @param \Twig_Environment $environment
* @param array $context
* @param string $string
* @return string
*/
protected function parseString( \Twig_Environment $environment, $context, $string ) {
$environment->setLoader( new \Twig_Loader_String( ) );
return $environment->render( $string, $context );
}
/**
* Based on Twig_Extension_Debug / twig_var_dump
@@ -765,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
*/
@@ -805,26 +876,13 @@ 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
*
* @param string $key key of item
* @param string $val value of item
* @param string $current_array optional array to add to
* @param array $current_array optional array to add to
*
* @return array
*/
@@ -832,11 +890,11 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
{
if (empty($current_array)) {
return array($key => $val);
} else {
}
$current_array[$key] = $val;
return $current_array;
}
}
/**
* Wrapper for array_intersect() method
@@ -849,10 +907,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
{
if ($array1 instanceof Collection && $array2 instanceof Collection) {
return $array1->intersect($array2);
} else {
return array_intersect($array1, $array2);
}
return array_intersect($array1, $array2);
}
/**
@@ -866,10 +923,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
{
if (is_array($value)) { //format the array as a string
return json_encode($value);
} else {
}
return $value;
}
}
/**
* Translate a string
@@ -894,7 +951,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/
public function authorize($action)
{
if (!$this->grav['user']->authenticated) {
/** @var User $user */
$user = $this->grav['user'];
if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
return false;
}
@@ -902,8 +962,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
foreach ($action as $key => $perms) {
$prefix = is_int($key) ? '' : $key . '.';
$perms = $prefix ? (array) $perms : [$perms => true];
foreach ($perms as $action => $authenticated) {
if ($this->grav['user']->authorize($prefix . $action)) {
foreach ($perms as $action2 => $authenticated) {
if ($user->authorize($prefix . $action2)) {
return $authenticated;
}
}
@@ -970,6 +1030,18 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return preg_replace($pattern, $replace, $subject, $limit);
}
/**
* Twig wrapper for PHP's preg_grep method
*
* @param $array
* @param $regex
* @param int $flags
* @return array
*/
public function regexFilter($array, $regex, $flags = 0) {
return preg_grep($regex, $array, $flags);
}
/**
* redirect browser from twig
*
@@ -979,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();
}
/**
@@ -1006,7 +1078,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
}
/**
@@ -1036,19 +1108,43 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
if ($exif_data) {
if ($raw) {
return $exif_data->getRawData();
} else {
}
return $exif_data->getData();
}
}
}
return null;
}
/**
* Simple function to read a file based on a filepath and output it
*
* @param $filepath
* @return bool|string
*/
public function readFileFunc($filepath)
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
if ($locator->isStream($filepath)) {
$filepath = $locator->findResource($filepath);
}
if (file_exists($filepath)) {
return file_get_contents($filepath);
}
return false;
}
/**
* Process a folder as Media and return a media object
*
* @param $media_dir
* @return Media
* @return Media|null
*/
public function mediaDirFunc($media_dir)
{
@@ -1063,6 +1159,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
return new Media($media_dir);
}
return null;
}
/**
@@ -1075,26 +1172,72 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
var_dump($var);
}
/**
* Returns a nicer more readable filesize based on bytes
*
* @param $bytes
* @return string
*/
public function niceFilesizeFunc($bytes)
{
if ($bytes >= 1073741824)
{
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
}
elseif ($bytes >= 1048576)
{
$bytes = number_format($bytes / 1048576, 2) . ' MB';
}
elseif ($bytes >= 1024)
{
$bytes = number_format($bytes / 1024, 1) . ' KB';
}
elseif ($bytes > 1)
{
$bytes = $bytes . ' bytes';
}
elseif ($bytes == 1)
{
$bytes = $bytes . ' byte';
}
else
{
$bytes = '0 bytes';
}
return $bytes;
}
/**
* Returns a nicer more readable number
*
* @param $number
* @param int|float $n
* @return bool|string
*/
public function niceNumberFunc($n)
{
// first strip any formatting;
$n = (0+str_replace(",", "", $n));
$n = 0 + str_replace(',', '', $n);
// is this a number?
if (!is_numeric($n)) return false;
if (!is_numeric($n)) {
return false;
}
// now filter it;
if ($n > 1000000000000) return round(($n/1000000000000), 2).' t';
elseif ($n > 1000000000) return round(($n/1000000000), 2).' b';
elseif ($n > 1000000) return round(($n/1000000), 2).' m';
elseif ($n > 1000) return round(($n/1000), 2).' k';
if ($n > 1000000000000) {
return round(($n/1000000000000), 2).' t';
}
if ($n > 1000000000) {
return round(($n/1000000000), 2).' b';
}
if ($n > 1000000) {
return round(($n/1000000), 2).' m';
}
if ($n > 1000) {
return round(($n/1000), 2).' k';
}
return number_format($n);
}
@@ -1103,11 +1246,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
* Get a theme variable
*
* @param $var
* @param bool $default
* @return string
*/
public function themeVarFunc($var)
public function themeVarFunc($var, $default = null)
{
return $this->config->get('theme.' . $var, false) ?: '';
$header = $this->grav['page']->header();
$header_classes = isset($header->$var) ? $header->$var : null;
return $header_classes ?: $this->config->get('theme.' . $var, $default);
}
/**
@@ -1126,10 +1272,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
foreach ((array)$classes as $class) {
if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
continue;
} else {
$val = $this->config->get('theme.' . $class, false) ? $class : false;
$body_classes .= $val ? ' ' . $val : '';
}
$val = $this->config->get('theme.' . $class, false) ? $class : false;
$body_classes .= $val ? ' ' . $val : '';
}
return $body_classes;
@@ -1166,5 +1312,30 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
}
}
}
return null;
}
/**
* Dump/Encode data into YAML format
*
* @param $data
* @param $inline integer number of levels of inline syntax
* @return mixed
*/
public function yamlEncodeFilter($data, $inline = 10)
{
return Yaml::dump($data, $inline);
}
/**
* Decode/Parse data from YAML format
*
* @param $data
* @return mixed
*/
public function yamlDecodeFilter($data)
{
return Yaml::parse($data);
}
}

View File

@@ -8,12 +8,25 @@
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
{
const HOSTNAME_REGEX = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/';
/** @var \Grav\Framework\Uri\Uri */
protected static $currentUri;
/** @var \Grav\Framework\Route\Route */
protected static $currentRoute;
public $url;
// Uri parts.
@@ -38,6 +51,8 @@ class Uri
protected $root;
protected $root_path;
protected $uri;
protected $content_type;
protected $post;
/**
* Uri constructor.
@@ -52,114 +67,6 @@ class Uri
}
}
/**
* @param array $env
*/
protected function createFromEnvironment(array $env)
{
// Build scheme.
if (isset($env['REQUEST_SCHEME'])) {
$this->scheme = $env['REQUEST_SCHEME'];
} else {
$https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
$this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
}
// Build user and password.
$this->user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : null;
$this->password = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : null;
// Build host.
$hostname = 'localhost';
if (isset($env['HTTP_HOST'])) {
$hostname = $env['HTTP_HOST'];
} elseif (isset($env['SERVER_NAME'])) {
$hostname = $env['SERVER_NAME'];
}
// Remove port from HTTP_HOST generated $hostname
$hostname = Utils::substrToString($hostname, ':');
// Validate the hostname
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
// Build port.
$this->port = isset($env['SERVER_PORT']) ? (int)$env['SERVER_PORT'] : null;
if ($this->hasStandardPort()) {
$this->port = null;
}
// Build path.
$request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
$this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
// Build query string.
$this->query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
if ($this->query === '') {
$this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
}
// Support ngnix routes.
if (strpos($this->query, '_url=') === 0) {
parse_str($this->query, $query);
unset($query['_url']);
$this->query = http_build_query($query);
}
// Build fragment.
$this->fragment = null;
// Filter userinfo, path and query string.
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
$this->query = static::filterQuery($this->query);
$this->reset();
}
/**
* Does this Uri use a standard port?
*
* @return bool
*/
protected function hasStandardPort()
{
return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
}
/**
* @param string $url
*/
protected function createFromString($url)
{
// Set Uri parts.
$parts = parse_url($url);
if ($parts === false) {
throw new \RuntimeException('Malformed URL: ' . $url);
}
$this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
$this->user = isset($parts['user']) ? $parts['user'] : null;
$this->password = isset($parts['pass']) ? $parts['pass'] : null;
$this->host = isset($parts['host']) ? $parts['host'] : null;
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
$this->path = isset($parts['path']) ? $parts['path'] : '';
$this->query = isset($parts['query']) ? $parts['query'] : '';
$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
// Validate the hostname
if ($this->host) {
$this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
}
// Filter userinfo, path, query string and fragment.
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
$this->query = static::filterQuery($this->query);
$this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
$this->reset();
}
/**
* Initialize the URI class with a url passed via parameter.
* Used for testing purposes.
@@ -193,33 +100,6 @@ class Uri
return $this;
}
protected function reset()
{
// resets
parse_str($this->query, $this->queries);
$this->extension = null;
$this->basename = null;
$this->paths = [];
$this->params = [];
$this->env = $this->buildEnvironment();
$this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : '');
$this->base = $this->buildBaseUrl();
$this->root_path = $this->buildRootPath();
$this->root = $this->base . $this->root_path;
$this->url = $this->base . $this->uri;
}
/**
* Calculate the parameter regex based on the param_sep setting
*
* @return string
*/
public static function paramsRegex()
{
return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
}
/**
* Validate a hostname
*
@@ -232,45 +112,6 @@ class Uri
return (bool)preg_match(static::HOSTNAME_REGEX, $hostname);
}
/**
* Get the base URI with port if needed
*
* @return string
*/
private function buildBaseUrl()
{
return $this->scheme() . $this->host;
}
/**
* Get the Grav Root Path
*
* @return string
*/
private function buildRootPath()
{
// In Windows script path uses backslash, convert it:
$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;
}
private function buildEnvironment()
{
// check for localhost variations
if ($this->host === '127.0.0.1' || $this->host === '::1') {
return 'localhost';
}
return $this->host ?: 'unknown';
}
/**
* Initializes the URI object based on the url set on the object
*/
@@ -278,7 +119,10 @@ class Uri
{
$grav = Grav::instance();
/** @var Config $config */
$config = $grav['config'];
/** @var Language $language */
$language = $grav['language'];
// add the port to the base for non-standard ports
@@ -293,7 +137,12 @@ class Uri
$custom_parts = parse_url($custom_base);
$orig_root_path = $this->root_path;
$this->root_path = isset($custom_parts['path']) ? rtrim($custom_parts['path'], '/') : '';
$this->root = isset($custom_parts['scheme']) ? $custom_base : $this->base . $this->root_path;
if (isset($custom_parts['scheme'])) {
$this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
$this->root = $custom_base;
} else {
$this->root = $this->base . $this->root_path;
}
$this->uri = Utils::replaceFirstOccurrence($orig_root_path, $this->root_path, $this->uri);
} else {
$this->root = $this->base . $this->root_path;
@@ -306,13 +155,7 @@ class Uri
// remove the setup.php based base if set:
$setup_base = $grav['pages']->base();
if ($setup_base) {
$uri = str_replace($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);
$uri = preg_replace('|^' . preg_quote($setup_base, '|') . '|', '', $uri);
}
// process params
@@ -359,34 +202,12 @@ 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'];
/**
* Process any params based in this URL, supports any valid delimiter
*
* @param $uri
* @param string $delimiter
*
* @return string
*/
private function processParams($uri, $delimiter = ':')
{
if (strpos($uri, $delimiter) !== false) {
preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$param = explode($delimiter, $match[1]);
if (count($param) === 2) {
$plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
$this->params[$param[0]] = $plain_var;
$uri = str_replace($match[0], '', $uri);
}
}
}
return $uri;
RouteFactory::setRoot($this->root_path);
RouteFactory::setLanguage($language->getLanguageURLPrefix());
}
/**
@@ -551,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
*
@@ -646,6 +478,21 @@ class Uri
return $this->basename;
}
/**
* Return the full uri
*
* @param bool $include_root
* @return mixed
*/
public function uri($include_root = true)
{
if ($include_root) {
return $this->uri;
}
return str_replace($this->root_path, '', $this->uri);
}
/**
* Return the base of the URI
*
@@ -666,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);
}
/**
@@ -737,6 +578,36 @@ class Uri
return substr($referrer, strlen($root));
}
public function __toString()
{
return static::buildUrl($this->toArray());
}
public function toArray()
{
return [
'scheme' => $this->scheme,
'host' => $this->host,
'port' => $this->port,
'user' => $this->user,
'pass' => $this->password,
'path' => $this->path,
'params' => $this->params,
'query' => $this->query,
'fragment' => $this->fragment
];
}
/**
* Calculate the parameter regex based on the param_sep setting
*
* @return string
*/
public static function paramsRegex()
{
return '/\/([^\:\#\/\?]*' . Grav::instance()['config']->get('system.param_sep') . '[^\:\#\/\?]*)/';
}
/**
* Return the IP address of the current user
*
@@ -761,7 +632,35 @@ class Uri
}
return $ip;
}
/**
* Returns current Uri.
*
* @return \Grav\Framework\Uri\Uri
*/
public static function getCurrentUri()
{
if (!static::$currentUri) {
static::$currentUri = UriFactory::createFromEnvironment($_SERVER);
}
return static::$currentUri;
}
/**
* Returns current route.
*
* @return \Grav\Framework\Route\Route
*/
public static function getCurrentRoute()
{
if (!static::$currentRoute) {
$uri = Grav::instance()['uri'];
static::$currentRoute = RouteFactory::createFromParts($uri->toArray());
}
return static::$currentRoute;
}
/**
@@ -776,26 +675,6 @@ class Uri
return Utils::startsWith($url, 'http');
}
public function __toString()
{
return static::buildUrl($this->toArray());
}
public function toArray()
{
return [
'scheme' => $this->scheme,
'host' => $this->host,
'port' => $this->port,
'user' => $this->user,
'pass' => $this->password,
'path' => $this->path,
'params' => $this->params,
'query' => $this->query,
'fragment' => $this->fragment
];
}
/**
* The opposite of built-in PHP method parse_url()
*
@@ -805,18 +684,19 @@ class Uri
*/
public static function buildUrl($parsed_url)
{
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : (isset($parsed_url['host']) ? '//' : '');
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
$pass = ($user || $pass) ? "{$pass}@" : '';
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . ':' : '';
$authority = isset($parsed_url['host']) ? '//' : '';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
$pass = ($user || $pass) ? "{$pass}@" : '';
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$path = !empty($parsed_url['params']) ? rtrim($path, '/') . static::buildParams($parsed_url['params']) : $path;
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
return "{$scheme}{$authority}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}";
}
/**
@@ -1001,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'));
@@ -1200,13 +1099,7 @@ class Uri
*/
public static function filterUserInfo($info)
{
return preg_replace_callback(
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',
function ($match) {
return rawurlencode($match[0]);
},
$info
);
return $info !== null ? UriPartsFilter::filterUserInfo($info) : '';
}
/**
@@ -1223,13 +1116,7 @@ class Uri
*/
public static function filterPath($path)
{
return preg_replace_callback(
'/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
function ($match) {
return rawurlencode($match[0]);
},
$path
);
return $path !== null ? UriPartsFilter::filterPath($path) : '';
}
/**
@@ -1240,12 +1127,254 @@ class Uri
*/
public static function filterQuery($query)
{
return preg_replace_callback(
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
function ($match) {
return rawurlencode($match[0]);
},
$query
);
return $query !== null ? UriPartsFilter::filterQueryOrFragment($query) : '';
}
/**
* @param array $env
*/
protected function createFromEnvironment(array $env)
{
// Build scheme.
if (isset($env['HTTP_X_FORWARDED_PROTO'])) {
$this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
} elseif (isset($env['X-FORWARDED-PROTO'])) {
$this->scheme = $env['X-FORWARDED-PROTO'];
} elseif (isset($env['REQUEST_SCHEME'])) {
$this->scheme = $env['REQUEST_SCHEME'];
} else {
$https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
$this->scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
}
// Build user and password.
$this->user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : null;
$this->password = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : null;
// Build host.
$hostname = 'localhost';
if (isset($env['HTTP_HOST'])) {
$hostname = $env['HTTP_HOST'];
} elseif (isset($env['SERVER_NAME'])) {
$hostname = $env['SERVER_NAME'];
}
// Remove port from HTTP_HOST generated $hostname
$hostname = Utils::substrToString($hostname, ':');
// Validate the hostname
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
// Build port.
if (isset($env['HTTP_X_FORWARDED_PORT'])) {
$this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
} elseif (isset($env['X-FORWARDED-PORT'])) {
$this->port = (int)$env['X-FORWARDED-PORT'];
} elseif (isset($env['SERVER_PORT'])) {
$this->port = (int)$env['SERVER_PORT'];
} else {
$this->port = null;
}
if ($this->hasStandardPort()) {
$this->port = null;
}
// Build path.
$request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
$this->path = rawurldecode(parse_url('http://example.com' . $request_uri, PHP_URL_PATH));
// Build query string.
$this->query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
if ($this->query === '') {
$this->query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
}
// Support ngnix routes.
if (strpos($this->query, '_url=') === 0) {
parse_str($this->query, $query);
unset($query['_url']);
$this->query = http_build_query($query);
}
// Build fragment.
$this->fragment = null;
// Filter userinfo, path and query string.
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
$this->query = static::filterQuery($this->query);
$this->reset();
}
/**
* Does this Uri use a standard port?
*
* @return bool
*/
protected function hasStandardPort()
{
return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
}
/**
* @param string $url
*/
protected function createFromString($url)
{
// Set Uri parts.
$parts = parse_url($url);
if ($parts === false) {
throw new \RuntimeException('Malformed URL: ' . $url);
}
$this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
$this->user = isset($parts['user']) ? $parts['user'] : null;
$this->password = isset($parts['pass']) ? $parts['pass'] : null;
$this->host = isset($parts['host']) ? $parts['host'] : null;
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
$this->path = isset($parts['path']) ? $parts['path'] : '';
$this->query = isset($parts['query']) ? $parts['query'] : '';
$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
// Validate the hostname
if ($this->host) {
$this->host = $this->validateHostname($this->host) ? $this->host : 'unknown';
}
// Filter userinfo, path, query string and fragment.
$this->user = $this->user !== null ? static::filterUserInfo($this->user) : null;
$this->password = $this->password !== null ? static::filterUserInfo($this->password) : null;
$this->path = empty($this->path) ? '/' : static::filterPath($this->path);
$this->query = static::filterQuery($this->query);
$this->fragment = $this->fragment !== null ? static::filterQuery($this->fragment) : null;
$this->reset();
}
protected function reset()
{
// resets
parse_str($this->query, $this->queries);
$this->extension = null;
$this->basename = null;
$this->paths = [];
$this->params = [];
$this->env = $this->buildEnvironment();
$this->uri = $this->path . (!empty($this->query) ? '?' . $this->query : '');
$this->base = $this->buildBaseUrl();
$this->root_path = $this->buildRootPath();
$this->root = $this->base . $this->root_path;
$this->url = $this->base . $this->uri;
}
/**
* Get's post from either $_POST or JSON response object
* By default returns all data, or can return a single item
*
* @param string $element
* @param string $filter_type
* @return array|mixed|null
*/
public function post($element = null, $filter_type = null)
{
if (!$this->post) {
$content_type = $this->getContentType();
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 && null !== $element) {
$item = Utils::getDotNotation($this->post, $element);
if ($filter_type) {
$item = filter_var($item, $filter_type);
}
return $item;
}
return $this->post;
}
/**
* Get content type from request
*
* @param bool $short
* @return null|string
*/
private function getContentType($short = true)
{
if (isset($_SERVER['CONTENT_TYPE'])) {
$content_type = $_SERVER['CONTENT_TYPE'];
if ($short) {
return Utils::substrToString($content_type,';');
}
return $content_type;
}
return null;
}
/**
* Get the base URI with port if needed
*
* @return string
*/
private function buildBaseUrl()
{
return $this->scheme() . $this->host;
}
/**
* Get the Grav Root Path
*
* @return string
*/
private function buildRootPath()
{
// In Windows script path uses backslash, convert it:
$scriptPath = str_replace('\\', '/', $_SERVER['PHP_SELF']);
$rootPath = str_replace(' ', '%20', rtrim(substr($scriptPath, 0, strpos($scriptPath, 'index.php')), '/'));
return $rootPath;
}
private function buildEnvironment()
{
// check for localhost variations
if ($this->host === '127.0.0.1' || $this->host === '::1') {
return 'localhost';
}
return $this->host ?: 'unknown';
}
/**
* Process any params based in this URL, supports any valid delimiter
*
* @param $uri
* @param string $delimiter
*
* @return string
*/
private function processParams($uri, $delimiter = ':')
{
if (strpos($uri, $delimiter) !== false) {
preg_match_all(static::paramsRegex(), $uri, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$param = explode($delimiter, $match[1]);
if (count($param) === 2) {
$plain_var = filter_var($param[1], FILTER_SANITIZE_STRING);
$this->params[$param[0]] = $plain_var;
$uri = str_replace($match[0], '', $uri);
}
}
}
return $uri;
}
}

View File

@@ -62,9 +62,9 @@ class User extends Data
$files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
// Try with username first, you never know!
if (in_array('username', $fields)) {
if (in_array('username', $fields, true)) {
$user = User::load($query);
unset($fields[array_search('username', $fields)]);
unset($fields[array_search('username', $fields, true)]);
} else {
$user = User::load('');
}
@@ -95,11 +95,41 @@ class User extends Data
public static function remove($username)
{
$file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
if ($file_path && unlink($file_path)) {
return true;
return $file_path && unlink($file_path);
}
/**
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
$value = parent::offsetExists($offset);
// Handle special case where user was logged in before 'authorized' was added to the user object.
if (false === $value && $offset === 'authorized') {
$value = $this->offsetExists('authenticated');
}
return false;
return $value;
}
/**
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
$value = parent::offsetGet($offset);
// Handle special case where user was logged in before 'authorized' was added to the user object.
if (null === $value && $offset === 'authorized') {
$value = $this->offsetGet('authenticated');
$this->offsetSet($offset, $value);
}
return $value;
}
/**
@@ -236,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);
}

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;
@@ -296,10 +308,6 @@ abstract class Utils
*/
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
{
if (mb_strlen($text) <= $length) {
return $text;
}
return Truncator::truncateLetters($text, $length, $ellipsis);
}
@@ -470,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
*
@@ -512,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
*
@@ -688,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);
}
@@ -709,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'])) {
@@ -724,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'));
@@ -772,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];
}
/**
@@ -822,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;
}
@@ -966,6 +1002,32 @@ abstract class Utils
return $ordered + $array;
}
/**
* Sort an array by a key value in the array
*
* @param $array
* @param $array_key
* @param int $direction
* @param int $sort_flags
* @return array
*/
public static function sortArrayByKey($array, $array_key, $direction = SORT_DESC, $sort_flags = SORT_REGULAR )
{
$output = [];
if (!is_array($array) || !$array) {
return $output;
}
foreach ($array as $key => $row) {
$output[$key] = $row[$array_key];
}
array_multisort($output, $direction, $sort_flags, $array);
return $array;
}
/**
* Get's path based on a token
*

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

@@ -120,6 +120,8 @@ class CleanCommand extends Command
'vendor/league/climate/composer.json',
'vendor/matthiasmullie/minify/bin',
'vendor/matthiasmullie/minify/composer.json',
'vendor/matthiasmullie/minify/docker-composer.yml',
'vendor/matthiasmullie/minify/Dockerfile',
'vendor/matthiasmullie/minify/CONTRIBUTING.md',
'vendor/matthiasmullie/path-converter/composer.json',
'vendor/maximebf/debugbar/bower.json',
@@ -151,6 +153,7 @@ class CleanCommand extends Command
'vendor/pimple/pimple/src/Pimple/Tests',
'vendor/psr/container/composer.json',
'vendor/psr/container/.gitignore',
'vendor/psr/simple-cache/composer.json',
'vendor/psr/log/composer.json',
'vendor/psr/log/.gitignore',
'vendor/rockettheme/toolbox/.git',

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

@@ -182,7 +182,7 @@ class InstallCommand extends ConsoleCommand
//We're done installing dependencies. Install the actual packages
foreach ($this->data as $data) {
foreach ($data as $package_name => $package) {
if (in_array($package_name, array_keys($dependencies))) {
if (array_key_exists($package_name, $dependencies)) {
$this->output->writeln("<green>Package " . $package_name . " already installed as dependency</green>");
} else {
$is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path);
@@ -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

@@ -102,8 +102,8 @@ class SelfupgradeCommand extends ConsoleCommand
if (!$this->upgrader->meetsRequirements()) {
$this->output->writeln("<red>ATTENTION:</red>");
$this->output->writeln(" Grav has increased the minimum PHP requirement.");
$this->output->writeln(" You are currently running PHP <red>" . PHP_VERSION . "</red>, but PHP <green>" . GRAV_PHP_MIN . "</green> is required.");
$this->output->writeln(" Additional information: <white>http://getgrav.org/blog/changing-php-requirements-to-5.5</white>");
$this->output->writeln(" You are currently running PHP <red>" . phpversion() . "</red>, but PHP <green>" . $this->upgrader->minPHPVersion() . "</green> is required.");
$this->output->writeln(" Additional information: <white>http://getgrav.org/blog/changing-php-requirements</white>");
$this->output->writeln("");
$this->output->writeln("Selfupgrade aborted.");
$this->output->writeln("");

View File

@@ -185,7 +185,7 @@ class UninstallCommand extends ConsoleCommand
if (is_array($dependency)) {
$dependency = $dependency['name'];
}
if ($dependency === 'grav') {
if ($dependency === 'grav' || $dependency === 'php') {
continue;
}

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

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

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