Merge branch 'release/1.1.9'

This commit is contained in:
Andy Miller
2016-12-13 13:06:15 -07:00
47 changed files with 1230 additions and 527 deletions

View File

@@ -1,3 +1,71 @@
# 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 [#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

285
composer.lock generated
View File

@@ -53,16 +53,16 @@
},
{
"name": "doctrine/cache",
"version": "v1.6.0",
"version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6"
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6",
"url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3",
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3",
"shasum": ""
},
"require": {
@@ -119,7 +119,7 @@
"cache",
"caching"
],
"time": "2015-12-31 16:37:02"
"time": "2016-10-29 11:16:17"
},
{
"name": "donatj/phpuseragentparser",
@@ -178,12 +178,12 @@
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cbc4b3f6126e484acc033ec4edf3886f319a0c0f"
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cbc4b3f6126e484acc033ec4edf3886f319a0c0f",
"reference": "cbc4b3f6126e484acc033ec4edf3886f319a0c0f",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb",
"reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb",
"shasum": ""
},
"require": {
@@ -212,7 +212,7 @@
"markdown",
"parser"
],
"time": "2016-10-09 10:04:16"
"time": "2016-11-02 15:56:58"
},
{
"name": "erusev/parsedown-extra",
@@ -466,16 +466,16 @@
},
{
"name": "matthiasmullie/minify",
"version": "1.3.38",
"version": "1.3.42",
"source": {
"type": "git",
"url": "https://github.com/matthiasmullie/minify.git",
"reference": "de4bcf23b6a3291bd828ce800aab834304eb50c5"
"reference": "b473affbb76ae6ec4e46d368b5081b5bfcdd3ed2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/de4bcf23b6a3291bd828ce800aab834304eb50c5",
"reference": "de4bcf23b6a3291bd828ce800aab834304eb50c5",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/b473affbb76ae6ec4e46d368b5081b5bfcdd3ed2",
"reference": "b473affbb76ae6ec4e46d368b5081b5bfcdd3ed2",
"shasum": ""
},
"require": {
@@ -484,6 +484,7 @@
"php": ">=5.3.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~1.0",
"matthiasmullie/scrapbook": "~1.0",
"phpunit/phpunit": "~4.8"
},
@@ -518,7 +519,7 @@
"minifier",
"minify"
],
"time": "2016-10-13 11:49:22"
"time": "2016-11-23 10:16:14"
},
{
"name": "matthiasmullie/path-converter",
@@ -632,16 +633,16 @@
},
{
"name": "monolog/monolog",
"version": "1.21.0",
"version": "1.22.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952"
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952",
"reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
"shasum": ""
},
"require": {
@@ -652,7 +653,7 @@
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
@@ -706,7 +707,7 @@
"logging",
"psr-3"
],
"time": "2016-07-29 03:23:52"
"time": "2016-11-26 00:15:39"
},
{
"name": "pimple/pimple",
@@ -899,16 +900,16 @@
},
{
"name": "symfony/console",
"version": "v2.8.12",
"version": "v2.8.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "d7a5a88178f94dcc29531ea4028ea614e35452d4"
"reference": "a871ba00e0f604dceac64c56c27f99fbeaf4854e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/d7a5a88178f94dcc29531ea4028ea614e35452d4",
"reference": "d7a5a88178f94dcc29531ea4028ea614e35452d4",
"url": "https://api.github.com/repos/symfony/console/zipball/a871ba00e0f604dceac64c56c27f99fbeaf4854e",
"reference": "a871ba00e0f604dceac64c56c27f99fbeaf4854e",
"shasum": ""
},
"require": {
@@ -956,7 +957,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-09-28 00:10:16"
"time": "2016-11-15 23:02:12"
},
{
"name": "symfony/debug",
@@ -1017,16 +1018,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v2.8.12",
"version": "v2.8.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "889983a79a043dfda68f38c38b6dba092dd49cd8"
"reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/889983a79a043dfda68f38c38b6dba092dd49cd8",
"reference": "889983a79a043dfda68f38c38b6dba092dd49cd8",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934",
"reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934",
"shasum": ""
},
"require": {
@@ -1073,20 +1074,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2016-07-28 16:56:28"
"time": "2016-10-13 01:43:15"
},
{
"name": "symfony/polyfill-iconv",
"version": "v1.2.0",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "b287e8554b1ffd9b5b20b5df940d906930ff4a10"
"reference": "cba36f3616d9866b3e52662e88da5c090fac1e97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/b287e8554b1ffd9b5b20b5df940d906930ff4a10",
"reference": "b287e8554b1ffd9b5b20b5df940d906930ff4a10",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/cba36f3616d9866b3e52662e88da5c090fac1e97",
"reference": "cba36f3616d9866b3e52662e88da5c090fac1e97",
"shasum": ""
},
"require": {
@@ -1098,7 +1099,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
"dev-master": "1.3-dev"
}
},
"autoload": {
@@ -1132,20 +1133,20 @@
"portable",
"shim"
],
"time": "2016-05-18 14:26:46"
"time": "2016-11-14 01:06:16"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.2.0",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "dff51f72b0706335131b00a7f49606168c582594"
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
"reference": "dff51f72b0706335131b00a7f49606168c582594",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
"reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
"shasum": ""
},
"require": {
@@ -1157,7 +1158,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
"dev-master": "1.3-dev"
}
},
"autoload": {
@@ -1191,20 +1192,20 @@
"portable",
"shim"
],
"time": "2016-05-18 14:26:46"
"time": "2016-11-14 01:06:16"
},
{
"name": "symfony/var-dumper",
"version": "v2.8.12",
"version": "v2.8.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "08761341cc4a3b2d9d9578364f7c655b178254aa"
"reference": "195c6238ec319cde9204b2d7f271654ceb69b71b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/08761341cc4a3b2d9d9578364f7c655b178254aa",
"reference": "08761341cc4a3b2d9d9578364f7c655b178254aa",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/195c6238ec319cde9204b2d7f271654ceb69b71b",
"reference": "195c6238ec319cde9204b2d7f271654ceb69b71b",
"shasum": ""
},
"require": {
@@ -1254,20 +1255,20 @@
"debug",
"dump"
],
"time": "2016-09-29 14:06:15"
"time": "2016-11-03 07:52:58"
},
{
"name": "symfony/yaml",
"version": "v2.8.12",
"version": "v2.8.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c"
"reference": "befb26a3713c97af90d25dd12e75621ef14d91ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/e7540734bad981fe59f8ef14b6fc194ae9df8d9c",
"reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c",
"url": "https://api.github.com/repos/symfony/yaml/zipball/befb26a3713c97af90d25dd12e75621ef14d91ff",
"reference": "befb26a3713c97af90d25dd12e75621ef14d91ff",
"shasum": ""
},
"require": {
@@ -1303,20 +1304,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-09-02 01:57:56"
"time": "2016-11-14 16:15:57"
},
{
"name": "twig/twig",
"version": "v1.26.1",
"version": "v1.28.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d"
"reference": "b22ce0eb070e41f7cba65d78fe216de29726459c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/b22ce0eb070e41f7cba65d78fe216de29726459c",
"reference": "b22ce0eb070e41f7cba65d78fe216de29726459c",
"shasum": ""
},
"require": {
@@ -1324,12 +1325,12 @@
},
"require-dev": {
"symfony/debug": "~2.7",
"symfony/phpunit-bridge": "~2.7"
"symfony/phpunit-bridge": "~3.2@dev"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.26-dev"
"dev-master": "1.28-dev"
}
},
"autoload": {
@@ -1364,22 +1365,22 @@
"keywords": [
"templating"
],
"time": "2016-10-05 18:57:41"
"time": "2016-11-23 18:41:40"
}
],
"packages-dev": [
{
"name": "behat/gherkin",
"version": "v4.4.4",
"version": "v4.4.5",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
"reference": "cf8cc94647101e02a33d690245896d83d880aea1"
"reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/cf8cc94647101e02a33d690245896d83d880aea1",
"reference": "cf8cc94647101e02a33d690245896d83d880aea1",
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74",
"reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74",
"shasum": ""
},
"require": {
@@ -1425,20 +1426,20 @@
"gherkin",
"parser"
],
"time": "2016-09-18 12:16:14"
"time": "2016-10-30 11:50:56"
},
{
"name": "codeception/codeception",
"version": "2.2.5",
"version": "2.2.7",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "b4729341e469d0f174f3cade85718ff5bf8dd751"
"reference": "86770e89d266557c20dd0b1de5390e706f4770c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/b4729341e469d0f174f3cade85718ff5bf8dd751",
"reference": "b4729341e469d0f174f3cade85718ff5bf8dd751",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/86770e89d266557c20dd0b1de5390e706f4770c1",
"reference": "86770e89d266557c20dd0b1de5390e706f4770c1",
"shasum": ""
},
"require": {
@@ -1456,7 +1457,7 @@
"symfony/browser-kit": ">=2.7 <4.0",
"symfony/console": ">=2.7 <4.0",
"symfony/css-selector": ">=2.7 <4.0",
"symfony/dom-crawler": ">=2.7 <4.0",
"symfony/dom-crawler": ">=2.7.5 <4.0",
"symfony/event-dispatcher": ">=2.7 <4.0",
"symfony/finder": ">=2.7 <4.0",
"symfony/yaml": ">=2.7 <4.0"
@@ -1472,7 +1473,8 @@
"pda/pheanstalk": "~3.0",
"php-amqplib/php-amqplib": "~2.4",
"predis/predis": "^1.0",
"squizlabs/php_codesniffer": "~2.0"
"squizlabs/php_codesniffer": "~2.0",
"vlucas/phpdotenv": "^2.4.0"
},
"suggest": {
"codeception/specify": "BDD-style code blocks",
@@ -1516,7 +1518,7 @@
"functional testing",
"unit testing"
],
"time": "2016-09-29 01:29:59"
"time": "2016-12-05 04:12:24"
},
{
"name": "doctrine/instantiator",
@@ -1731,16 +1733,16 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.2.0",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
"reference": "2693c101803ca78b27972d84081d027fca790a1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
"reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
"url": "https://api.github.com/repos/guzzle/promises/zipball/2693c101803ca78b27972d84081d027fca790a1e",
"reference": "2693c101803ca78b27972d84081d027fca790a1e",
"shasum": ""
},
"require": {
@@ -1778,7 +1780,7 @@
"keywords": [
"promise"
],
"time": "2016-05-18 16:56:05"
"time": "2016-11-18 17:47:58"
},
{
"name": "guzzlehttp/psr7",
@@ -1939,16 +1941,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "0.2",
"version": "0.2.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443"
"reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443",
"reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
"reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
"shasum": ""
},
"require": {
@@ -1982,20 +1984,20 @@
"email": "me@mikevanriel.com"
}
],
"time": "2016-06-10 07:14:17"
"time": "2016-11-25 06:54:22"
},
{
"name": "phpspec/prophecy",
"version": "v1.6.1",
"version": "v1.6.2",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "58a8137754bc24b25740d4281399a4a3596058e0"
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0",
"reference": "58a8137754bc24b25740d4281399a4a3596058e0",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb",
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb",
"shasum": ""
},
"require": {
@@ -2003,10 +2005,11 @@
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
"sebastian/comparator": "^1.1",
"sebastian/recursion-context": "^1.0"
"sebastian/recursion-context": "^1.0|^2.0"
},
"require-dev": {
"phpspec/phpspec": "^2.0"
"phpspec/phpspec": "^2.0",
"phpunit/phpunit": "^4.8 || ^5.6.5"
},
"type": "library",
"extra": {
@@ -2044,7 +2047,7 @@
"spy",
"stub"
],
"time": "2016-06-07 08:13:47"
"time": "2016-11-21 14:58:47"
},
{
"name": "phpunit/php-code-coverage",
@@ -2110,16 +2113,16 @@
},
{
"name": "phpunit/php-file-iterator",
"version": "1.4.1",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"shasum": ""
},
"require": {
@@ -2153,7 +2156,7 @@
"filesystem",
"iterator"
],
"time": "2015-06-21 13:08:43"
"time": "2016-10-03 07:40:28"
},
{
"name": "phpunit/php-text-template",
@@ -2242,16 +2245,16 @@
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.8",
"version": "1.4.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b",
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b",
"shasum": ""
},
"require": {
@@ -2287,20 +2290,20 @@
"keywords": [
"tokenizer"
],
"time": "2015-09-15 10:49:45"
"time": "2016-11-15 14:06:22"
},
{
"name": "phpunit/phpunit",
"version": "4.8.27",
"version": "4.8.30",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90"
"reference": "a534e04d0bd39c557c2881c341efd06fa6f1292a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90",
"reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a534e04d0bd39c557c2881c341efd06fa6f1292a",
"reference": "a534e04d0bd39c557c2881c341efd06fa6f1292a",
"shasum": ""
},
"require": {
@@ -2316,7 +2319,7 @@
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.1",
"sebastian/comparator": "~1.2.2",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
@@ -2359,7 +2362,7 @@
"testing",
"xunit"
],
"time": "2016-07-21 06:48:14"
"time": "2016-12-01 17:05:48"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -2469,22 +2472,22 @@
},
{
"name": "sebastian/comparator",
"version": "1.2.0",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
"reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f",
"reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2"
"sebastian/exporter": "~1.2 || ~2.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
@@ -2529,7 +2532,7 @@
"compare",
"equality"
],
"time": "2015-07-26 15:48:44"
"time": "2016-11-19 09:18:40"
},
{
"name": "sebastian/diff",
@@ -2841,16 +2844,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v3.1.5",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "901319a31c9b3cee7857b4aeeb81b5d64dfa34fc"
"reference": "34348c2691ce6254e8e008026f4c5e72c22bb318"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/901319a31c9b3cee7857b4aeeb81b5d64dfa34fc",
"reference": "901319a31c9b3cee7857b4aeeb81b5d64dfa34fc",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/34348c2691ce6254e8e008026f4c5e72c22bb318",
"reference": "34348c2691ce6254e8e008026f4c5e72c22bb318",
"shasum": ""
},
"require": {
@@ -2867,7 +2870,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@@ -2894,20 +2897,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2016-09-06 11:02:40"
"time": "2016-10-13 13:35:11"
},
{
"name": "symfony/css-selector",
"version": "v3.1.5",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac"
"reference": "e1241f275814827c411d922ba8e64cf2a00b2994"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
"reference": "ca809c64072e0fe61c1c7fb3c76cdc32265042ac",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e1241f275814827c411d922ba8e64cf2a00b2994",
"reference": "e1241f275814827c411d922ba8e64cf2a00b2994",
"shasum": ""
},
"require": {
@@ -2916,7 +2919,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@@ -2947,20 +2950,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2016-09-06 11:02:40"
"time": "2016-11-03 08:11:03"
},
{
"name": "symfony/dom-crawler",
"version": "v3.1.5",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7"
"reference": "c6b6111f5aae7c58698cdc10220785627ac44a2c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7",
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c6b6111f5aae7c58698cdc10220785627ac44a2c",
"reference": "c6b6111f5aae7c58698cdc10220785627ac44a2c",
"shasum": ""
},
"require": {
@@ -2976,7 +2979,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@@ -3003,20 +3006,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2016-08-05 08:37:39"
"time": "2016-11-25 12:32:42"
},
{
"name": "symfony/finder",
"version": "v3.1.5",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f"
"reference": "4263e35a1e342a0f195c9349c0dee38148f8a14f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
"reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f",
"url": "https://api.github.com/repos/symfony/finder/zipball/4263e35a1e342a0f195c9349c0dee38148f8a14f",
"reference": "4263e35a1e342a0f195c9349c0dee38148f8a14f",
"shasum": ""
},
"require": {
@@ -3025,7 +3028,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@@ -3052,24 +3055,24 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2016-09-28 00:11:12"
"time": "2016-11-03 08:11:03"
},
{
"name": "webmozart/assert",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "bb2d123231c095735130cc8f6d31385a44c7b308"
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/bb2d123231c095735130cc8f6d31385a44c7b308",
"reference": "bb2d123231c095735130cc8f6d31385a44c7b308",
"url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
"reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
"shasum": ""
},
"require": {
"php": "^5.3.3|^7.0"
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
@@ -3078,7 +3081,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
"dev-master": "1.3-dev"
}
},
"autoload": {
@@ -3102,7 +3105,7 @@
"check",
"validate"
],
"time": "2016-08-09 15:02:57"
"time": "2016-11-23 20:04:58"
}
],
"aliases": [

View File

@@ -2,7 +2,7 @@ title: PLUGIN_ADMIN.DEFAULT
rules:
slug:
pattern: "[a-zа-я][a-zа-я0-9_\-]+"
pattern: '[a-zа-я][a-zа-я0-9_\-]+'
min: 2
max: 80
@@ -139,7 +139,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

@@ -5,7 +5,7 @@ title: PLUGIN_ADMIN:EXTERNAL
rules:
slug:
pattern: "[a-zа-я][a-zа-я0-9_\-]+"
pattern: '[a-zа-я][a-zа-я0-9_\-]+'
min: 2
max: 80

View File

@@ -1,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -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,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -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,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -12,6 +12,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,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -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,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -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,6 +1,6 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
pattern: '[a-z][a-z0-9_\-]+'
min: 2
max: 80
@@ -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
@@ -56,7 +56,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 +77,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

@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.1.8');
define('GRAV_VERSION', '1.1.9');
define('GRAV_TESTING', false);
define('DS', '/');
define('GRAV_PHP_MIN', '5.5.9');

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.
@@ -356,36 +356,38 @@ 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_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();
}
}

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;
@@ -569,6 +570,7 @@ class Validation
return null;
}
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
foreach ($values as $key => $value) {
@@ -586,6 +588,18 @@ class Validation
}
}
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] = $value;
}
}
return $values;
}

