Compare commits

...

224 Commits
1.1.6 ... 1.2.0

Author SHA1 Message Date
Andy Miller
aabc8aeb31 Merge branch 'release/1.2.0' 2017-03-31 11:28:48 -06:00
Andy Miller
ca4f6f3c5c Prepare for release 2017-03-31 11:28:34 -06:00
Andy Miller
3a645ab4a2 updated changelog 2017-03-31 11:09:40 -06:00
Andy Miller
25ba6198e5 switched to stable composer vendor libs 2017-03-31 11:09:29 -06:00
Andy Miller
6564ea98b1 Analysis fixes 2017-03-30 14:30:20 -07:00
Andy Miller
8fe018a7dd Add file upload for user avatar in admin profile 2017-03-29 22:43:26 -07:00
Hugh Barnes
5c40337ff0 CSS pipeline rewrite URL fix for webserver root-based paths (#1383)
* Implement fix to not rewrite paths in CSS pipeline when CSS URLs use a path startng at webserver root (e.g. url('/path/to/asset')) - fixes #1382

* Amend CSS pipeline's resource location checking predicates to use Utils::startsWith, and consolidate into a single predicate

* Add back colon to data URL prefix test

Copy error in last commit would have broken any relative path beginning with the string 'data'
2017-03-26 17:50:22 -07:00
Frédéric Potvin
41e7142dfc [#1200] Clarified install and direct-install (#1384)
To help user that need to install specific version of a package.
So far it wasn't absolutely clear how to achieve and some
users ended up asking for help or opening issues asking
for the feature.
2017-03-26 10:02:50 -07:00
Frédéric Potvin
f45362b5b5 Invite users to join Slack instead of Gitter (#1385)
Since the team is now on Slack.
2017-03-26 10:00:42 -07:00
Andy Miller
f8fbc82196 Prepare for release 2017-03-22 17:30:00 -06:00
Andy Miller
c153039457 Fixed loading issues with improperly named themes #1373 2017-03-22 17:29:47 -06:00
Andy Miller
88ccc25c47 Added an admin only section for child ordering 2017-03-22 17:12:36 -06:00
Andy Miller
b8164d3fe6 changelog updated 2017-03-22 12:10:37 -06:00
Andy Miller
a1d4199e68 Merge branch 'feature/ordering-refactor' into develop 2017-03-22 12:09:47 -06:00
Andy Miller
30f8eca905 Allow Twig url() function to pass through external URLs 2017-03-22 12:04:24 -06:00
Andy Miller
ef7ff9ec4e Improved reorder logic and removed old logic 2017-03-21 20:20:08 -06:00
Andy Miller
0d471599d7 Changed logic for order handling 2017-03-21 15:35:16 -06:00
Andy Miller
13bc19f1e3 Moved debugger logic to avoid null page instances. 2017-03-20 15:25:41 -06:00
Andy Miller
da61196b7b Cleanup package files via GPM install to make them more windows-friendly #1361 2017-03-20 13:30:53 -06:00
Andy Miller
04e1710de1 Updated composer.json to pick up latest Gregwar Image library 2017-03-20 11:26:31 -06:00
Andy Miller
330a90b0ab Added language_codes to Twig init to allow for easy language name/code/native-name lookup 2017-03-20 10:36:26 -06:00
Andy Miller
5c34556246 Fix for page-level debugger override changing the option site-wide 2017-03-20 10:19:37 -06:00
Andy Miller
10a15ef33f Simplified some logic and fixed an issue with system-level process twig logic #1351 2017-03-19 21:24:46 -06:00
Andy Miller
4eebc81808 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2017-03-18 13:54:29 -06:00
Andy Miller
cd37ea0689 Chaned image quality field to use new range field 2017-03-18 13:54:25 -06:00
ricardo118
7e50c340c7 Fix class for Config/System"Default ordering" (#1359)
Fix class for both "Default ordering" and "Append URL extension"
2017-03-18 11:50:37 -06:00
Ole Vik
701f18e782 Fix field class for Config/Site "Default language" (#1357) 2017-03-18 11:50:18 -06:00
Andy Miller
66a21db504 Prepare for release 2017-03-17 15:52:39 -06:00
Andy Miller
0290ffd26e Added the ability for a specific page to disable debugger 2017-03-17 15:13:12 -06:00
Andy Miller
b5cfb53d78 Updated vendor libraries 2017-03-15 18:16:03 -06:00
Andy Miller
252b2f71a0 Stopped invalid derivates throwing fatal error #1341 2017-03-15 18:15:34 -06:00
Andy Miller
262951ece4 Updated changelog 2017-03-15 17:28:00 -06:00
Andy Miller
874160480f Optimized theme autoloader to work with hyphenated classnames #1353 2017-03-15 17:21:49 -06:00
Andy Miller
79d6b8ab22 Set default release to testing during RC 2017-03-13 19:20:08 -06:00
Andy Miller
04228ecee9 Prepare for release 2017-03-13 18:38:06 -06:00
Andy Miller
3cf8bd5928 Improved evaluate() Twig function to use environment/context 2017-03-13 13:39:17 -06:00
Josh Weiss
c86d791d44 Fix for the blueprint fix. (#1334)
Discussed with @mahagr.

Saved YAML configs gave undesired extra parameter (!!float 1 for example) because floats were not being cast back to integers upon save. This was even true when the filterNumber function was giving back a correct parameter. filter_var from validateFloat was actually perserving the float variable type which the YAML engine perserved upon Yaml::dump.

This is a unconventional fix, but it is the simplest way to handle this edge case.
2017-03-09 07:54:26 -07:00
Andy Miller
ec9342ced1 Removed ID from nonce_field() twig function causing validation error
https://github.com/getgrav/grav-plugin-form/issues/115
2017-03-08 17:08:22 -07:00
Andy Miller
23ba8a1386 Fixed Page::expires(0) that was not getting picked up 2017-03-08 15:35:15 -07:00
Andy Miller
4726873b57 Added new Page::folderExists() method 2017-03-05 18:38:34 -07:00
Andy Miller
90ea2fc067 Added block/line option to process markdown 2017-03-04 13:29:06 -07:00
Matias Griese
175fcb9415 Fixed exception when trying to find user account and there is no user://accounts folder 2017-02-27 12:53:24 +02:00
Andy Miller
da2a0f507b Added a fragment + query test 2017-02-26 14:10:43 -07:00
Flavio Copes
e771938672 Changelog 2017-02-26 08:55:22 +01:00
Josh Weiss
08e2bb558a Fixing a minor bug in Number validation. (#1329)
The function validateNumber only checks for numeric values.

**By PHP isNumeric**

"42",
1337,
0x539,
02471,
0b10100111001,
1337e0,
"not numeric",
array(),
9.1

**Will evaluate respectively**

'42' is numeric
'1337' is numeric
'1337' is numeric
'1337' is numeric
'1337' is numeric
'1337' is numeric
'not numeric' is NOT numeric
'Array' is NOT numeric
'9.1' is numeric

Grav though does not support all value types for a variety of reasons. One being YAML Blueprint definitions, where it makes sense to make a new type value for a more specialized format. Specifically for numbers if a more advance number format it would make sense to make a new type for that number format.

YAML spec specifically allows both Integer or Float contexts, as seen in the validate class validateInt and validateFloat. This is useful when the output formats explicitly needs to be a certain format.

However, in the case of generic numeric contexts, which numbers could be floats or ints dynamically, the values are cast back to an int currently in Grav numeric validation. Having dynamic primitive number formats is important, true in an interpreted language like JavaScript where 1 and 1.0 will both work for a number value.

The reason to cast the type of the variable still is to prevent wide selection of number formats, and to keep Grav in line with primitive YAML field formats.
2017-02-26 08:51:05 +01:00
Andy Miller
af304f14f4 Added offical_gpm_only check 2017-02-22 13:26:40 -07:00
Andy Miller
389cc296c2 Added Uri::isValidUrl() 2017-02-21 18:08:08 -07:00
Andy Miller
9f193904b5 Initial work to genericize direct-install (#1321) 2017-02-21 16:23:26 +01:00
Andy Miller
17e6e3ec11 Merge branch 'release/1.1.17' 2017-02-17 14:57:59 -07:00
Andy Miller
b969ab7deb Merge tag '1.1.17' into develop
Release v1.1.17
2017-02-17 14:57:59 -07:00
Andy Miller
50b355aaea Prepare for release 2017-02-17 14:57:48 -07:00
Matias Griese
b9424922a2 Changelog update 2017-02-17 10:38:05 +02:00
Matias Griese
14bde9f31f Fix Whoops displaying error page if there is PHP core warning or error (#980) 2017-02-17 10:35:33 +02:00
Flavio Copes
a3ccae5915 Fix GRAV_PHP_MIN for CLI 2017-02-16 10:37:35 +01:00
Flavio Copes
e2cc55a4e3 Fix syntax error in PHP 5.3. Move the version check before requiring the autoloaded deps 2017-02-16 10:16:30 +01:00
Andy Miller
724f24335a Updated changelog 2017-02-15 17:22:53 -07:00
Andy Miller
33a63de4f1 Fix for double extension during some redirects #1307 2017-02-15 17:21:16 -07:00
Pia Mancini
7ab0aee44a add space for sponsor's logo's to show inline (#1308) 2017-02-15 15:14:51 -08:00
Andy Miller
3e786e0ea9 Merge tag '1.1.16' into develop
Release v1.1.16
2017-02-10 11:52:33 -07:00
Andy Miller
994793acea Merge branch 'release/1.1.16' 2017-02-10 11:52:33 -07:00
Andy Miller
5b03125150 Prepare for release 2017-02-10 11:52:21 -07:00
Andy Miller
c36b26878f Updated changelog 2017-02-10 11:51:30 -07:00
Matias Griese
f8822de6fe Misc fixes to classes (type hinting, return types, missing variables...) (#1299) 2017-02-09 20:51:56 -07:00
Flavio Copes
97fe8095bd Add Languages::resetFallbackPageExtensions (#1276)
Resets the page_extensions value.
Useful to re-initialize the pages and change site language at runtime,
example:
```
$this->grav['language']->setActive('it');
$this->grav['language']->resetFallbackPageExtensions();
$this->grav['pages']->init();
```
2017-02-09 11:38:04 -08:00
Flavio Copes
212d35221a Fix blueprints slug validation (#1292)
* Fix blueprints slug validation

* 255 -> 200
2017-02-09 11:37:11 -08:00
Andy Miller
570b02a760 Added Pages::getPagesCacheId() 2017-02-08 16:38:05 -07:00
Andy Miller
c89914e63d Merge branch 'develop' of https://github.com/getgrav/grav into develop 2017-02-08 10:56:05 -07:00
Andy Miller
929fefbef4 Updated links to https:// 2017-02-08 10:55:57 -07:00
Matias Griese
030b020add Add some missing docblocks and typehints 2017-02-08 11:59:34 +02:00
Andy Miller
9100c54244 Fixes to allow not just ‘default/file’ cache in CLI (#1294)
* Fixes to allow not just ‘default/file’ cache in CLI

* vendor updates

* Better solution

* Added cli_compatibility to system blueprint

* Fall back to ‘file’ if cli_compatibility and (auto or volatile)
2017-02-07 13:48:50 -07:00
Flavio Copes
cab21f9834 Typo 2017-02-03 19:23:54 +01:00
Andy Miller
b707007e7d Added code to error output 2017-02-01 17:51:34 -07:00
Flavio Copes
a4c5d570ae Add query information to default Caddy config 2017-01-31 09:17:22 +01:00
Andy Miller
3dc0e84351 Updated readme with Gitter -> Slack 2017-01-30 18:45:14 -07:00
Andy Miller
993d93b067 Merge branch 'release/1.1.15' 2017-01-30 14:14:16 -07:00
Andy Miller
d269989c50 Merge branch 'release/1.1.15' into develop 2017-01-30 14:14:16 -07:00
Andy Miller
e17721b4bd Prepare for release 2017-01-30 14:13:32 -07:00
Andy Miller
f37bebaacd Added open collective info in readme 2017-01-30 13:29:36 -07:00
Andy Miller
f287dab16d Need to experiment more.. This is causing more issues than it fixes. 2017-01-30 13:19:21 -07:00
Andy Miller
31fe300a1e Merge branch 'develop' of https://github.com/getgrav/grav into develop 2017-01-30 13:17:25 -07:00
Andy Miller
f1faea26e5 Code insight fix 2017-01-30 13:17:20 -07:00
Pia Mancini
422b9907cf Add backers and sponsors from Open Collective (#1281)
This is for your open collective backers and sponsors to appear directly on your README. 
see how it'll look [here](https://github.com/apex/apex#backers)
[More info](https://github.com/opencollective/opencollective/wiki/Github-banner)

Also add badges on top.
2017-01-30 11:47:09 -08:00
Andy Miller
24bffcde80 Don’t set default param_sep, use system constant (#1275)
* Don’t set default param_sep, use system constant, but still allow override

* Automatically fix param_sep on windows

* Reverted back to original system.yaml syntax

* Comment fix

* PSR formating fixes

* Ensure automatic override of `param_sep` is only in cases where it’s different from system + windows + apache
2017-01-30 11:36:37 -07:00
Andy Miller
b7c76b5a53 Composer libs updated 2017-01-30 10:27:03 -07:00
Andy Miller
c7619e5b52 Updated composer libs 2017-01-27 16:45:23 -07:00
Andy Miller
3d05574d08 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2017-01-27 07:21:08 -07:00
Andy Miller
6e8455fde6 Updated composer.phar 2017-01-27 07:20:58 -07:00
Matias Griese
c6c8577b6f Silence E_WARNING: Zend OPcache API is restricted by "restrict_api" configuration directive 2017-01-27 10:29:55 +02:00
A----
108312e8c0 Provided empty alt if '' is supplied (#1262)
* Provided empty alt if '' is supplied

* Fixing some tests

* Regexp tests

* Again.
2017-01-26 16:30:39 -07:00
Andy Miller
060f21f83d Skip symlinks in cache clearing if found - #1269 2017-01-24 20:36:22 -07:00
Flavio Copes
4d904e6f70 Add an additional parameter to GPM::findPackage to avoid throwing an exception, for use in Twig, re #1008 2017-01-22 15:48:26 +01:00
Andy Miller
6968e2edff Fix Response object to handle 303 redirects when open_basedir in effect [#1267] 2017-01-21 16:25:13 -07:00
Andy Miller
300f65c22e Implode an array returned by sort order #1264 2017-01-20 15:17:28 -07:00
Andy Miller
5ecf240c26 Added new Collection::merge() method #1258 2017-01-20 10:47:24 -07:00
Andy Miller
9536f2e418 Merge branch 'release/1.1.14' into develop 2017-01-18 15:13:14 -07:00
Andy Miller
645285ca5c Merge branch 'release/1.1.14' 2017-01-18 15:13:13 -07:00
Andy Miller
c7fd01a644 Prepare for release 2017-01-18 15:12:14 -07:00
Andy Miller
61c0c31992 Fix to allow you to enable content-encoding: identity. Set to ‘none’ by default. #548 2017-01-18 15:02:53 -07:00
Matias Griese
e2ee02a71d Fixed page.collection() returning array and not Collection object when header variable did not exist 2017-01-18 10:42:13 +02:00
Andy Miller
4e283322ea Merge branch 'release/1.1.13' into develop 2017-01-17 13:20:08 -07:00
Andy Miller
b5b452e585 Merge branch 'release/1.1.13' 2017-01-17 13:20:07 -07:00
Andy Miller
138abdcab1 Prepare for release 2017-01-17 13:19:19 -07:00
Andy Miller
bf661c4355 Vendor updates 2017-01-17 12:06:59 -07:00
Flavio Copes
085ca323e2 Fix #1254 issue in trying to process broken symlink 2017-01-17 14:25:42 +01:00
Matias Griese
c22b28f312 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	CHANGELOG.md
2017-01-13 12:28:23 +02:00
Matias Griese
9b5ef4c263 Fixed all $_GET parameters missing in Nginx (please update your nginx.conf) [#1245](https://github.com/getgrav/grav/issues/1245) 2017-01-13 12:27:21 +02:00
Andy Miller
3cffe74965 Updated changelog 2017-01-12 11:55:10 -07:00
Andy Miller
759ba5143f Added ability to never cache twig. This makes it possible to cache content, but always process twig. Useful for regular but especially modular pages. (#1244) 2017-01-12 11:51:12 -07:00
Andy Miller
9ebff2287c Fixed an error in the ‘file’ type description 2017-01-12 10:59:05 -07:00
Andy Miller
e492fbde21 Hash added to blueprints 2017-01-12 06:40:51 -07:00
Andy Miller
7255556819 Fixed issue with multi-lang site caching non-lang specific pages for all langs #1211 2017-01-11 16:03:51 -07:00
Matias Griese
c718b8f32a Fixed broken hash method on page modifications detection 2017-01-09 21:23:11 +02:00
Andy Miller
07b66dd5d0 Vendor updates 2017-01-06 15:51:14 -07:00
Andy Miller
2e4686fada Updated changelog 2017-01-05 16:04:50 -07:00
ka7
e2544feeaf spelling fixes (#1236) 2017-01-05 16:02:45 -07:00
Andy Miller
970bf77492 Feature/theme dev improvements (#1232)
* Initial improvements to help theme development

* Added default language to site
2017-01-05 16:02:23 -07:00
Flavio Copes
0145f454b7 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	CHANGELOG.md
2017-01-05 16:25:01 +01:00
Flavio Copes
e984d9b68f Lint 2017-01-05 16:08:37 +01:00
Flavio Copes
1cd6773ded Fix renaming the folder name if the page, in the default language, had a custom slug set in its header 2017-01-05 16:08:25 +01:00
Matias Griese
0445aa707b Fixed issue with Content-Encoding: none. It should really be Content-Encoding: identity instead 2017-01-05 12:03:22 +02:00
Flavio Copes
cc96d160a4 Try rename before doing copy&delete 2017-01-04 16:43:56 +01:00
Flavio Copes
4331ab374e Fix issue with pages folders validation not accepting uppercase letters 2017-01-04 16:35:38 +01:00
Flavio Copes
61005360a5 Add a better fix for #635. Overwrites https://github.com/getgrav/grav/pull/1214 2017-01-03 21:39:57 +01:00
Flavio Copes
335c44385a Fixes #635 use mv instead of rename as that does not support cross volume operations (#1214)
* Fixes #635 use mv instead of rename as that does not support cross volume operations

* Handle case exec is disabled

* Handle case is windows, where mv does not work

* Move isWindows check to Utils
2017-01-03 09:28:25 +01:00
Nicolas Lœuillet
88e9ad3df2 Updated gitignore for user/config/security.yaml (#1231) 2017-01-02 20:51:33 -07:00
Andy Miller
2b19414598 Merge branch 'release/1.1.12' 2016-12-26 09:50:32 -07:00
Andy Miller
b00cd00259 Merge branch 'release/1.1.12' into develop 2016-12-26 09:50:32 -07:00
Andy Miller
6097431021 Prepare for release 2016-12-26 09:50:18 -07:00
Andy Miller
55a9356681 Fix for #1227 issue - Admin JSON calls throwing errors with debugger on 2016-12-26 09:47:51 -07:00
Flavio Copes
8aee946682 Fix changelog 2016-12-23 16:12:21 +01:00
Andy Miller
523d3a331a Merge branch 'release/1.1.11' into develop 2016-12-22 11:43:10 -07:00
Andy Miller
397107b611 Merge branch 'release/1.1.11' 2016-12-22 11:43:09 -07:00
Andy Miller
342af3deba Prepare for release 2016-12-22 11:42:56 -07:00
Flavio Copes
dc92498cd0 Fix #900 issue with modular pages folders validation 2016-12-22 17:36:53 +01:00
Andy Miller
364209a27d Fixed type on template not found 2016-12-21 15:16:30 -07:00
Andy Miller
46d741a2ed Merge branch 'release/1.1.10' 2016-12-21 13:06:53 -07:00
Andy Miller
b5be9ee3f0 Merge branch 'release/1.1.10' into develop 2016-12-21 13:06:53 -07:00
Andy Miller
e0f17a48d5 Prepare for release 2016-12-21 13:06:42 -07:00
Andy Miller
fa27856bc0 Reworked changelog 2016-12-21 12:50:01 -07:00
Andy Miller
befaf5d387 Improved theme/plugin detect logic 2016-12-21 12:35:58 -07:00
Flavio Copes
9571e992d9 Improve changelog line 2016-12-20 18:05:04 +01:00
Flavio Copes
e40bed5be2 Explicitly expose array_unique Twig filter 2016-12-20 17:37:32 +01:00
Flavio Copes
442249c3a1 Fix issue with Inflector when translation is disabled [https://github.com/getgrav/grav-plugin-simplesearch/issues/87](https://github.com/getgrav/grav-plugin-simplesearch/issues/87) 2016-12-19 21:07:43 +01:00
Flavio Copes
8b8d8bcc5b Fix https://github.com/getgrav/grav-plugin-admin/issues/891 Add pattern for frontend validation of folder slugs 2016-12-17 18:10:19 +01:00
Flavio Copes
d2152cb48e Changelog 2016-12-17 15:09:58 +01:00
Andy Miller
81fc0d47ac Reworked PHP CLI router PR #1218 (#1219) 2016-12-17 15:07:40 +01:00
ChrisGitIt
8450f77443 fixed array handling (#1208)
if the input array looks like this: 
array('valA','valB') (not a keyed array)

The return result before my change was:
array(array('valA'),array('valB'))

The return result after my change is:
array('valA','valB')
2016-12-16 12:09:23 -07:00
Flavio Copes
0ccc34d860 Improve detection of home path. Also allow ~/.grav on Windows, drop ConsoleTrait::isWindows() method, used just for that. (#1204)
* Improve detection of home path. Also allow ~/.grav on Windows, drop ConsoleTrait::isWindows() method, used just for that.

* Extract loadLocalConfig method to ConsoleTrait

* Fix issue with using Yaml::parse direcly on a filename, now deprecated

* Changelog
2016-12-16 19:10:00 +01:00
Flavio Copes
5b6452d89e Revert "Fix #635 use mv instead of rename as that does not support cross volume operations"
This reverts commit d61d260ef1.
2016-12-14 23:17:02 +01:00
Flavio Copes
d61d260ef1 Fix #635 use mv instead of rename as that does not support cross volume operations 2016-12-14 22:40:42 +01:00
Djamil Legato
1125b51f27 Fixed case where extracting a package would cause an error during rename 2016-12-13 18:45:34 -08:00
Andy Miller
bf552e22f1 Merge branch 'release/1.1.9' into develop 2016-12-13 13:06:16 -07:00
Andy Miller
b740142668 Merge branch 'release/1.1.9' 2016-12-13 13:06:15 -07:00
Andy Miller
d23f829559 prepare for release 2016-12-13 13:05:57 -07:00
Matias Griese
85bf215dc6 Changelog update 2016-12-12 09:37:02 +02:00
Matias Griese
9db04abd1c Add support for calling Media object as function to get medium by filename 2016-12-12 09:31:33 +02:00
Andy Miller
156f645576 Set Grav to be in 'testing' while in RC state 2016-12-11 18:22:54 -07:00
Flavio Copes
d3b654bdb0 Added checks before accessing admin reference during Page::blueprints() call 2016-12-10 18:34:13 +01:00
Andy Miller
7ed078ce31 unused 'use' statements 2016-12-08 16:10:44 -07:00
Andy Miller
3c6df48b8b Insight fixes 2016-12-08 15:49:45 -07:00
Matias Griese
833cd497bb YAML syntax fixes 2016-12-08 12:57:40 +02:00
Matias Griese
625f3d3a34 Better error handling in cache clear
Added new parameter `remove` for `onBeforeCacheClear` event
2016-12-08 12:57:18 +02:00
Andy Miller
380157f9cc prepare for release 2016-12-07 16:05:12 -07:00
Andy Miller
74c005d39c Added new onBeforeCacheClear event 2016-12-07 14:48:00 -07:00
Aaron Dalton
7b2716dab1 Added warning to command when new Grav version is available (#1194) 2016-12-07 14:16:00 -07:00
Djamil Legato
906c090bd4 Fixed issue with redirect of a page getting moved to a different location 2016-12-07 12:56:00 -08:00
Andy Miller
27ad9a24eb Updated vendor libraries 2016-12-07 11:51:56 -07:00
Matias Griese
83fdecbdd1 Added stream support for images (![Sepia Image](image://image.jpg?sepia))
Added stream support for links (`[Download PDF](user://data/pdf/my.pdf)`)
2016-12-07 19:51:06 +02:00
Flavio Copes
6c1a76b901 Use permissions field in group details. Fix saving permissions 2016-12-05 19:17:46 +01:00
Flavio Copes
4a5847784a Thanks to @hughbris for taxonomy filter issue 896fb8138b 2016-12-05 10:06:47 +01:00
Flavio Copes
896fb8138b Fix #1184 Fix issue with using a multiple taxonomy filter of which one had no results
If there are 2+ taxonomies required but one has 0 items, it was just
picking the ones corresponding to the other taxonomy, thus generating a
wrong result. Adding an empty array will later make array_intersect_key
generate an empty array if the operation is `and`
2016-12-03 13:47:25 +01:00
Andy Miller
678c445799 updated changelog 2016-12-02 10:27:39 -07:00
Fredrik Ekelund
36428e4735 Added ability to pass an array of explicit widths to ImageMedium#derivatives (#1133)
* Added ability to pass an array of explicit widths to ImageMedium#derivatives

Allows for more precise control than the min-width, max-width and
step parameters.

* ImageMedium#derivatives can now be called with an array from Markdown as well

Previously it was only possible from Twig code or PHP code
2016-12-02 10:21:59 -07:00
Vivien HAAG
10da784d53 Fix Page Collections problem with @page.modular (#1178) 2016-12-02 10:16:05 -07:00
Djamil Legato
c64cdb5dad Typos in changelog 2016-12-01 12:42:59 -08:00
Djamil Legato
52b68a0a1b Add range(int min, int max, int step) twig function to generate an array of numbers between min and max, inclusive 2016-12-01 12:40:21 -08:00
Flavio Copes
ee1742af1f Sync webserver-configs/htaccess.txt with .htaccess 2016-11-29 11:33:50 +01:00
Matias Griese
6315283a3a Fix publish date ordering 2016-11-29 12:29:16 +02:00
Flavio Copes
d8b3f215a2 Use new permissions field in user account 2016-11-28 19:00:25 +01:00
Flavio Copes
3838de1d97 If an array field has ignore_empty: true, only save options with a value 2016-11-28 18:59:41 +01:00
Flavio Copes
f6ddba52d8 Added alias selfupdate to the self-upgrade bin/gpm CLI command 2016-11-28 13:27:23 +01:00
Andy Miller
f7b35c3b79 Updated version 2016-11-26 20:22:33 -07:00
Flavio Copes
8dd65b709d Fix #946 issue with the sample nginx.conf, security section moved before PHP handler, and fixed backup folder uncorrectly named as backups 2016-11-25 22:32:14 +01:00
Flavio Copes
3064fe8ad9 Fix #713 Remove mappings missing if not cloned from github 2016-11-25 18:33:52 +01:00
Matias Griese
287a329a4d Added two new sort order options for pages: publish_date and unpublish_date 2016-11-24 15:03:51 +02:00
Flavio Copes
923b2469f9 Fix https://github.com/getgrav/grav-plugin-admin/issues/874 use updated class namespace 2016-11-24 13:50:15 +01:00
Flavio Copes
55bb4cf2fa Fix #1179 issue when we have a meta file without corresponding media
Also update changelog entries
2016-11-22 17:22:27 +01:00
Flavio Copes
5105be338a Decode single taxonomy params (#1164)
Allows to use commas in taxonomy terms
2016-11-19 11:32:54 -07:00
Amélie Turgeon
20e36c8a00 Add 2 new language values for French (#1174)
set the name and native name for these two.
(fr-FR and fr-CA)
2016-11-19 11:32:05 -07:00
Flavio Copes
9dd4f690a8 Allow to override sorting flags for page header-based or default ordering. (#1173)
If the `intl` PHP extension is loaded, only these flags are available:
https://secure.php.net/manual/en/collator.asort.php. Otherwise, you can
use the PHP standard sorting flags
(https://secure.php.net/manual/en/array.constants.php)
[#1169](https://github.com/getgrav/grav/issues/1169)
2016-11-19 11:29:28 -07:00
Matias Griese
9a15b5ebdc Multisite: Create image cache folder if it doesn't exist 2016-11-15 16:10:34 +02:00
Andy Miller
5c003d38be Ready for an RC.1 release 2016-11-09 10:58:13 -07:00
Andy Miller
95ab80b8f9 Added message service into core Grav 2016-11-09 10:48:36 -07:00
Andy Miller
079468c609 Added User::find() method 2016-11-08 11:17:01 -07:00
Andy Miller
7c98ca7134 Added Base32 encode/decode class 2016-11-07 12:19:04 -07:00
Matias Griese
fbf9c345b9 Fixed URI path in multi-site when query parameters were used in front page 2016-11-07 08:45:02 +02:00
Andy Miller
491e73eade Fixed CompiledJsonFile as it was not caching properly 2016-11-04 06:47:27 -06:00
Djamil Legato
e73773672b Removed internal docs reference for Folder::create and Folder::mkdir 2016-11-01 17:07:06 -07:00
Andy Miller
98d022ee49 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2016-11-01 13:03:53 -06:00
Andy Miller
abca8ce433 CompiledJsonFile::content() should support $assoc 2016-11-01 13:03:43 -06:00
Flavio Copes
2dcd4aeaad Changelog 2016-11-01 13:54:06 +01:00
Fredrik Ekelund
58d4e3384e Use correct ratio when applying image filters to image alternatives (#1147)
Because of a previous change, the keys of the image alternatives array
is no longer the ratio, but rather the image width. We now make sure to
calculate the ratio correctly and use the appropriate one when applying
the image filters.
2016-11-01 13:52:50 +01:00
Andy Miller
76aed8a119 Added CompiledJsonFile 2016-10-28 13:49:11 -06:00
Flavio Copes
37b8ffb7d2 Fix #1135 return max available number of items when calling random() on a collection passing an int > available items 2016-10-27 21:04:25 +02:00
Flavio Copes
af53d79e5e Add Caddyfile for newer Caddy versions #1115 2016-10-25 18:58:52 +02:00
Flavio Copes
c3aa11abeb Changelog 2016-10-25 16:33:57 +02:00
Gilles van Eeden
8e1454b3ab functions added to taxonomy and twigextension (#1124)
* Update Taxonomy.php

* Update TwigExtension.php

* Update TwigExtension.php

Incorrect comment updated.
2016-10-25 16:32:18 +02:00
Flavio Copes
1c32f4eaee Changelog 2016-10-25 16:27:24 +02:00
Adam Roe
cd15b9197b Don't truncate HTML if content length is less than summary size (#1125), fixes #1114 2016-10-25 16:25:56 +02:00
Andy Miller
afc18236c2 Fixed issue with site redirects/routes not processing with extensions 2016-10-24 10:13:21 -06:00
Andy Miller
9bac4df02a Merge branch 'release/1.1.8' 2016-10-22 22:38:47 -06:00
Andy Miller
fb1c8eb80d Merge branch 'release/1.1.8' into develop 2016-10-22 22:38:47 -06:00
Andy Miller
19f3a24257 version update 2016-10-22 22:38:37 -06:00
Andy Miller
5bf95d8b87 Fixed an issues with unset SSL setting #1132 2016-10-22 22:36:58 -06:00
Andy Miller
7d7bb0d52a Merge branch 'release/1.1.7' 2016-10-22 20:48:51 -06:00
Andy Miller
0a459d256d Merge branch 'release/1.1.7' into develop 2016-10-22 20:48:51 -06:00
Andy Miller
6185edcc1b version update 2016-10-22 20:48:37 -06:00
Andy Miller
814c726323 Changelog updated 2016-10-22 20:47:58 -06:00
Fredrik Ekelund
db4c9c1844 ImageMedium#derivatives now works with image filters (#1107)
* ImageMedium->derivatives now works with image filters

Previously, using ImageMedium->derivatives would not work well in
combination with image filters or the other method of generating
srcset variants of images (by appending eg. "@2x" to their
filenames). This commit hopefully fixes that.

* Modified initialization of image alternatives

The biggest alternative will now become the base medium, and
alternatives will be filled out as necessary in a descending
manner.

* Fully reset image when derivatives method is called

Otherwise we get some funky results, with the possibility of two
different images being rendered between the full-width srcset
version and the original src version.

* Account for risk of original file not existing when generating image derivatives

* Fixed an issue where too many alternatives would be generated

When using naming conventions to generate image alternatives, this
patch would previously generate a “@1x” alternative if one didn’t
exist. That’s no longer the case

* Add an "@1x" alternative when an image lacks a base medium

When an image only has an alternative medium - ie. the only
version of the image ends in eg. "@3x", then we construct the
missing alternatives automatically. Previously, we would only do
this down till "@2x", meaning that the image that would have been
the base medium, had the image been manually resized, wasn't
created. This has now been fixed.

* Always make smallest image alternative the base medium

When an image lacks a base medium on disk (eg. the only existing
image is an @2x version), then we make a scaled down version the
base medium, which ensures that the smaller version is served up
in the src attribute in the HTML.

Also, don't reset the image alternatives when calling
ImageMedium#derivatives, instead only generate the image
alternatives that are missing.

* Set better prettynames for image derivatives

* Changed image derivatives prettynames to be width based

Instead of example2x.jpeg, we now have eg. example1280w.jpeg
2016-10-22 20:44:28 -06:00
Djamil Legato
a96820af36 Ensuring fallback to true for verify_peer, just in case 2016-10-20 11:01:11 -07:00
Djamil Legato
f2c6829cd9 Reverted change 2016-10-20 10:58:47 -07:00
Djamil Legato
790429e286 Only pass verify_peer settings to cURL and fopen if the setting is disabled 2016-10-20 10:54:05 -07:00
Andy Miller
b42366cad2 remote image test 2016-10-19 13:28:35 -06:00
Andy Miller
122db6330e Merge branch 'release/1.1.6' into develop 2016-10-19 09:29:14 -06:00
106 changed files with 2990 additions and 1362 deletions

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ user/plugins/*
user/themes/*
!user/themes/.*
user/localhost/config/security.yaml
user/config/security.yaml
# OS Generated
.DS_Store*

View File

@@ -1,3 +1,225 @@
# v1.2.0
## 03/31/2017
1. [](#new)
* Added file upload for user avatar in user/admin blueprint
1. [](#improved)
* Analysis fixes
* Switched to stable composer lib versions
# v1.2.0-rc.3
## 03/22/2017
1. [](#new)
* Refactored Page re-ordering to handle all siblings at once
* Added `language_codes` to Twig init to allow for easy language name/code/native-name lookup
1. [](#improved)
* Added an _Admin Overrides_ section with option to choose the order of children in Pages Management
1. [](#bugfix)
* Fixed loading issues with improperly named themes (use old broken method first) [#1373](https://github.com/getgrav/grav/issues/1373)
* Simplified modular/twig processing logic and fixed an issue with system process config [#1351](https://github.com/getgrav/grav/issues/1351)
* Cleanup package files via GPM install to make them more windows-friendly [#1361](https://github.com/getgrav/grav/pull/1361)
* Fix for page-level debugger override changing the option site-wide
* Allow `url()` twig function to pass-through external links
# v1.2.0-rc.2
## 03/17/2017
1. [](#improved)
* Updated vendor libraries to latest
* Added the ability to disable debugger on a per-page basis with `debugger: false` in page frontmatter
1. [](#bugfix)
* Fixed an issue with theme inheritance and hyphenated base themes [#1353](https://github.com/getgrav/grav/issues/1353)
* Fixed an issue when trying to use an `@2x` derivative on a non-image media file [#1341](https://github.com/getgrav/grav/issues/1341)
# v1.2.0-rc.1
## 03/13/2017
1. [](#new)
* Added default setting to only allow `direct-installs` from official GPM. Can be configured in `system.yaml`
* Added a new `Utils::isValidUrl()` method
* Added optional parameter to `|markdown(false)` filter to toggle block/line processing (default|true = `block`)
* Added new `Page::folderExists()` method
1. [](#improved)
* `Twig::evaluate()` now takes current environment and context into account
* Genericized `direct-install` so it can be called via Admin plugin
1. [](#bugfix)
* Fixed a minor bug in Number validation [#1329](https://github.com/getgrav/grav/issues/1329)
* Fixed exception when trying to find user account and there is no `user://accounts` folder
* Fixed issue when setting `Page::expires(0)` [Admin #1009](https://github.com/getgrav/grav-plugin-admin/issues/1009)
* Removed ID from `nonce_field()` Twig function causing validation errors [Form #115](https://github.com/getgrav/grav-plugin-form/issues/115)
# v1.1.17
## 02/17/2017
1. [](#bugfix)
* Fix for double extensions getting added during some redirects [#1307](https://github.com/getgrav/grav/issues/1307)
* Fix syntax error in PHP 5.3. Move the version check before requiring the autoloaded deps
* Fix Whoops displaying error page if there is PHP core warning or error [Admin #980](https://github.com/getgrav/grav-plugin-admin/issues/980)
# v1.1.16
## 02/10/2017
1. [](#new)
* Exposed the Pages cache ID for use by plugins (e.g. Form) via `Pages::getPagesCacheId()`
* Added `Languages::resetFallbackPageExtensions()` regarding [#1276](https://github.com/getgrav/grav/pull/1276)
1. [](#improved)
* Allowed CLI to use non-volatile cache drivers for better integration with CLI and Web caches
* Added Gantry5-compatible query information to Caddy configuration
* Added some missing docblocks and type-hints
* Various code cleanups (return types, missing variables in doclbocks, etc.)
1. [](#bugfix)
* Fix blueprints slug validation [https://github.com/getgrav/grav-plugin-admin/issues/955](https://github.com/getgrav/grav-plugin-admin/issues/955)
# v1.1.15
## 01/30/2017
1. [](#new)
* Added a new `Collection::merge()` method to allow merging of multiple collections [#1258](https://github.com/getgrav/grav/pull/1258)
* Added [OpenCollective](https://opencollective.com/grav) backer/sponsor info to `README.md`
1. [](#improved)
* Add an additional parameter to GPM::findPackage to avoid throwing an exception, for use in Twig [#1008](https://github.com/getgrav/grav/issues/1008)
* Skip symlinks if found while clearing cache [#1269](https://github.com/getgrav/grav/issues/1269)
1. [](#bugfix)
* Fixed an issue when page collection with header-based `sort.by` returns an array [#1264](https://github.com/getgrav/grav/issues/1264)
* Fix `Response` object to handle `303` redirects when `open_basedir` in effect [#1267](https://github.com/getgrav/grav/issues/1267)
* Silence `E_WARNING: Zend OPcache API is restricted by "restrict_api" configuration directive`
# v1.1.14
## 01/18/2017
1. [](#bugfix)
* Fixed `Page::collection()` returning array and not Collection object when header variable did not exist
* Revert `Content-Encoding: identity` fix, and let you set `cache: allow_webserver_gzip:` option to switch to `identity` [#548](https://github.com/getgrav/grav/issues/548)
# v1.1.13
## 01/17/2017
1. [](#new)
* Added new `never_cache_twig` page option in `system.yaml` and frontmatter. Allows dynamic Twig logic in regular and modular Twig templates [#1244](https://github.com/getgrav/grav/pull/1244)
1. [](#improved)
* Several improvements to aid theme development [#232](https://github.com/getgrav/grav/pull/1232)
* Added `hash` cache check option and made dropdown more descriptive [Admin #923](https://github.com/getgrav/grav-plugin-admin/issues/923)
1. [](#bugfix)
* Fixed cross volume file system operations [#635](https://github.com/getgrav/grav/issues/635)
* Fix issue with pages folders validation not accepting uppercase letters
* Fix renaming the folder name if the page, in the default language, had a custom slug set in its header
* Fixed issue with `Content-Encoding: none`. It should really be `Content-Encoding: identity` instead
* Fixed broken `hash` method on page modifications detection
* Fixed issue with multi-lang pages not caching independently without unique `.md` file [#1211](https://github.com/getgrav/grav/issues/1211)
* Fixed all `$_GET` parameters missing in Nginx (please update your nginx.conf) [#1245](https://github.com/getgrav/grav/issues/1245)
* Fixed issue in trying to process broken symlink [#1254](https://github.com/getgrav/grav/issues/1254)
# v1.1.12
## 12/26/2016
1. [](#bugfix)
* Fixed issue with JSON calls throwing errors due to debugger enabled [#1227](https://github.com/getgrav/grav/issues/1227)
# v1.1.11
## 12/22/2016
1. [](#improved)
* Fall back properly to HTML if template type not found
1. [](#bugfix)
* Fix issue with modular pages folders validation [#900](https://github.com/getgrav/grav-plugin-admin/issues/900)
# v1.1.10
## 12/21/2016
1. [](#improved)
* Improve detection of home path. Also allow `~/.grav` on Windows, drop `ConsoleTrait::isWindows()` method, used only for that [#1204](https://github.com/getgrav/grav/pull/1204)
* Reworked PHP CLI router [#1219](https://github.com/getgrav/grav/pull/1219)
* More robust theme/plugin logic in `bin/gpm direct-install`
1. [](#bugfix)
* Fixed case where extracting a package would cause an error during rename
* Fix issue with using `Yaml::parse` direcly on a filename, now deprecated
* Add pattern for frontend validation of folder slugs [#891](https://github.com/getgrav/grav-plugin-admin/issues/891)
* Fix issue with Inflector when translation is disabled [SimpleSearch #87](https://github.com/getgrav/grav-plugin-simplesearch/issues/87)
* Explicitly expose `array_unique` Twig filter [Admin #897](https://github.com/getgrav/grav-plugin-admin/issues/897)
# v1.1.9
## 12/13/2016
1. [](#new)
* RC released as stable
1. [](#improved)
* Better error handling in cache clear
* YAML syntax fixes for the future compatibility
* Added new parameter `remove` for `onBeforeCacheClear` event
* Add support for calling Media object as function to get medium by filename
1. [](#bugfix)
* Added checks before accessing admin reference during `Page::blueprints()` call. Allows to access `page.blueprints` from Twig in the frontend
# v1.1.9-rc.3
## 12/07/2016
1. [](#new)
* Add `ignore_empty` property to be used on array fields, if positive only save options with a value
* Use new `permissions` field in user account
* Add `range(int start, int end, int step)` twig function to generate an array of numbers between start and end, inclusive
* New retina Media image derivatives array support (`![](image.jpg?derivatives=[640,1024,1440])`) [#1147](https://github.com/getgrav/grav/pull/1147)
* Added stream support for images (`![Sepia Image](image://image.jpg?sepia)`)
* Added stream support for links (`[Download PDF](user://data/pdf/my.pdf)`)
* Added new `onBeforeCacheClear` event to add custom paths to cache clearing process
1. [](#improved)
* Added alias `selfupdate` to the `self-upgrade` `bin/gpm` CLI command
* Synced `webserver-configs/htaccess.txt` with `.htaccess`
* Use permissions field in group details.
* Updated vendor libraries
* Added a warning on GPM update to update Grav first if needed [#1194](https://github.com/getgrav/grav/pull/1194)
1. [](#bugfix)
* Fix page collections problem with `@page.modular` [#1178](https://github.com/getgrav/grav/pull/1178)
* Fix issue with using a multiple taxonomy filter of which one had no results, thanks to @hughbris [#1184](https://github.com/getgrav/grav/issues/1184)
* Fix saving permissions in group
* Fixed issue with redirect of a page getting moved to a different location
# v1.1.9-rc.2
## 11/26/2016
1. [](#new)
* Added two new sort order options for pages: `publish_date` and `unpublish_date` [#1173](https://github.com/getgrav/grav/pull/1173))
1. [](#improved)
* Multisite: Create image cache folder if it doesn't exist
* Add 2 new language values for French [#1174](https://github.com/getgrav/grav/issues/1174)
1. [](#bugfix)
* Fixed issue when we have a meta file without corresponding media [#1179](https://github.com/getgrav/grav/issues/1179)
* Update class namespace for Admin class [Admin #874](https://github.com/getgrav/grav-plugin-admin/issues/874)
# v1.1.9-rc.1
## 11/09/2016
1. [](#new)
* Added a `CompiledJsonFile` object to better handle Json files.
* Added Base32 encode/decode class
* Added a new `User::find()` method
1. [](#improved)
* Moved `messages` object into core Grav from login plugin
* Added `getTaxonomyItemKeys` to the Taxonomy object [#1124](https://github.com/getgrav/grav/issues/1124)
* Added a `redirect_me` Twig function [#1124](https://github.com/getgrav/grav/issues/1124)
* Added a Caddyfile for newer Caddy versions [#1115](https://github.com/getgrav/grav/issues/1115)
* Allow to override sorting flags for page header-based or default ordering. If the `intl` PHP extension is loaded, only these flags are available: https://secure.php.net/manual/en/collator.asort.php. Otherwise, you can use the PHP standard sorting flags (https://secure.php.net/manual/en/array.constants.php) [#1169](https://github.com/getgrav/grav/issues/1169)
1. [](#bugfix)
* Fixed an issue with site redirects/routes, not processing with extension (.html, .json, etc.)
* Don't truncate HTML if content length is less than summary size [#1125](https://github.com/getgrav/grav/issues/1125)
* Return max available number when calling random() on a collection passing an int > available items [#1135](https://github.com/getgrav/grav/issues/1135)
* Use correct ratio when applying image filters to image alternatives [#1147](https://github.com/getgrav/grav/issues/1147)
* Fixed URI path in multi-site when query parameters were used in front page
# v1.1.8
## 10/22/2016
1. [](#bugfix)
* Fixed warning with unset `ssl` option when using GPM [#1132](https://github.com/getgrav/grav/issues/1132)
# v1.1.7
## 10/22/2016
1. [](#improved)
* Improved the capabilities of Image derivatives [#1107](https://github.com/getgrav/grav/pull/1107)
1. [](#bugfix)
* Only pass verify_peer settings to cURL and fopen if the setting is disabled [#1120](https://github.com/getgrav/grav/issues/1120)
# v1.1.6
## 10/19/2016
@@ -25,7 +247,7 @@
1. [](#bugfix)
* Fixed missing `progress` method in the DirectInstall Command
* `Response` class now handles better unsuccessful requests such as 404 and 401
* Fixed saving of `external` page types [admin #789](https://github.com/getgrav/grav-plugin-admin/issues/789)
* Fixed saving of `external` page types [Admin #789](https://github.com/getgrav/grav-plugin-admin/issues/789)
* Fixed issue deleting parent folder of folder with `param_sep` in the folder name [admin #796](https://github.com/getgrav/grav-plugin-admin/issues/796)
* Fixed an issue with streams in `bin/plugin`
* Fixed `jpeg` file format support in Media
@@ -830,7 +1052,7 @@
* Added new `onImageMediumSaved()` event (useful for post-image processing)
* Added `Vary: Accept-Encoding` option
2. [](#improved)
* Multilang-safe delimeter position
* Multilang-safe delimiter position
* Refactored Twig classes and added optional umask setting
* Removed `pageinit()` timing
* `Page->routable()` now takes `published()` state into account

View File

@@ -110,7 +110,7 @@ Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**Please ask first** in Gitter or in the Forum before embarking on any significant pull request (e.g.
**Please ask first** in [Slack](https://getgrav.org/slack) or in the Forum before embarking on any significant pull request (e.g.
implementing features, refactoring code..),
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.

View File

@@ -1,6 +1,6 @@
# ![](https://avatars1.githubusercontent.com/u/8237355?v=2&s=50) Grav
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/getgrav/grav.svg?branch=develop)](https://travis-ci.org/getgrav/grav)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Slack](https://grav-chat.now.sh/badge.svg)](https://chat.getgrav.org) [![Build Status](https://travis-ci.org/getgrav/grav.svg?branch=develop)](https://travis-ci.org/getgrav/grav) [![OpenCollective](https://opencollective.com/grav/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/grav/sponsors/badge.svg)](#sponsors)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
@@ -18,8 +18,8 @@ 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](http://learn.getgrav.org/basics/requirements#php-requirements)
- Check the [Apache](http://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](http://learn.getgrav.org/basics/requirements#iis-requirements) requirements
- PHP 5.5.9 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
@@ -27,7 +27,7 @@ These are the options to get Grav:
### Downloading a Grav Package
You can download a **ready-built** package from the [Downloads page on http://getgrav.org](http://getgrav.org/downloads)
You can download a **ready-built** package from the [Downloads page on https://getgrav.org](https://getgrav.org/downloads)
### With Composer
@@ -45,17 +45,17 @@ $ composer create-project getgrav/grav ~/webroot/grav
$ git clone https://github.com/getgrav/grav.git
```
2. Install the **plugin** and **theme dependencies** by using the [Grav CLI application](http://learn.getgrav.org/advanced/grav-cli) `bin/grav`:
2. Install the **plugin** and **theme dependencies** by using the [Grav CLI application](https://learn.getgrav.org/advanced/grav-cli) `bin/grav`:
```
$ cd ~/webroot/grav
$ bin/grav install
```
Check out the [install procedures](http://learn.getgrav.org/basics/installation) for more information.
Check out the [install procedures](https://learn.getgrav.org/basics/installation) for more information.
# Adding Functionality
You can download [plugins](http://getgrav.org/downloads/plugins) or [themes](http://getgrav.org/downloads/themes) manually from the appropriate tab on the [Downloads page on http://getgrav.org](http://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
You can download [plugins](https://getgrav.org/downloads/plugins) or [themes](https://getgrav.org/downloads/themes) manually from the appropriate tab on the [Downloads page on https://getgrav.org](https://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](https://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm index
@@ -69,7 +69,7 @@ $ bin/gpm install <plugin/theme>
# Updating
To update Grav you should use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
To update Grav you should use the [Grav Package Manager](https://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm selfupgrade
@@ -97,17 +97,86 @@ What you mainly want to know is that:
# Getting Started
* [What is Grav?](http://learn.getgrav.org/basics/what-is-grav)
* [Install](http://learn.getgrav.org/basics/installation) Grav in few seconds
* Understand the [Configuration](http://learn.getgrav.org/basics/grav-configuration)
* Take a peek at our available free [Skeletons](http://getgrav.org/downloads/skeletons)
* If you have questions, jump on our [Gitter Room](https://gitter.im/getgrav/grav)!
* [What is Grav?](https://learn.getgrav.org/basics/what-is-grav)
* [Install](https://learn.getgrav.org/basics/installation) Grav in few seconds
* Understand the [Configuration](https://learn.getgrav.org/basics/grav-configuration)
* Take a peek at our available free [Skeletons](https://getgrav.org/downloads/skeletons)
* If you have questions, jump on our [Slack Room](https://getgrav.org/slack)!
* Have fun!
# Exploring More
* Have a look at our [Basic Tutorial](http://learn.getgrav.org/basics/basic-tutorial)
* Dive into more [advanced](http://learn.getgrav.org/advanced) functions
* Have a look at our [Basic Tutorial](https://learn.getgrav.org/basics/basic-tutorial)
* Dive into more [advanced](https://learn.getgrav.org/advanced) functions
# Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/grav#backer)]
<a href="https://opencollective.com/grav/backer/0/website" target="_blank"><img src="https://opencollective.com/grav/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/1/website" target="_blank"><img src="https://opencollective.com/grav/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/2/website" target="_blank"><img src="https://opencollective.com/grav/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/3/website" target="_blank"><img src="https://opencollective.com/grav/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/4/website" target="_blank"><img src="https://opencollective.com/grav/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/5/website" target="_blank"><img src="https://opencollective.com/grav/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/6/website" target="_blank"><img src="https://opencollective.com/grav/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/7/website" target="_blank"><img src="https://opencollective.com/grav/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/8/website" target="_blank"><img src="https://opencollective.com/grav/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/9/website" target="_blank"><img src="https://opencollective.com/grav/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/10/website" target="_blank"><img src="https://opencollective.com/grav/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/11/website" target="_blank"><img src="https://opencollective.com/grav/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/12/website" target="_blank"><img src="https://opencollective.com/grav/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/13/website" target="_blank"><img src="https://opencollective.com/grav/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/14/website" target="_blank"><img src="https://opencollective.com/grav/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/15/website" target="_blank"><img src="https://opencollective.com/grav/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/16/website" target="_blank"><img src="https://opencollective.com/grav/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/17/website" target="_blank"><img src="https://opencollective.com/grav/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/18/website" target="_blank"><img src="https://opencollective.com/grav/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/19/website" target="_blank"><img src="https://opencollective.com/grav/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/20/website" target="_blank"><img src="https://opencollective.com/grav/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/21/website" target="_blank"><img src="https://opencollective.com/grav/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/22/website" target="_blank"><img src="https://opencollective.com/grav/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/23/website" target="_blank"><img src="https://opencollective.com/grav/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/24/website" target="_blank"><img src="https://opencollective.com/grav/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/25/website" target="_blank"><img src="https://opencollective.com/grav/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/26/website" target="_blank"><img src="https://opencollective.com/grav/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/27/website" target="_blank"><img src="https://opencollective.com/grav/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/28/website" target="_blank"><img src="https://opencollective.com/grav/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/grav/backer/29/website" target="_blank"><img src="https://opencollective.com/grav/backer/29/avatar.svg"></a>
# Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/grav#sponsor)]
<a href="https://opencollective.com/grav/sponsor/0/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/1/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/2/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/3/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/4/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/5/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/6/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/7/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/8/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/9/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/10/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/11/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/12/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/13/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/14/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/15/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/16/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/17/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/18/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/19/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/20/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/21/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/22/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/23/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/24/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/25/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/26/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/27/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/28/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/grav/sponsor/29/website" target="_blank"><img src="https://opencollective.com/grav/sponsor/29/avatar.svg"></a>
# License

Binary file not shown.

View File

@@ -8,7 +8,7 @@
"require": {
"php": ">=5.5.9",
"twig/twig": "~1.24",
"erusev/parsedown": "dev-master as 1.6.0",
"erusev/parsedown": "~1.6",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.8",
"symfony/console": "~2.8",
@@ -19,7 +19,7 @@
"filp/whoops": "~2.0",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "dev-master#72568cfbeb77515278f2ccb386fc344e874b7ae8",
"gregwar/image": "~2.0",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "~1.0",

619
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
*/
namespace Grav;
define('GRAV_PHP_MIN', '5.5.9');
// Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php';
@@ -14,16 +15,22 @@ if (!is_file($autoload)) {
die("Please run: <i>bin/grav install</i>");
}
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>");
}
}
use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
// Register the auto-loader.
$loader = require_once $autoload;
if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
}
// Register the auto-loader.
$loader = require_once $autoload;
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
@@ -44,6 +51,6 @@ $grav = Grav::instance(
try {
$grav->process();
} catch (\Exception $e) {
$grav->fireEvent('onFatalException', new Event(['exception' => $e]));
$grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
throw $e;
}

View File

@@ -16,6 +16,13 @@ form:
placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER
help: PLUGIN_ADMIN.SITE_TITLE_HELP
default_lang:
type: text
label: PLUGIN_ADMIN.SITE_DEFAULT_LANG
size: x-small
placeholder: PLUGIN_ADMIN.SITE_DEFAULT_LANG_PLACEHOLDER
help: PLUGIN_ADMIN.SITE_DEFAULT_LANG_HELP
author.name:
type: text
size: large

View File

@@ -102,7 +102,7 @@ form:
pages.order.by:
type: select
size: long
size: large
classes: fancy
label: PLUGIN_ADMIN.DEFAULT_ORDERING
help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP
@@ -155,6 +155,7 @@ form:
pages.append_url_extension:
type: text
size: x-small
placeholder: "e.g. .html"
label: PLUGIN_ADMIN.APPEND_URL_EXT
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
@@ -244,6 +245,17 @@ form:
validate:
type: bool
pages.never_cache_twig:
type: toggle
label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.frontmatter.process_twig:
type: toggle
label: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG
@@ -461,14 +473,15 @@ form:
cache.check.method:
type: select
size: small
size: medium
classes: fancy
label: PLUGIN_ADMIN.CACHE_CHECK_METHOD
help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP
options:
file: File
folder: Folder
none: None
file: Markdown + Yaml file timestamps
folder: Folder timestamps
hash: All files timestamps
none: No timestamp checking
cache.driver:
type: select
@@ -487,6 +500,55 @@ form:
wincache: WinCache
redis: Redis
cache.prefix:
type: text
size: x-small
label: PLUGIN_ADMIN.CACHE_PREFIX
help: PLUGIN_ADMIN.CACHE_PREFIX_HELP
placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER
cache.cli_compatibility:
type: toggle
label: PLUGIN_ADMIN.CLI_COMPATIBILITY
help: PLUGIN_ADMIN.CLI_COMPATIBILITY_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
cache.lifetime:
type: text
size: small
append: NICETIME.SECOND_PLURAL
label: PLUGIN_ADMIN.LIFETIME
help: PLUGIN_ADMIN.LIFETIME_HELP
validate:
type: number
cache.gzip:
type: toggle
label: PLUGIN_ADMIN.GZIP_COMPRESSION
help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
cache.allow_webserver_gzip:
type: toggle
label: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP
help: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
cache.memcache.server:
type: text
size: medium
@@ -536,32 +598,6 @@ form:
help: PLUGIN_ADMIN.REDIS_PORT_HELP
placeholder: "6379"
cache.prefix:
type: text
size: x-small
label: PLUGIN_ADMIN.CACHE_PREFIX
help: PLUGIN_ADMIN.CACHE_PREFIX_HELP
placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER
cache.lifetime:
type: text
size: small
append: NICETIME.SECOND_PLURAL
label: PLUGIN_ADMIN.LIFETIME
help: PLUGIN_ADMIN.LIFETIME_HELP
validate:
type: number
cache.gzip:
type: toggle
label: PLUGIN_ADMIN.GZIP_COMPRESSION
help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twig:
type: section
@@ -821,13 +857,12 @@ form:
fields:
images.default_image_quality:
type: text
type: range
append: '%'
label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY
help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP
classes: x-small
validate:
type: number
min: 1
max: 100
@@ -1022,6 +1057,19 @@ form:
fopen: PLUGIN_ADMIN.FOPEN
curl: PLUGIN_ADMIN.CURL
gpm.official_gpm_only:
type: toggle
label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
highlight: auto
help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool
gpm.verify_peer:
type: toggle
label: PLUGIN_ADMIN.GPM_VERIFY_PEER
@@ -1091,6 +1139,6 @@ form:
custom_base_url:
type: text
size: medium
placeholder: "e.g. http://localhost:8080"
placeholder: "e.g. http://yoursite.com/yourpath"
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP

View File

@@ -2,9 +2,9 @@ title: PLUGIN_ADMIN.DEFAULT
rules:
slug:
pattern: "[a-zа-я][a-zа0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -117,29 +117,18 @@ form:
title: PLUGIN_ADMIN.SETTINGS
underline: true
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
validate:
type: slug
rule: slug
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::rawRoute'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
@@ -165,9 +154,20 @@ form:
title: PLUGIN_ADMIN.ORDERING
underline: true
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
order:
type: order
label: PLUGIN_ADMIN.PAGE_ORDER
label: PLUGIN_ADMIN.SORTABLE_PAGES
sitemap:
overrides:
@@ -224,6 +224,30 @@ form:
twig: Twig
use: keys
header.twig_first:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.TWIG_FIRST
help: PLUGIN_ADMIN.TWIG_FIRST_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.never_cache_twig:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.child_type:
type: select
toggleable: true
@@ -267,6 +291,18 @@ form:
validate:
type: bool
header.debugger:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.DEBUGGER
help: PLUGIN_ADMIN.DEBUGGER_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header.template:
type: text
toggleable: true
@@ -278,6 +314,25 @@ form:
toggleable: true
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
admin_only:
type: section
title: PLUGIN_ADMIN.ADMIN_SPECIFIC_OVERRIDES
underline: true
fields:
header.admin.children_display_order:
type: select
label: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER
help: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER_HELP
toggleable: true
classes: fancy
default: 'collection'
options:
'default': 'Ordered by Folder name (default)'
'collection': 'Ordered by Collection definition'
header.order_by:
type: hidden

View File

@@ -3,12 +3,6 @@ title: PLUGIN_ADMIN:EXTERNAL
type: default
context: blueprints://pages
rules:
slug:
pattern: "[a-zа-я][a-zа-я0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:

View File

@@ -1,8 +1,8 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -22,7 +22,7 @@ form:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
validate:
type: slug
rule: slug
required: true
route:
@@ -30,7 +30,7 @@ form:
label: PLUGIN_ADMIN.PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::rawRoute'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
validate:
required: true

View File

@@ -1,8 +1,8 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -71,7 +71,7 @@ form:
type: text
label: PLUGIN_ADMIN.FILENAME
validate:
type: slug
rule: slug
required: true
route:
@@ -79,7 +79,7 @@ form:
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::rawRoute'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'': PLUGIN_ADMIN.DEFAULT_OPTION_SELECT
validate:

View File

@@ -1,9 +1,3 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
@@ -12,6 +6,6 @@ form:
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::rawRoute'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT

View File

@@ -1,8 +1,8 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -24,7 +24,7 @@ form:
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
type: slug
rule: slug
required: true
route:
@@ -32,7 +32,7 @@ form:
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::getLastPageRoute'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
validate:
@@ -44,7 +44,7 @@ form:
label: PLUGIN_ADMIN.PAGE_FILE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
data-options@: '\Grav\Common\Page\Pages::types'
data-default@: '\Grav\Plugin\admin::getLastPageName'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageName'
validate:
required: true

View File

@@ -1,8 +1,8 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -17,7 +17,7 @@ form:
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
type: slug
rule: slug
required: true
route:
@@ -25,7 +25,7 @@ form:
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::getLastPageRoute'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
validate:

View File

@@ -1,8 +1,8 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
@@ -71,7 +71,7 @@ form:
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
type: slug
rule: slug
required: true
route:
@@ -79,7 +79,7 @@ form:
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\admin::rawRoute'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT

View File

@@ -1,4 +1,4 @@
title: Site
title: Account
form:
validation: loose
@@ -8,6 +8,13 @@ form:
type: userinfo
size: large
avatar:
type: file
size: large
destination: 'user://accounts/avatars'
multiple: false
random_name: true
content:
type: section
title: PLUGIN_ADMIN.ACCOUNT
@@ -56,7 +63,7 @@ form:
label: PLUGIN_ADMIN.LANGUAGE
size: medium
classes: fancy
data-options@: '\Grav\Plugin\admin::adminLanguages'
data-options@: '\Grav\Plugin\Admin\Admin::adminLanguages'
default: 'en'
help: PLUGIN_ADMIN.LANGUAGE_HELP
@@ -77,16 +84,9 @@ form:
validate:
type: commalist
access.admin:
type: array
label: PLUGIN_ADMIN.ADMIN_ACCESS
multiple: false
validate:
type: array
access.site:
type: array
label: PLUGIN_ADMIN.SITE_ACCESS
multiple: false
access:
type: permissions
label: PLUGIN_ADMIN.PERMISSIONS
ignore_empty: true
validate:
type: array

View File

@@ -29,16 +29,9 @@ form:
size: small
label: PLUGIN_ADMIN_PRO.ICON
access.admin:
type: array
label: PLUGIN_ADMIN.ADMIN_ACCESS
multiple: false
validate:
type: array
access.site:
type: array
label: PLUGIN_ADMIN.SITE_ACCESS
multiple: false
access:
type: permissions
label: PLUGIN_ADMIN.PERMISSIONS
ignore_empty: true
validate:
type: array

View File

@@ -1,4 +1,5 @@
title: Grav # Name of the site
default_lang: en # Default language for site (potentially used by theme)
author:
name: John Appleseed # Default author name

View File

@@ -5,7 +5,7 @@ param_sep: ':' # Parameter separator, use ';' for A
wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
reverse_proxy_setup: false # Running in a reverse proxy scenario with different webserver ports than proxy
force_ssl: false # If enabled, Grav forces to be accessed via HTTPS (NOTE: Not an ideal solution)
custom_base_url: '' # Set the base_url manually
custom_base_url: '' # Set the base_url manually, e.g. http://yoursite.com/yourpath
languages:
supported: [] # List of languages supported. eg: [en, fr, de]
@@ -36,6 +36,7 @@ pages:
markdown: true # Process Markdown
twig: false # Process Twig
twig_first: false # Process Twig before markdown when processing both on a page
never_cache_twig: false # Only cache content, never cache twig processed in content (incompatible with `twig_first: true`)
events:
page: true # Enable page level events
twig: true # Enable Twig level events
@@ -50,7 +51,7 @@ pages:
types: [txt,xml,html,htm,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
last_modified: false # Set the last modified date header based on file modifcation timestamp
last_modified: false # Set the last modified date header based on file modification timestamp
etag: false # Set the etag header tag
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
redirect_default_route: false # Automatically redirect to a page's default route
@@ -70,8 +71,10 @@ cache:
method: file # Method to check for updates in pages: file|folder|hash|none
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
allow_webserver_gzip: false # If true, `content-encoding: identity` but connection isn't closed before `onShutDown()` event
redis:
socket: false # Path to redis unix socket (e.g. /var/run/redis/redis.sock), false = use server and port to connect
@@ -135,3 +138,4 @@ gpm:
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security

View File

@@ -8,10 +8,13 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.1.6');
define('GRAV_VERSION', '1.2.0');
define('GRAV_TESTING', false);
define('DS', '/');
define('GRAV_PHP_MIN', '5.5.9');
if (!defined('GRAV_PHP_MIN')) {
define('GRAV_PHP_MIN', '5.5.9');
}
// Directories and Paths
if (!defined('GRAV_ROOT')) {

26
system/router.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
/**
* @package Grav.Core
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
if (PHP_SAPI !== 'cli-server') {
exit('This script cannot be run from browser. Run it from a CLI.');
}
$_SERVER['PHP_CLI_ROUTER'] = true;
if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
return false;
}
$_SERVER = array_merge($_SERVER, $_ENV);
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'index.php';
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php';
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php';
require 'index.php';
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);

View File

@@ -889,7 +889,7 @@ class Assets
/**
* Removes an item from the CSS array if set
*
* @param $key the asset key
* @param string $key The asset key
*/
public function removeCss($key)
{
@@ -902,7 +902,7 @@ class Assets
/**
* Removes an item from the JS array if set
*
* @param $key the asset key
* @param string $key The asset key
*/
public function removeJs($key)
{
@@ -1255,13 +1255,8 @@ class Assets
$old_url = $matches[2];
// ensure this is not a data url
if (strpos($old_url, 'data:') === 0) {
return $matches[0];
}
// ensure this is not a remote url
if ($this->isRemoteLink($old_url)) {
// Ensure link is not rooted to webserver, a data URL, or to a remote host
if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
return $matches[0];
}

View File

@@ -11,7 +11,7 @@ namespace Grav\Common;
use \Doctrine\Common\Cache as DoctrineCache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
@@ -173,6 +173,12 @@ class Cache extends Getters
$setting = $this->driver_setting;
$driver_name = 'file';
// CLI compatibility requires a non-volatile cache driver
if ($this->config->get('system.cache.cli_compatibility') && (
$setting == 'auto' || $this->isVolatileDriver($setting))) {
$setting = $driver_name;
}
if (!$setting || $setting == 'auto') {
if (extension_loaded('apcu')) {
$driver_name = 'apcu';
@@ -250,7 +256,7 @@ class Cache extends Getters
*
* @param string $id the id of the cached entry
*
* @return object returns the cached entry, can be any type, or false if doesn't exist
* @return object|bool returns the cached entry, can be any type, or false if doesn't exist
*/
public function fetch($id)
{
@@ -356,36 +362,40 @@ class Cache extends Getters
$remove_paths = self::$standard_remove;
}
// Clearing cache event to add paths to clear
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
foreach ($remove_paths as $stream) {
// Convert stream to a real path
try {
$path = $locator->findResource($stream, true, true);
} catch (\Exception $e) {
// stream not found..
continue;
}
$anything = false;
$files = glob($path . '/*');
$anything = false;
$files = glob($path . '/*');
if (is_array($files)) {
foreach ($files as $file) {
if (is_file($file)) {
if (@unlink($file)) {
$anything = true;
}
} elseif (is_dir($file)) {
if (Folder::delete($file)) {
$anything = true;
if (is_array($files)) {
foreach ($files as $file) {
if (is_link($file)) {
$output[] = '<yellow>Skipping symlink: </yellow>' . $file;
} elseif (is_file($file)) {
if (@unlink($file)) {
$anything = true;
}
} elseif (is_dir($file)) {
if (Folder::delete($file)) {
$anything = true;
}
}
}
}
}
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '/*';
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '/*';
}
} catch (\Exception $e) {
// stream not found or another error while deleting files.
$output[] = '<red>ERROR: </red>' . $e->getMessage();
}
}
@@ -433,4 +443,39 @@ class Cache extends Getters
return $this->lifetime;
}
/**
* Returns the current driver name
*
* @return mixed
*/
public function getDriverName()
{
return $this->driver_name;
}
/**
* Returns the current driver setting
*
* @return mixed
*/
public function getDriverSetting()
{
return $this->driver_setting;
}
/**
* is this driver a volatile driver in that it resides in PHP process memory
*
* @param $setting
* @return bool
*/
public function isVolatileDriver($setting)
{
if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
return true;
} else {
return false;
}
}
}

View File

@@ -59,7 +59,7 @@ class CompiledLanguages extends CompiledBase
{
$file = CompiledYamlFile::instance($filename);
if (preg_match('|languages\.yaml$|', $filename)) {
$this->object->mergeRecursive($file->content());
$this->object->mergeRecursive((array)$file->content());
} else {
$this->object->join($name, $file->content(), '/');
}

View File

@@ -9,6 +9,7 @@
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;
@@ -338,7 +339,7 @@ class Validation
protected static function filterNumber($value, array $params, array $field)
{
return (int) $value;
return (string)(int)$value !== (string)(float)$value ? (float) $value : (int) $value;
}
protected static function filterDateTime($value, array $params, array $field)
@@ -569,6 +570,7 @@ class Validation
return null;
}
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
foreach ($values as $key => $value) {
@@ -580,9 +582,22 @@ class Validation
foreach ($values as $key => $value) {
if (is_array($value)) {
$value = implode(',', $value);
$values[$key] = array_map('trim', explode(',', $value));
} else {
$values[$key] = trim($value);
}
}
}
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
foreach ($values as $key => $value) {
foreach ($value as $inner_key => $inner_value) {
if ($inner_value == '') {
unset($value[$inner_key]);
}
}
$values[$key] = array_map('trim', explode(',', $value));
$values[$key] = $value;
}
}

View File

@@ -87,6 +87,14 @@ class Debugger
public function addAssets()
{
if ($this->enabled()) {
// Only add assets if Page is HTML
$page = $this->grav['page'];
if ($page->templateFormat() != 'html') {
$this->enabled = false;
return $this;
}
/** @var Assets $assets */
$assets = $this->grav['assets'];

View File

@@ -20,7 +20,8 @@ class Errors
$jsonRequest = $_SERVER && isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] == 'application/json';
// Setup Whoops-based error handler
$whoops = new \Whoops\Run;
$system = new SystemFacade;
$whoops = new \Whoops\Run($system);
$verbosity = 1;

View File

@@ -0,0 +1,39 @@
<?php
/**
* @package Grav.Common.Errors
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Errors;
class SystemFacade extends \Whoops\Util\SystemFacade
{
protected $whoopsShutdownHandler;
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)
{
$this->whoopsShutdownHandler = $function;
register_shutdown_function([$this, 'handleShutdown']);
}
/**
* Special case to deal with Fatal errors and the like.
*/
public function handleShutdown()
{
$error = $this->getLastError();
// Ignore core warnings and errors.
if ($error && !($error['type'] & (E_CORE_WARNING | E_CORE_ERROR))) {
$handler = $this->whoopsShutdownHandler;
$handler();
}
}
}

View File

@@ -23,58 +23,64 @@ trait CompiledFile
// Set some options
$this->settings(['native' => true, 'compat' => true]);
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
$key = md5($this->filename);
$file = PhpFile::instance(CACHE_DIR . DS . "compiled/files/{$key}{$this->extension}.php");
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) {
$key = md5($this->filename);
$file = PhpFile::instance(CACHE_DIR . DS . "compiled/files/{$key}{$this->extension}.php");
$modified = $this->modified();
$modified = $this->modified();
if (!$modified) {
return $this->decode($this->raw());
}
$class = get_class($this);
$cache = $file->exists() ? $file->content() : null;
// Load real file if cache isn't up to date (or is invalid).
if (
!isset($cache['@class'])
|| $cache['@class'] != $class
|| $cache['modified'] != $modified
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
try {
$file->lock(false);
} catch (\Exception $e) {
// Another process has locked the file; we will check this in a bit.
if (!$modified) {
return $this->decode($this->raw());
}
// Decode RAW file into compiled array.
$data = (array) $this->decode($this->raw());
$cache = [
'@class' => $class,
'filename' => $this->filename,
'modified' => $modified,
'data' => $data
];
$class = get_class($this);
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
$cache = $file->exists() ? $file->content() : null;
// Compile cached file into bytecode cache
if (function_exists('opcache_invalidate')) {
opcache_invalidate($file->filename(), true);
// Load real file if cache isn't up to date (or is invalid).
if (
!isset($cache['@class'])
|| $cache['@class'] != $class
|| $cache['modified'] != $modified
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
try {
$file->lock(false);
} catch (\Exception $e) {
// Another process has locked the file; we will check this in a bit.
}
// Decode RAW file into compiled array.
$data = (array)$this->decode($this->raw());
$cache = [
'@class' => $class,
'filename' => $this->filename,
'modified' => $modified,
'data' => $data
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
// Compile cached file into bytecode cache
if (function_exists('opcache_invalidate')) {
// Silence error if function exists, but is restricted.
@opcache_invalidate($file->filename(), true);
}
}
}
}
$file->free();
$file->free();
$this->content = $cache['data'];
$this->content = $cache['data'];
}
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
}
return parent::content($var);

View File

@@ -0,0 +1,28 @@
<?php
/**
* @package Grav.Common.File
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\JsonFile;
class CompiledJsonFile extends JsonFile
{
use CompiledFile;
/**
* Decode RAW string into contents.
*
* @param string $var
* @param bool $assoc
* @return array mixed
*/
protected function decode($var, $assoc = true)
{
return (array) json_decode($var, $assoc);
}
}

View File

@@ -70,9 +70,13 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $filepath => $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
try {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
} catch (\Exception $e) {
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
}
}
@@ -340,12 +344,10 @@ abstract class Folder
// Make sure that path to the target exists before moving.
self::create(dirname($target));
// Just rename the directory.
$success = @rename($source, $target);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
self::copy($source, $target);
self::delete($source);
}
// Make sure that the change will be detected when caching.
@@ -386,7 +388,6 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
{
@@ -396,7 +397,6 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function create($folder)
{

View File

@@ -17,7 +17,7 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
/**
* Create a RecursiveFilterIterator from a RecursiveIterator
*
* @param RecursiveIterator $iterator
* @param \RecursiveIterator $iterator
*/
public function __construct(\RecursiveIterator $iterator)
{

View File

@@ -9,6 +9,7 @@
namespace Grav\Common\GPM;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
@@ -74,7 +75,7 @@ class GPM extends Iterator
* Returns the Locally installable packages
*
* @param array $list_type_installed
* @return Iterator The installed packages
* @return array The installed packages
*/
public function getInstallable($list_type_installed = ['plugins' => true, 'themes' => true])
{
@@ -117,6 +118,8 @@ class GPM extends Iterator
if (isset($this->installed['themes'][$slug])) {
return $this->installed['themes'][$slug];
}
return null;
}
/**
@@ -199,7 +202,7 @@ class GPM extends Iterator
/**
* Returns an array of Plugins and Themes that can be updated.
* Plugins and Themes are extended with the `available` property that relies to the remote version
* @param $list_type_update specifies what type of package to update
* @param array $list_type_update specifies what type of package to update
* @return array Array of updatable Plugins and Themes.
* Format: ['total' => int, 'plugins' => array, 'themes' => array]
*/
@@ -222,7 +225,7 @@ class GPM extends Iterator
/**
* Returns an array of Plugins that can be updated.
* The Plugins are extended with the `available` property that relies to the remote version
* @return Iterator Array of updatable Plugins
* @return array Array of updatable Plugins
*/
public function getUpdatablePlugins()
{
@@ -302,7 +305,7 @@ class GPM extends Iterator
/**
* Returns an array of Themes that can be updated.
* The Themes are extended with the `available` property that relies to the remote version
* @return Iterator Array of updatable Themes
* @return array Array of updatable Themes
*/
public function getUpdatableThemes()
{
@@ -435,7 +438,7 @@ class GPM extends Iterator
/**
* Returns the list of Plugins and Themes available in the repository
* @return array Array of available Plugins and Themes
* @return Remote\Packages Available Plugins and Themes
* Format: ['plugins' => array, 'themes' => array]
*/
public function getRepository()
@@ -446,9 +449,10 @@ class GPM extends Iterator
/**
* Searches for a Package in the repository
* @param string $search Can be either the slug or the name
* @return Remote\Package Package if found, FALSE if not
* @param bool $ignore_exception True if should not fire an exception (for use in Twig)
* @return Remote\Package|bool Package if found, FALSE if not
*/
public function findPackage($search)
public function findPackage($search, $ignore_exception = false)
{
$search = strtolower($search);
@@ -470,6 +474,10 @@ class GPM extends Iterator
throw new \RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
}
if ($ignore_exception) {
return false;
}
throw new \RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
}
@@ -492,6 +500,151 @@ class GPM extends Iterator
return false;
}
/**
* Download the zip package via the URL
*
* @param $package_file
* @param $tmp
* @return null|string
*/
public static function downloadPackage($package_file, $tmp)
{
$package = parse_url($package_file);
$filename = basename($package['path']);
if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && $package['host'] !== 'getgrav.org') {
throw new \RuntimeException("Only offical GPM URLs are allowed. You can modify this behavior in the System configuration.");
}
$output = Response::get($package_file, []);
if ($output) {
Folder::mkdir($tmp);
file_put_contents($tmp . DS . $filename, $output);
return $tmp . DS . $filename;
}
return null;
}
/**
* Copy the local zip package to tmp
*
* @param $package_file
* @param $tmp
* @return null|string
*/
public static function copyPackage($package_file, $tmp)
{
$package_file = realpath($package_file);
if (file_exists($package_file)) {
$filename = basename($package_file);
Folder::mkdir($tmp);
copy(realpath($package_file), $tmp . DS . $filename);
return $tmp . DS . $filename;
}
return null;
}
/**
* Try to guess the package type from the source files
*
* @param $source
* @return bool|string
*/
public static function getPackageType($source)
{
$plugin_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Plugin/m';
$theme_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Theme/m';
if (
file_exists($source . 'system/defines.php') &&
file_exists($source . 'system/config/system.yaml')
) {
return 'grav';
} else {
// must have a blueprint
if (!file_exists($source . 'blueprints.yaml')) {
return false;
}
// either theme or plugin
$name = basename($source);
if (Utils::contains($name, 'theme')) {
return 'theme';
} elseif (Utils::contains($name, 'plugin')) {
return 'plugin';
}
foreach (glob($source . "*.php") as $filename) {
$contents = file_get_contents($filename);
if (preg_match($theme_regex, $contents)) {
return 'theme';
} elseif (preg_match($plugin_regex, $contents)) {
return 'plugin';
}
}
// Assume it's a theme
return 'theme';
}
}
/**
* Try to guess the package name from the source files
*
* @param $source
* @return bool|string
*/
public static function getPackageName($source)
{
foreach (glob($source . "*.yaml") as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if ($name == 'blueprints') {
continue;
}
return $name;
}
return false;
}
/**
* Find/Parse the blueprint file
*
* @param $source
* @return array|bool
*/
public static function getBlueprints($source)
{
$blueprint_file = $source . 'blueprints.yaml';
if (!file_exists($blueprint_file)) {
return false;
}
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
return $blueprint;
}
/**
* Get the install path for a name and a particular type of package
*
* @param $type
* @param $name
* @return string
*/
public static function getInstallPath($type, $name)
{
$locator = Grav::instance()['locator'];
if ($type == 'theme') {
$install_path = $locator->findResource('themes://', false) . DS . $name;
} else {
$install_path = $locator->findResource('plugins://', false) . DS . $name;
}
return $install_path;
}
/**
* Searches for a list of Packages in the repository
* @param array $searches An array of either slugs or names
@@ -595,6 +748,8 @@ class GPM extends Iterator
return $dependency[$dependency_slug];
}
}
return null;
}
/**

View File

@@ -181,7 +181,7 @@ class Installer
return false;
}
$package_folder_name = $zip->getNameIndex(0);
$package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
$zip->close();
$extracted_folder = $destination . '/' . $package_folder_name;

View File

@@ -115,23 +115,25 @@ class Response
// SSL Verify Peer and Proxy Setting
$settings = [
'method' => $config->get('system.gpm.method', self::$method),
'verify_peer' => $config->get('system.gpm.verify_peer'),
'verify_peer' => $config->get('system.gpm.verify_peer', true),
// `system.proxy_url` is for fallback
// introduced with 1.1.0-beta.1 probably safe to remove at some point
'proxy_url' => $config->get('system.gpm.proxy_url', $config->get('system.proxy_url', false)),
];
$overrides = array_replace_recursive([], $overrides, [
'curl' => [
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
],
'fopen' => [
'ssl' => [
'verify_peer' => $settings['verify_peer'],
'verify_peer_name' => $settings['verify_peer'],
if (!$settings['verify_peer']) {
$overrides = array_replace_recursive([], $overrides, [
'curl' => [
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
],
'fopen' => [
'ssl' => [
'verify_peer' => $settings['verify_peer'],
'verify_peer_name' => $settings['verify_peer'],
]
]
]
]);
]);
}
// Proxy Setting
if ($settings['proxy_url']) {
@@ -188,11 +190,19 @@ class Response
}
/**
* Progress normalized for cURL and Fopen
* Accepts a vsariable length of arguments passed in by stream method
* Is this a remote file or not
*
* @return array Normalized array with useful data.
* Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int]
* @param $file
* @return bool
*/
public static function isRemote($file)
{
return (bool) filter_var($file, FILTER_VALIDATE_URL);
}
/**
* Progress normalized for cURL and Fopen
* Accepts a variable length of arguments passed in by stream method
*/
public static function progress()
{
@@ -241,6 +251,8 @@ class Response
if (self::isCurlAvailable()) {
return self::getCurl(func_get_args());
}
return null;
}
/**
@@ -262,13 +274,18 @@ class Response
$options['fopen']['notification'] = ['self', 'progress'];
}
$ssl = $options['fopen']['ssl'];
unset($options['fopen']['ssl']);
if (isset($options['fopen']['ssl'])) {
$ssl = $options['fopen']['ssl'];
unset($options['fopen']['ssl']);
$stream = stream_context_create([
'http' => $options['fopen'],
'ssl' => $ssl
], $options['fopen']);
} else {
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
}
$stream = stream_context_create([
'http' => $options['fopen'],
'ssl' => $ssl
], $options['fopen']);
$content = @file_get_contents($uri, false, $stream);
@@ -284,7 +301,7 @@ class Response
case '401':
throw new \RuntimeException("Invalid LICENSE");
default:
throw new \RuntimeException("Error while trying to download '$uri'\n");
throw new \RuntimeException("Error while trying to download (code: $code): $uri \n");
}
}
@@ -320,7 +337,7 @@ class Response
case '401':
throw new \RuntimeException("Invalid LICENSE");
default:
throw new \RuntimeException("Error while trying to download '$uri'\nMessage: $error_message");
throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
}
}
@@ -354,7 +371,7 @@ class Response
return curl_exec($ch);
}
$max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 3;
$max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 5;
$options['curl'][CURLOPT_FOLLOWLOCATION] = false;
// open_basedir set but no redirects to follow, we can disable followlocation and proceed normally
@@ -379,7 +396,7 @@ class Response
$code = 0;
} else {
$code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
if ($code == 301 || $code == 302) {
if ($code == 301 || $code == 302 || $code == 303) {
preg_match('/Location:(.*?)\n/', $header, $matches);
$uri = trim(array_pop($matches));
} else {

View File

@@ -8,6 +8,8 @@
namespace Grav\Common\GPM;
use Grav\Common\GPM\Remote\GravCore;
/**
* Class Upgrader
*
@@ -18,17 +20,10 @@ class Upgrader
/**
* Remote details about latest Grav version
*
* @var Packages
* @var GravCore
*/
private $remote;
/**
* Internal cache
*
* @var Iterator
*/
protected $cache;
/**
* Creates a new GPM instance with Local and Remote packages available
*

View File

@@ -8,7 +8,11 @@
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;
use RocketTheme\Toolbox\DI\Container;
use RocketTheme\Toolbox\Event\Event;
@@ -29,6 +33,7 @@ class Grav extends Container
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'cache' => 'Grav\Common\Cache',
'session' => 'Grav\Common\Session',
'Grav\Common\Service\MessagesServiceProvider',
'plugins' => 'Grav\Common\Plugins',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
@@ -305,7 +310,12 @@ class Grav extends Container
} else {
// Without gzip we have no other choice than to prevent server from compressing the output.
// This action turns off mod_deflate which would prevent us from closing the connection.
header('Content-Encoding: none');
if ($this['config']->get('system.cache.allow_webserver_gzip')) {
header('Content-Encoding: identity');
} else {
header('Content-Encoding: none');
}
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* @package Grav.Common.Helpers
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
class Base32 {
protected static $base32Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
protected static $base32Lookup = array(
0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
);
/**
* Encode in Base32
*
* @param $bytes
* @return string
*/
public static function encode( $bytes ) {
$i = 0; $index = 0; $digit = 0;
$base32 = "";
$bytes_len = strlen($bytes);
while( $i < $bytes_len ) {
$currByte = ord($bytes{$i});
/* Is the current digit going to span a byte boundary? */
if( $index > 3 ) {
if( ($i + 1) < $bytes_len ) {
$nextByte = ord($bytes{$i+1});
} else {
$nextByte = 0;
}
$digit = $currByte & (0xFF >> $index);
$index = ($index + 5) % 8;
$digit <<= $index;
$digit |= $nextByte >> (8 - $index);
$i++;
} else {
$digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
$index = ($index + 5) % 8;
if( $index == 0 ) $i++;
}
$base32 .= self::$base32Chars{$digit};
}
return $base32;
}
/**
* Decode in Base32
*
* @param $base32
* @return string
*/
public static function decode( $base32 ) {
$bytes = array();
$base32_len = strlen($base32);
for( $i=$base32_len*5/8-1; $i>=0; --$i ) {
$bytes[] = 0;
}
for( $i = 0, $index = 0, $offset = 0; $i < $base32_len; $i++ ) {
$lookup = ord($base32{$i}) - ord('0');
/* Skip chars outside the lookup table */
if( $lookup < 0 || $lookup >= count(self::$base32Lookup) ) {
continue;
}
$digit = self::$base32Lookup[$lookup];
/* If this digit is not in the table, ignore it */
if( $digit == 0xFF ) continue;
if( $index <= 3 ) {
$index = ($index + 5) % 8;
if( $index == 0) {
$bytes[$offset] |= $digit;
$offset++;
if( $offset >= count($bytes) ) break;
} else {
$bytes[$offset] |= $digit << (8 - $index);
}
} else {
$index = ($index + 5) % 8;
$bytes[$offset] |= ($digit >> $index);
$offset++;
if ($offset >= count($bytes) ) break;
$bytes[$offset] |= $digit << (8 - $index);
}
}
$bites = "";
foreach( $bytes as $byte ) $bites .= chr($byte);
return $bites;
}
}

View File

@@ -9,21 +9,22 @@
namespace Grav\Common\Helpers;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Common\Page\Medium\Medium;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Excerpts
{
/**
* Process Grav image media URL from HTML tag
*
* @param $html HTML tag e.g. `<img src="image.jpg" />`
* @param $page The current page object
* @return string Returns final HTML string
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
* @param Page $page The current page object
* @return string Returns final HTML string
*/
public static function processImageHtml($html, $page)
public static function processImageHtml($html, Page $page)
{
$excerpt = static::getExcerptFromHtml($html, 'img');
@@ -47,8 +48,8 @@ class Excerpts
/**
* Get an Excerpt array from a chunk of HTML
*
* @param $html Chunk of HTML
* @param $tag a tag, for example `img`
* @param string $html Chunk of HTML
* @param string $tag A tag, for example `img`
* @return array|null returns nested array excerpt
*/
public static function getExcerptFromHtml($html, $tag)
@@ -109,17 +110,17 @@ class Excerpts
* Process a Link excerpt
*
* @param $excerpt
* @param $page
* @param Page $page
* @param string $type
* @return mixed
*/
public static function processLinkExcerpt($excerpt, $page, $type = 'link')
public static function processLinkExcerpt($excerpt, Page $page, $type = 'link')
{
$url = $excerpt['element']['attributes']['href'];
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['href']));
$url_parts = parse_url(htmlspecialchars_decode(urldecode($url)));
$url_parts = static::parseUrl($url);
// if there is a query, then parse it and build action calls
// If there is a query, then parse it and build action calls.
if (isset($url_parts['query'])) {
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
@@ -129,19 +130,19 @@ class Excerpts
return $carry;
}, []);
// valid attributes supported
// Valid attributes supported.
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
// Unless told to not process, go through actions
// Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) {
unset($actions['noprocess']);
} else {
// loop through actions for the image and call them
// Loop through actions for the image and call them.
foreach ($actions as $attrib => $value) {
$key = $attrib;
if (in_array($attrib, $valid_attributes)) {
// support both class and classes
// support both class and classes.
if ($attrib == 'classes') {
$attrib = 'class';
}
@@ -154,25 +155,33 @@ class Excerpts
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
}
// if no query elements left, unset query
// If no query elements left, unset query.
if (empty($url_parts['query'])) {
unset ($url_parts['query']);
}
// set path to / if not set
// Set path to / if not set.
if (empty($url_parts['path'])) {
$url_parts['path'] = '';
}
// if special scheme, just return
if(isset($url_parts['scheme']) && !Utils::startsWith($url_parts['scheme'], 'http')) {
// If scheme isn't http(s)..
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
// Handle custom streams.
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
unset($url_parts['stream'], $url_parts['scheme']);
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
}
return $excerpt;
}
// handle paths and such
// Handle paths and such.
$url_parts = Uri::convertUrl($page, $url_parts, $type);
// build the URL from the component parts and set it on the element
// Build the URL from the component parts and set it on the element.
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
return $excerpt;
@@ -181,68 +190,72 @@ class Excerpts
/**
* Process an image excerpt
*
* @param $excerpt
* @param $page
* @param array $excerpt
* @param Page $page
* @return mixed
*/
public static function processImageExcerpt($excerpt, $page)
public static function processImageExcerpt(array $excerpt, Page $page)
{
$url = $excerpt['element']['attributes']['src'];
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
$url_parts = static::parseUrl($url);
$url_parts = parse_url(htmlspecialchars_decode(urldecode($url)));
$media = null;
$filename = null;
if (isset($url_parts['scheme']) && !Utils::startsWith($url_parts['scheme'], 'http')) {
$stream_path = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'];
$url_parts['path'] = $stream_path;
unset($url_parts['host']);
unset($url_parts['scheme']);
}
if (!empty($url_parts['stream'])) {
$filename = $url_parts['scheme'] . '://' . (isset($url_parts['path']) ? $url_parts['path'] : '');
$this_host = isset($url_parts['host']) && $url_parts['host'] == Grav::instance()['uri']->host();
$media = $page->media();
// if there is no host set but there is a path, the file is local
if ((!isset($url_parts['host']) || $this_host) && isset($url_parts['path'])) {
} else {
// File is also local if scheme is http(s) and host matches.
$local_file = isset($url_parts['path'])
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https']))
&& (empty($url_parts['host']) || $url_parts['host'] == Grav::instance()['uri']->host());
$path_parts = pathinfo($url_parts['path']);
$media = null;
if ($local_file) {
$filename = basename($url_parts['path']);
$folder = dirname($url_parts['path']);
// get the local path to page media if possible
if ($path_parts['dirname'] == $page->url(false, false, false)) {
// get the media objects for this page
$media = $page->media();
} else {
// see if this is an external page to this one
$base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $path_parts['dirname']), '/');
$ext_page = Grav::instance()['pages']->dispatch($page_route, true);
if ($ext_page) {
$media = $ext_page->media();
// Get the local path to page media if possible.
if ($folder === $page->url(false, false, false)) {
// Get the media objects for this page.
$media = $page->media();
} else {
Grav::instance()->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
// see if this is an external page to this one
$base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
/** @var Page $ext_page */
$ext_page = Grav::instance()['pages']->dispatch($page_route, true);
if ($ext_page) {
$media = $ext_page->media();
} else {
Grav::instance()->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
}
}
}
}
// if there is a media file that matches the path referenced..
if ($media && isset($media->all()[$path_parts['basename']])) {
// get the medium object
/** @var Medium $medium */
$medium = $media->all()[$path_parts['basename']];
// If there is a media file that matches the path referenced..
if ($media && $filename && isset($media[$filename])) {
// Get the medium object.
/** @var Medium $medium */
$medium = $media[$filename];
// Process operations
$medium = static::processMediaActions($medium, $url_parts);
// Process operations
$medium = static::processMediaActions($medium, $url_parts);
$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($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'] : '';
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class, $id, true);
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class, $id, true);
} else {
// not a current page media file, see if it needs converting to relative
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
}
} else {
// Not a current page media file, see if it needs converting to relative.
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
}
return $excerpt;
@@ -282,8 +295,15 @@ class Excerpts
// loop through actions for the image and call them
foreach ($actions as $action) {
$medium = call_user_func_array([$medium, $action['method']],
explode(',', $action['params']));
$matches = [];
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
$args = [explode(',', $matches[1])];
} else {
$args = explode(',', $action['params']);
}
$medium = call_user_func_array([$medium, $action['method']], $args);
}
if (isset($url_parts['fragment'])) {
@@ -293,4 +313,40 @@ class Excerpts
return $medium;
}
/**
* Variation of parse_url() which works also with local streams.
*
* @param string $url
* @return array|bool
*/
protected static function parseUrl($url)
{
$url_parts = parse_url($url);
if (isset($url_parts['scheme'])) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
// Special handling for the streams.
if ($locator->schemeExists($url_parts['scheme'])) {
if (isset($url_parts['host'])) {
// Merge host and path into a path.
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
unset($url_parts['host']);
}
$url_parts['stream'] = true;
}
}
return $url_parts;
}
protected static function resolveStream($url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
return $locator->isStream($url) ? ($locator->findResource($url, false) ?: $locator->findResource($url, false, true)) : $url;
}
}

View File

@@ -10,6 +10,8 @@ namespace Grav\Common\Helpers;
use DOMText;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMWordsIterator;
use DOMLettersIterator;
@@ -118,7 +120,7 @@ class Truncator {
/**
* Builds a DOMDocument object from a string containing HTML.
* @param string HTML to load
* @param string $html HTML to load
* @returns DOMDocument Returns a DOMDocument object.
*/
public static function htmlToDomDocument($html)

View File

@@ -26,11 +26,11 @@ class Inflector
{
if (empty($this->plural)) {
$language = Grav::instance()['language'];
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true);
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true);
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true);
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true);
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true);
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true) ?: [];
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true) ?: [];
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true) ?: [];
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true) ?: [];
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true) ?: [];
}
}

View File

@@ -189,6 +189,10 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function random($num = 1)
{
if ($num > count($this->items)) {
$num = count($this->items);
}
$this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num)));
return $this;

View File

@@ -293,6 +293,21 @@ class Language
return $this->page_extensions;
}
/**
* Resets the page_extensions value.
*
* Useful to re-initialize the pages and change site language at runtime, example:
*
* ```
* $this->grav['language']->setActive('it');
* $this->grav['language']->resetFallbackPageExtensions();
* $this->grav['pages']->init();
* ```
*/
public function resetFallbackPageExtensions() {
$this->page_extensions = null;
}
/**
* Gets an array of languages with active first, then fallback languages
*

View File

@@ -52,6 +52,8 @@ class LanguageCodes
'fi' => [ 'name' => 'Finnish', 'nativeName' => 'Suomi' ],
'fj-FJ' => [ 'name' => 'Fijian', 'nativeName' => 'Vosa vaka-Viti' ],
'fr' => [ 'name' => 'French', 'nativeName' => 'Français' ],
'fr-CA' => [ 'name' => 'French (Canada)', 'nativeName' => 'Français (Canada)' ],
'fr-FR' => [ 'name' => 'French (France)', 'nativeName' => 'Français (France)' ],
'fur' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
'fur-IT' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
'fy' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ],

View File

@@ -10,6 +10,7 @@ namespace Grav\Common\Markdown;
use Grav\Common\Grav;
use Grav\Common\Helpers\Excerpts;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
trait ParsedownGravTrait
@@ -166,6 +167,8 @@ trait ParsedownGravTrait
return $Block;
}
return null;
}
protected function inlineSpecialCharacter($Excerpt)
@@ -183,6 +186,8 @@ trait ParsedownGravTrait
'extent' => 1,
];
}
return null;
}
protected function inlineImage($excerpt)
@@ -243,5 +248,7 @@ trait ParsedownGravTrait
return call_user_func_array($func, $args);
}
return null;
}
}

View File

@@ -74,6 +74,21 @@ class Collection extends Iterator
return new static($this->items, $this->params, $this->pages);
}
/**
*
* Merge another collection with the current collection
*
* @param Collection $collection
* @return $this
*/
public function merge(Collection $collection)
{
foreach($collection as $page) {
$this->addPage($page);
}
return $this;
}
/**
* Set parameters to the Collection
*
@@ -84,7 +99,6 @@ class Collection extends Iterator
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
@@ -147,7 +161,7 @@ class Collection extends Iterator
*
* @param Page|string|null $key
*
* @return $this|void
* @return $this
* @throws \InvalidArgumentException
*/
public function remove($key = null)
@@ -172,12 +186,13 @@ class Collection extends Iterator
* @param string $by
* @param string $dir
* @param array $manual
* @param string $sort_flags
*
* @return $this
*/
public function order($by, $dir = 'asc', $manual = null)
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
{
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual);
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags);
return $this;
}

View File

@@ -8,51 +8,77 @@
namespace Grav\Common\Page;
use Grav\Common\Getters;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\AbstractMedia;
use Grav\Common\Page\Medium\GlobalMedia;
use Grav\Common\Page\Medium\MediumFactory;
class Media extends Getters
class Media extends AbstractMedia
{
protected $gettersVariable = 'instances';
protected $path;
protected static $global;
protected $instances = [];
protected $images = [];
protected $videos = [];
protected $audios = [];
protected $files = [];
protected $path;
/**
* @param $path
*/
public function __construct($path)
{
$this->path = $path;
if (!isset(static::$global)) {
// Add fallback to global media.
static::$global = new GlobalMedia($path);
}
$this->init();
}
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
}
/**
* @param mixed $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
return parent::offsetGet($offset) ?: static::$global[$offset];
}
/**
* Initialize class.
*/
protected function init()
{
// Handle special cases where page doesn't exist in filesystem.
if (!is_dir($path)) {
if (!is_dir($this->path)) {
return;
}
$this->path = $path;
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$iterator = new \FilesystemIterator($this->path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename() === '.DS_Store') {
if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename()[0] === '.') {
continue;
}
// Find out what type we're dealing with
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
$media["{$basename}.{$ext}"] = isset($media["{$basename}.{$ext}"]) ? $media["{$basename}.{$ext}"] : [];
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : [];
$media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ];
} else {
$media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
@@ -74,16 +100,16 @@ class Media extends Getters
}
// Create the base medium
if (!empty($types['base'])) {
if (empty($types['base'])) {
if (!isset($types['alternative'])) {
continue;
}
$max = max(array_keys($types['alternative']));
$medium = $types['alternative'][$max]['file'];
$medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file'];
} else {
$medium = MediumFactory::fromFile($types['base']['file']);
$medium && $medium->set('size', $types['base']['size']);
} else if (!empty($types['alternative'])) {
$altMedium = reset($types['alternative']);
$ratio = key($types['alternative']);
$altMedium = $altMedium['file'];
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1)['file'];
}
if (empty($medium)) {
@@ -103,10 +129,9 @@ class Media extends Getters
// Build missing alternatives
if (!empty($types['alternative'])) {
$alternatives = $types['alternative'];
$max = max(array_keys($alternatives));
for ($i=2; $i < $max; $i++) {
for ($i=$max; $i > 1; $i--) {
if (isset($alternatives[$i])) {
continue;
}
@@ -114,142 +139,19 @@ class Media extends Getters
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
}
foreach ($types['alternative'] as $ratio => $altMedium) {
$medium->addAlternative($ratio, $altMedium['file']);
foreach ($types['alternative'] as $altMedium) {
if ($altMedium['file'] != $medium) {
$altWidth = $altMedium['file']->get('width');
$medWidth = $medium->get('width');
if ($altWidth && $medWidth) {
$ratio = $altWidth / $medWidth;
$medium->addAlternative($ratio, $altMedium['file']);
}
}
}
}
$this->add($name, $medium);
}
}
/**
* Get medium by filename.
*
* @param string $filename
* @return Medium|null
*/
public function get($filename)
{
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
}
/**
* Get a list of all media.
*
* @return array|Medium[]
*/
public function all()
{
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
return $this->instances;
}
/**
* Get a list of all image media.
*
* @return array|Medium[]
*/
public function images()
{
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
return $this->images;
}
/**
* Get a list of all video media.
*
* @return array|Medium[]
*/
public function videos()
{
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
return $this->videos;
}
/**
* Get a list of all audio media.
*
* @return array|Medium[]
*/
public function audios()
{
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
return $this->audios;
}
/**
* Get a list of all file media.
*
* @return array|Medium[]
*/
public function files()
{
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
return $this->files;
}
/**
* @internal
*/
protected function add($name, $file)
{
$this->instances[$name] = $file;
switch ($file->type) {
case 'image':
$this->images[$name] = $file;
break;
case 'video':
$this->videos[$name] = $file;
break;
case 'audio':
$this->audios[$name] = $file;
break;
default:
$this->files[$name] = $file;
}
}
/**
* Get filename, extension and meta part.
*
* @param string $filename
* @return array
*/
protected function getFileParts($filename)
{
$fileParts = explode('.', $filename);
$name = array_shift($fileParts);
$type = 'base';
$extra = null;
if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
$name = $matches[1];
$extension = $matches[3];
$extra = (int) $matches[2];
$type = 'alternative';
if ($extra === 1) {
$type = 'base';
$extra = null;
}
} else {
$extension = null;
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta' && $part != 'thumb') {
if (isset($extension)) {
$name .= '.' . $extension;
}
$extension = $part;
} else {
$type = $part;
$extra = '.' . $part . '.' . implode('.', $fileParts);
break;
}
}
}
return array($name, $extension, $type, $extra);
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* @package Grav.Common.Page
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Getters;
abstract class AbstractMedia extends Getters
{
protected $gettersVariable = 'instances';
protected $instances = [];
protected $images = [];
protected $videos = [];
protected $audios = [];
protected $files = [];
/**
* Get medium by filename.
*
* @param string $filename
* @return Medium|null
*/
public function get($filename)
{
return $this->offsetGet($filename);
}
/**
* Call object as function to get medium by filename.
*
* @param string $filename
* @return mixed
*/
public function __invoke($filename)
{
return $this->offsetGet($filename);
}
/**
* Get a list of all media.
*
* @return array|Medium[]
*/
public function all()
{
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
return $this->instances;
}
/**
* Get a list of all image media.
*
* @return array|Medium[]
*/
public function images()
{
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
return $this->images;
}
/**
* Get a list of all video media.
*
* @return array|Medium[]
*/
public function videos()
{
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
return $this->videos;
}
/**
* Get a list of all audio media.
*
* @return array|Medium[]
*/
public function audios()
{
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
return $this->audios;
}
/**
* Get a list of all file media.
*
* @return array|Medium[]
*/
public function files()
{
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
return $this->files;
}
/**
* @param string $name
* @param Medium $file
*/
protected function add($name, $file)
{
$this->instances[$name] = $file;
switch ($file->type) {
case 'image':
$this->images[$name] = $file;
break;
case 'video':
$this->videos[$name] = $file;
break;
case 'audio':
$this->audios[$name] = $file;
break;
default:
$this->files[$name] = $file;
}
}
/**
* Get filename, extension and meta part.
*
* @param string $filename
* @return array
*/
protected function getFileParts($filename)
{
if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
$name = $matches[1];
$extension = $matches[3];
$extra = (int) $matches[2];
$type = 'alternative';
if ($extra === 1) {
$type = 'base';
$extra = null;
}
} else {
$fileParts = explode('.', $filename);
$name = array_shift($fileParts);
$extension = null;
$extra = null;
$type = 'base';
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta' && $part != 'thumb') {
if (isset($extension)) {
$name .= '.' . $extension;
}
$extension = $part;
} else {
$type = $part;
$extra = '.' . $part . '.' . implode('.', $fileParts);
break;
}
}
}
return array($name, $extension, $type, $extra);
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* @package Grav.Common.Page
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Medium;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class GlobalMedia extends AbstractMedia
{
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
}
/**
* @param mixed $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
return parent::offsetGet($offset) ?: $this->addMedium($offset);
}
/**
* @param string $filename
* @return string|null
*/
protected function resolveStream($filename)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
return $locator->isStream($filename) ? ($locator->findResource($filename) ?: null) : null;
}
/**
* @param string $stream
* @return Medium|null
*/
protected function addMedium($stream)
{
$filename = $this->resolveStream($stream);
if (!$filename) {
return null;
}
$path = dirname($filename);
list($basename, $ext,, $extra) = $this->getFileParts(basename($filename));
$medium = MediumFactory::fromFile($filename);
if (empty($medium)) {
return null;
}
$medium->set('size', filesize($filename));
$scale = (int) ($extra ?: 1);
if ($scale !== 1) {
$altMedium = $medium;
// Create scaled down regular sized image.
$medium = MediumFactory::scaledFromMedium($altMedium, $scale, 1)['file'];
if (empty($medium)) {
return null;
}
// Add original sized image as alternative.
$medium->addAlternative($scale, $altMedium['file']);
// Locate or generate smaller retina images.
for ($i = $scale-1; $i > 1; $i--) {
$altFilename = "{$path}/{$basename}@{$i}x.{$ext}";
if (file_exists($altFilename)) {
$scaled = MediumFactory::fromFile($altFilename);
} else {
$scaled = MediumFactory::scaledFromMedium($altMedium, $scale, $i)['file'];
}
if ($scaled) {
$medium->addAlternative($i, $scaled);
}
}
}
$meta = "{$path}/{$basename}.{$ext}.yaml";
if (file_exists($meta)) {
$medium->addMetaFile($meta);
}
$meta = "{$path}/{$basename}.{$ext}.meta.yaml";
if (file_exists($meta)) {
$medium->addMetaFile($meta);
}
$thumb = "{$path}/{$basename}.thumb.{$ext}";
if (file_exists($thumb)) {
$medium->set('thumbnails.page', $thumb);
}
$this->add($stream, $medium);
return $medium;
}
}

View File

@@ -65,11 +65,6 @@ class ImageMedium extends Medium
'zoomCrop' => [0, 1]
];
/**
* @var array
*/
protected $derivatives = [];
/**
* @var string
*/
@@ -197,27 +192,19 @@ class ImageMedium extends Medium
*/
public function srcset($reset = true)
{
if (empty($this->alternatives) && empty($this->derivatives)) {
if (empty($this->alternatives)) {
if ($reset) {
$this->reset();
}
return '';
}
if (!empty($this->derivatives)) {
asort($this->derivatives);
foreach ($this->derivatives as $url => $width) {
$srcset[] = $url . ' ' . $width . 'w';
}
$srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w';
} else {
$srcset = [$this->url($reset) . ' ' . $this->get('width') . 'w'];
foreach ($this->alternatives as $ratio => $medium) {
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
}
$srcset = [];
foreach ($this->alternatives as $ratio => $medium) {
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
}
$srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w';
return implode(', ', $srcset);
}
@@ -249,39 +236,76 @@ class ImageMedium extends Medium
}
/**
* Generate derivatives
* Generate alternative image widths, using either an array of integers, or
* a min width, a max width, and a step parameter to fill out the necessary
* widths. Existing image alternatives won't be overwritten.
*
* @param int $min_width
* @param int $max_width
* @param int $step
* @param int|int[] $min_width
* @param int [$max_width=2500]
* @param int [$step=200]
* @return $this
*/
public function derivatives($min_width, $max_width, $step = 200) {
$width = $min_width;
// Do not upscale images.
if ($max_width > $this->get('width')) {
$max_width = $this->get('width');
}
while ($width <= $max_width) {
$ratio = $width / $this->get('width');
$derivative = MediumFactory::scaledFromMedium($this, 1, $ratio);
if (is_array($derivative)) {
$this->addDerivative($derivative['file']);
public function derivatives($min_width, $max_width = 2500, $step = 200) {
if (!empty($this->alternatives)) {
$max = max(array_keys($this->alternatives));
$base = $this->alternatives[$max];
} else {
$base = $this;
}
$width += $step;
}
return $this;
}
/**
* Add a derivative
*
* @param ImageMedium $image
*/
public function addDerivative(ImageMedium $image) {
$this->derivatives[$image->url()] = $image->get('width');
$widths = [];
if (func_num_args() === 1) {
foreach ((array) func_get_arg(0) as $width) {
if ($width < $base->get('width')) {
$widths[] = $width;
}
}
} else {
$max_width = min($max_width, $base->get('width'));
for ($width = $min_width; $width < $max_width; $width = $width + $step) {
$widths[] = $width;
}
}
foreach ($widths as $width) {
// Only generate image alternatives that don't already exist
if (array_key_exists((int) $width, $this->alternatives)) {
continue;
}
$derivative = MediumFactory::fromFile($base->get('filepath'));
// 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)) {
$index = 2;
$alt_widths = array_keys($this->alternatives);
sort($alt_widths);
foreach ($alt_widths as $i => $key) {
if ($width > $key) {
$index += max($i, 1);
}
}
$basename = preg_replace('/(@\d+x){0,1}$/', "@{$width}w", $base->get('basename'), 1);
$derivative->setImagePrettyName($basename);
$ratio = $base->get('width') / $width;
$height = $derivative->get('height') / $ratio;
$derivative->resize($width, $height);
$derivative->set('width', $width);
$derivative->set('height', $height);
$this->addAlternative($ratio, $derivative);
}
}
return $this;
}
/**
@@ -488,7 +512,11 @@ class ImageMedium extends Medium
try {
call_user_func_array([$this->image, $method], $args);
foreach ($this->alternatives as $ratio => $medium) {
foreach ($this->alternatives as $medium) {
if (!$medium->image) {
$medium->image();
}
$args_copy = $args;
// regular image: resize 400x400 -> 200x200
@@ -496,7 +524,7 @@ class ImageMedium extends Medium
if (isset(self::$magic_resize_actions[$method])) {
foreach (self::$magic_resize_actions[$method] as $param) {
if (isset($args_copy[$param])) {
$args_copy[$param] = (int) $args_copy[$param] * $ratio;
$args_copy[$param] *= $medium->get('ratio');
}
}
}
@@ -519,7 +547,9 @@ class ImageMedium extends Medium
$locator = Grav::instance()['locator'];
$file = $this->get('filepath');
$cacheDir = $locator->findResource('cache://images', true);
// Use existing cache folder or if it doesn't exist, create it.
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
$this->image = ImageFile::open($file)
->setCacheDir($cacheDir)

View File

@@ -57,7 +57,7 @@ class Link implements RenderableInterface
*
* @param string $method
* @param mixed $args
* @return $this|mixed
* @return mixed
*/
public function __call($method, $args)
{

View File

@@ -70,7 +70,7 @@ class Medium extends Data implements RenderableInterface
/**
* Return just metadata from the Medium object
*
* @return $this
* @return Data
*/
public function meta()
{
@@ -84,7 +84,7 @@ class Medium extends Data implements RenderableInterface
*/
public function addMetaFile($filepath)
{
$this->merge(CompiledYamlFile::instance($filepath)->content());
$this->merge((array)CompiledYamlFile::instance($filepath)->content());
}
/**
@@ -100,7 +100,9 @@ class Medium extends Data implements RenderableInterface
}
$alternative->set('ratio', $ratio);
$this->alternatives[(float) $ratio] = $alternative;
$width = $alternative->get('width');
$this->alternatives[$width] = $alternative;
}
/**
@@ -148,8 +150,8 @@ class Medium extends Data implements RenderableInterface
/**
* Get/set querystring for the file's url
*
* @param string $hash
* @param boolean $withHash
* @param string $querystring
* @param boolean $withQuestionmark
* @return string
*/
public function querystring($querystring = null, $withQuestionmark = true)
@@ -227,7 +229,7 @@ class Medium extends Data implements RenderableInterface
}
if (empty($attributes['alt'])) {
if (!empty($alt)) {
if (!empty($alt) || $alt === '') {
$attributes['alt'] = $alt;
} elseif (!empty($this->items['alt'])) {
$attributes['alt'] = $this->items['alt'];

View File

@@ -119,8 +119,8 @@ class MediumFactory
}
$ratio = $to / $from;
$width = (int) ($medium->get('width') * $ratio);
$height = (int) ($medium->get('height') * $ratio);
$width = $medium->get('width') * $ratio;
$height = $medium->get('height') * $ratio;
$prev_basename = $medium->get('basename');
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename);

View File

@@ -15,7 +15,7 @@ trait StaticResizeTrait
*
* @param int $width
* @param int $height
* @return Medium
* @return $this
*/
public function resize($width = null, $height = null)
{

View File

@@ -86,6 +86,7 @@ class Page
protected $hide_home_route;
protected $ssl;
protected $template_format;
protected $debugger;
/**
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
@@ -224,7 +225,7 @@ class Page
*
* @param string $var Raw content string
*
* @return Object Raw content string
* @return string Raw content string
*/
public function raw($var = null)
{
@@ -424,6 +425,9 @@ class Page
if (isset($this->header->template_format)) {
$this->template_format = $this->header->template_format;
}
if (isset($this->header->debugger)) {
$this->debugger = (bool)$this->header->debugger;
}
}
return $this->header;
@@ -566,22 +570,23 @@ class Page
$process_markdown = $this->shouldProcess('markdown');
$process_twig = $this->shouldProcess('twig');
$process_twig = $this->shouldProcess('twig') || $this->modularTwig() ;
$cache_enable = isset($this->header->cache_enable) ? $this->header->cache_enable : $config->get('system.cache.enabled',
true);
$twig_first = isset($this->header->twig_first) ? $this->header->twig_first : $config->get('system.pages.twig_first',
true);
// never cache twig means it's always run after content
$never_cache_twig = isset($this->header->never_cache_twig) ? $this->header->never_cache_twig : $config->get('system.pages.never_cache_twig',
false);
// if no cached-content run everything
if ($this->content === false || $cache_enable === false) {
$this->content = $this->raw_content;
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
if ($never_cache_twig) {
if ($this->content === false || $cache_enable === false) {
$this->content = $this->raw_content;
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
if ($twig_first) {
if ($process_twig) {
$this->processTwig();
}
if ($process_markdown) {
$this->processMarkdown();
}
@@ -589,21 +594,47 @@ class Page
// Content Processed but not cached yet
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
} else {
if ($process_markdown) {
$this->processMarkdown();
}
// Content Processed but not cached yet
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
if ($process_twig) {
$this->processTwig();
if ($cache_enable) {
$this->cachePageContent();
}
}
if ($cache_enable) {
$this->cachePageContent();
if ($process_twig) {
$this->processTwig();
}
} else {
if ($this->content === false || $cache_enable === false) {
$this->content = $this->raw_content;
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
if ($twig_first) {
if ($process_twig) {
$this->processTwig();
}
if ($process_markdown) {
$this->processMarkdown();
}
// Content Processed but not cached yet
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
} else {
if ($process_markdown) {
$this->processMarkdown();
}
// Content Processed but not cached yet
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
if ($process_twig) {
$this->processTwig();
}
}
if ($cache_enable) {
$this->cachePageContent();
}
}
}
@@ -773,6 +804,9 @@ class Page
if ($name == 'folder') {
return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder);
}
if ($name == 'slug') {
return $this->slug();
}
if ($name == 'name') {
$language = $this->language() ? '.' . $this->language() : '';
$name_val = str_replace($language . '.md', '', $this->name());
@@ -856,12 +890,12 @@ class Page
/**
* Save page if there's a file assigned to it.
*
* @param bool $reorder Internal use.
* @param bool|mixed $reorder Internal use.
*/
public function save($reorder = true)
{
// Perform move, copy or reordering if needed.
$this->doRelocation($reorder);
// Perform move, copy [or reordering] if needed.
$this->doRelocation();
$file = $this->file();
if ($file) {
@@ -870,6 +904,13 @@ class Page
$file->markdown($this->raw_content);
$file->save();
}
// Perform reorder if required
if ($reorder && is_array($reorder)) {
$this->doReorder($reorder);
}
$this->_original = null;
}
/**
@@ -902,6 +943,8 @@ class Page
$this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug());
}
$this->raw_route = null;
return $this;
}
@@ -1121,7 +1164,7 @@ class Page
}
if (empty($this->template_format)) {
$this->template_format = Grav::instance()['uri']->extension();
$this->template_format = Grav::instance()['uri']->extension('html');
}
return $this->template_format;
@@ -1180,7 +1223,7 @@ class Page
$this->expires = $var;
}
return empty($this->expires) ? Grav::instance()['config']->get('system.pages.expires') : $this->expires;
return !isset($this->expires) ? Grav::instance()['config']->get('system.pages.expires') : $this->expires;
}
/**
@@ -1345,6 +1388,20 @@ class Page
return $this->process;
}
/**
* Returns the state of the debugger override etting for this page
*
* @return mixed
*/
public function debugger()
{
if (isset($this->debugger) && $this->debugger === false) {
return false;
} else {
return true;
}
}
/**
* Function to merge page metadata tags and build an array of Metadata objects
* that can then be rendered in the page.
@@ -1456,22 +1513,16 @@ class Page
{
if ($var !== null) {
$order = !empty($var) ? sprintf('%02d.', (int)$var) : '';
$this->folder($order . $this->slug());
$this->folder($order . preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
return $order;
}
preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order);
return isset($order[0]) ? $order[0] : false;
}
/**
* Gets the URL with host information, aka Permalink.
* @return string The permalink.
*/
public function permalink()
{
return $this->url(true);
}
/**
* Gets the URL for a page - alias of url().
*
@@ -1484,16 +1535,36 @@ class Page
return $this->url($include_host);
}
/**
* Gets the URL with host information, aka Permalink.
* @return string The permalink.
*/
public function permalink()
{
return $this->url(true, false, true, true);
}
/**
* Returns the canonical URL for a page
*
* @param bool $include_lang
* @return string
*/
public function canonical($include_lang = true)
{
return $this->url(true, true, $include_lang);
}
/**
* 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 $canonical true to return the canonical URL
* @param bool $include_lang
*
* @param bool $raw_route
* @return string The url.
*/
public function url($include_host = false, $canonical = false, $include_lang = true)
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
{
$grav = Grav::instance();
@@ -1529,6 +1600,8 @@ class Page
// get canonical route if requested
if ($canonical) {
$route = $pre_route . $this->routeCanonical();
} elseif ($raw_route) {
$route = $pre_route . $this->rawRoute();
} else {
$route = $pre_route . $this->route();
}
@@ -1669,7 +1742,10 @@ class Page
public function id($var = null)
{
if ($var !== null) {
$this->id = $var;
// store unique per language
$active_lang = Grav::instance()['language']->getLanguage() ?: '';
$id = $active_lang . $var;
$this->id = $id;
}
return $this->id;
@@ -1696,7 +1772,7 @@ class Page
*
* @param string $var redirect url
*
* @return array
* @return string
*/
public function redirect($var = null)
{
@@ -1980,7 +2056,6 @@ class Page
if ($var !== null) {
$this->modular_twig = (bool)$var;
if ($var) {
$this->process['twig'] = true;
$this->visible(false);
// some routable logic
if (empty($this->header->routable)) {
@@ -2119,7 +2194,7 @@ class Page
*
* @param integer $direction either -1 or +1
*
* @return Page the sibling page
* @return Page|bool the sibling page
*/
public function adjacentSibling($direction = 1)
{
@@ -2241,7 +2316,7 @@ class Page
}
if (!isset($params['items'])) {
return [];
return new Collection();
}
$collection = $this->evaluate($params['items']);
@@ -2269,6 +2344,7 @@ class Page
continue;
}
foreach ($items as $item) {
$item = rawurldecode($item);
if (empty($page->taxonomy[$taxonomy]) || !in_array(htmlspecialchars_decode($item,
ENT_QUOTES), $page->taxonomy[$taxonomy])
) {
@@ -2291,7 +2367,16 @@ class Page
$by = isset($params['order']['by']) ? $params['order']['by'] : 'default';
$dir = isset($params['order']['dir']) ? $params['order']['dir'] : 'asc';
$custom = isset($params['order']['custom']) ? $params['order']['custom'] : null;
$collection->order($by, $dir, $custom);
$sort_flags = isset($params['order']['sort_flags']) ? $params['order']['sort_flags'] : null;
if (is_array($sort_flags)) {
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
return $a | $b;
}, 0); //merge constant values using bit or
}
$collection->order($by, $dir, $custom, $sort_flags);
}
/** @var Grav $grav */
@@ -2411,7 +2496,10 @@ class Page
switch ($parts[0]) {
case 'modular':
$results = new Collection();
$results = $results->addPage($page)->Modular();
foreach ($page->children() as $child) {
$results = $results->addPage($child);
}
$results->modular();
break;
case 'page':
case 'self':
@@ -2500,6 +2588,16 @@ class Page
return $file && $file->exists();
}
/**
* Returns whether or not the current folder exists
*
* @return bool
*/
public function folderExists()
{
return file_exists($this->path());
}
/**
* Cleans the path.
*
@@ -2518,65 +2616,62 @@ class Page
}
/**
* Moves or copies the page in filesystem.
* Reorders all siblings according to a defined order
*
* @internal
*
* @param bool $reorder
*
* @throws Exception
* @param $new_order
*/
protected function doRelocation($reorder)
protected function doReorder($new_order)
{
if (!$this->_original) {
return;
}
// Do reordering.
if ($reorder && $this->order() != $this->_original->order()) {
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$pages = Grav::instance()['pages'];
$pages->init();
$parent = $this->parent();
$this->_original->path($this->path());
// Extract visible children from the parent page.
$list = [];
/** @var Page $page */
foreach ($parent->children()->visible() as $page) {
if ($page->order()) {
$list[$page->slug] = $page->path();
}
}
$siblings = $this->parent()->children();
$siblings->order('slug', 'asc', $new_order);
// If page was moved, take it out of the list.
if ($this->_action == 'move') {
unset($list[$this->slug()]);
}
$counter = 0;
$list = array_values($list);
// Reorder all moved pages.
foreach ($siblings as $slug => $page) {
$order = intval(trim($page->order(),'.'));
$counter++;
// Then add it back to the new location (if needed).
if ($this->order()) {
array_splice($list, min($this->order() - 1, count($list)), 0, [$this->path()]);
}
// Reorder all moved pages.
foreach ($list as $order => $path) {
if ($path == $this->path()) {
if ($order) {
if ($page->path() == $this->path() && $this->folderExists()) {
// Handle current page; we do want to change ordering number, but nothing else.
$this->order($order + 1);
$this->order($counter);
$this->save(false);
} else {
// Handle all the other pages.
$page = $pages->get($path);
if ($page && $page->exists() && !$page->_action && $page->order() != $order + 1) {
$page = $page->move($parent);
$page->order($order + 1);
$page = $pages->get($page->path());
if ($page && $page->folderExists() && !$page->_action) {
$page = $page->move($this->parent());
$page->order($counter);
$page->save(false);
}
}
}
}
}
/**
* Moves or copies the page in filesystem.
*
* @internal
*
* @throws Exception
*/
protected function doRelocation()
{
if (!$this->_original) {
return;
}
if (is_dir($this->_original->path())) {
if ($this->_action == 'move') {
Folder::move($this->_original->path(), $this->path());
@@ -2592,12 +2687,11 @@ class Page
}
}
$this->_original = null;
}
protected function setPublishState()
{
// Handle publishing dates if no explict published option set
// Handle publishing dates if no explicit published option set
if (Grav::instance()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
// unpublish if required, if not clear cache right before page should be unpublished
if ($this->unpublishDate()) {

View File

@@ -21,6 +21,7 @@ use Grav\Plugin\Admin;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Whoops\Exception\ErrorException;
use Collator as Collator;
class Pages
{
@@ -89,6 +90,8 @@ class Pages
*/
static protected $home_route;
protected $pages_cache_id;
/**
* Constructor
*
@@ -153,7 +156,7 @@ class Pages
/**
* Returns a list of all pages.
*
* @return Page
* @return array|Page[]
*/
public function instances()
{
@@ -197,7 +200,7 @@ class Pages
*
* @return array
*/
public function sort(Page $page, $order_by = null, $order_dir = null)
public function sort(Page $page, $order_by = null, $order_dir = null, $sort_flags = null)
{
if ($order_by === null) {
$order_by = $page->orderBy();
@@ -214,7 +217,7 @@ class Pages
}
if (!isset($this->sort[$path][$order_by])) {
$this->buildSort($path, $children, $order_by, $page->orderManual());
$this->buildSort($path, $children, $order_by, $page->orderManual(), $sort_flags);
}
$sort = $this->sort[$path][$order_by];
@@ -235,7 +238,7 @@ class Pages
* @return array
* @internal
*/
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null)
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null, $sort_flags = null)
{
$items = $collection->toArray();
if (!$items) {
@@ -244,7 +247,7 @@ class Pages
$lookup = md5(json_encode($items) . json_encode($orderManual) . $orderBy . $orderDir);
if (!isset($this->sort[$lookup][$orderBy])) {
$this->buildSort($lookup, $items, $orderBy, $orderManual);
$this->buildSort($lookup, $items, $orderBy, $orderManual, $sort_flags);
}
$sort = $this->sort[$lookup][$orderBy];
@@ -342,13 +345,20 @@ class Pages
$page = $this->dispatch($route, $all);
} else {
// Try Regex style redirects
$uri = $this->grav['uri'];
$source_url = $url;
$extension = $uri->extension();
if (isset($extension) && !Utils::endsWith($uri->url(), $extension)) {
$source_url.= '.' . $extension;
}
$site_redirects = $config->get("site.redirects");
if (is_array($site_redirects)) {
foreach ((array)$site_redirects as $pattern => $replace) {
$pattern = '#' . $pattern . '#';
try {
$found = preg_replace($pattern, $replace, $url);
if ($found != $url) {
$found = preg_replace($pattern, $replace, $source_url);
if ($found != $source_url) {
$this->grav->redirectLangSafe($found);
}
} catch (ErrorException $e) {
@@ -363,8 +373,8 @@ class Pages
foreach ((array)$site_routes as $pattern => $replace) {
$pattern = '#' . $pattern . '#';
try {
$found = preg_replace($pattern, $replace, $url);
if ($found != $url) {
$found = preg_replace($pattern, $replace, $source_url);
if ($found != $source_url) {
$page = $this->dispatch($found, $all);
}
} catch (ErrorException $e) {
@@ -564,17 +574,21 @@ class Pages
*/
public static function pageTypes()
{
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
if (isset(Grav::instance()['admin'])) {
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
/** @var Page $page */
$page = $admin->getPage($admin->route);
/** @var Page $page */
$page = $admin->getPage($admin->route);
if ($page && $page->modular()) {
return static::modularTypes();
if ($page && $page->modular()) {
return static::modularTypes();
}
return static::types();
}
return static::types();
return [];
}
/**
@@ -647,11 +661,12 @@ class Pages
$parents = $pages->getList(null, 0, $rawRoutes);
/** @var Admin $admin */
$admin = $grav['admin'];
if (isset($grav['admin'])) {
// Remove current route from parents
/** @var Admin $admin */
$admin = $grav['admin'];
// Remove current route from parents
if (isset($admin)) {
$page = $admin->getPage($admin->route);
$page_route = $page->route();
if (isset($parents[$page_route])) {
@@ -752,18 +767,19 @@ class Pages
break;
case 'hash':
$hash = Folder::hashAllFiles($pages_dir);
break;
default:
$hash = Folder::lastModifiedFile($pages_dir);
}
$page_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($this->pages_cache_id);
if (!$this->instances) {
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
// recurse pages and cache result
$this->resetPages($pages_dir, $page_cache_id);
$this->resetPages($pages_dir, $this->pages_cache_id);
} else {
// If pages was found in cache, set the taxonomy
@@ -780,9 +796,8 @@ class Pages
* Accessible method to manually reset the pages cache
*
* @param $pages_dir
* @param $page_cache_id
*/
public function resetPages($pages_dir, $page_cache_id)
public function resetPages($pages_dir)
{
$this->recurse($pages_dir);
$this->buildRoutes();
@@ -795,7 +810,7 @@ class Pages
$taxonomy = $this->grav['taxonomy'];
// save pages, routes, taxonomy, and sort to cache
$cache->save($page_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
$cache->save($this->pages_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
}
}
@@ -1017,12 +1032,11 @@ class Pages
* @throws \RuntimeException
* @internal
*/
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null, $sort_flags = null)
{
$list = [];
$header_default = null;
$header_query = null;
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
// do this header query work only once
if (strpos($order_by, 'header.') === 0) {
@@ -1050,6 +1064,14 @@ class Pages
$list[$key] = $child->modified();
$sort_flags = SORT_REGULAR;
break;
case 'publish_date':
$list[$key] = $child->publishDate();
$sort_flags = SORT_REGULAR;
break;
case 'unpublish_date':
$list[$key] = $child->unpublishDate();
$sort_flags = SORT_REGULAR;
break;
case 'slug':
$list[$key] = $child->slug();
break;
@@ -1059,21 +1081,27 @@ class Pages
case (is_string($header_query[0])):
$child_header = new Header((array)$child->header());
$header_value = $child_header->get($header_query[0]);
if ($header_value) {
if (is_array($header_value)) {
$list[$key] = implode(',',$header_value);
} elseif ($header_value) {
$list[$key] = $header_value;
} else {
$list[$key] = $header_default ?: $key;
}
$sort_flags = SORT_REGULAR;
$sort_flags = $sort_flags ?: SORT_REGULAR;
break;
case 'manual':
case 'default':
default:
$list[$key] = $key;
$sort_flags = SORT_REGULAR;
$sort_flags = $sort_flags ?: SORT_REGULAR;
}
}
if (!$sort_flags) {
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
}
// handle special case when order_by is random
if ($order_by == 'random') {
$list = $this->arrayShuffle($list);
@@ -1081,7 +1109,7 @@ class Pages
// else just sort the list according to specified key
if (extension_loaded('intl')) {
$locale = setlocale(LC_COLLATE, 0); //`setlocale` with a 0 param returns the current locale set
$col = \Collator::create($locale);
$col = Collator::create($locale);
if ($col) {
$col->asort($list, $sort_flags);
} else {
@@ -1138,4 +1166,17 @@ class Pages
return $new;
}
/**
* Get the Pages cache ID
*
* this is particularly useful to know if pages have changed and you want
* to sync another cache with pages cache - works best in `onPagesInitialized()`
*
* @return mixed
*/
public function getPagesCacheId()
{
return $this->pages_cache_id;
}
}

View File

@@ -246,21 +246,23 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
/**
* Merge global and page configurations.
*
* @param Page $page The page to merge the configurations with the
* @param Page $page The page to merge the configurations with the
* plugin settings.
* @param bool $deep Should you use deep or shallow merging
* @param array $params Array of additional configuration options to
* @param mixed $deep false = shallow|true = recursive|merge = recursive+unique
* @param array $params Array of additional configuration options to
* merge with the plugin settings.
* @param string $type Is this 'plugins' or 'themes'
*
* @return \Grav\Common\Data\Data
* @return Data
*/
protected function mergeConfig(Page $page, $deep = false, $params = [])
protected function mergeConfig(Page $page, $deep = false, $params = [], $type = 'plugins')
{
$class_name = $this->name;
$class_name_merged = $class_name . '.merged';
$defaults = $this->config->get('plugins.' . $class_name, []);
$defaults = $this->config->get($type . '.' . $class_name, []);
$page_header = $page->header();
$header = [];
if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) {
// Get default plugin configurations and retrieve page header configuration
$config = $page_header->$class_name;
@@ -269,11 +271,8 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
$config = ['enabled' => $config];
}
// Merge page header settings using deep or shallow merging technique
if ($deep) {
$header = array_replace_recursive($defaults, $config);
} else {
$header = array_merge($defaults, $config);
}
$header = $this->mergeArrays($deep, $defaults, $config);
// Create new config object and set it on the page object so it's cached for next time
$page->modifyHeader($class_name_merged, new Data($header));
} else if (isset($page_header->$class_name_merged)) {
@@ -284,16 +283,31 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
$header = $defaults;
}
// Merge additional parameter with configuration options
if ($deep) {
$header = array_replace_recursive($header, $params);
} else {
$header = array_merge($header, $params);
}
$header = $this->mergeArrays($deep, $header, $params);
// Return configurations as a new data config class
return new Data($header);
}
/**
* Merge arrays based on deepness
*
* @param bool $deep
* @param $array1
* @param $array2
* @return array|mixed
*/
private function mergeArrays($deep = false, $array1, $array2)
{
if ($deep == 'merge') {
return Utils::arrayMergeRecursiveUnique($array1, $array2);
} elseif ($deep === true) {
return array_replace_recursive($array1, $array2);
} else {
return array_merge($array1, $array2);
}
}
/**
* Persists to disk the plugin parameters currently stored in the Grav Config object
*

View File

@@ -8,14 +8,14 @@
namespace Grav\Common\Processors;
class AssetsProcessor extends ProcessorBase implements ProcessorInterface {
class AssetsProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'assets';
public $title = 'Assets';
public function process() {
$this->container['assets']->init();
$this->container->fireEvent('onAssetsInitialized');
public function process()
{
$this->container['assets']->init();
$this->container->fireEvent('onAssetsInitialized');
}
}

View File

@@ -8,14 +8,14 @@
namespace Grav\Common\Processors;
class ConfigurationProcessor extends ProcessorBase implements ProcessorInterface {
class ConfigurationProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = '_config';
public $title = 'Configuration';
public function process() {
$this->container['config']->init();
return $this->container['plugins']->setup();
public function process()
{
$this->container['config']->init();
return $this->container['plugins']->setup();
}
}

View File

@@ -8,13 +8,13 @@
namespace Grav\Common\Processors;
class DebuggerAssetsProcessor extends ProcessorBase implements ProcessorInterface {
class DebuggerAssetsProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'debugger_assets';
public $title = 'Debugger Assets';
public function process() {
$this->container['debugger']->addAssets();
public function process()
{
$this->container['debugger']->addAssets();
}
}

View File

@@ -8,13 +8,13 @@
namespace Grav\Common\Processors;
class DebuggerInitProcessor extends ProcessorBase implements ProcessorInterface {
class DebuggerInitProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = '_debugger';
public $title = 'Init Debugger';
public function process() {
$this->container['debugger']->init();
public function process()
{
$this->container['debugger']->init();
}
}

View File

@@ -8,13 +8,13 @@
namespace Grav\Common\Processors;
class ErrorsProcessor extends ProcessorBase implements ProcessorInterface {
class ErrorsProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = '_errors';
public $title = 'Error Handlers Reset';
public function process() {
$this->container['errors']->resetHandlers();
public function process()
{
$this->container['errors']->resetHandlers();
}
}

View File

@@ -8,12 +8,13 @@
namespace Grav\Common\Processors;
class InitializeProcessor extends ProcessorBase implements ProcessorInterface {
class InitializeProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'init';
public $title = 'Initialize';
public function process() {
public function process()
{
$this->container['config']->debug();
// Use output buffering to prevent headers from being sent too early.
@@ -36,5 +37,4 @@ class InitializeProcessor extends ProcessorBase implements ProcessorInterface {
$this->container->setLocale();
}
}

View File

@@ -12,7 +12,6 @@ use Grav\Common\Page\Page;
class PagesProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'pages';
public $title = 'Pages';
@@ -41,5 +40,4 @@ class PagesProcessor extends ProcessorBase implements ProcessorInterface
}
}
}

View File

@@ -8,14 +8,14 @@
namespace Grav\Common\Processors;
class PluginsProcessor extends ProcessorBase implements ProcessorInterface {
class PluginsProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'plugins';
public $title = 'Plugins';
public function process() {
$this->container['plugins']->init();
$this->container->fireEvent('onPluginsInitialized');
public function process()
{
$this->container['plugins']->init();
$this->container->fireEvent('onPluginsInitialized');
}
}

View File

@@ -8,10 +8,18 @@
namespace Grav\Common\Processors;
class ProcessorBase {
use Grav\Common\Grav;
public function __construct($container) {
$this->container = $container;
}
class ProcessorBase
{
/**
* @var Grav
*/
protected $container;
public function __construct(Grav $container)
{
$this->container = $container;
}
}

View File

@@ -8,6 +8,7 @@
namespace Grav\Common\Processors;
interface ProcessorInterface {
public function process();
interface ProcessorInterface
{
public function process();
}

View File

@@ -8,14 +8,14 @@
namespace Grav\Common\Processors;
class RenderProcessor extends ProcessorBase implements ProcessorInterface {
class RenderProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'render';
public $title = 'Render';
public function process() {
$this->container->output = $this->container['output'];
$this->container->fireEvent('onOutputGenerated');
public function process()
{
$this->container->output = $this->container['output'];
$this->container->fireEvent('onOutputGenerated');
}
}

View File

@@ -8,14 +8,14 @@
namespace Grav\Common\Processors;
class SiteSetupProcessor extends ProcessorBase implements ProcessorInterface {
class SiteSetupProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = '_setup';
public $title = 'Site Setup';
public function process() {
$this->container['setup']->init();
$this->container['streams'];
public function process()
{
$this->container['setup']->init();
$this->container['streams'];
}
}

View File

@@ -8,16 +8,16 @@
namespace Grav\Common\Processors;
class TasksProcessor extends ProcessorBase implements ProcessorInterface {
class TasksProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'tasks';
public $title = 'Tasks';
public function process() {
public function process()
{
$task = $this->container['task'];
if ($task) {
$this->container->fireEvent('onTask.' . $task);
}
}
}

View File

@@ -8,13 +8,13 @@
namespace Grav\Common\Processors;
class ThemesProcessor extends ProcessorBase implements ProcessorInterface {
class ThemesProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'themes';
public $title = 'Themes';
public function process() {
$this->container['themes']->init();
public function process()
{
$this->container['themes']->init();
}
}

View File

@@ -8,13 +8,14 @@
namespace Grav\Common\Processors;
class TwigProcessor extends ProcessorBase implements ProcessorInterface {
class TwigProcessor extends ProcessorBase implements ProcessorInterface
{
public $id = 'twig';
public $title = 'Twig';
public function process() {
$this->container['twig']->init();
public function process()
{
$this->container['twig']->init();
}
}

View File

@@ -14,7 +14,8 @@ use Grav\Common\Assets;
class AssetsServiceProvider implements ServiceProviderInterface
{
public function register(Container $container) {
public function register(Container $container)
{
$container['assets'] = new Assets();
}
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* @package Grav.Common.Service
*
* @copyright Copyright (C) 2014 - 2016 RocketTheme, 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

@@ -13,7 +13,8 @@ use Pimple\ServiceProviderInterface;
class OutputServiceProvider implements ServiceProviderInterface
{
public function register(Container $container) {
public function register(Container $container)
{
$container['output'] = function ($c) {
return $c['twig']->processSite($c['page']->templateFormat());
};

View File

@@ -8,21 +8,23 @@
namespace Grav\Common\Service;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Uri;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class PageServiceProvider implements ServiceProviderInterface
{
public function register(Container $container) {
public function register(Container $container)
{
$container['page'] = function ($c) {
/** @var Grav $c */
/** @var Pages $pages */
$pages = $c['pages'];
/** @var Language $language */
$language = $c['language'];
/** @var Uri $uri */
$uri = $c['uri'];
@@ -34,6 +36,14 @@ class PageServiceProvider implements ServiceProviderInterface
// Redirection tests
if ($page) {
/** @var Language $language */
$language = $c['language'];
// some debugger override logic
if ($page->debugger() === false) {
Grav::instance()['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"];

View File

@@ -8,12 +8,14 @@
namespace Grav\Common\Service;
use Grav\Common\Grav;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
class TaskServiceProvider implements ServiceProviderInterface
{
public function register(Container $container) {
public function register(Container $container)
{
$container['task'] = function ($c) {
/** @var Grav $c */
return !empty($_POST['task']) ? $_POST['task'] : $c['uri']->param('task');

View File

@@ -93,6 +93,8 @@ class Taxonomy
foreach ((array)$items as $item) {
if (isset($this->taxonomy_map[$taxonomy][$item])) {
$matches[] = $this->taxonomy_map[$taxonomy][$item];
} else {
$matches[] = [];
}
}
}
@@ -126,4 +128,22 @@ class Taxonomy
return $this->taxonomy_map;
}
/**
* Gets item keys per taxonomy
*
* @param string $taxonomy taxonomy name
*
* @return array keys of this taxonomy
*/
public function getTaxonomyItemKeys($taxonomy) {
if (isset($this->taxonomy_map[$taxonomy])) {
$results = array_keys($this->taxonomy_map[$taxonomy]);
return $results;
}
return [];
}
}

View File

@@ -8,6 +8,7 @@
namespace Grav\Common;
use Grav\Common\Page\Page;
use Grav\Common\Config\Config;
use RocketTheme\Toolbox\File\YamlFile;
@@ -59,6 +60,13 @@ class Theme extends Plugin
return true;
}
/**
* Override the mergeConfig method to work for themes
*/
protected function mergeConfig(Page $page, $deep = 'merge', $params = [], $type = 'themes') {
return parent::mergeConfig($page, $deep, $params, $type);
}
/**
* Simpler getter for the theme blueprint
*

View File

@@ -311,17 +311,23 @@ class Themes extends Iterator
*/
protected function autoloadTheme($class)
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$prefix = "Grav\\Theme";
if (false !== strpos($class, $prefix)) {
// Remove prefix from class
$class = substr($class, strlen($prefix));
// Replace namespace tokens to directory separators
// Try Old style theme classes
$path = strtolower(ltrim(preg_replace('#\\\|_(?!.+\\\)#', '/', $class), '/'));
$file = $locator->findResource("themes://{$path}/{$path}.php");
$file = $this->grav['locator']->findResource("themes://{$path}/{$path}.php");
// Load class
if (file_exists($file)) {
return include_once($file);
}
// Replace namespace tokens to directory separators
$path = $this->grav['inflector']->hyphenize(ltrim($class,"\\"));
$file = $this->grav['locator']->findResource("themes://{$path}/{$path}.php");
// Load class
if (file_exists($file)) {

View File

@@ -11,6 +11,7 @@ namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Language\LanguageCodes;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
@@ -152,21 +153,28 @@ class Twig
$this->grav->fireEvent('onTwigExtensions');
$base_url = $this->grav['base_url'] . $path_append;
// Set some standard variables for twig
$this->twig_vars = $this->twig_vars + [
'config' => $config,
'uri' => $this->grav['uri'],
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_url' => $this->grav['base_url'] . $path_append,
'base_url_simple' => $this->grav['base_url'],
'base_url_absolute' => $this->grav['base_url_absolute'] . $path_append,
'base_url_relative' => $this->grav['base_url_relative'] . $path_append,
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
'system' => $config->get('system'),
'theme' => $config->get('theme'),
'site' => $config->get('site'),
'uri' => $this->grav['uri'],
'assets' => $this->grav['assets'],
'taxonomy' => $this->grav['taxonomy'],
'browser' => $this->grav['browser'],
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_url' => $base_url,
'base_url_simple' => $this->grav['base_url'],
'base_url_absolute' => $this->grav['base_url_absolute'] . $path_append,
'base_url_relative' => $this->grav['base_url_relative'] . $path_append,
'home_url' => $base_url == '' ? '/' : $base_url,
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
'language_codes' => new LanguageCodes,
];
}
}
@@ -222,25 +230,22 @@ class Twig
$twig_vars['header'] = $item->header();
$local_twig = clone($this->twig);
$modular_twig = $item->modularTwig();
$process_twig = isset($item->header()->process['twig']) ? $item->header()->process['twig'] : false;
$output = '';
try {
// Process Modular Twig
if ($modular_twig) {
if ($item->modularTwig()) {
$twig_vars['content'] = $content;
$template = $item->template() . TEMPLATE_EXT;
$output = $content = $local_twig->render($template, $twig_vars);
}
// Process in-page Twig
if (!$modular_twig || ($modular_twig && $process_twig)) {
if ($item->shouldProcess('twig')) {
$name = '@Page:' . $item->path();
$this->setTemplate($name, $content);
$output = $local_twig->render($name, $twig_vars);
}
} catch (\Twig_Error_Loader $e) {
throw new \RuntimeException($e->getRawMessage(), 404, $e);
}
@@ -320,6 +325,7 @@ class Twig
$twig_vars = $this->twig_vars;
$twig_vars['theme'] = $this->grav['config']->get('theme');
$twig_vars['pages'] = $pages->root();
$twig_vars['page'] = $page;
$twig_vars['header'] = $page->header();
@@ -343,6 +349,7 @@ class Twig
// Try html version of this template if initial template was NOT html
if ($ext != '.html' . TWIG_EXT) {
try {
$page->templateFormat('html');
$output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $twig_vars);
} catch (\Twig_Error_Loader $e) {
throw new \RuntimeException($error_msg, 400, $e);

View File

@@ -87,6 +87,7 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
new \Twig_SimpleFilter('array_unique', 'array_unique'),
];
}
@@ -103,7 +104,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_simpleFunction('authorize', [$this, 'authorize']),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
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('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
@@ -115,6 +117,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
];
}
@@ -429,9 +433,10 @@ class TwigExtension extends \Twig_Extension
/**
* @param $string
*
* @param bool $block Block or Line processing
* @return mixed|string
*/
public function markdownFilter($string)
public function markdownFilter($string, $block = true)
{
$page = $this->grav['page'];
$defaults = $this->config->get('system.pages.markdown');
@@ -443,7 +448,12 @@ class TwigExtension extends \Twig_Extension
$parsedown = new Parsedown($page, $defaults);
}
$string = $parsedown->text($string);
if ($block) {
$string = $parsedown->text($string);
} else {
$string = $parsedown->line($string);
}
return $string;
}
@@ -560,15 +570,22 @@ class TwigExtension extends \Twig_Extension
$domain = true;
}
if (Grav::instance()['uri']->isExternal($input)) {
return $input;
}
if (strpos((string)$input, '://')) {
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
// Get relative path to the resource (or false if not found).
$resource = $locator->findResource((string)$input, false);
$resource = $locator->findResource($input, false);
} else {
$resource = (string)$input;
$resource = $input;
}
/** @var Uri $uri */
@@ -578,22 +595,51 @@ class TwigExtension extends \Twig_Extension
}
/**
* Evaluate a string
* This function will evaluate Twig $twig through the $environment, and return its results.
*
* @example {{ evaluate('grav.language.getLanguage') }}
*
* @param string $input String to be evaluated
*
* @return string Returns the evaluated string
* @param \Twig_Environment $environment
* @param array $context
* @param string $twig
* @return mixed
*/
public function evaluateFunc($input)
{
if (!$input) { //prevent an obscure Twig error if $input is not set
$input = '""';
}
return $this->grav['twig']->processString("{{ $input }}");
public function evaluateTwigFunc( \Twig_Environment $environment, $context, $twig ) {
$loader = $environment->getLoader( );
$parsed = $this->parseString( $environment, $context, $twig );
$environment->setLoader( $loader );
return $parsed;
}
/**
* 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 )
{
$parsed = $this->evaluateTwigFunc($environment, $context, "{{ $string }}");
return $parsed;
}
/**
* 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
* (c) 2011 Fabien Potencier
@@ -775,7 +821,7 @@ class TwigExtension extends \Twig_Extension
*/
public function nonceFieldFunc($action, $nonceParamName = 'nonce')
{
$string = '<input type="hidden" id="' . $nonceParamName . '" name="' . $nonceParamName . '" value="' . Utils::getNonce($action) . '" />';
$string = '<input type="hidden" name="' . $nonceParamName . '" value="' . Utils::getNonce($action) . '" />';
return $string;
}
@@ -820,4 +866,30 @@ class TwigExtension extends \Twig_Extension
{
return preg_replace($pattern, $replace, $subject, $limit);
}
/**
* redirect browser from twig
*
* @param string $url the url to redirect to
* @param int $statusCode statusCode, default 303
*/
public function redirectFunc($url, $statusCode = 303)
{
header('Location: ' . $url, true, $statusCode);
die();
}
/**
* Generates an array containing a range of elements, optionally stepped
*
* @param int $start Minimum number, default 0
* @param int $end Maximum number, default `getrandmax()`
* @param int $step Increment between elements in the sequence, default 1
*
* @return array
*/
public function rangeFunc($start = 0, $end = 100, $step = 1)
{
return range($start, $end, $step);
}
}

View File

@@ -277,7 +277,13 @@ class Uri
}
// Set some defaults
$this->root = $grav['config']->get('system.custom_base_url') ?: $this->base . $this->root_path;
if ($grav['config']->get('system.custom_base_url')) {
$this->root_path = parse_url($grav['config']->get('system.custom_base_url'), PHP_URL_PATH);
$this->root = $grav['config']->get('system.custom_base_url');
} else {
$this->root = $this->base . $this->root_path;
}
$this->url = $this->base . $this->uri;
// get any params and remove them
@@ -304,11 +310,10 @@ class Uri
$bits = parse_url($uri);
// process query string
if (isset($bits['query']) && isset($bits['path'])) {
if (isset($bits['query'])) {
if (!$this->query) {
$this->query = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
}
$uri = $bits['path'];
}
//process fragment
@@ -316,8 +321,11 @@ class Uri
$this->fragment = $bits['fragment'];
}
// Get the path. If there's no path, make sure pathinfo() still returns dirname variable
$path = isset($bits['path']) ? $bits['path'] : '/';
// remove the extension if there is one set
$parts = pathinfo($uri);
$parts = pathinfo($path);
// set the original basename
$this->basename = $parts['basename'];
@@ -331,12 +339,12 @@ class Uri
// Strip the file extension for valid page types
if (preg_match('/\.(' . $valid_page_types . ')$/', $parts['basename'])) {
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
}
// set the new url
$this->url = $this->root . $uri;
$this->path = $uri;
$this->url = $this->root . $path;
$this->path = $path;
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
if ($this->content_path != '') {
$this->paths = explode('/', $this->content_path);
@@ -719,7 +727,7 @@ class Uri
*
* @return boolean is eternal state
*/
public function isExternal($url)
public static function isExternal($url)
{
if (Utils::startsWith($url, 'http')) {
return true;
@@ -1078,4 +1086,20 @@ class Uri
return $urlWithNonce;
}
/**
* Is the passed in URL a valid URL?
*
* @param $url
* @return bool
*/
public static function isValidUrl($url)
{
$regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
if (preg_match($regex, $url)) {
return true;
} else {
return false;
}
}
}

View File

@@ -87,7 +87,7 @@ class Group extends Data
$config->set("groups.$this->groupname.$value", $this->items['data'][$value]);
}
}
if ($field['type'] == 'array') {
if ($field['type'] == 'array' || $field['type'] == 'permissions') {
$value = $field['name'];
$arrayValues = Utils::getDotNotation($this->items['data'], $field['name']);

View File

@@ -54,6 +54,42 @@ class User extends Data
return $user;
}
/**
* Find a user by username, email, etc
*
* @param string $query the query to search for
* @param array $fields the fields to search
* @return User
*/
public static function find($query, $fields = ['username', 'email'])
{
$account_dir = Grav::instance()['locator']->findResource('account://');
$files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
// Try with username first, you never know!
if (in_array('username', $fields)) {
$user = User::load($query);
unset($fields[array_search('username', $fields)]);
} else {
$user = User::load('');
}
// If not found, try the fields
if (!$user->exists()) {
foreach ($files as $file) {
if (Utils::endsWith($file, YAML_EXT)) {
$find_user = User::load(trim(pathinfo($file, PATHINFO_FILENAME)));
foreach ($fields as $field) {
if ($find_user[$field] == $query) {
return $find_user;
}
}
}
}
}
return $user;
}
/**
* Remove user account.
*
@@ -64,7 +100,7 @@ class User extends Data
public static function remove($username)
{
$file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
if (file_exists($file_path) && unlink($file_path)) {
if ($file_path && unlink($file_path)) {
return true;
}
@@ -203,4 +239,20 @@ class User extends Data
{
return $this->authorize($action);
}
/**
* Return the User's avatar URL
*
* @return string
*/
public function avatarUrl()
{
if ($this->avatar) {
$avatar = $this->avatar;
$avatar = array_shift($avatar);
return Grav::instance()['base_url'] . '/' . $avatar['path'];
} else {
return 'https://www.gravatar.com/avatar/' . md5($this->email);
}
}
}

View File

@@ -109,6 +109,26 @@ abstract class Utils
return (object)array_merge((array)$obj1, (array)$obj2);
}
/**
* Recursive Merge with uniqueness
*
* @param $array1
* @param $array2
* @return mixed
*/
public static function arrayMergeRecursiveUnique($array1, $array2)
{
if (empty($array1)) return $array2; //optimize the base case
foreach ($array2 as $key => $value) {
if (is_array($value) && is_array(@$array1[$key])) {
$value = static::arrayMergeRecursiveUnique($array1[$key], $value);
}
$array1[$key] = $value;
}
return $array1;
}
/**
* Return the Grav date formats allowed
*
@@ -188,7 +208,11 @@ abstract class Utils
*/
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
{
return Truncator::truncateLetters($text, $length, $ellipsis);
if (mb_strlen($text) <= $length) {
return $text;
} else {
return Truncator::truncateLetters($text, $length, $ellipsis);
}
}
/**
@@ -809,4 +833,22 @@ abstract class Utils
return $array;
}
/**
* Utility method to determine if the current OS is Windows
*
* @return bool
*/
public static function isWindows() {
return strncasecmp(PHP_OS, 'WIN', 3) == 0;
}
/**
* Utility to determine if the server running PHP is Apache
*
* @return bool
*/
public static function isApache() {
return strpos($_SERVER["SERVER_SOFTWARE"], 'Apache') !== false;
}
}

View File

@@ -65,20 +65,15 @@ class InstallCommand extends ConsoleCommand
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
$this->user_path = $this->destination . USER_PATH;
if (false === $this->isWindows()) {
$local_config_file = exec('eval echo ~/.grav/config');
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse($local_config_file);
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
}
if ($local_config_file = $this->loadLocalConfig()) {
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
}
// Look for dependencies file in ROOT and USER dir
if (file_exists($this->user_path . $dependencies_file)) {
$this->config = Yaml::parse($this->user_path . $dependencies_file);
$this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
} elseif (file_exists($this->destination . $dependencies_file)) {
$this->config = Yaml::parse($this->destination . $dependencies_file);
$this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
} else {
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
}

View File

@@ -47,7 +47,6 @@ class SandboxCommand extends ConsoleCommand
* @var array
*/
protected $mappings = [
'/.editorconfig' => '/.editorconfig',
'/.gitignore' => '/.gitignore',
'/CHANGELOG.md' => '/CHANGELOG.md',
'/LICENSE.txt' => '/LICENSE.txt',
@@ -59,7 +58,6 @@ class SandboxCommand extends ConsoleCommand
'/system' => '/system',
'/vendor' => '/vendor',
'/webserver-configs' => '/webserver-configs',
'/codeception.yml' => '/codeception.yml',
];
/**

View File

@@ -16,6 +16,7 @@ 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
{
@@ -40,12 +41,11 @@ trait ConsoleTrait
*/
public function setupConsole(InputInterface $input, OutputInterface $output)
{
if (Grav::instance()) {
Grav::instance()['config']->set('system.cache.driver', 'default');
}
// Initialize cache with CLI compatibility
Grav::instance()['config']->set('system.cache.cli_compatibility', true);
Grav::instance()['cache'];
$this->argv = $_SERVER['argv'][0];
$this->input = $input;
$this->output = $output;
@@ -113,19 +113,20 @@ trait ConsoleTrait
}
/**
* Validate if the system is based on windows or not.
* Load the local config file
*
* @return bool
* @return mixed string the local config file name. false if local config does not exist
*/
public function isWindows()
public function loadLocalConfig()
{
$keys = [
'CYGWIN_NT-5.1',
'WIN32',
'WINNT',
'Windows'
];
$home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH');
$local_config_file = $home_folder . '/.grav/config';
return array_key_exists(PHP_OS, $keys);
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse(file_get_contents($local_config_file));
return $local_config_file;
}
return false;
}
}

View File

@@ -9,14 +9,13 @@
namespace Grav\Console\Gpm;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Yaml\Yaml;
class DirectInstallCommand extends ConsoleCommand
{
@@ -32,14 +31,14 @@ class DirectInstallCommand extends ConsoleCommand
->addArgument(
'package-file',
InputArgument::REQUIRED,
'The local location or remote URL to an installable package file'
'Installable package local <path> or remote <URL>. Can install specific version'
)
->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
}
/**
* @return int|null|void
* @return bool
*/
protected function serve()
{
@@ -63,10 +62,30 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln("Preparing to install <cyan>" . $package_file . "</cyan>");
if ($this->isRemote($package_file)) {
$zip = $this->downloadPackage($package_file, $tmp_zip);
if (Response::isRemote($package_file)) {
$this->output->write(" |- Downloading package... 0%");
try {
$zip = GPM::downloadPackage($package_file, $tmp_zip);
} catch (\RuntimeException $e) {
$this->output->writeln('');
$this->output->writeln(" `- <red>ERROR: " . $e->getMessage() . "</red>");
$this->output->writeln('');
exit;
}
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
}
} else {
$zip = $this->copyPackage($package_file, $tmp_zip);
$this->output->write(" |- Copying package... 0%");
$zip = GPM::copyPackage($package_file, $tmp_zip);
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Copying package... 100%");
$this->output->writeln('');
}
}
if (file_exists($zip)) {
@@ -84,7 +103,8 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write("\x0D");
$this->output->writeln(" |- Extracting package... <green>ok</green>");
$type = $this->getPackageType($extracted);
$type = GPM::getPackageType($extracted);
if (!$type) {
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
@@ -92,7 +112,7 @@ class DirectInstallCommand extends ConsoleCommand
exit;
}
$blueprint = $this->getBlueprints($extracted);
$blueprint = GPM::getBlueprints($extracted);
if ($blueprint) {
if (isset($blueprint['dependencies'])) {
$depencencies = [];
@@ -136,7 +156,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write(" |- Installing package... ");
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
} else {
$name = $this->getPackageName($extracted);
$name = GPM::getPackageName($extracted);
if (!$name) {
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
@@ -144,7 +164,7 @@ class DirectInstallCommand extends ConsoleCommand
exit;
}
$install_path = $this->getInstallPath($type, $name);
$install_path = GPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
$this->output->write(" |- Checking destination... ");
@@ -172,7 +192,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write("\x0D");
if(Installer::lastErrorCode()) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln(" '- <red>" . Installer::lastErrorMsg() . "</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" |- Installing package... <green>ok</green>");
@@ -192,183 +212,4 @@ class DirectInstallCommand extends ConsoleCommand
return true;
}
/**
* Get the install path for a name and a particular type of package
*
* @param $type
* @param $name
* @return string
*/
protected function getInstallPath($type, $name)
{
$locator = Grav::instance()['locator'];
if ($type == 'theme') {
$install_path = $locator->findResource('themes://', false) . DS . $name;
} else {
$install_path = $locator->findResource('plugins://', false) . DS . $name;
}
return $install_path;
}
/**
* Try to guess the package name from the source files
*
* @param $source
* @return bool|string
*/
protected function getPackageName($source)
{
foreach (glob($source . "*.yaml") as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if ($name == 'blueprints') {
continue;
}
return $name;
}
return false;
}
/**
* Try to guess the package type from the source files
*
* @param $source
* @return bool|string
*/
protected function getPackageType($source)
{
if (
file_exists($source . 'system/defines.php') &&
file_exists($source . 'system/config/system.yaml')
) {
return 'grav';
} else {
// must have a blueprint
if (!file_exists($source . 'blueprints.yaml')) {
return false;
}
// either theme or plugin
$name = basename($source);
if (Utils::contains($name, 'theme')) {
return 'theme';
} elseif (Utils::contains($name, 'plugin')) {
return 'plugin';
}
foreach (glob($source . "*.php") as $filename) {
$contents = file_get_contents($filename);
if (Utils::contains($contents, 'Grav\Common\Theme')) {
return 'theme';
} elseif (Utils::contains($contents, 'Grav\Common\Plugin')) {
return 'plugin';
}
}
// Assume it's a theme
return 'theme';
}
}
/**
* Determine if this is a local or a remote file
*
* @param $file
* @return bool
*/
protected function isRemote($file)
{
return (bool) filter_var($file, FILTER_VALIDATE_URL);
}
/**
* Find/Parse the blueprint file
*
* @param $source
* @return array|bool
*/
protected function getBlueprints($source)
{
$blueprint_file = $source . 'blueprints.yaml';
if (!file_exists($blueprint_file)) {
return false;
}
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
return $blueprint;
}
/**
* Download the zip package via the URL
*
* @param $package_file
* @param $tmp
* @return null|string
*/
private function downloadPackage($package_file, $tmp)
{
$this->output->write(" |- Downloading package... 0%");
$package = parse_url($package_file);
$filename = basename($package['path']);
$output = Response::get($package_file, [], [$this, 'progress']);
if ($output) {
Folder::mkdir($tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
file_put_contents($tmp . DS . $filename, $output);
return $tmp . DS . $filename;
}
return null;
}
/**
* Copy the local zip package to tmp
*
* @param $package_file
* @param $tmp
* @return null|string
*/
private function copyPackage($package_file, $tmp)
{
$this->output->write(" |- Copying package... 0%");
$package_file = realpath($package_file);
if (file_exists($package_file)) {
$filename = basename($package_file);
Folder::mkdir($tmp);
$this->output->write("\x0D");
$this->output->write(" |- Copying package... 100%");
$this->output->writeln('');
copy(realpath($package_file), $tmp . DS . $filename);
return $tmp . DS . $filename;
}
return null;
}
/**
* @param $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
STR_PAD_LEFT) . '%');
}
}

View File

@@ -147,7 +147,7 @@ class InfoCommand extends ConsoleCommand
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log['content']);

View File

@@ -20,7 +20,6 @@ use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Yaml\Yaml;
define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/');
@@ -81,7 +80,7 @@ class InstallCommand extends ConsoleCommand
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'The package(s) that are desired to be installed. Use the "index" command for a list of packages'
'Package(s) to install. Use "bin/gpm index" to list packages. Use "bin/gpm direct-install" to install a specific version'
)
->setDescription("Performs the installation of plugins and themes")
->setHelp('The <info>install</info> command allows to install plugins and themes');
@@ -98,7 +97,7 @@ class InstallCommand extends ConsoleCommand
}
/**
* @return int|null|void|bool
* @return bool
*/
protected function serve()
{
@@ -112,13 +111,7 @@ class InstallCommand extends ConsoleCommand
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = $this->gpm->findPackages($packages);
if (false === $this->isWindows() && @is_file(getenv("HOME") . '/.grav/config')) {
$local_config_file = exec('eval echo ~/.grav/config');
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse($local_config_file);
}
}
$this->loadLocalConfig();
if (
!Installer::isGravInstance($this->destination) ||
@@ -427,7 +420,7 @@ class InstallCommand extends ConsoleCommand
/**
* @param $package
*
* @return array
* @return array|bool
*/
private function getGitRegexMatches($package)
{
@@ -550,8 +543,12 @@ class InstallCommand extends ConsoleCommand
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
return true;
}
}
return false;
}
/**
@@ -566,6 +563,7 @@ class InstallCommand extends ConsoleCommand
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
$filename = $package->slug . basename($package->zipball_url);
$filename = preg_replace('/[\\\\\/:"*?&<>|]+/mi', '-', $filename);
$query = '';
if ($package->premium) {

View File

@@ -59,7 +59,7 @@ class SelfupgradeCommand extends ConsoleCommand
{
$this
->setName("self-upgrade")
->setAliases(['selfupgrade'])
->setAliases(['selfupgrade', 'selfupdate'])
->addOption(
'force',
'f',
@@ -143,7 +143,7 @@ class SelfupgradeCommand extends ConsoleCommand
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log['content']);

View File

@@ -11,6 +11,7 @@ namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Console\ConsoleCommand;
use Grav\Common\GPM\Upgrader;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -51,6 +52,11 @@ class UpdateCommand extends ConsoleCommand
protected $overwrite;
/**
* @var Upgrader
*/
protected $upgrader;
/**
*
*/
@@ -109,6 +115,22 @@ class UpdateCommand extends ConsoleCommand
*/
protected function serve()
{
$this->upgrader = new Upgrader($this->input->getOption('force'));
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
if ($local !== $remote) {
$this->output->writeln("<yellow>WARNING</yellow>: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.");
$this->output->writeln("");
$questionHelper = $this->getHelper('question');
$question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("<red>Update aborted. Exiting...</red>");
exit;
}
}
$this->gpm = new GPM($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');

View File

@@ -18,7 +18,7 @@ use Symfony\Component\Yaml\Yaml;
class VersionCommand extends ConsoleCommand
{
/**
* @var
* @var GPM
*/
protected $gpm;

View File

@@ -78,24 +78,27 @@ class ParsedownTest extends \Codeception\TestCase\Test
$this->grav['language'] = new Language($this->grav);
$this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init();
$this->assertSame('<p><img src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="\/images\/.*-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/images\/.*-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cropResize=200,200&foo)'));
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
$this->assertSame('<p><img src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="\/images\/.*-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/images\/.*-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cropResize=200,200&foo)'));
$this->assertRegexp('|<p><img src="\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](/home-cache-image.jpg?cache)'));
$this->assertSame('<p><img src="/item2/item2-2/missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](missing-image.jpg)'));
$this->assertSame('<p><img src="/home-missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](/home-missing-image.jpg)'));
$this->assertSame('<p><img src="/home-missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](/home-missing-image.jpg)'));
$this->assertSame('<p><img src="https://getgrav-grav.netdna-ssl.com/user/pages/media/grav-logo.svg" alt="" /></p>',
$this->parsedown->text('![](https://getgrav-grav.netdna-ssl.com/user/pages/media/grav-logo.svg)'));
}
@@ -103,11 +106,11 @@ class ParsedownTest extends \Codeception\TestCase\Test
{
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init();
$this->assertRegexp('|<p><img src="\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](/home-cache-image.jpg?cache)'));
$this->assertSame('<p><img src="/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cache)'));
$this->assertSame('<p><img src="/subdir/item2/item2-2/missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](missing-image.jpg)'));
@@ -121,11 +124,11 @@ class ParsedownTest extends \Codeception\TestCase\Test
$this->config->set('system.absolute_urls', true);
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
$this->assertSame('<p><img src="http://testing.dev/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="http://testing.dev/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cache)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](/home-cache-image.jpg?cache)'));
$this->assertSame('<p><img src="http://testing.dev/item2/item2-2/missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](missing-image.jpg)'));
@@ -138,11 +141,11 @@ class ParsedownTest extends \Codeception\TestCase\Test
$this->config->set('system.absolute_urls', true);
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init();
$this->assertSame('<p><img src="http://testing.dev/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="http://testing.dev/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cache)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](/home-cache-image.jpg?cropResize=200,200)'));
$this->assertSame('<p><img src="http://testing.dev/subdir/item2/item2-2/missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](missing-image.jpg)'));
@@ -154,13 +157,13 @@ class ParsedownTest extends \Codeception\TestCase\Test
{
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
$this->assertSame('<p><img title="My Title" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img title="My Title" alt="" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg "My Title")'));
$this->assertSame('<p><img class="foo" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" class="foo" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg?classes=foo)'));
$this->assertSame('<p><img class="foo bar" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" class="foo bar" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg?classes=foo,bar)'));
$this->assertSame('<p><img id="foo" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" id="foo" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg?id=foo)'));
$this->assertSame('<p><img alt="Alt Text" id="foo" src="/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![Alt Text](sample-image.jpg?id=foo)'));
@@ -185,11 +188,11 @@ class ParsedownTest extends \Codeception\TestCase\Test
$page = $this->pages->dispatch('/');
$this->parsedown = new Parsedown($page, $defaults);
$this->assertSame('<p><img src="/tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="/tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg" /></p>',
$this->parsedown->text('![](home-sample-image.jpg)'));
$this->assertRegexp('|<p><img src="\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](home-cache-image.jpg?cache)'));
$this->assertRegexp('|<p><img src="\/images\/.*-home-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="\/images\/.*-home-cache-image.jpe?g\?foo=1" \/><\/p>|',
$this->parsedown->text('![](home-cache-image.jpg?cropResize=200,200&foo)'));
$this->assertSame('<p><img src="/home-missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](/home-missing-image.jpg)'));
@@ -199,7 +202,7 @@ class ParsedownTest extends \Codeception\TestCase\Test
$this->grav['language'] = new Language($this->grav);
$this->uri->initializeWithURL('http://testing.dev/fr/item2/item2-2')->init();
$this->assertSame('<p><img src="/tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="/tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg" /></p>',
$this->parsedown->text('![](home-sample-image.jpg)'));
@@ -210,11 +213,11 @@ class ParsedownTest extends \Codeception\TestCase\Test
$this->config->set('system.absolute_urls', true);
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init();
$this->assertSame('<p><img src="http://testing.dev/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->assertSame('<p><img alt="" src="http://testing.dev/subdir/tests/fake/nested-site/user/pages/02.item2/02.item2-2/sample-image.jpg" /></p>',
$this->parsedown->text('![](sample-image.jpg)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/subdir\/images\/.*-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](cache-image.jpg?cache)'));
$this->assertRegexp('|<p><img src="http:\/\/testing.dev\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->assertRegexp('|<p><img alt="" src="http:\/\/testing.dev\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',
$this->parsedown->text('![](/home-cache-image.jpg?cropResize=200,200)'));
$this->assertSame('<p><img src="http://testing.dev/subdir/item2/item2-2/missing-image.jpg" alt="" /></p>',
$this->parsedown->text('![](missing-image.jpg)'));
@@ -672,6 +675,8 @@ class ParsedownTest extends \Codeception\TestCase\Test
{
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
$this->assertSame('<p><a href="#something" class="button">Anchor Class</a></p>',
$this->parsedown->text('[Anchor Class](?classes=button#something)'));
$this->assertSame('<p><a href="/item2/item2-3" class="button">Relative Class</a></p>',
$this->parsedown->text('[Relative Class](../item2-3?classes=button)'));
$this->assertSame('<p><a href="/item2/item2-3" id="unique">Relative ID</a></p>',

View File

@@ -183,4 +183,26 @@ class TwigExtensionTest extends \Codeception\TestCase\Test
{
}
public function testRangeFunc()
{
$hundred = [];
for($i = 0; $i <= 100; $i++) { $hundred[] = $i; }
$this->assertSame([0], $this->twig_ext->rangeFunc(0, 0));
$this->assertSame([0, 1, 2], $this->twig_ext->rangeFunc(0, 2));
$this->assertSame([0, 5, 10, 15], $this->twig_ext->rangeFunc(0, 16, 5));
// default (min 0, max 100, step 1)
$this->assertSame($hundred, $this->twig_ext->rangeFunc());
// 95 items, starting from 5, (min 5, max 100, step 1)
$this->assertSame(array_slice($hundred, 5), $this->twig_ext->rangeFunc(5));
// reversed range
$this->assertSame(array_reverse($hundred), $this->twig_ext->rangeFunc(100, 0));
$this->assertSame([4, 2, 0], $this->twig_ext->rangeFunc(4, 0, 2));
}
}

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