diff --git a/CHANGELOG.md b/CHANGELOG.md index 80890b180..8b3eec986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# v1.6.12 +## 08/11/2019 + +1. [](#new) + * Added support for custom `FormFlash` save locations + * Added a new `Utils::arrayLower()` method for lowercasing arrays + * Support new GRAV_BASEDIR environment variable [#2541](https://github.com/getgrav/grav/pull/2541) + * Allow users to override plugin handler priorities [#2165](https://github.com/getgrav/grav/pull/2165) +1. [](#improved) + * Use new `Utils::getSupportedPageTypes()` to enforce `html,htm` at the front of the list [#2531](https://github.com/getgrav/grav/issues/2531) + * Updated vendor libraries + * Markdown filter is now page-aware so that it works with modular references [admin#1731](https://github.com/getgrav/grav-plugin-admin/issues/1731) + * Check of `GRAV_USER_INSTANCE` constant is already defined [#2621](https://github.com/getgrav/grav/pull/2621) +1. [](#bugfix) + * Fixed some potential issues when `$grav['user']` is not set + * Fixed error when calling `Media::add($name, null)` + * Fixed `url()` returning wrong path if using stream with grav root path in it, eg: `user-data://shop` when Grav is in `/shop` + * Fixed `url()` not returning a path to non-existing file (`user-data://shop` => `/user/data/shop`) if it is set to fail gracefully + * Fixed `url()` returning false on unknown streams, such as `ftp://domain.com`, they should be treated as external URL + * Fixed Flex User to have permissions to save and delete his own user + * Fixed new Flex User creation not being possible because of username could not be given + * Fixed fatal error 'Expiration date must be an integer, a DateInterval or null, "double" given' [#2529](https://github.com/getgrav/grav/issues/2529) + * Fixed non-existing Flex object having a bad media folder + * Fixed collections using `page@.self:` should allow modular pages if requested + * Fixed an error when trying to delete a file from non-existing Flex Object + * Fixed `FlexObject::exists()` failing sometimes just after the object has been saved + * Fixed CSV formatter not encoding strings with `"` and `,` properly + * Fixed var order in `Validation.php` [#2610](https://github.com/getgrav/grav/issues/2610) + # v1.6.11 ## 06/21/2019 diff --git a/composer.lock b/composer.lock index be6bf65b6..d44899510 100644 --- a/composer.lock +++ b/composer.lock @@ -52,25 +52,25 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.4", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", - "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f26a67e397be0e5c00d7c52ec7b5010098e15ce5", + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0 || ^4.0" }, @@ -104,7 +104,7 @@ "ssl", "tls" ], - "time": "2019-01-28T09:30:10+00:00" + "time": "2019-08-02T09:05:43+00:00" }, { "name": "doctrine/cache", @@ -183,16 +183,16 @@ }, { "name": "doctrine/collections", - "version": "v1.6.1", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956" + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956", - "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956", + "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", "shasum": "" }, "require": { @@ -249,7 +249,7 @@ "iterators", "php" ], - "time": "2019-03-25T19:03:48+00:00" + "time": "2019-06-09T13:48:14+00:00" }, { "name": "donatj/phpuseragentparser", @@ -437,16 +437,16 @@ }, { "name": "filp/whoops", - "version": "2.3.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7" + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", + "url": "https://api.github.com/repos/filp/whoops/zipball/cde50e6720a39fdacb240159d3eea6865d51fd96", + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96", "shasum": "" }, "require": { @@ -480,8 +480,8 @@ "authors": [ { "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "role": "Developer", + "homepage": "https://github.com/filp" } ], "description": "php error handling for cool kids", @@ -494,7 +494,7 @@ "throwable", "whoops" ], - "time": "2018-10-23T09:00:00+00:00" + "time": "2019-08-07T09:00:00+00:00" }, { "name": "gregwar/cache", @@ -593,33 +593,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.5.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9f83dded91781a01c63574e387eaa769be769115" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", - "reference": "9f83dded91781a01c63574e387eaa769be769115", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-zlib": "*", "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -656,20 +660,20 @@ "uri", "url" ], - "time": "2018-12-04T20:46:45+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "kodus/psr7-server", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/kodus/psr7-server.git", - "reference": "7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13" + "reference": "dcfd0116451b0f0e7c6b23b831757ed288347278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kodus/psr7-server/zipball/7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13", - "reference": "7c0e9c72e6cb282bf58e9e73386e4ded26c6ae13", + "url": "https://api.github.com/repos/kodus/psr7-server/zipball/dcfd0116451b0f0e7c6b23b831757ed288347278", + "reference": "dcfd0116451b0f0e7c6b23b831757ed288347278", "shasum": "" }, "require": { @@ -711,7 +715,7 @@ "psr-17", "psr-7" ], - "time": "2018-12-05T09:09:19+00:00" + "time": "2019-06-17T10:48:13+00:00" }, { "name": "league/climate", @@ -946,24 +950,25 @@ }, { "name": "miljar/php-exif", - "version": "v0.6.4", + "version": "v0.6.5", "source": { "type": "git", "url": "https://github.com/PHPExif/php-exif.git", - "reference": "361c15b8bc7d5ef26a9492fe537f09c920fe6511" + "reference": "41f23db39d7b48e4af0e134c2e80e577c1782ac9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPExif/php-exif/zipball/361c15b8bc7d5ef26a9492fe537f09c920fe6511", - "reference": "361c15b8bc7d5ef26a9492fe537f09c920fe6511", + "url": "https://api.github.com/repos/PHPExif/php-exif/zipball/41f23db39d7b48e4af0e134c2e80e577c1782ac9", + "reference": "41f23db39d7b48e4af0e134c2e80e577c1782ac9", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.4" }, "require-dev": { + "jakub-onderka/php-parallel-lint": "^1.0", "phpmd/phpmd": "~2.2", - "phpunit/phpunit": "3.7.*", + "phpunit/phpunit": ">=4.0 <6.0", "satooshi/php-coveralls": "~0.6", "sebastian/phpcpd": "1.4.*@stable", "squizlabs/php_codesniffer": "1.4.*@stable" @@ -997,7 +1002,7 @@ "jpeg", "tiff" ], - "time": "2018-03-27T10:41:55+00:00" + "time": "2019-02-11T13:47:52+00:00" }, { "name": "monolog/monolog", @@ -1282,6 +1287,52 @@ ], "time": "2018-01-21T07:42:36+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/container", "version": "1.0.0", @@ -1636,24 +1687,24 @@ }, { "name": "ralouphie/getallheaders", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~3.7.0", - "satooshi/php-coveralls": ">=1.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -1672,7 +1723,7 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2016-02-11T07:05:27+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { "name": "rockettheme/toolbox", @@ -1774,25 +1825,27 @@ }, { "name": "symfony/console", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", - "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "url": "https://api.github.com/repos/symfony/console/zipball/8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, "provide": { @@ -1802,9 +1855,10 @@ "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", @@ -1815,7 +1869,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1842,38 +1896,44 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-04-08T14:23:48+00:00" + "time": "2019-07-24T17:13:59+00:00" }, { "name": "symfony/contracts", - "version": "v1.1.0", + "version": "v1.1.5", "source": { "type": "git", "url": "https://github.com/symfony/contracts.git", - "reference": "d3636025e8253c6144358ec0a62773cae588395b" + "reference": "3f3f796d5f24a098a9da62828b8daa1b11494c1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b", - "reference": "d3636025e8253c6144358ec0a62773cae588395b", + "url": "https://api.github.com/repos/symfony/contracts/zipball/3f3f796d5f24a098a9da62828b8daa1b11494c1b", + "reference": "3f3f796d5f24a098a9da62828b8daa1b11494c1b", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "replace": { + "symfony/cache-contracts": "self.version", + "symfony/event-dispatcher-contracts": "self.version", + "symfony/http-client-contracts": "self.version", + "symfony/service-contracts": "self.version", + "symfony/translation-contracts": "self.version" }, "require-dev": { - "psr/cache": "^1.0", - "psr/container": "^1.0", "symfony/polyfill-intl-idn": "^1.10" }, "suggest": { - "psr/cache": "When using the Cache contracts", - "psr/container": "When using the Service contracts", - "symfony/cache-contracts-implementation": "", + "psr/event-dispatcher": "When using the EventDispatcher contracts", + "symfony/cache-implementation": "", "symfony/event-dispatcher-implementation": "", - "symfony/http-client-contracts-implementation": "", - "symfony/service-contracts-implementation": "", - "symfony/translation-contracts-implementation": "" + "symfony/http-client-implementation": "", + "symfony/service-implementation": "", + "symfony/translation-implementation": "" }, "type": "library", "extra": { @@ -1913,34 +1973,40 @@ "interoperability", "standards" ], - "time": "2019-04-27T14:29:50+00:00" + "time": "2019-06-20T06:46:26+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02" + "reference": "212b020949331b6531250584531363844b34a94e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fbce53cd74ac509cbe74b6f227622650ab759b02", - "reference": "fbce53cd74ac509cbe74b6f227622650ab759b02", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/212b020949331b6531250584531363844b34a94e", + "reference": "212b020949331b6531250584531363844b34a94e", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0" + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4" }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { @@ -1950,7 +2016,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -1977,20 +2043,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-04-06T13:51:08+00:00" + "time": "2019-06-27T06:42:14+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -2002,7 +2068,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2019,12 +2085,12 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { - "name": "Gert de Pagter", - "email": "backendtea@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -2035,20 +2101,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7" + "reference": "685968b11e61a347c18bf25db32effa478be610f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", - "reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", + "reference": "685968b11e61a347c18bf25db32effa478be610f", "shasum": "" }, "require": { @@ -2060,7 +2126,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2094,20 +2160,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -2119,7 +2185,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2153,20 +2219,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + "reference": "04ce3335667451138df4307d6a9b61565560199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", "shasum": "" }, "require": { @@ -2175,7 +2241,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2208,20 +2274,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd" + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", - "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", "shasum": "" }, "require": { @@ -2230,7 +2296,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2266,20 +2332,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/process", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe" + "reference": "856d35814cf287480465bb7a6c413bb7f5f5e69c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8cf39fb4ccff793340c258ee7760fd40bfe745fe", - "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe", + "url": "https://api.github.com/repos/symfony/process/zipball/856d35814cf287480465bb7a6c413bb7f5f5e69c", + "reference": "856d35814cf287480465bb7a6c413bb7f5f5e69c", "shasum": "" }, "require": { @@ -2288,7 +2354,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2315,20 +2381,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-04-10T16:20:36+00:00" + "time": "2019-05-30T16:10:05+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce" + "reference": "e4110b992d2cbe198d7d3b244d079c1c58761d07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce", - "reference": "3c4084cb1537c0e2ad41aad622bbf55a44a5c9ce", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e4110b992d2cbe198d7d3b244d079c1c58761d07", + "reference": "e4110b992d2cbe198d7d3b244d079c1c58761d07", "shasum": "" }, "require": { @@ -2357,7 +2423,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2391,20 +2457,20 @@ "debug", "dump" ], - "time": "2019-05-01T12:55:36+00:00" + "time": "2019-07-27T06:42:46+00:00" }, { "name": "symfony/yaml", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1" + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1", - "reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/34d29c2acd1ad65688f58452fd48a46bd996d5a6", + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6", "shasum": "" }, "require": { @@ -2423,7 +2489,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -2450,20 +2516,20 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-03-30T15:58:42+00:00" + "time": "2019-07-24T14:47:54+00:00" }, { "name": "twig/twig", - "version": "v1.41.0", + "version": "v1.42.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "575cd5028362da591facde1ef5d7b94553c375c9" + "reference": "21707d6ebd05476854805e4f91b836531941bcd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/575cd5028362da591facde1ef5d7b94553c375c9", - "reference": "575cd5028362da591facde1ef5d7b94553c375c9", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/21707d6ebd05476854805e4f91b836531941bcd4", + "reference": "21707d6ebd05476854805e4f91b836531941bcd4", "shasum": "" }, "require": { @@ -2473,12 +2539,12 @@ "require-dev": { "psr/container": "^1.0", "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.41-dev" + "dev-master": "1.42-dev" } }, "autoload": { @@ -2516,7 +2582,7 @@ "keywords": [ "templating" ], - "time": "2019-05-14T11:59:08+00:00" + "time": "2019-06-18T15:35:16+00:00" }, { "name": "willdurand/negotiation", @@ -2798,16 +2864,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "d17708133b6c276d6e42ef887a877866b909d892" + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", - "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", "shasum": "" }, "require": { @@ -2838,7 +2904,7 @@ "Xdebug", "performance" ], - "time": "2019-01-28T20:25:53+00:00" + "time": "2019-05-27T17:52:04+00:00" }, { "name": "doctrine/instantiator", @@ -2898,16 +2964,16 @@ }, { "name": "facebook/webdriver", - "version": "1.6.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + "reference": "e43de70f3c7166169d0f14a374505392734160e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/e43de70f3c7166169d0f14a374505392734160e5", + "reference": "e43de70f3c7166169d0f14a374505392734160e5", "shasum": "" }, "require": { @@ -2954,7 +3020,7 @@ "selenium", "webdriver" ], - "time": "2018-05-16T17:37:13+00:00" + "time": "2019-06-13T08:02:18+00:00" }, { "name": "fzaninotto/faker", @@ -3296,16 +3362,16 @@ }, { "name": "nette/di", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/nette/di.git", - "reference": "19d83539245aaacb59470828919182411061841f" + "reference": "4aff517a1c6bb5c36fa09733d4cea089f529de6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/19d83539245aaacb59470828919182411061841f", - "reference": "19d83539245aaacb59470828919182411061841f", + "url": "https://api.github.com/repos/nette/di/zipball/4aff517a1c6bb5c36fa09733d4cea089f529de6d", + "reference": "4aff517a1c6bb5c36fa09733d4cea089f529de6d", "shasum": "" }, "require": { @@ -3365,7 +3431,7 @@ "nette", "static" ], - "time": "2019-04-03T19:35:46+00:00" + "time": "2019-08-07T12:11:33+00:00" }, { "name": "nette/finder", @@ -3492,16 +3558,16 @@ }, { "name": "nette/php-generator", - "version": "v3.2.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "acff8b136fad84b860a626d133e791f95781f9f5" + "reference": "aea6e81437bb238e5f0e5b5ce06337433908e63b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/acff8b136fad84b860a626d133e791f95781f9f5", - "reference": "acff8b136fad84b860a626d133e791f95781f9f5", + "url": "https://api.github.com/repos/nette/php-generator/zipball/aea6e81437bb238e5f0e5b5ce06337433908e63b", + "reference": "aea6e81437bb238e5f0e5b5ce06337433908e63b", "shasum": "" }, "require": { @@ -3547,7 +3613,7 @@ "php", "scaffolding" ], - "time": "2019-03-15T03:41:13+00:00" + "time": "2019-07-05T13:01:56+00:00" }, { "name": "nette/robot-loader", @@ -3746,16 +3812,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.2.1", + "version": "v4.2.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0" + "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/5221f49a608808c1e4d436df32884cbc1b821ac0", - "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bd73cc04c3843ad8d6b0bfc0956026a151fc420", + "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420", "shasum": "" }, "require": { @@ -3793,7 +3859,7 @@ "parser", "php" ], - "time": "2019-02-16T20:54:15+00:00" + "time": "2019-05-25T20:07:01+00:00" }, { "name": "ocramius/package-versions", @@ -4101,16 +4167,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -4131,8 +4197,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -4160,20 +4226,20 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "0.3.3", + "version": "0.3.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "472d3161d289f652713a5e353532fa4592663a57" + "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/472d3161d289f652713a5e353532fa4592663a57", - "reference": "472d3161d289f652713a5e353532fa4592663a57", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/8c4ef2aefd9788238897b678a985e1d5c8df6db4", + "reference": "8c4ef2aefd9788238897b678a985e1d5c8df6db4", "shasum": "" }, "require": { @@ -4207,20 +4273,20 @@ "MIT" ], "description": "PHPDoc parser with support for nullable, intersection and generic types", - "time": "2019-04-23T20:26:19+00:00" + "time": "2019-06-07T19:13:52+00:00" }, { "name": "phpstan/phpstan", - "version": "0.11.6", + "version": "0.11.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81" + "reference": "56b3eb2a371b60537fd20794e24af9e7e8ed4e30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81", - "reference": "7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/56b3eb2a371b60537fd20794e24af9e7e8ed4e30", + "reference": "56b3eb2a371b60537fd20794e24af9e7e8ed4e30", "shasum": "" }, "require": { @@ -4229,10 +4295,11 @@ "nette/bootstrap": "^2.4 || ^3.0", "nette/di": "^2.4.7 || ^3.0", "nette/robot-loader": "^3.0.1", + "nette/schema": "^1.0", "nette/utils": "^2.4.5 || ^3.0", "nikic/php-parser": "^4.0.2", "php": "~7.1", - "phpstan/phpdoc-parser": "^0.3", + "phpstan/phpdoc-parser": "^0.3.5", "symfony/console": "~3.2 || ~4.0", "symfony/finder": "~3.2 || ~4.0" }, @@ -4245,6 +4312,7 @@ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", "ext-intl": "*", "ext-mysqli": "*", + "ext-simplexml": "*", "ext-soap": "*", "ext-zip": "*", "jakub-onderka/php-parallel-lint": "^1.0", @@ -4280,26 +4348,26 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2019-05-08T16:33:56+00:00" + "time": "2019-07-08T06:55:18+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "0.11", + "version": "0.11.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "449fee6223220b337760abca4444801ddcc8b38d" + "reference": "5685fe48873efc5af1f2cc95d9c1b8ae82c728fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/449fee6223220b337760abca4444801ddcc8b38d", - "reference": "449fee6223220b337760abca4444801ddcc8b38d", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/5685fe48873efc5af1f2cc95d9c1b8ae82c728fe", + "reference": "5685fe48873efc5af1f2cc95d9c1b8ae82c728fe", "shasum": "" }, "require": { "nikic/php-parser": "^4.0", "php": "~7.1", - "phpstan/phpstan": "^0.11" + "phpstan/phpstan": "^0.11.8" }, "require-dev": { "consistence/coding-standard": "^3.0.1", @@ -4310,10 +4378,15 @@ "phpunit/phpunit": "^7.0", "slevomat/coding-standard": "^4.5.2" }, - "type": "library", + "type": "phpstan-extension", "extra": { "branch-alias": { "dev-master": "0.11-dev" + }, + "phpstan": { + "includes": [ + "rules.neon" + ] } }, "autoload": { @@ -4326,7 +4399,7 @@ "MIT" ], "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", - "time": "2018-12-05T18:04:16+00:00" + "time": "2019-05-28T19:54:04+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4484,16 +4557,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -4529,20 +4602,20 @@ "keywords": [ "timer" ], - "time": "2019-02-20T10:12:59+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", "shasum": "" }, "require": { @@ -4555,7 +4628,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -4578,20 +4651,20 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-07-25T05:29:42+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.11", + "version": "7.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "64cb33f5b520da490a7b13149d39b43cf3c890c6" + "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/64cb33f5b520da490a7b13149d39b43cf3c890c6", - "reference": "64cb33f5b520da490a7b13149d39b43cf3c890c6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2834789aeb9ac182ad69bfdf9ae91856a59945ff", + "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff", "shasum": "" }, "require": { @@ -4662,7 +4735,7 @@ "testing", "xunit" ], - "time": "2019-05-14T04:53:02+00:00" + "time": "2019-07-15T06:24:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5232,16 +5305,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "c09c18cca96d7067152f78956faf55346c338283" + "reference": "a29dd02a1f3f81b9a15c7730cc3226718ddb55ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c09c18cca96d7067152f78956faf55346c338283", - "reference": "c09c18cca96d7067152f78956faf55346c338283", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a29dd02a1f3f81b9a15c7730cc3226718ddb55ca", + "reference": "a29dd02a1f3f81b9a15c7730cc3226718ddb55ca", "shasum": "" }, "require": { @@ -5250,6 +5323,8 @@ }, "require-dev": { "symfony/css-selector": "~3.4|~4.0", + "symfony/http-client": "^4.3", + "symfony/mime": "^4.3", "symfony/process": "~3.4|~4.0" }, "suggest": { @@ -5258,7 +5333,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -5285,20 +5360,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2019-04-07T09:56:43+00:00" + "time": "2019-06-11T15:41:59+00:00" }, { "name": "symfony/css-selector", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" + "reference": "105c98bb0c5d8635bea056135304bd8edcc42b4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/105c98bb0c5d8635bea056135304bd8edcc42b4d", + "reference": "105c98bb0c5d8635bea056135304bd8edcc42b4d", "shasum": "" }, "require": { @@ -5307,7 +5382,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -5323,14 +5398,14 @@ "MIT" ], "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" @@ -5338,20 +5413,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2019-01-16T20:31:39+00:00" + "time": "2019-01-16T21:53:39+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb" + "reference": "291397232a2eefb3347eaab9170409981eaad0e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/53c97769814c80a84a8403efcf3ae7ae966d53bb", - "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/291397232a2eefb3347eaab9170409981eaad0e2", + "reference": "291397232a2eefb3347eaab9170409981eaad0e2", "shasum": "" }, "require": { @@ -5359,7 +5434,11 @@ "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "masterminds/html5": "<2.6" + }, "require-dev": { + "masterminds/html5": "^2.6", "symfony/css-selector": "~3.4|~4.0" }, "suggest": { @@ -5368,7 +5447,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -5395,20 +5474,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-06-13T11:03:18+00:00" }, { "name": "symfony/finder", - "version": "v4.2.8", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e45135658bd6c14b61850bf131c4f09a55133f69" + "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e45135658bd6c14b61850bf131c4f09a55133f69", - "reference": "e45135658bd6c14b61850bf131c4f09a55133f69", + "url": "https://api.github.com/repos/symfony/finder/zipball/9638d41e3729459860bb96f6247ccb61faaa45f2", + "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2", "shasum": "" }, "require": { @@ -5417,7 +5496,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -5444,20 +5523,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-04-06T13:51:08+00:00" + "time": "2019-06-28T13:16:30+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", - "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -5484,7 +5563,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2019-04-04T09:56:43+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "victorjonsson/markdowndocs", diff --git a/system/blueprints/user/account_new.yaml b/system/blueprints/user/account_new.yaml index 6c6505bdb..7a9251851 100644 --- a/system/blueprints/user/account_new.yaml +++ b/system/blueprints/user/account_new.yaml @@ -12,5 +12,7 @@ form: type: text label: PLUGIN_ADMIN.USERNAME help: PLUGIN_ADMIN.USERNAME_HELP + unset-disabled@: true + unset-readonly@: true validate: required: true diff --git a/system/blueprints/user/accounts.yaml b/system/blueprints/user/accounts.yaml index 892a14a25..49b0f71b3 100644 --- a/system/blueprints/user/accounts.yaml +++ b/system/blueprints/user/accounts.yaml @@ -27,3 +27,13 @@ config: title: Accounts icon: fa-users authorize: ['admin.users', 'admin.accounts', 'admin.super'] + +form: + fields: + username: + flex-disabled@: exists + disabled: false + flex-readonly@: exists + readonly: false + validate: + required: true \ No newline at end of file diff --git a/system/defines.php b/system/defines.php index f78e479be..4405719be 100644 --- a/system/defines.php +++ b/system/defines.php @@ -8,7 +8,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.6.11'); +define('GRAV_VERSION', '1.6.12'); define('GRAV_TESTING', false); define('DS', '/'); diff --git a/system/router.php b/system/router.php index 9ab055892..43571b354 100644 --- a/system/router.php +++ b/system/router.php @@ -17,11 +17,22 @@ if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_N return false; } +$grav_index = 'index.php'; + +/* Check the GRAV_BASEDIR environment variable and use if set */ +$grav_basedir = getenv('GRAV_BASEDIR') ?: ''; + +if (isset($grav_basedir)) { + $grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index; + $grav_basedir = DIRECTORY_SEPARATOR . trim($grav_basedir, DIRECTORY_SEPARATOR); + define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . $grav_basedir); +} + $_SERVER = array_merge($_SERVER, $_ENV); -$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'index.php'; -$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php'; -$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php'; +$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . $grav_basedir .DIRECTORY_SEPARATOR . 'index.php'; +$_SERVER['SCRIPT_NAME'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php'; +$_SERVER['PHP_SELF'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php'; error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4); -require 'index.php'; +require $grav_index; diff --git a/system/src/Grav/Common/Cache.php b/system/src/Grav/Common/Cache.php index 8b8317736..94eae595e 100644 --- a/system/src/Grav/Common/Cache.php +++ b/system/src/Grav/Common/Cache.php @@ -531,7 +531,6 @@ class Cache extends Getters } - /** * Set the cache lifetime programmatically * @@ -543,7 +542,7 @@ class Cache extends Getters return; } - $interval = $future - $this->now; + $interval = (int)($future - $this->now); if ($interval > 0 && $interval < $this->getLifetime()) { $this->lifetime = $interval; } @@ -558,7 +557,7 @@ class Cache extends Getters public function getLifetime() { if ($this->lifetime === null) { - $this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default + $this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default } return $this->lifetime; diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php index 2beeea828..e7037774f 100644 --- a/system/src/Grav/Common/Data/Blueprint.php +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -378,14 +378,12 @@ class Blueprint extends BlueprintForm $grav = Grav::instance(); $actions = (array)$call['params']; - /** @var UserInterface $user */ - if (isset($grav['user'])) { - $user = Grav::instance()['user']; - foreach ($actions as $action) { - if (!$user->authorize($action)) { - $this->addPropertyRecursive($field, 'validate', ['ignore' => true]); - return; - } + /** @var UserInterface|null $user */ + $user = $grav['user'] ?? null; + foreach ($actions as $action) { + if (!$user || !$user->authorize($action)) { + $this->addPropertyRecursive($field, 'validate', ['ignore' => true]); + return; } } } diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php index b1c4cd373..7779b1c27 100644 --- a/system/src/Grav/Common/Data/Validation.php +++ b/system/src/Grav/Common/Data/Validation.php @@ -27,8 +27,9 @@ class Validation if (!isset($field['type'])) { $field['type'] = 'text'; } - $type = $validate['type'] ?? $field['type']; + $validate = (array)($field['validate'] ?? null); + $type = $validate['type'] ?? $field['type']; $required = $validate['required'] ?? false; // If value isn't required, we will stop validation if empty value is given. diff --git a/system/src/Grav/Common/GPM/Licenses.php b/system/src/Grav/Common/GPM/Licenses.php index fd0f21c6e..14c925832 100644 --- a/system/src/Grav/Common/GPM/Licenses.php +++ b/system/src/Grav/Common/GPM/Licenses.php @@ -103,7 +103,7 @@ class Licenses } /** - * Get's the License File object + * Get the License File object * * @return \RocketTheme\Toolbox\File\FileInterface */ diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php index 81abbc11a..a625146a2 100644 --- a/system/src/Grav/Common/Language/Language.php +++ b/system/src/Grav/Common/Language/Language.php @@ -234,7 +234,7 @@ class Language } /** - * Get's a URL prefix based on configuration + * Get a URL prefix based on configuration * * @param string|null $lang * @return string diff --git a/system/src/Grav/Common/Page/Medium/AbstractMedia.php b/system/src/Grav/Common/Page/Medium/AbstractMedia.php index 0ad4573d8..f67cd650e 100644 --- a/system/src/Grav/Common/Page/Medium/AbstractMedia.php +++ b/system/src/Grav/Common/Page/Medium/AbstractMedia.php @@ -154,6 +154,9 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac */ public function add($name, $file) { + if (!$file) { + return; + } $this->offsetSet($name, $file); switch ($file->type) { case 'image': diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index 936f696a9..1925bef81 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -232,7 +232,7 @@ class ImageMedium extends Medium } /** - * Allows the ability to override the Inmage's Pretty name stored in cache + * Allows the ability to override the image's pretty name stored in cache * * @param string $name */ diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index bcf7539bd..6da3bbaed 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -1410,7 +1410,7 @@ class Page implements PageInterface if (is_string($http_accept)) { $negotiator = new Negotiator(); - $supported_types = Grav::instance()['config']->get('system.pages.types', ['html', 'json']); + $supported_types = Utils::getSupportPageTypes(['html', 'json']); $priorities = Utils::getMimeTypes($supported_types); $media_type = $negotiator->getBest($http_accept, $priorities); @@ -2938,7 +2938,7 @@ class Page implements PageInterface case 'page': case 'self': $results = new Collection(); - $results = $results->addPage($page)->nonModular(); + $results = $results->addPage($page); break; case 'descendants': diff --git a/system/src/Grav/Common/Plugin.php b/system/src/Grav/Common/Plugin.php index d85fcb4cf..8729a519f 100644 --- a/system/src/Grav/Common/Plugin.php +++ b/system/src/Grav/Common/Plugin.php @@ -151,15 +151,32 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess if (\is_string($params)) { $dispatcher->addListener($eventName, [$this, $params]); } elseif (\is_string($params[0])) { - $dispatcher->addListener($eventName, [$this, $params[0]], $params[1] ?? 0); + $dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName)); } else { foreach ($params as $listener) { - $dispatcher->addListener($eventName, [$this, $listener[0]], $listener[1] ?? 0); + $dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName)); } } } } + /** + * @param array $params + * @param string $eventName + */ + private function getPriority($params, $eventName) + { + $grav = Grav::instance(); + $override = implode('.', ["priorities", $this->name, $eventName, $params[0]]); + if ($grav['config']->get($override) !== null) + { + return $grav['config']->get($override); + } elseif (isset($params[1])) { + return $params[1]; + } + return 0; + } + /** * @param array $events */ diff --git a/system/src/Grav/Common/Service/AccountsServiceProvider.php b/system/src/Grav/Common/Service/AccountsServiceProvider.php index ca15f5a62..e0778bebe 100644 --- a/system/src/Grav/Common/Service/AccountsServiceProvider.php +++ b/system/src/Grav/Common/Service/AccountsServiceProvider.php @@ -27,9 +27,10 @@ class AccountsServiceProvider implements ServiceProviderInterface public function register(Container $container) { $container['accounts'] = function (Container $container) { - /** @var Debugger $debugger */ - $debugger = $container['debugger']; - if ($container['config']->get('system.accounts.type') === 'flex') { + $type = strtolower(defined('GRAV_USER_INSTANCE') ? GRAV_USER_INSTANCE : $container['config']->get('system.accounts.type', 'data')); + if ($type === 'flex') { + /** @var Debugger $debugger */ + $debugger = $container['debugger']; $debugger->addMessage('User Accounts: Flex Directory'); return $this->flexAccounts($container); } @@ -46,7 +47,9 @@ class AccountsServiceProvider implements ServiceProviderInterface protected function dataAccounts(Container $container) { - define('GRAV_USER_INSTANCE', 'DATA'); + if (!defined('GRAV_USER_INSTANCE')) { + define('GRAV_USER_INSTANCE', 'DATA'); + } // Use User class for backwards compatibility. return new DataUser\UserCollection(User::class); @@ -54,7 +57,9 @@ class AccountsServiceProvider implements ServiceProviderInterface protected function flexAccounts(Container $container) { - define('GRAV_USER_INSTANCE', 'FLEX'); + if (!defined('GRAV_USER_INSTANCE')) { + define('GRAV_USER_INSTANCE', 'FLEX'); + } /** @var Config $config */ $config = $container['config']; diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php index 7e596d98a..cf24f7643 100644 --- a/system/src/Grav/Common/Twig/TwigExtension.php +++ b/system/src/Grav/Common/Twig/TwigExtension.php @@ -84,7 +84,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']), new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']), new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']), - new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]), + new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]), new \Twig_SimpleFilter('md5', [$this, 'md5Filter']), new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']), new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']), @@ -455,7 +455,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn } /** - * Gets a human readable output for cron sytnax + * Gets a human readable output for cron syntax * * @param $at * @return string @@ -616,9 +616,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn * @param bool $block Block or Line processing * @return mixed|string */ - public function markdownFunction($string, $block = true) + public function markdownFunction($context = false, $string, $block = true) { - return Utils::processMarkdown($string, $block); + $page = $context['page'] ?? null; + return Utils::processMarkdown($string, $block, $page); } /** @@ -1004,10 +1005,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn */ public function authorize($action) { - /** @var UserInterface $user */ - $user = $this->grav['user']; + /** @var UserInterface|null $user */ + $user = $this->grav['user'] ?? null; - if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) { + if (!$user || !$user->authenticated || (isset($user->authorized) && !$user->authorized)) { return false; } @@ -1136,7 +1137,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn } /** - * Get's the Exif data for a file + * Get the Exif data for a file * * @param string $image * @param bool $raw diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 0bcba9a8c..16d7fb166 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -1286,7 +1286,7 @@ class Uri } /** - * Get's post from either $_POST or JSON response object + * Get post from either $_POST or JSON response object * By default returns all data, or can return a single item * * @param string $element @@ -1345,7 +1345,7 @@ class Uri */ public function isValidExtension($extension) { - $valid_page_types = implode('|', Grav::instance()['config']->get('system.pages.types')); + $valid_page_types = implode('|', Utils::getSupportPageTypes()); // Strip the file extension for valid page types if (preg_match('/(' . $valid_page_types . ')/', $extension)) { diff --git a/system/src/Grav/Common/User/FlexUser/User.php b/system/src/Grav/Common/User/FlexUser/User.php index 5ef381912..e95992aae 100644 --- a/system/src/Grav/Common/User/FlexUser/User.php +++ b/system/src/Grav/Common/User/FlexUser/User.php @@ -9,6 +9,7 @@ namespace Grav\Common\User\FlexUser; +use Grav\Common\Data\Blueprint; use Grav\Common\Grav; use Grav\Common\Media\Interfaces\MediaCollectionInterface; use Grav\Common\Page\Media; @@ -381,6 +382,31 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa return $this->getBlueprint()->extra($this->toArray()); } + /** + * @param string $name + * @return Blueprint + */ + public function getBlueprint(string $name = '') + { + $blueprint = clone parent::getBlueprint($name); + + $blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) { + $params = (array)$call['params']; + $method = array_shift($params); + + if (method_exists($this, $method)) { + $value = $this->{$method}(...$params); + if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) { + $field[$property] = array_merge_recursive($field[$property], $value); + } else { + $field[$property] = $value; + } + } + }); + + return $blueprint->init(); + } + /** * Return unmodified data as raw string. * @@ -431,6 +457,20 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa return parent::save(); } + public function isAuthorized(string $action, string $scope = null, UserInterface $user = null): bool + { + if (null === $user) { + /** @var UserInterface $user */ + $user = Grav::instance()['user'] ?? null; + } + + if ($user instanceof User && $user->getStorageKey() === $this->getStorageKey()) { + return true; + } + + return parent::isAuthorized($action, $scope, $user); + } + /** * @return array */ diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index a19373585..56b2d891b 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -28,7 +28,7 @@ abstract class Utils /** * Simple helper method to make getting a Grav URL easier * - * @param string $input + * @param string|object $input * @param bool $domain * @param bool $fail_gracefully * @return bool|null|string @@ -43,47 +43,80 @@ abstract class Utils } } - if (Grav::instance()['config']->get('system.absolute_urls', false)) { - $domain = true; - } + $input = (string)$input; if (Uri::isExternal($input)) { return $input; } + $grav = Grav::instance(); + /** @var Uri $uri */ - $uri = Grav::instance()['uri']; + $uri = $grav['uri']; - $root = $uri->rootUrl(); - $input = Utils::replaceFirstOccurrence($root, '', $input); - - $input = ltrim((string)$input, '/'); - - if (Utils::contains((string)$input, '://')) { + if (static::contains((string)$input, '://')) { /** @var UniformResourceLocator $locator */ - $locator = Grav::instance()['locator']; + $locator = $grav['locator']; + $parts = Uri::parseUrl($input); - if ($parts) { - try { - $resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false); - } catch (\Exception $e) { - if ($fail_gracefully) { - return $input; - } else { - return false; + if (is_array($parts)) { + // Make sure we always have scheme, host, port and path. + $scheme = $parts['scheme'] ?? ''; + $host = $parts['host'] ?? ''; + $port = $parts['port'] ?? ''; + $path = $parts['path'] ?? ''; + + if ($scheme && !$port) { + // If URL has a scheme, we need to check if it's one of Grav streams. + if (!$locator->schemeExists($scheme)) { + // If scheme does not exists as a stream, assume it's external. + return str_replace(' ', '%20', $input); + } + + // Attempt to find the resource (because of parse_url() we need to put host back to path). + $resource = $locator->findResource("{$scheme}://{$host}{$path}", false); + + if ($resource === false) { + if (!$fail_gracefully) { + return false; + } + + // Return location where the file would be if it was saved. + $resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true); + } + + } elseif ($host || $port) { + // If URL doesn't have scheme but has host or port, it is external. + return str_replace(' ', '%20', $input); + } + + if (!empty($resource)) { + // Add query string back. + if (isset($parts['query'])) { + $resource .= '?' . $parts['query']; + } + + // Add fragment back. + if (isset($parts['fragment'])) { + $resource .= '#' . $parts['fragment']; } } - if ($resource && isset($parts['query'])) { - $resource = $resource . '?' . $parts['query']; - } } else { // Not a valid URL (can still be a stream). $resource = $locator->findResource($input, false); } } else { + $root = $uri->rootUrl(); + + if (static::startsWith($input, $root)) { + $input = static::replaceFirstOccurrence($root, '', $input); + } + + $input = ltrim($input, '/'); + $resource = $input; } @@ -91,6 +124,8 @@ abstract class Utils return false; } + $domain = $domain ?: $grav['config']->get('system.absolute_urls', false); + return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? ''); } @@ -288,6 +323,35 @@ abstract class Utils return (object)array_merge((array)$obj1, (array)$obj2); } + /** + * Lowercase an entire array. Useful when combined with `in_array()` + * + * @param array $a + * @return array|false + */ + public static function arrayLower(Array $a) + { + return array_map('mb_strtolower', $a); + } + + /** + * Simple function to remove item/s in an array by value + * + * @param $search array + * @param $value string|array + * @return array + */ + public static function arrayRemoveValue(Array $search, $value) + { + foreach ((array) $value as $val) { + $key = array_search($val, $search); + if ($key !== false) { + unset($search[$key]); + } + } + return $search; + } + /** * Recursive Merge with uniqueness * @@ -1070,12 +1134,9 @@ abstract class Utils */ private static function generateNonceString($action, $previousTick = false) { - $username = ''; - if (isset(Grav::instance()['user'])) { - $user = Grav::instance()['user']; - $username = $user->username; - } + $grav = Grav::instance(); + $username = isset($grav['user']) ? $grav['user']->username : ''; $token = session_id(); $i = self::nonceTick(); @@ -1083,7 +1144,7 @@ abstract class Utils $i--; } - return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt')); + return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . $grav['config']->get('security.salt')); } /** @@ -1297,7 +1358,7 @@ abstract class Utils } /** - * Get's path based on a token + * Get path based on a token * * @param string $path * @param PageInterface|null $page @@ -1465,13 +1526,15 @@ abstract class Utils * * @param string $string * - * @param bool $block Block or Line processing + * @param bool $block Block or Line processing + * @param null $page * @return string + * @throws \Exception */ - public static function processMarkdown($string, $block = true) + public static function processMarkdown($string, $block = true, $page = null) { $grav = Grav::instance(); - $page = $grav['page'] ?? null; + $page = $page ?? $grav['page'] ?? null; $defaults = [ 'markdown' => $grav['config']->get('system.pages.markdown', []), 'images' => $grav['config']->get('system.images', []) @@ -1534,4 +1597,23 @@ abstract class Utils return $subnet; } + + /** + * Wrapper to ensure html, htm in the front of the supported page types + * + * @param array|null $defaults + * @return array|mixed + */ + public static function getSupportPageTypes(array $defaults = null) + { + $types = Grav::instance()['config']->get('system.pages.types', $defaults); + + // remove html/htm + $types = static::arrayRemoveValue($types, ['html', 'htm']); + + // put them back at the front + $types = array_merge(['html', 'htm'], $types); + + return $types; + } } diff --git a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php index 7299c0961..0e4fae22d 100644 --- a/system/src/Grav/Framework/Collection/AbstractIndexCollection.php +++ b/system/src/Grav/Framework/Collection/AbstractIndexCollection.php @@ -437,7 +437,7 @@ abstract class AbstractIndexCollection implements CollectionInterface } /** - * Implementes JsonSerializable interface. + * Implements JsonSerializable interface. * * @return array */ diff --git a/system/src/Grav/Framework/Collection/ArrayCollection.php b/system/src/Grav/Framework/Collection/ArrayCollection.php index d3a200247..f79ab9614 100644 --- a/system/src/Grav/Framework/Collection/ArrayCollection.php +++ b/system/src/Grav/Framework/Collection/ArrayCollection.php @@ -84,7 +84,7 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface } /** - * Implementes JsonSerializable interface. + * Implements JsonSerializable interface. * * @return array */ diff --git a/system/src/Grav/Framework/File/Formatter/CsvFormatter.php b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php index 2a700258c..8945df004 100644 --- a/system/src/Grav/Framework/File/Formatter/CsvFormatter.php +++ b/system/src/Grav/Framework/File/Formatter/CsvFormatter.php @@ -53,11 +53,11 @@ class CsvFormatter extends AbstractFormatter $header = array_keys(reset($data)); // Encode the field names - $string = implode($delimiter, $header). "\n"; + $string = $this->encodeLine($header, $delimiter); // Encode the data foreach ($data as $row) { - $string .= implode($delimiter, $row). "\n"; + $string .= $this->encodeLine($row, $delimiter); } return $string; @@ -87,4 +87,23 @@ class CsvFormatter extends AbstractFormatter return $list; } + + protected function encodeLine(array $line, $delimiter = null): string + { + foreach ($line as $key => &$value) { + $value = $this->escape((string)$value); + } + unset($value); + + return implode($delimiter, $line). "\n"; + } + + protected function escape(string $value) + { + if (preg_match('/[,"\r\n]/u', $value)) { + $value = '"' . preg_replace('/"/', '""', $value) . '"'; + } + + return $value; + } } diff --git a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php index 05b09a515..189626d28 100644 --- a/system/src/Grav/Framework/File/Formatter/YamlFormatter.php +++ b/system/src/Grav/Framework/File/Formatter/YamlFormatter.php @@ -97,7 +97,7 @@ class YamlFormatter extends AbstractFormatter @ini_set('yaml.decode_php', $saved); if ($decoded !== false) { - return $decoded; + return (array) $decoded; } } diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 01f45d079..7fbdbc076 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -550,6 +550,15 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface } } + // FIXME: For some reason locator caching isn't cleared for the file, investigate! + $locator = Grav::instance()['locator']; + $locator->clearCache(); + + // Make sure that the object exists before continuing (just in case). + if (!$this->exists()) { + throw new \RuntimeException('Saving failed: Object does not exist!'); + } + if (method_exists($this, 'saveUpdatedMedia')) { $this->saveUpdatedMedia(); } diff --git a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php index 643004e74..e1b63d966 100644 --- a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php +++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php @@ -27,10 +27,10 @@ trait FlexAuthorizeTrait { if (null === $user) { /** @var UserInterface $user */ - $user = Grav::instance()['user']; + $user = Grav::instance()['user'] ?? null; } - return $this->isAuthorizedAction($user, $action, $scope) || $this->isAuthorizedSuperAdmin($user); + return $user && ($this->isAuthorizedAction($user, $action, $scope) || $this->isAuthorizedSuperAdmin($user)); } protected function isAuthorizedSuperAdmin(UserInterface $user): bool diff --git a/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php index 6d3f873de..8b2270604 100644 --- a/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php +++ b/system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php @@ -49,7 +49,7 @@ trait FlexMediaTrait */ public function getStorageFolder() { - return $this->getFlexDirectory()->getStorageFolder($this->getStorageKey()); + return $this->exists() ? $this->getFlexDirectory()->getStorageFolder($this->getStorageKey()) : ''; } /** @@ -57,7 +57,7 @@ trait FlexMediaTrait */ public function getMediaFolder() { - return $this->getFlexDirectory()->getMediaFolder($this->getStorageKey()); + return $this->exists() ? $this->getFlexDirectory()->getMediaFolder($this->getStorageKey()) : ''; } /** @@ -153,6 +153,12 @@ trait FlexMediaTrait /** @var UniformResourceLocator $locator */ $locator = $grav['locator']; $path = $media->getPath(); + if (!$path) { + $language = $grav['language']; + + throw new RuntimeException($language->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400); + } + if ($locator->isStream($path)) { $path = $locator->findResource($path, true, true); $locator->clearCache($path); @@ -202,12 +208,16 @@ trait FlexMediaTrait } $media = $this->getMedia(); + $path = $media->getPath(); + if (!$path) { + return; + } /** @var UniformResourceLocator $locator */ $locator = $grav['locator']; - $targetPath = $media->getPath() . '/' . $dirname; - $targetFile = $media->getPath() . '/' . $filename; + $targetPath = $path . '/' . $dirname; + $targetFile = $path . '/' . $filename; if ($locator->isStream($targetFile)) { $targetPath = $locator->findResource($targetPath, true, true); $targetFile = $locator->findResource($targetFile, true, true); diff --git a/system/src/Grav/Framework/Form/FormFlash.php b/system/src/Grav/Framework/Form/FormFlash.php index 825a75ae7..e2ba82658 100644 --- a/system/src/Grav/Framework/Form/FormFlash.php +++ b/system/src/Grav/Framework/Form/FormFlash.php @@ -44,7 +44,7 @@ class FormFlash implements FormFlashInterface protected $uploadedFiles; /** @var string[] */ protected $uploadObjects; - /** @var string|null */ + /** @var string */ protected $folder; /** @@ -66,14 +66,13 @@ class FormFlash implements FormFlashInterface $this->sessionId = $config['session_id'] ?? 'no-session'; $this->uniqueId = $config['unique_id'] ?? ''; - $this->folder = $config['folder'] ?? 'tmp://forms'; + + $folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : ''); /** @var UniformResourceLocator $locator */ $locator = Grav::instance()['locator']; - if ($locator->isStream($this->folder)) { - $this->folder = $locator->findResource($this->folder, true, true); - } + $this->folder = $folder && $locator->isStream($folder) ? $locator->findResource($folder, true, true) : $folder; $file = $this->getTmpIndex(); $this->exists = $file->exists(); @@ -203,7 +202,7 @@ class FormFlash implements FormFlashInterface */ public function save(): self { - if (!$this->sessionId && $this->uniqueId) { + if (!($this->folder && $this->uniqueId)) { return $this; } @@ -225,7 +224,7 @@ class FormFlash implements FormFlashInterface */ public function delete(): self { - if ($this->sessionId && $this->uniqueId) { + if ($this->folder && $this->uniqueId) { $this->removeTmpDir(); $this->files = []; $this->exists = false; @@ -434,7 +433,7 @@ class FormFlash implements FormFlashInterface */ public function getTmpDir(): string { - return $this->sessionId && $this->uniqueId ? "{$this->folder}/{$this->sessionId}/{$this->uniqueId}" : ''; + return $this->folder && $this->uniqueId ? "{$this->folder}/{$this->uniqueId}" : ''; } /** @@ -474,8 +473,8 @@ class FormFlash implements FormFlashInterface */ protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void { - if (!$this->sessionId || !$this->uniqueId) { - throw new \RuntimeException('Cannot upload files: unique id not defined'); + if (!($this->folder && $this->uniqueId)) { + throw new \RuntimeException('Cannot upload files: form flash folder not defined'); } $field = $field ?: 'undefined'; diff --git a/system/src/Grav/Framework/Form/Traits/FormTrait.php b/system/src/Grav/Framework/Form/Traits/FormTrait.php index 25af38d30..31c4ff920 100644 --- a/system/src/Grav/Framework/Form/Traits/FormTrait.php +++ b/system/src/Grav/Framework/Form/Traits/FormTrait.php @@ -15,9 +15,11 @@ use Grav\Common\Data\ValidationException; use Grav\Common\Form\FormFlash; use Grav\Common\Grav; use Grav\Common\Twig\Twig; +use Grav\Common\User\Interfaces\UserInterface; use Grav\Common\Utils; use Grav\Framework\ContentBlock\HtmlBlock; use Grav\Framework\Form\Interfaces\FormInterface; +use Grav\Framework\Session\SessionInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; use Twig\Error\LoaderError; @@ -338,10 +340,10 @@ trait FormTrait if (null === $this->flash) { $grav = Grav::instance(); $config = [ - 'session_id' => $this->getFlashId() ?? '', + 'session_id' => $this->getSessionId(), 'unique_id' => $this->getUniqueId(), 'form_name' => $this->getName(), - 'folder' => $this->getBlueprint()->get('form/folder') ?? 'tmp://forms' + 'folder' => $this->getFlashFolder() ]; @@ -359,9 +361,8 @@ trait FormTrait */ public function getAllFlashes(): array { - $id = $this->getFlashId(); - $folder = ($this->getBlueprint()->get('form/folder') ?? 'tmp://forms') . "/{$id}"; - if (!$id || !is_dir($folder)) { + $folder = $this->getFlashFolder(); + if (!$folder || !is_dir($folder)) { return []; } @@ -370,10 +371,12 @@ trait FormTrait $list = []; /** @var \SplFileInfo $file */ foreach (new \FilesystemIterator($folder) as $file) { + $uniqueId = $file->getFilename(); $config = [ - 'session_id' => $id, - 'unique_id' => $file->getFilename(), - 'form_name' => $name + 'session_id' => $this->getSessionId(), + 'unique_id' => $uniqueId, + 'form_name' => $name, + 'folder' => $this->getFlashFolder() ]; $flash = new FormFlash($config); if ($flash->exists() && $flash->getFormName() === $name) { @@ -409,28 +412,15 @@ trait FormTrait return $block; } - protected function getFlashId(): ?string + protected function getSessionId(): string { /** @var Grav $grav */ $grav = Grav::instance(); - $rememberState = $this->getBlueprint()->get('form/remember_state'); - - if ($rememberState === 'user') { - $user = $grav['user'] ?? null; - if (isset($user)) { - return $user->username; - } - } - - // Session Required for flash form + /** @var SessionInterface $session */ $session = $grav['session'] ?? null; - if (isset($session)) { - // By default store flash by the session id. - return $session->getId(); - } - return null; + return $session ? ($session->getId() ?? '') : ''; } protected function unsetFlash(): void @@ -438,6 +428,35 @@ trait FormTrait $this->flash = null; } + protected function getFlashFolder(): ?string + { + $grav = Grav::instance(); + + /** @var UserInterface $user */ + $user = $grav['user'] ?? null; + $userExists = $user && $user->exists(); + $username = $userExists ? $user->username : null; + $mediaFolder = $userExists ? $user->getMediaFolder() : null; + $session = $grav['session'] ?? null; + $sessionId = $session ? $session->getId() : null; + + // Fill template token keys/value pairs. + $dataMap = [ + '[FORM_NAME]' => $this->getName(), + '[SESSIONID]' => $sessionId ?? '!!', + '[USERNAME]' => $username ?? '!!', + '[USERNAME_OR_SESSIONID]' => $username ?? $sessionId ?? '!!', + '[ACCOUNT]' => $mediaFolder ?? '!!' + ]; + + $flashFolder = $this->getBlueprint()->get('form/flash_folder', 'tmp://forms/[SESSIONID]'); + + $path = str_replace(array_keys($dataMap), array_values($dataMap), $flashFolder); + + // Make sure we only return valid paths. + return strpos($path, '!!') === false ? rtrim($path, '/') : null; + } + /** * Set a single error. * diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php index e5be6394b..e7862427c 100644 --- a/tests/unit/Grav/Common/UtilsTest.php +++ b/tests/unit/Grav/Common/UtilsTest.php @@ -382,30 +382,60 @@ class UtilsTest extends \Codeception\TestCase\Test // Fail hard $this->assertSame(false, Utils::url('', true)); $this->assertSame(false, Utils::url('')); - $this->assertSame(false, Utils::url('foo://bar/baz')); $this->assertSame(false, Utils::url(new stdClass())); $this->assertSame(false, Utils::url(['foo','bar','baz'])); + $this->assertSame(false, Utils::url('user://does/not/exist')); // Fail Gracefully $this->assertSame('/', Utils::url('/', false, true)); $this->assertSame('/', Utils::url('', false, true)); - $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true)); $this->assertSame('/', Utils::url(new stdClass(), false, true)); $this->assertSame('/', Utils::url(['foo','bar','baz'], false, true)); + $this->assertSame('/user/does/not/exist', Utils::url('user://does/not/exist', false, true)); + // Simple paths $this->assertSame('/', Utils::url('/')); - $this->assertSame('http://testing.dev/', Utils::url('/', true)); - $this->assertSame('http://testing.dev/path1', Utils::url('/path1', true)); $this->assertSame('/path1', Utils::url('/path1')); $this->assertSame('/path1/path2', Utils::url('/path1/path2')); + $this->assertSame('/random/path1/path2', Utils::url('/random/path1/path2')); + $this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg')); + $this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg')); + $this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg')); + $this->assertSame('/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg')); + + // Simple paths with domain + $this->assertSame('http://testing.dev/', Utils::url('/', true)); + $this->assertSame('http://testing.dev/path1', Utils::url('/path1', true)); + $this->assertSame('http://testing.dev/path1/path2', Utils::url('/path1/path2', true)); + $this->assertSame('http://testing.dev/random/path1/path2', Utils::url('/random/path1/path2', true)); + $this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true)); + $this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); + $this->assertSame('http://testing.dev/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true)); + $this->assertSame('http://testing.dev/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true)); + + // Relative paths from Grav root. + $this->assertSame('/subdir', Utils::url('subdir')); + $this->assertSame('/subdir/path1', Utils::url('subdir/path1')); + $this->assertSame('/subdir/path1/path2', Utils::url('subdir/path1/path2')); + $this->assertSame('/path1', Utils::url('path1')); + $this->assertSame('/path1/path2', Utils::url('path1/path2')); + $this->assertSame('/foobar.jpg', Utils::url('foobar.jpg')); + $this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true)); + + // Relative paths from Grav root with domain. $this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true)); $this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true)); $this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); - $this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg')); - $this->assertSame('/foobar.jpg', Utils::url('foobar.jpg')); - $this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg')); - $this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg')); + // All Non-existing streams should be treated as external URI / protocol. + $this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path')); + $this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path')); + $this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path')); + $this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com')); + $this->assertSame('pop://domain.com', Utils::url('pop://domain.com')); + $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz')); + $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true)); + // $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <- } public function testUrlWithRoot() @@ -415,31 +445,69 @@ class UtilsTest extends \Codeception\TestCase\Test // Fail hard $this->assertSame(false, Utils::url('', true)); $this->assertSame(false, Utils::url('')); - $this->assertSame(false, Utils::url('foo://bar/baz')); + $this->assertSame(false, Utils::url(new stdClass())); + $this->assertSame(false, Utils::url(['foo','bar','baz'])); + $this->assertSame(false, Utils::url('user://does/not/exist')); // Fail Gracefully $this->assertSame('/subdir/', Utils::url('/', false, true)); $this->assertSame('/subdir/', Utils::url('', false, true)); - $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true)); + $this->assertSame('/subdir/', Utils::url(new stdClass(), false, true)); + $this->assertSame('/subdir/', Utils::url(['foo','bar','baz'], false, true)); + $this->assertSame('/subdir/user/does/not/exist', Utils::url('user://does/not/exist', false, true)); - $this->assertSame('http://testing.dev/subdir/', Utils::url('/', true)); - $this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true)); - $this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true)); + // Simple paths $this->assertSame('/subdir/', Utils::url('/')); $this->assertSame('/subdir/path1', Utils::url('/path1')); $this->assertSame('/subdir/path1/path2', Utils::url('/path1/path2')); - $this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2')); - - $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true)); - $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true)); - $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true)); - $this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); - $this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true)); + $this->assertSame('/subdir/random/path1/path2', Utils::url('/random/path1/path2')); $this->assertSame('/subdir/foobar.jpg', Utils::url('/foobar.jpg')); - $this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg')); - $this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg')); $this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg')); + $this->assertSame('/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg')); + $this->assertSame('/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg')); + + // Simple paths with domain + $this->assertSame('http://testing.dev/subdir/', Utils::url('/', true)); + $this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true)); + $this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/path1/path2', true)); + $this->assertSame('http://testing.dev/subdir/random/path1/path2', Utils::url('/random/path1/path2', true)); + $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true)); + $this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true)); + $this->assertSame('http://testing.dev/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true)); + $this->assertSame('http://testing.dev/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true)); + + // Paths including the grav base. + $this->assertSame('/subdir/', Utils::url('/subdir')); + $this->assertSame('/subdir/path1', Utils::url('/subdir/path1')); + $this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2')); + $this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg')); $this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg')); + + // Relative paths from Grav root with domain. + $this->assertSame('http://testing.dev/subdir/', Utils::url('/subdir', true)); + $this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true)); + $this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/subdir/path1/path2', true)); + $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true)); + $this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true)); + + // Relative paths from Grav root. + $this->assertSame('/subdir/subdir', Utils::url('subdir')); + $this->assertSame('/subdir/subdir/path1', Utils::url('subdir/path1')); + $this->assertSame('/subdir/subdir/path1/path2', Utils::url('subdir/path1/path2')); + $this->assertSame('/subdir/path1', Utils::url('path1')); + $this->assertSame('/subdir/path1/path2', Utils::url('path1/path2')); + $this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg')); + $this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true)); + + // All Non-existing streams should be treated as external URI / protocol. + $this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path')); + $this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path')); + $this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path')); + $this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com')); + $this->assertSame('pop://domain.com', Utils::url('pop://domain.com')); + $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz')); + $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true)); + // $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <- } public function testUrlWithStreams()