View File

@@ -23,58 +23,63 @@ 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')) {
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

@@ -386,7 +386,6 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
{
@@ -396,7 +395,6 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function create($folder)
{

View File

@@ -29,6 +29,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',

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

@@ -10,9 +10,9 @@ namespace Grav\Common\Helpers;
use Grav\Common\Grav;
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
{
@@ -115,11 +115,11 @@ class Excerpts
*/
public static function processLinkExcerpt($excerpt, $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 +129,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 +154,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;
@@ -187,62 +195,65 @@ class Excerpts
*/
public static function processImageExcerpt($excerpt, $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), '/');
$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 +293,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 +311,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

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

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

@@ -84,7 +84,6 @@ class Collection extends Iterator
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
@@ -172,12 +171,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() ];
@@ -75,6 +101,9 @@ class Media extends Getters
// Create the base medium
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'];
@@ -110,8 +139,9 @@ class Media extends Getters
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
}
foreach ($types['alternative'] as $ratio => $altMedium) {
foreach ($types['alternative'] as $altMedium) {
if ($altMedium['file'] != $medium) {
$ratio = $altMedium['file']->get('width') / $medium->get('width');
$medium->addAlternative($ratio, $altMedium['file']);
}
}
@@ -120,134 +150,4 @@ class Media extends Getters
$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

@@ -236,14 +236,16 @@ 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) {
public function derivatives($min_width, $max_width = 2500, $step = 200) {
if (!empty($this->alternatives)) {
$max = max(array_keys($this->alternatives));
$base = $this->alternatives[$max];
@@ -251,10 +253,23 @@ class ImageMedium extends Medium
$base = $this;
}
// Do not upscale images.
$max_width = min($max_width, $base->get('width'));
$widths = [];
for ($width = $min_width; $width < $max_width; $width = $width + $step) {
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;
@@ -267,10 +282,10 @@ class ImageMedium extends Medium
// retrieved from the page cache
if (isset($derivative)) {
$index = 2;
$widths = array_keys($this->alternatives);
sort($widths);
$alt_widths = array_keys($this->alternatives);
sort($alt_widths);
foreach ($widths as $i => $key) {
foreach ($alt_widths as $i => $key) {
if ($width > $key) {
$index += max($i, 1);
}
@@ -497,7 +512,7 @@ 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();
}
@@ -509,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');
}
}
}
@@ -532,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

@@ -150,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)

View File

@@ -902,6 +902,8 @@ class Page
$this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug());
}
$this->raw_route = null;
return $this;
}
@@ -2269,6 +2271,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 +2294,14 @@ 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 +2421,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':

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
{
@@ -197,7 +198,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 +215,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 +236,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 +245,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 +343,19 @@ class Pages
$page = $this->dispatch($route, $all);
} else {
// Try Regex style redirects
$source_url = $url;
$extension = $this->grav['uri']->extension();
if (isset($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 +370,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 +571,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 +658,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])) {
@@ -1017,12 +1029,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 +1061,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;
@@ -1064,16 +1083,20 @@ class Pages
} 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 +1104,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 {

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

@@ -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,20 @@ 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;
}
}
}

