diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e44f6fb..bdea66039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/composer.lock b/composer.lock index 92c0cb53d..d00fccb24 100644 --- a/composer.lock +++ b/composer.lock @@ -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": [ diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml index 14116aab0..f38957ccf 100644 --- a/system/blueprints/pages/default.yaml +++ b/system/blueprints/pages/default.yaml @@ -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 diff --git a/system/blueprints/pages/external.yaml b/system/blueprints/pages/external.yaml index 568e2f61f..9f95d3e0c 100644 --- a/system/blueprints/pages/external.yaml +++ b/system/blueprints/pages/external.yaml @@ -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 diff --git a/system/blueprints/pages/modular_new.yaml b/system/blueprints/pages/modular_new.yaml index 57ede83e8..273292363 100644 --- a/system/blueprints/pages/modular_new.yaml +++ b/system/blueprints/pages/modular_new.yaml @@ -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 diff --git a/system/blueprints/pages/modular_raw.yaml b/system/blueprints/pages/modular_raw.yaml index 6e71eaa13..ed25992a3 100644 --- a/system/blueprints/pages/modular_raw.yaml +++ b/system/blueprints/pages/modular_raw.yaml @@ -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: diff --git a/system/blueprints/pages/move.yaml b/system/blueprints/pages/move.yaml index 5fda52fd2..ed3c46312 100644 --- a/system/blueprints/pages/move.yaml +++ b/system/blueprints/pages/move.yaml @@ -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 diff --git a/system/blueprints/pages/new.yaml b/system/blueprints/pages/new.yaml index 0b2fe75fc..49b85b729 100644 --- a/system/blueprints/pages/new.yaml +++ b/system/blueprints/pages/new.yaml @@ -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 diff --git a/system/blueprints/pages/new_folder.yaml b/system/blueprints/pages/new_folder.yaml index c4fe96165..16024b749 100644 --- a/system/blueprints/pages/new_folder.yaml +++ b/system/blueprints/pages/new_folder.yaml @@ -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: diff --git a/system/blueprints/pages/raw.yaml b/system/blueprints/pages/raw.yaml index fe5dba2fe..60870eaf7 100644 --- a/system/blueprints/pages/raw.yaml +++ b/system/blueprints/pages/raw.yaml @@ -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 diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml index bd1caa85f..ff657b4c4 100644 --- a/system/blueprints/user/account.yaml +++ b/system/blueprints/user/account.yaml @@ -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 diff --git a/system/blueprints/user/group.yaml b/system/blueprints/user/group.yaml index 0adcbb5c0..a5a7477b6 100644 --- a/system/blueprints/user/group.yaml +++ b/system/blueprints/user/group.yaml @@ -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 diff --git a/system/defines.php b/system/defines.php index 550246281..956dd1b1d 100644 --- a/system/defines.php +++ b/system/defines.php @@ -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'); diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php index 048260527..5891d75ed 100644 --- a/system/src/Grav/Common/Cache.php +++ b/system/src/Grav/Common/Cache.php @@ -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[] = 'Cleared: ' . $path . '/*'; + if ($anything) { + $output[] = 'Cleared: ' . $path . '/*'; + } + } catch (\Exception $e) { + // stream not found or another error while deleting files. + $output[] = 'ERROR: ' . $e->getMessage(); } } diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php index 22f6d8454..7dbf34838 100644 --- a/system/src/Grav/Common/Data/Validation.php +++ b/system/src/Grav/Common/Data/Validation.php @@ -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; } diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php index fcb0de4d2..16e67f4ea 100644 --- a/system/src/Grav/Common/File/CompiledFile.php +++ b/system/src/Grav/Common/File/CompiledFile.php @@ -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); diff --git a/system/src/Grav/Common/File/CompiledJsonFile.php b/system/src/Grav/Common/File/CompiledJsonFile.php new file mode 100644 index 000000000..236680809 --- /dev/null +++ b/system/src/Grav/Common/File/CompiledJsonFile.php @@ -0,0 +1,28 @@ + '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', diff --git a/system/src/Grav/Common/Helpers/Base32.php b/system/src/Grav/Common/Helpers/Base32.php new file mode 100644 index 000000000..eebb95b5d --- /dev/null +++ b/system/src/Grav/Common/Helpers/Base32.php @@ -0,0 +1,103 @@ +', '?' + 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; + } +} diff --git a/system/src/Grav/Common/Helpers/Excerpts.php b/system/src/Grav/Common/Helpers/Excerpts.php index 090dd1121..05afd3df4 100644 --- a/system/src/Grav/Common/Helpers/Excerpts.php +++ b/system/src/Grav/Common/Helpers/Excerpts.php @@ -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; + } } diff --git a/system/src/Grav/Common/Iterator.php b/system/src/Grav/Common/Iterator.php index ad5722dfc..2618c1064 100644 --- a/system/src/Grav/Common/Iterator.php +++ b/system/src/Grav/Common/Iterator.php @@ -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; diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php index 85b3236e3..de56a79bf 100644 --- a/system/src/Grav/Common/Language/LanguageCodes.php +++ b/system/src/Grav/Common/Language/LanguageCodes.php @@ -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' ], diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php index 5a70ba99c..53baf620d 100644 --- a/system/src/Grav/Common/Page/Collection.php +++ b/system/src/Grav/Common/Page/Collection.php @@ -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; } diff --git a/system/src/Grav/Common/Page/Media.php b/system/src/Grav/Common/Page/Media.php index c4451e08c..eb12eeb1c 100644 --- a/system/src/Grav/Common/Page/Media.php +++ b/system/src/Grav/Common/Page/Media.php @@ -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); - } } diff --git a/system/src/Grav/Common/Page/Medium/AbstractMedia.php b/system/src/Grav/Common/Page/Medium/AbstractMedia.php new file mode 100644 index 000000000..e3ad3e3cb --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/AbstractMedia.php @@ -0,0 +1,164 @@ +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); + } +} diff --git a/system/src/Grav/Common/Page/Medium/GlobalMedia.php b/system/src/Grav/Common/Page/Medium/GlobalMedia.php new file mode 100644 index 000000000..6b5629eca --- /dev/null +++ b/system/src/Grav/Common/Page/Medium/GlobalMedia.php @@ -0,0 +1,117 @@ +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; + } +} diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index a2bb7b169..2e92ccfd8 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -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) diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php index b9e338854..342efb3ae 100644 --- a/system/src/Grav/Common/Page/Medium/Medium.php +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -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) diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 0806fff9e..fb683cae5 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -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': diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index 719f48937..2771d131d 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -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 { diff --git a/system/src/Grav/Common/Service/MessagesServiceProvider.php b/system/src/Grav/Common/Service/MessagesServiceProvider.php new file mode 100644 index 000000000..78238fb9b --- /dev/null +++ b/system/src/Grav/Common/Service/MessagesServiceProvider.php @@ -0,0 +1,30 @@ +messages)) { + $session->messages = new Message; + } + + return $session->messages; + }; + } +} diff --git a/system/src/Grav/Common/Taxonomy.php b/system/src/Grav/Common/Taxonomy.php index f4c49ab3d..25be7edd3 100644 --- a/system/src/Grav/Common/Taxonomy.php +++ b/system/src/Grav/Common/Taxonomy.php @@ -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; + } + } } diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index f67e441f2..aa9f1de04 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -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); + } } diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index edfc79725..10cacb47e 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -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); diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php index 7092cfce4..2a29340db 100644 --- a/system/src/Grav/Common/User/Group.php +++ b/system/src/Grav/Common/User/Group.php @@ -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']); diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index 340797011..fcc8f6c43 100644 --- a/system/src/Grav/Common/User/User.php +++ b/system/src/Grav/Common/User/User.php @@ -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. * diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index c63aa8ffe..c7b9bb4e2 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -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); + } } /** diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php index 769bf6a33..918806b47 100644 --- a/system/src/Grav/Console/Cli/SandboxCommand.php +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -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', ]; /** diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php index 9179ae7c7..f15ca8902 100644 --- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -59,7 +59,7 @@ class SelfupgradeCommand extends ConsoleCommand { $this ->setName("self-upgrade") - ->setAliases(['selfupgrade']) + ->setAliases(['selfupgrade', 'selfupdate']) ->addOption( 'force', 'f', diff --git a/system/src/Grav/Console/Gpm/UpdateCommand.php b/system/src/Grav/Console/Gpm/UpdateCommand.php index 6428e90a0..a0227fc2b 100644 --- a/system/src/Grav/Console/Gpm/UpdateCommand.php +++ b/system/src/Grav/Console/Gpm/UpdateCommand.php @@ -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("WARNING: 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("Update aborted. Exiting..."); + exit; + } + } + $this->gpm = new GPM($this->input->getOption('force')); $this->all_yes = $this->input->getOption('all-yes'); diff --git a/tests/unit/Grav/Common/Twig/TwigExtensionTest.php b/tests/unit/Grav/Common/Twig/TwigExtensionTest.php index df70fc53c..1c1f6fa0a 100644 --- a/tests/unit/Grav/Common/Twig/TwigExtensionTest.php +++ b/tests/unit/Grav/Common/Twig/TwigExtensionTest.php @@ -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)); + } } diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php index d45eee356..61149f239 100644 --- a/tests/unit/Grav/Common/UtilsTest.php +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -129,6 +129,7 @@ class UtilsTest extends \Codeception\TestCase\Test $this->assertEquals('

This is a string to truncate

', Utils::truncateHtml('

This is a string to truncate

', 100)); $this->assertEquals('', Utils::truncateHtml('', 6)); $this->assertEquals('
  1. item 1 so...
', Utils::truncateHtml('
  1. item 1 something
  2. item 2 bold
', 10)); + $this->assertEquals("

This is a string.

\n

It splits two lines.

", Utils::truncateHtml("

This is a string.

\n

It splits two lines.

", 100)); } public function testSafeTruncateHtml() diff --git a/webserver-configs/Caddyfile b/webserver-configs/Caddyfile index 682947087..9d3b43760 100644 --- a/webserver-configs/Caddyfile +++ b/webserver-configs/Caddyfile @@ -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. diff --git a/webserver-configs/Caddyfile-0.8.x b/webserver-configs/Caddyfile-0.8.x new file mode 100644 index 000000000..520eafb42 --- /dev/null +++ b/webserver-configs/Caddyfile-0.8.x @@ -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} +} diff --git a/webserver-configs/htaccess.txt b/webserver-configs/htaccess.txt index c82ca8fa6..faef479f7 100644 --- a/webserver-configs/htaccess.txt +++ b/webserver-configs/htaccess.txt @@ -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 / @@ -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 \ No newline at end of file diff --git a/webserver-configs/nginx.conf b/webserver-configs/nginx.conf index 18db9fdd5..640964a6d 100644 --- a/webserver-configs/nginx.conf +++ b/webserver-configs/nginx.conf @@ -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 }