diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ecf3b72..c49db82b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,93 @@ -# v1.1.7 +# v1.1.10 ## XX/XX/2016 +1. [](#improved) + * Improve detection of home path. Also allow ~/.grav on Windows, drop ConsoleTrait::isWindows() method, used only for that [#1204](https://github.com/getgrav/grav/pull/1204) +1. [](#bugfix) + * Fixed case where extracting a package would cause an error during rename + * Fix issue with using Yaml::parse direcly on a filename, now deprecated + +# 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 + +1. [](#bugfix) + * Fixed warning with unset `ssl` option when using GPM [#1132](https://github.com/getgrav/grav/issues/1132) + +# v1.1.7 +## 10/22/2016 + +1. [](#improved) + * Improved the capabilities of Image derivatives [#1107](https://github.com/getgrav/grav/pull/1107) 1. [](#bugfix) - * Only pass verify_peer settings to cURL and fopen if the setting is disabled + * Only pass verify_peer settings to cURL and fopen if the setting is disabled [#1120](https://github.com/getgrav/grav/issues/1120) # v1.1.6 ## 10/19/2016 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 7ef5561b4..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.6'); +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 @@ 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 @@ public static function clearCache($remove = 'standard') $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 . '/*'); - - 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; + $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 ($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..b597cd642 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 @@ protected static function filterArray($value, $params, $field) return null; } + if ($options) { $useKey = isset($field['use']) && $field['use'] == 'keys'; foreach ($values as $key => $value) { @@ -580,9 +582,22 @@ protected static function filterArray($value, $params, $field) foreach ($values as $key => $value) { if (is_array($value)) { $value = implode(',', $value); + $values[$key] = array_map('trim', explode(',', $value)); + } else { + $values[$key] = trim($value); + } + } + } + + if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) { + foreach ($values as $key => $value) { + foreach ($value as $inner_key => $inner_value) { + if ($inner_value == '') { + unset($value[$inner_key]); + } } - $values[$key] = array_map('trim', explode(',', $value)); + $values[$key] = $value; } } 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 @@ public function content($var = null) // 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()); - } + if (!$modified) { + return $this->decode($this->raw()); + } - $class = get_class($this); + $class = get_class($this); - $cache = $file->exists() ? $file->content() : null; + $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. - } + // 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 - ]; + // 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(); + // 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); + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate')) { + opcache_invalidate($file->filename(), true); + } } } + $file->free(); + + $this->content = $cache['data']; } - $file->free(); - $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 @@ +getNameIndex(0); + $package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0)); $zip->close(); $extracted_folder = $destination . '/' . $package_folder_name; diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php index 6b33c67b1..3bb6b2062 100644 --- a/system/src/Grav/Common/GPM/Response.php +++ b/system/src/Grav/Common/GPM/Response.php @@ -264,13 +264,18 @@ private static function getFopen() $options['fopen']['notification'] = ['self', 'progress']; } - $ssl = $options['fopen']['ssl']; - unset($options['fopen']['ssl']); + if (isset($options['fopen']['ssl'])) { + $ssl = $options['fopen']['ssl']; + unset($options['fopen']['ssl']); + + $stream = stream_context_create([ + 'http' => $options['fopen'], + 'ssl' => $ssl + ], $options['fopen']); + } else { + $stream = stream_context_create(['http' => $options['fopen']], $options['fopen']); + } - $stream = stream_context_create([ - 'http' => $options['fopen'], - 'ssl' => $ssl - ], $options['fopen']); $content = @file_get_contents($uri, false, $stream); diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 20fc5d554..c18fa4c2a 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -29,6 +29,7 @@ class Grav extends Container 'events' => 'RocketTheme\Toolbox\Event\EventDispatcher', 'cache' => 'Grav\Common\Cache', 'session' => 'Grav\Common\Session', + 'Grav\Common\Service\MessagesServiceProvider', 'plugins' => 'Grav\Common\Plugins', 'themes' => 'Grav\Common\Themes', 'twig' => 'Grav\Common\Twig\Twig', 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 @@ 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 @@ public static function getHtmlFromExcerpt($excerpt) */ 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 @@ public static function processLinkExcerpt($excerpt, $page, $type = 'link') 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 @@ public static function processLinkExcerpt($excerpt, $page, $type = 'link') $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 @@ public static function processLinkExcerpt($excerpt, $page, $type = 'link') */ 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(); - - // 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'])) { - - $path_parts = pathinfo($url_parts['path']); - $media = null; - - // 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']), '/'); + $media = $page->media(); - $ext_page = Grav::instance()['pages']->dispatch($page_route, true); - if ($ext_page) { - $media = $ext_page->media(); + } 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()); + + if ($local_file) { + $filename = basename($url_parts['path']); + $folder = dirname($url_parts['path']); + + // 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 @@ public static function processMediaActions($medium, $url) // 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 @@ public static function processMediaActions($medium, $url) 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 @@ public function slice($offset, $length = null) */ 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 @@ public function copy() public function setParams(array $params) { $this->params = array_merge($this->params, $params); - return $this; } @@ -172,12 +171,13 @@ public function remove($key = null) * @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 c8385ea15..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() ]; @@ -74,16 +100,16 @@ public function __construct($path) } // Create the base medium - if (!empty($types['base'])) { + if (empty($types['base'])) { + if (!isset($types['alternative'])) { + continue; + } + $max = max(array_keys($types['alternative'])); + $medium = $types['alternative'][$max]['file']; + $medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file']; + } else { $medium = MediumFactory::fromFile($types['base']['file']); $medium && $medium->set('size', $types['base']['size']); - } else if (!empty($types['alternative'])) { - $altMedium = reset($types['alternative']); - $ratio = key($types['alternative']); - - $altMedium = $altMedium['file']; - - $medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1)['file']; } if (empty($medium)) { @@ -103,10 +129,9 @@ public function __construct($path) // Build missing alternatives if (!empty($types['alternative'])) { $alternatives = $types['alternative']; - $max = max(array_keys($alternatives)); - for ($i=2; $i < $max; $i++) { + for ($i=$max; $i > 1; $i--) { if (isset($alternatives[$i])) { continue; } @@ -114,142 +139,15 @@ public function __construct($path) $types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i); } - foreach ($types['alternative'] as $ratio => $altMedium) { - $medium->addAlternative($ratio, $altMedium['file']); + foreach ($types['alternative'] as $altMedium) { + if ($altMedium['file'] != $medium) { + $ratio = $altMedium['file']->get('width') / $medium->get('width'); + $medium->addAlternative($ratio, $altMedium['file']); + } } } $this->add($name, $medium); } } - - /** - * Get medium by filename. - * - * @param string $filename - * @return Medium|null - */ - public function get($filename) - { - return isset($this->instances[$filename]) ? $this->instances[$filename] : null; - } - - /** - * Get a list of all media. - * - * @return array|Medium[] - */ - public function all() - { - ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE); - return $this->instances; - } - - /** - * Get a list of all image media. - * - * @return array|Medium[] - */ - public function images() - { - ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE); - return $this->images; - } - - /** - * Get a list of all video media. - * - * @return array|Medium[] - */ - public function videos() - { - ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE); - return $this->videos; - } - - /** - * Get a list of all audio media. - * - * @return array|Medium[] - */ - public function audios() - { - ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE); - return $this->audios; - } - - /** - * Get a list of all file media. - * - * @return array|Medium[] - */ - public function files() - { - ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE); - return $this->files; - } - - /** - * @internal - */ - protected function add($name, $file) - { - $this->instances[$name] = $file; - switch ($file->type) { - case 'image': - $this->images[$name] = $file; - break; - case 'video': - $this->videos[$name] = $file; - break; - case 'audio': - $this->audios[$name] = $file; - break; - default: - $this->files[$name] = $file; - } - } - - /** - * Get filename, extension and meta part. - * - * @param string $filename - * @return array - */ - protected function getFileParts($filename) - { - $fileParts = explode('.', $filename); - - $name = array_shift($fileParts); - $type = 'base'; - $extra = null; - - if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) { - $name = $matches[1]; - $extension = $matches[3]; - $extra = (int) $matches[2]; - $type = 'alternative'; - - if ($extra === 1) { - $type = 'base'; - $extra = null; - } - } else { - $extension = null; - while (($part = array_shift($fileParts)) !== null) { - if ($part != 'meta' && $part != 'thumb') { - if (isset($extension)) { - $name .= '.' . $extension; - } - $extension = $part; - } else { - $type = $part; - $extra = '.' . $part . '.' . implode('.', $fileParts); - break; - } - } - } - - return array($name, $extension, $type, $extra); - } } 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 e3900baef..2e92ccfd8 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -65,11 +65,6 @@ class ImageMedium extends Medium 'zoomCrop' => [0, 1] ]; - /** - * @var array - */ - protected $derivatives = []; - /** * @var string */ @@ -197,27 +192,19 @@ public function cache() */ public function srcset($reset = true) { - if (empty($this->alternatives) && empty($this->derivatives)) { + if (empty($this->alternatives)) { if ($reset) { $this->reset(); } + return ''; } - if (!empty($this->derivatives)) { - asort($this->derivatives); - - foreach ($this->derivatives as $url => $width) { - $srcset[] = $url . ' ' . $width . 'w'; - } - - $srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w'; - } else { - $srcset = [$this->url($reset) . ' ' . $this->get('width') . 'w']; - foreach ($this->alternatives as $ratio => $medium) { - $srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w'; - } + $srcset = []; + foreach ($this->alternatives as $ratio => $medium) { + $srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w'; } + $srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w'; return implode(', ', $srcset); } @@ -249,39 +236,76 @@ public function getImagePrettyName() } /** - * Generate derivatives + * Generate alternative image widths, using either an array of integers, or + * a min width, a max width, and a step parameter to fill out the necessary + * widths. Existing image alternatives won't be overwritten. * - * @param int $min_width - * @param int $max_width - * @param int $step + * @param int|int[] $min_width + * @param int [$max_width=2500] + * @param int [$step=200] * @return $this */ - public function derivatives($min_width, $max_width, $step = 200) { - $width = $min_width; + public function derivatives($min_width, $max_width = 2500, $step = 200) { + if (!empty($this->alternatives)) { + $max = max(array_keys($this->alternatives)); + $base = $this->alternatives[$max]; + } else { + $base = $this; + } - // Do not upscale images. - if ($max_width > $this->get('width')) { - $max_width = $this->get('width'); - } + $widths = []; - while ($width <= $max_width) { - $ratio = $width / $this->get('width'); - $derivative = MediumFactory::scaledFromMedium($this, 1, $ratio); - if (is_array($derivative)) { - $this->addDerivative($derivative['file']); + 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; + } } - $width += $step; - } - return $this; - } - /** - * Add a derivative - * - * @param ImageMedium $image - */ - public function addDerivative(ImageMedium $image) { - $this->derivatives[$image->url()] = $image->get('width'); + foreach ($widths as $width) { + // Only generate image alternatives that don't already exist + if (array_key_exists((int) $width, $this->alternatives)) { + continue; + } + + $derivative = MediumFactory::fromFile($base->get('filepath')); + + // It's possible that MediumFactory::fromFile returns null if the + // original image file no longer exists and this class instance was + // retrieved from the page cache + if (isset($derivative)) { + $index = 2; + $alt_widths = array_keys($this->alternatives); + sort($alt_widths); + + foreach ($alt_widths as $i => $key) { + if ($width > $key) { + $index += max($i, 1); + } + } + + $basename = preg_replace('/(@\d+x){0,1}$/', "@{$width}w", $base->get('basename'), 1); + $derivative->setImagePrettyName($basename); + + $ratio = $base->get('width') / $width; + $height = $derivative->get('height') / $ratio; + + $derivative->resize($width, $height); + $derivative->set('width', $width); + $derivative->set('height', $height); + + $this->addAlternative($ratio, $derivative); + } + } + + return $this; } /** @@ -488,7 +512,11 @@ public function __call($method, $args) try { call_user_func_array([$this->image, $method], $args); - foreach ($this->alternatives as $ratio => $medium) { + foreach ($this->alternatives as $medium) { + if (!$medium->image) { + $medium->image(); + } + $args_copy = $args; // regular image: resize 400x400 -> 200x200 @@ -496,7 +524,7 @@ public function __call($method, $args) if (isset(self::$magic_resize_actions[$method])) { foreach (self::$magic_resize_actions[$method] as $param) { if (isset($args_copy[$param])) { - $args_copy[$param] = (int) $args_copy[$param] * $ratio; + $args_copy[$param] *= $medium->get('ratio'); } } } @@ -519,7 +547,9 @@ protected function image() $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 b13306f52..342efb3ae 100644 --- a/system/src/Grav/Common/Page/Medium/Medium.php +++ b/system/src/Grav/Common/Page/Medium/Medium.php @@ -100,7 +100,9 @@ public function addAlternative($ratio, Medium $alternative) } $alternative->set('ratio', $ratio); - $this->alternatives[(float) $ratio] = $alternative; + $width = $alternative->get('width'); + + $this->alternatives[$width] = $alternative; } /** @@ -148,8 +150,8 @@ public function url($reset = true) /** * 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/Medium/MediumFactory.php b/system/src/Grav/Common/Page/Medium/MediumFactory.php index c75044f5c..3743a81cf 100644 --- a/system/src/Grav/Common/Page/Medium/MediumFactory.php +++ b/system/src/Grav/Common/Page/Medium/MediumFactory.php @@ -119,8 +119,8 @@ public static function scaledFromMedium($medium, $from, $to) } $ratio = $to / $from; - $width = (int) ($medium->get('width') * $ratio); - $height = (int) ($medium->get('height') * $ratio); + $width = $medium->get('width') * $ratio; + $height = $medium->get('height') * $ratio; $prev_basename = $medium->get('basename'); $basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename); 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 @@ public function move(Page $parent) $this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug()); } + $this->raw_route = null; + return $this; } @@ -2269,6 +2271,7 @@ public function collection($params = 'content', $pagination = true) 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 @@ public function collection($params = 'content', $pagination = true) $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 @@ public function evaluate($value) 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 RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use Whoops\Exception\ErrorException; +use Collator as Collator; class Pages { @@ -197,7 +198,7 @@ public function addPage(Page $page, $route = null) * * @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 @@ public function sort(Page $page, $order_by = null, $order_dir = null) } 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 @@ public function sort(Page $page, $order_by = null, $order_dir = null) * @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 @@ public function sortCollection(Collection $collection, $orderBy, $orderDir = 'as $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 @@ public function dispatch($url, $all = false, $redirect = true) $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 @@ public function dispatch($url, $all = false, $redirect = true) 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 @@ public static function modularTypes() */ 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 @@ private static function getParents($rawRoutes) $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 @@ protected function buildRoutes() * @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 @@ protected function buildSort($path, array $pages, $order_by = 'default', $manual $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 @@ protected function buildSort($path, array $pages, $order_by = 'default', $manual } 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 @@ protected function buildSort($path, array $pages, $order_by = 'default', $manual // 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 37975f7a1..5df36a3e7 100644 --- a/system/src/Grav/Common/Taxonomy.php +++ b/system/src/Grav/Common/Taxonomy.php @@ -93,6 +93,8 @@ public function findTaxonomy($taxonomies, $operator = 'and') foreach ((array)$items as $item) { if (isset($this->taxonomy_map[$taxonomy][$item])) { $matches[] = $this->taxonomy_map[$taxonomy][$item]; + } else { + $matches[] = []; } } } @@ -126,7 +128,11 @@ public function taxonomy($var = null) return $this->taxonomy_map; } +<<<<<<< HEAD +======= + +>>>>>>> origin/develop /** * Gets item keys per taxonomy * diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index 43d3cddf4..af23880b8 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -117,7 +117,11 @@ public function getFunctions() new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']), new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']), new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']), +<<<<<<< HEAD new \Twig_SimpleFunction('ishuman', [$this, 'isHuman']), +======= + new \Twig_SimpleFunction('range', [$this, 'rangeFunc']), +>>>>>>> origin/develop ]; } @@ -823,20 +827,29 @@ public function regexReplace($subject, $pattern, $replace, $limit = -1) { return preg_replace($pattern, $replace, $subject, $limit); } +<<<<<<< HEAD +======= + +>>>>>>> origin/develop /** * redirect browser from twig * * @param string $url the url to redirect to +<<<<<<< HEAD * @param string $statusCode statuscode, default 303 * * @return none +======= + * @param int $statusCode statusCode, default 303 +>>>>>>> origin/develop */ public function redirectFunc($url, $statusCode = 303) { header('Location: ' . $url, true, $statusCode); die(); } +<<<<<<< HEAD /** * detect (in)humane agent @@ -847,5 +860,20 @@ public function isHuman() { $agent = new Browser; return $agent->isHuman(); +======= + + /** + * 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); +>>>>>>> origin/develop } } 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 @@ public function init() $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 @@ public function init() $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 @@ public function init() // 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 @@ public function save() $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 @@ public static function load($username) 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 @@ public static function safeTruncate($string, $limit = 150) */ 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/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php index a3768ba4e..49b66a097 100644 --- a/system/src/Grav/Console/Cli/InstallCommand.php +++ b/system/src/Grav/Console/Cli/InstallCommand.php @@ -65,20 +65,15 @@ protected function serve() // fix trailing slash $this->destination = rtrim($this->destination, DS) . DS; $this->user_path = $this->destination . USER_PATH; - - if (false === $this->isWindows()) { - $local_config_file = exec('eval echo ~/.grav/config'); - if (file_exists($local_config_file)) { - $this->local_config = Yaml::parse($local_config_file); - $this->output->writeln('Read local config from ' . $local_config_file . ''); - } + if ($local_config_file = $this->loadLocalConfig()) { + $this->output->writeln('Read local config from ' . $local_config_file . ''); } // Look for dependencies file in ROOT and USER dir if (file_exists($this->user_path . $dependencies_file)) { - $this->config = Yaml::parse($this->user_path . $dependencies_file); + $this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file)); } elseif (file_exists($this->destination . $dependencies_file)) { - $this->config = Yaml::parse($this->destination . $dependencies_file); + $this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file)); } else { $this->output->writeln('ERROR Missing .dependencies file in user/ folder'); } 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/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php index 66ecd07fd..e03789102 100644 --- a/system/src/Grav/Console/ConsoleTrait.php +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Yaml\Yaml; trait ConsoleTrait { @@ -113,19 +114,20 @@ public function clearCache($all = []) } /** - * Validate if the system is based on windows or not. + * Load the local config file * - * @return bool + * @return mixed string the local config file name. false if local config does not exist */ - public function isWindows() + public function loadLocalConfig() { - $keys = [ - 'CYGWIN_NT-5.1', - 'WIN32', - 'WINNT', - 'Windows' - ]; - - return array_key_exists(PHP_OS, $keys); + $home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH'); + $local_config_file = $home_folder . '/.grav/config'; + + if (file_exists($local_config_file)) { + $this->local_config = Yaml::parse(file_get_contents($local_config_file)); + return $local_config_file; + } + + return false; } } diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php index f8103e2c2..bae3dd30a 100644 --- a/system/src/Grav/Console/Gpm/InstallCommand.php +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -20,7 +20,6 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Question\ConfirmationQuestion; -use Symfony\Component\Yaml\Yaml; define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/'); @@ -112,13 +111,7 @@ protected function serve() $packages = array_map('strtolower', $this->input->getArgument('package')); $this->data = $this->gpm->findPackages($packages); - - if (false === $this->isWindows() && @is_file(getenv("HOME") . '/.grav/config')) { - $local_config_file = exec('eval echo ~/.grav/config'); - if (file_exists($local_config_file)) { - $this->local_config = Yaml::parse($local_config_file); - } - } + $this->loadLocalConfig(); if ( !Installer::isGravInstance($this->destination) || 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 @@ protected function configure() { $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 @@ 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 @@ protected function configure() */ 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 @@ public function stringFunc() { } + + 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 @@ public function testTruncateHtml() $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 }