View File

@@ -115,6 +115,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']),
];
}
@@ -820,4 +822,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

@@ -304,11 +304,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 +315,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 +333,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);

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 $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 = 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.
*

View File

@@ -188,7 +188,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);
}
}
/**

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

@@ -59,7 +59,7 @@ class SelfupgradeCommand extends ConsoleCommand
{
$this
->setName("self-upgrade")
->setAliases(['selfupgrade'])
->setAliases(['selfupgrade', 'selfupdate'])
->addOption(
'force',
'f',

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;
@@ -109,6 +110,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

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

View File

@@ -129,6 +129,7 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertEquals('<p>This is a string to truncate</p>', Utils::truncateHtml('<p>This is a string to truncate</p>', 100));
$this->assertEquals('<input type="file" id="file" multiple>', Utils::truncateHtml('<input type="file" id="file" multiple />', 6));
$this->assertEquals('<ol><li>item 1 <i>so...</i></li></ol>', Utils::truncateHtml('<ol><li>item 1 <i>something</i></li><li>item 2 <strong>bold</strong></li></ol>', 10));
$this->assertEquals("<p>This is a string.</p>\n<p>It splits two lines.</p>", Utils::truncateHtml("<p>This is a string.</p>\n<p>It splits two lines.</p>", 100));
}
public function testSafeTruncateHtml()

View File

@@ -6,23 +6,25 @@ fastcgi / 127.0.0.1:9000 php
# deny all direct access for these folders
rewrite {
r /(.git|cache|bin|logs|backups|tests)/.*$
status 403
to /403
}
# deny running scripts inside core system folders
rewrite {
r /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$
status 403
to /403
}
# deny running scripts inside user folder
rewrite {
r /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$
status 403
to /403
}
# deny access to specific files in the root folder
rewrite {
r /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)
status 403
to /403
}
status 403 /403
## End - Security
# global rewrite should come last.

View File

@@ -0,0 +1,33 @@
# Caddyfile for Caddy 0.8.x and below
:8080
gzip
fastcgi / 127.0.0.1:9000 php
# Begin - Security
# deny all direct access for these folders
rewrite {
r /(.git|cache|bin|logs|backups|tests)/.*$
status 403
}
# deny running scripts inside core system folders
rewrite {
r /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$
status 403
}
# deny running scripts inside user folder
rewrite {
r /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$
status 403
}
# deny access to specific files in the root folder
rewrite {
r /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)
status 403
}
## End - Security
# global rewrite should come last.
rewrite {
to {path} {path}/ /index.php?_url={uri}
}

View File

@@ -3,7 +3,7 @@
RewriteEngine On
## Begin RewriteBase
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
# along the lines of: RewriteBase /<your_sub_folder>
@@ -13,6 +13,16 @@ RewriteEngine On
## End - RewriteBase
## Begin - X-Forwarded-Proto
# In some hosted or load balanced environments, SSL negotiation happens upstream.
# In order for Grav to recognize the connection as secure, you need to uncomment
# the following lines.
#
# RewriteCond %{HTTP:X-Forwarded-Proto} https
# RewriteRule .* - [E=HTTPS:on]
#
## End - X-Forwarded-Proto
## Begin - Exploits
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
@@ -52,7 +62,7 @@ RewriteRule ^(user)/(.*)\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block all direct access to .md files:
RewriteRule \.md$ error [F]
# Block all direct access to files and folders beginning with a dot
RewriteRule (^\.|/\.) - [F]
RewriteRule (^|/)\.(?!well-known) - [F]
# Block access to specific files in the root folder
RewriteRule ^(LICENSE.txt|composer.lock|composer.json|\.htaccess)$ error [F]
## End - Security
@@ -62,4 +72,4 @@ RewriteRule ^(LICENSE.txt|composer.lock|composer.json|\.htaccess)$ error [F]
# Begin - Prevent Browsing and Set Default Resources
Options -Indexes
DirectoryIndex index.php index.html index.htm
# End - Prevent Browsing and Set Default Resources
# End - Prevent Browsing and Set Default Resources

View File

@@ -16,6 +16,17 @@ server {
}
## End - Index
## Begin - Security
# deny all direct access for these folders
location ~* /(.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
## End - Security
## Begin - PHP
location ~ \.php$ {
# Choose either a socket or TCP/IP address
@@ -28,16 +39,5 @@ server {
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
}
## End - PHP
## Begin - Security
# deny all direct access for these folders
location ~* /(.git|cache|bin|logs|backups|tests)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
## End - Security
}