Compare commits

...

91 Commits

Author SHA1 Message Date
Andy Miller
575a1e4603 Merge tag '1.6.17' into develop
Release v1.6.17
2019-11-06 17:51:32 -07:00
Andy Miller
c862b0bc26 Merge branch 'release/1.6.17' 2019-11-06 17:51:32 -07:00
Andy Miller
a74ccad282 fixed changelog date 2019-11-06 17:50:35 -07:00
Andy Miller
ffeb5648c6 Merge branch 'release/1.6.17' 2019-11-06 16:07:07 -07:00
Andy Miller
86c87929ec Merge tag '1.6.17' into develop
Release v1.6.17
2019-11-06 16:07:07 -07:00
Andy Miller
e0e92b843c prepare for release 2019-11-06 16:06:45 -07:00
Keith Bentrup
8678f22f6b do NOT ignore "." dirs OR ignore "." dirs and all children (#2581)
If you ignore any "files" beginning with "." including directories, then the all() method will exclude .somedir, but not .somedir/somefile. Subsequently, when trying to copy all files returned from all(), it will fail when the method tries to copy a file into a directory that has not yet been created because .somedir was omitted from the return array of all().
I found this bug when trying to install the admin plugin and ./tmp was a mount and thus rename() failed and self:copy() was invoked instead (line 365).
2019-10-23 15:39:05 -06:00
Jérôme Nadaud
8322a0cfa3 Make script name more explicit (#2637) 2019-10-17 05:57:25 -06:00
Jérôme Nadaud
ab6b82eaaa Fix cache image generation when using cropresize (#2639) 2019-10-17 05:56:46 -06:00
buzatuAda
b16e8066ca fix exception array_merge() when $this->header->metadata is not array (#2701)
Exception gets thrown when $this->header->metadata is not an array, added extra verification in order to make sure it is and array before doing array_merge()
2019-10-17 05:55:27 -06:00
Matias Griese
bc1dd2a7b4 Added working ETag (304 Not Modified) support based on the final rendered HTML 2019-10-16 23:40:08 +03:00
Djamil Legato
d11772b681 Change of Behavior: Inflector::hyphenize will now automatically trim dashes at beginning and end of a string. 2019-10-16 11:02:44 -07:00
Matias Griese
feeee9ef86 Fixed PHP 7.1 bug in Flex 2019-10-15 19:00:25 +03:00
Jeremy Gonyea
eb1b9567df Updated for latest ddev version (#2676) 2019-10-03 14:54:05 -06:00
Andy Miller
c795ead402 Updated changelog 2019-10-01 17:52:14 -06:00
Andy Miller
91270c9c66 CSVFormatter null char support 2019-10-01 17:51:29 -06:00
Andy Miller
342eac1047 Smarter CSV handling 2019-09-26 18:35:25 -06:00
Andy Miller
f72eb1b002 Merge tag '1.6.16' into develop
Release v1.6.16

# gpg: Signature made Thu Sep 19 16:42:43 2019 MDT
# gpg:                using RSA key 6AA7DB4F23BD1A7280C4CC63E82B8D0EAB94EFB9
# gpg: Good signature from "Andy Miller <rhuk@mac.com>" [ultimate]
2019-09-19 16:42:44 -06:00
Andy Miller
25caa5138a Merge branch 'release/1.6.16' 2019-09-19 16:42:43 -06:00
Andy Miller
dffb227df6 prepare for release 2019-09-19 16:42:21 -06:00
Matias Griese
5c9eb1cdb8 Fixed Badly encoded JSON data warning when uploading files [#2663] 2019-09-17 22:02:21 +03:00
Matias Griese
e30ab9a043 Fixed Flex user creation if file storage is being used [#2444] 2019-09-17 11:37:09 +03:00
Newb I the Newbd
651b354d3e Correct non-published collection filter evaluation (#2668) 2019-09-14 10:10:24 -06:00
Andy Miller
dd8b503aa0 Merge tag '1.6.15' into develop
Release v1.6.15
2019-08-20 17:22:26 -06:00
Andy Miller
dab30673e0 Merge branch 'release/1.6.15' 2019-08-20 17:22:25 -06:00
Andy Miller
13689c2065 prepare for release 2019-08-20 17:22:16 -06:00
Andy Miller
6e23627f26 update changelog 2019-08-20 17:21:32 -06:00
Andy Miller
7db85cc79c Force Symfony 4.2 2019-08-20 17:06:19 -06:00
Matias Griese
e5cedd074b Fixed broken markdown Twig tag [#2635] 2019-08-19 10:46:03 +03:00
Daithí Seán Ó Foghlú
ed87faad92 Update robots.txt (#2632)
I have found that Bing/Yahoo/DuckDuckGo, Yandex and Google report crawl errors when using the default robots.txt. Specifically their bots will not crawl the the path '/' or any sub-paths. I agree that the current robots.txt should work and properly implements the specification. However it still does not work.

In my experience explicitly permitting the path '/' by adding the directive Allow: / resolves the issue.

More details can be found in a blog post about the issue here: https://www.dfoley.ie/blog/starting-with-the-indieweb
2019-08-18 11:22:33 -06:00
Andy Miller
239f34d40c Merge branch 'release/1.6.14' 2019-08-18 09:52:55 -06:00
Andy Miller
20b9ca56fa Merge tag '1.6.14' into develop
Release v1.6.14
2019-08-18 09:52:55 -06:00
Andy Miller
647ae0fda3 prepare for release 2019-08-18 09:52:45 -06:00
Andy Miller
806dbd9ee5 refactor 2019-08-18 09:51:10 -06:00
Andy Miller
1ab8442630 add fix 2019-08-18 09:50:56 -06:00
Andy Miller
505661404b Merge branch 'release/1.6.13' 2019-08-16 09:53:21 -06:00
Andy Miller
a2ea6faf4d Merge tag '1.6.13' into develop
Release v1.6.13
2019-08-16 09:53:21 -06:00
Andy Miller
ce51491b4d prepare for release 2019-08-16 09:53:12 -06:00
Andy Miller
d241223aa3 update changelog 2019-08-16 07:45:40 -06:00
Andy Miller
d16f83fdd8 Merge tag '1.6.12' into develop
Release v1.6.12
2019-08-14 16:19:44 -06:00
Andy Miller
02e10ff8fe Merge branch 'release/1.6.12' 2019-08-14 16:19:43 -06:00
Andy Miller
6a44d8f286 prepare for release 2019-08-14 16:19:21 -06:00
Andy Miller
4b614d871f update changelog 2019-08-14 16:18:38 -06:00
Matias Griese
3286d70092 Lock Grav user class once GRAV_USER_INSTANCE has been defined 2019-08-14 10:12:00 +03:00
Thomas Threadgold
9fc37e46fa check if GRAV_USER_INSTANCE constants are already defined (#2621) 2019-08-13 15:30:20 -06:00
Matias Griese
f304f429c5 Fix bad check in CSV escaping 2019-08-13 18:19:28 +03:00
Andy Miller
65c73f639f Update changelog 2019-08-12 16:06:16 -06:00
Aaron Dalton
d2833a1997 Allow users to override plugin handler priorities (#2165) 2019-08-12 15:22:21 -06:00
ale rimoldi
aa8f764436 run route.php when grav is not at the root of the server (#2541) 2019-08-12 15:11:41 -06:00
Andy Miller
618a59921a make markdown more page context aware 2019-08-09 18:23:05 -06:00
Andy Miller
039f71dc61 fix for var order in Validation.php 2019-08-09 17:16:31 -06:00
Andy Miller
27b8db4c10 Updated vendor libs 2019-08-09 13:33:35 -06:00
Matias Griese
afd53d76c2 Fixed CSV formatter not encoding strings with " and , properly 2019-07-18 16:11:46 +03:00
Matias Griese
57c65ad881 Fixed FlexObject::exists() failing sometimes just after the object has been saved 2019-07-16 09:50:30 +03:00
Matias Griese
a372ae90c2 Fixed an error when trying to delete a file from non-existing Flex Object 2019-07-16 09:11:11 +03:00
Andy Miller
c8739c40a5 Fixed collections using page@.self: should allow modular pages if requested 2019-07-12 12:02:28 -06:00
Andy Miller
00ff9ac42d fix 2019-07-11 08:50:09 -06:00
Andy Miller
e13a8304e6 Added arrayLower() method 2019-07-10 11:01:17 -06:00
Matias Griese
3c2b17853c Fixed non-existing Flex object having a bad media folder 2019-07-10 12:30:59 +03:00
Matias Griese
288b2a1953 Fixed fatal error 'Expiration date must be an integer, a DateInterval or null, "double" given' [#2529] 2019-07-09 13:43:51 +03:00
Matias Griese
86b1f1fbac Workaround bug in flex forms 2019-07-02 22:01:38 +03:00
Matias Griese
b5e26133a7 Fixed new Flex User creation not being possible because of username could not be given 2019-07-02 20:27:12 +03:00
Matias Griese
c97faa0238 ixed Flex User to have permissions to save and delete his own user 2019-07-01 22:25:29 +03:00
Matias Griese
69b39b4b21 Fixed multiple url() issues with streams
* 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
2019-06-28 13:36:37 +03:00
Matias Griese
02f544f813 Merge remote-tracking branch 'origin/develop' into develop 2019-06-27 18:08:21 +03:00
Matias Griese
69b5a779e4 Fixed error when calling Media::add($name, null) 2019-06-27 18:08:12 +03:00
Andy Miller
fa5c1e495d Use new Utils::getSupportedPageTypes() to enforce html,htm at the front of the list #2531 2019-06-26 08:59:24 -06:00
Matias Griese
7fdb2c10cb Minor code cleanup 2019-06-26 12:14:57 +03:00
Keith Bentrup
e422eebd3c fixed typos in comments / API docs 2019-06-26 11:46:08 +03:00
Keith Bentrup
3dca7e3539 fixed typos in comments / API docs 2019-06-26 11:46:08 +03:00
Matias Griese
75210b102e Changelog update 2019-06-26 10:48:06 +03:00
Matias Griese
f0e97a7277 Fixed some potential issues when $grav['user'] is not set 2019-06-26 10:38:38 +03:00
Newb I the Newbd
e16c81516e Make yaml_decode only return array, again 2019-06-25 10:02:40 +03:00
Matias Griese
140c9a941f Added support for custom FormFlash save locations 2019-06-24 13:15:27 +03:00
Andy Miller
eb58fe9e97 Merge branch 'release/1.6.11' 2019-06-21 15:19:04 -06:00
Andy Miller
5d4ea87402 Merge tag '1.6.11' into develop
Release v1.6.11
2019-06-21 15:19:04 -06:00
Andy Miller
9f1d7240a9 Prepare for release 2019-06-21 15:18:54 -06:00
Matias Griese
84894274f0 Fixed some FormFlash bugs, cleaned up the code 2019-06-21 22:59:54 +03:00
Matias Griese
ea09002012 Changed FormFlashInterface constructor to take $config array 2019-06-21 13:19:01 +03:00
Matias Griese
601ec5cb7a Added FormFlashInterface 2019-06-21 11:47:21 +03:00
Matias Griese
85ec2ee3a0 Fixed error if user has no form flashes 2019-06-20 21:19:10 +03:00
Matias Griese
965f69f680 Fixed issue with FormFlash objects when there is no session initialized 2019-06-20 21:10:48 +03:00
Matias Griese
13a56dd4da Initialize pages only once 2019-06-20 19:21:52 +03:00
Matias Griese
4cf1b8c400 Better fix for Route class 2019-06-20 19:21:35 +03:00
Matias Griese
9c805a4317 Added created timestamp to FormFlash 2019-06-20 12:18:55 +03:00
Matias Griese
b7b1182e14 Added timestamp to FormFlash objects 2019-06-20 11:47:23 +03:00
Matias Griese
f695aaaea4 Added FormTrait::getAllFlashes() method to get all the available form flash objects for the form 2019-06-20 00:51:20 +03:00
Matias Griese
b398d04d96 Fixed a bug in Route::withParam() method 2019-06-20 00:45:59 +03:00
Matias Griese
ac9ef4da76 Fixed empty form flash name after update 2019-06-19 20:56:23 +03:00
Matias Griese
a10893eaad Fixed error in ImageMedium::url() if the image cache folder does not exist 2019-06-19 11:23:12 +03:00
Andy Miller
e6c8b30882 Merge tag '1.6.10' into develop
Release v1.6.10
2019-06-14 13:59:12 -06:00
43 changed files with 1466 additions and 708 deletions

View File

@@ -1,3 +1,89 @@
# v1.6.17
## 11/06/2019
1. [](#new)
* Added working ETag (304 Not Modified) support based on the final rendered HTML
1. [](#improved)
* Safer file handling + customizable null char replacement in `CsvFormatter::decode()`
* Change of Behavior: `Inflector::hyphenize` will now automatically trim dashes at beginning and end of a string.
* Change in Behavior for `Folder::all()` so no longer fails if trying to copy non-existent dot file [#2581](https://github.com/getgrav/grav/pull/2581)
* renamed composer `test-plugins` script to `phpstan-plugins` to be more explicit [#2637](https://github.com/getgrav/grav/pull/2637)
1. [](#bugfix)
* Fixed PHP 7.1 bug in FlexMedia
* Fix cache image generation when using cropResize [#2639](https://github.com/getgrav/grav/pull/2639)
* Fix `array_merge()` exception with non-array page header metadata [#2701](https://github.com/getgrav/grav/pull/2701)
# v1.6.16
## 09/19/2019
1. [](#bugfix)
* Fixed Flex user creation if file storage is being used [#2444](https://github.com/getgrav/grav/issues/2444)
* Fixed `Badly encoded JSON data` warning when uploading files [#2663](https://github.com/getgrav/grav/issues/2663)
# v1.6.15
## 08/20/2019
1. [](#improved)
* Improved robots.txt [#2632](https://github.com/getgrav/grav/issues/2632)
1. [](#bugfix)
* Fixed broken markdown Twig tag [#2635](https://github.com/getgrav/grav/issues/2635)
* Force Symfony 4.2 in Grav 1.6 to remove a bunch of deprecated messages
# v1.6.14
## 08/18/2019
1. [](#bugfix)
* Actually include fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
# v1.6.13
## 08/16/2019
1. [](#bugfix)
* Regression fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
# v1.6.12
## 08/14/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
1. [](#new)
* Added `FormTrait::getAllFlashes()` method to get all the available form flash objects for the form
* Added creation and update timestamps to `FormFlash` objects
1. [](#improved)
* Added `FormFlashInterface`, changed constructor to take `$config` array
1. [](#bugfix)
* Fixed error in `ImageMedium::url()` if the image cache folder does not exist
* Fixed empty form flash name after file upload or form state update
* Fixed a bug in `Route::withParam()` method
* Fixed issue with `FormFlash` objects when there is no session initialized
# v1.6.10
## 06/14/2019

View File

@@ -2,7 +2,13 @@
"name": "getgrav/grav",
"type": "project",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"keywords": [
"cms",
"flat-file cms",
"flat cms",
"flatfile cms",
"php"
],
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
@@ -16,27 +22,23 @@
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*",
"nyholm/psr7": "^1.0",
"twig/twig": "~1.40",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~4.2",
"symfony/console": "~4.2",
"symfony/event-dispatcher": "~4.2",
"symfony/var-dumper": "~4.2",
"symfony/process": "~4.2",
"symfony/yaml": "~4.2.0",
"symfony/console": "~4.2.0",
"symfony/event-dispatcher": "~4.2.0",
"symfony/var-dumper": "~4.2.0",
"symfony/process": "~4.2.0",
"doctrine/cache": "^1.8",
"doctrine/collections": "^1.5",
"guzzlehttp/psr7": "^1.4",
"filp/whoops": "~2.2",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "2.*",
@@ -83,10 +85,14 @@
"psr-4": {
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
"files": [
"system/defines.php"
]
},
"archive": {
"exclude": ["VERSION"]
"exclude": [
"VERSION"
]
},
"scripts": {
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
@@ -94,7 +100,7 @@
"post-create-project-cmd": "bin/grav install",
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=256M",
"phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
"test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
},

654
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,3 +10,4 @@ Disallow: /user/
Allow: /user/pages/
Allow: /user/themes/
Allow: /user/images/
Allow: /

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.6.10');
define('GRAV_VERSION', '1.6.17');
define('GRAV_TESTING', false);
define('DS', '/');

View File

@@ -17,11 +17,21 @@ 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 ($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;

View File

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

View File

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

View File

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

View File

@@ -235,7 +235,7 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if (strpos($file->getFilename(), '.') === 0) {
if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
continue;
}
if (!$folders && $file->isDir()) {

View File

@@ -10,54 +10,10 @@
namespace Grav\Common\Form;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\User\Interfaces\UserInterface;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
class FormFlash extends \Grav\Framework\Form\FormFlash
class FormFlash extends FrameworkFormFlash
{
/**
* @param string $sessionId
*/
public static function clearSession(string $sessionId): void
{
$folder = static::getSessionTmpDir($sessionId);
if (is_dir($folder)) {
Folder::delete($folder);
}
}
/**
* @param string $sessionId
* @return string
*/
public static function getSessionTmpDir(string $sessionId): string
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
return $locator->findResource("tmp://forms/{$sessionId}", true, true);
}
/**
* @param UserInterface|null $user
* @return $this
*/
public function setUser(UserInterface $user = null)
{
if ($user && $user->username) {
$this->user = [
'username' => $user->username,
'email' => $user->email ?? ''
];
} else {
$this->user = null;
}
return $this;
}
/**
* @return array
* @deprecated 1.6 For backwards compatibility only, do not use
@@ -89,8 +45,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
*/
public function uploadFile(string $field, string $filename, array $upload): bool
{
$tmp_dir = $this->getTmpDir();
if (!$this->uniqueId) {
return false;
}
$tmp_dir = $this->getTmpDir();
Folder::create($tmp_dir);
$tmp_file = $upload['file']['tmp_name'];
@@ -118,8 +77,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
*/
public function cropFile(string $field, string $filename, array $upload, array $crop): bool
{
$tmp_dir = $this->getTmpDir();
if (!$this->uniqueId) {
return false;
}
$tmp_dir = $this->getTmpDir();
Folder::create($tmp_dir);
$tmp_file = $upload['file']['tmp_name'];
@@ -136,32 +98,4 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
return true;
}
/**
* @return YamlFile
*/
protected function getTmpIndex(): YamlFile
{
// Do not use CompiledYamlFile as the file can change multiple times per second.
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
}
/**
* @param string $name
*/
protected function removeTmpFile(string $name): void
{
$filename = $this->getTmpDir() . '/' . $name;
if ($name && is_file($filename)) {
unlink($filename);
}
}
protected function removeTmpDir(): void
{
$tmpDir = $this->getTmpDir();
if (file_exists($tmpDir)) {
Folder::delete($tmpDir);
}
}
}

View File

@@ -103,7 +103,7 @@ class Licenses
}
/**
* Get's the License File object
* Get the License File object
*
* @return \RocketTheme\Toolbox\File\FileInterface
*/

View File

@@ -247,9 +247,21 @@ class Grav extends Container
$collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']);
$body = $response->getBody();
// Handle ETag and If-None-Match headers.
if ($response->getHeaderLine('ETag') === '1') {
$etag = md5($body);
$response = $response->withHeader('ETag', $etag);
if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
$response = $response->withStatus(304);
$body = '';
}
}
$this->header($response);
echo $response->getBody();
echo $body;
$debugger->render();

View File

@@ -193,6 +193,8 @@ class Inflector
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
$regex4 = trim($regex4, '-');
return strtolower($regex4);
}

View File

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

View File

@@ -125,5 +125,5 @@ trait MediaTrait
/**
* @return string
*/
abstract protected function getCacheKey();
abstract protected function getCacheKey(): string;
}

View File

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

View File

@@ -170,8 +170,7 @@ class ImageMedium extends Medium
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$image_path = $locator->findResource('cache://images', true);
$image_dir = $locator->findResource('cache://images', false);
$image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
$saved_image_path = $this->saveImage();
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
@@ -181,6 +180,7 @@ class ImageMedium extends Medium
}
if (Utils::startsWith($output, $image_path)) {
$image_dir = $locator->findResource('cache://images', false);
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
}
@@ -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
*/

View File

@@ -529,9 +529,9 @@ class Page implements PageInterface
$headers['Last-Modified'] = $last_modified_date;
}
// Calculate ETag based on the raw file
// Ask Grav to calculate ETag from the final content.
if ($this->eTag()) {
$headers['ETag'] = '"' . md5($this->raw() . $this->modified()).'"';
$headers['ETag'] = '1';
}
// Set Vary: Accept-Encoding header
@@ -1200,7 +1200,7 @@ class Page implements PageInterface
/**
* @return string
*/
protected function getCacheKey()
protected function getCacheKey(): string
{
return $this->id();
}
@@ -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);
@@ -1696,7 +1696,7 @@ class Page implements PageInterface
// Get initial metadata for the page
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata'));
if (isset($this->header->metadata)) {
if (isset($this->header->metadata) && is_array($this->header->metadata)) {
// Merge any site.metadata settings in with page metadata
$metadata = array_merge($metadata, $this->header->metadata);
}
@@ -2855,9 +2855,9 @@ class Page implements PageInterface
$result = [];
foreach ((array)$value as $key => $val) {
if (is_int($key)) {
$result = $result + $this->evaluate($val)->toArray();
$result = $result + $this->evaluate($val, $only_published)->toArray();
} else {
$result = $result + $this->evaluate([$key => $val])->toArray();
$result = $result + $this->evaluate([$key => $val], $only_published)->toArray();
}
}
@@ -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':

View File

@@ -91,6 +91,10 @@ class Pages
/** @var string */
protected $check_method;
protected $pages_cache_id;
protected $initialized = false;
/**
* @var Types
*/
@@ -101,8 +105,6 @@ class Pages
*/
static protected $home_route;
protected $pages_cache_id;
/**
* Constructor
*
@@ -239,6 +241,10 @@ class Pages
*/
public function init()
{
if ($this->initialized) {
return;
}
$config = $this->grav['config'];
$this->ignore_files = $config->get('system.pages.ignore_files');
$this->ignore_folders = $config->get('system.pages.ignore_folders');

View File

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

View File

@@ -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'];

View File

@@ -41,6 +41,6 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
->write('$lines = explode("\n", $content);' . PHP_EOL)
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
->write('$content = join("\n", $content);' . PHP_EOL)
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($content);' . PHP_EOL);
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
}
}

View File

@@ -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
@@ -613,12 +613,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
/**
* @param string $string
*
* @param array $context
* @param bool $block Block or Line processing
* @return mixed|string
*/
public function markdownFunction($string, $block = true)
public function markdownFunction($context, $string, $block = true)
{
return Utils::processMarkdown($string, $block);
$page = $context['page'] ?? null;
return Utils::processMarkdown($string, $block, $page);
}
/**
@@ -1004,10 +1006,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 +1138,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

View File

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

View File

@@ -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;
@@ -21,6 +22,7 @@ use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\Formatter\YamlFormatter;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Storage\FileStorage;
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
use Grav\Framework\Form\FormFlashFile;
@@ -381,6 +383,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.
*
@@ -420,6 +447,15 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*/
public function save()
{
// TODO: We may want to handle this in the storage layer in the future.
$key = $this->getStorageKey();
if (!$key || strpos($key, '@@')) {
$storage = $this->getFlexDirectory()->getStorage();
if ($storage instanceof FileStorage) {
$this->setStorageKey($this->getKey());
}
}
$password = $this->getProperty('password');
if (null !== $password) {
$this->unsetProperty('password');
@@ -431,6 +467,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
*/

View File

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

View File

@@ -437,7 +437,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
}
/**
* Implementes JsonSerializable interface.
* Implements JsonSerializable interface.
*
* @return array
*/

View File

@@ -84,7 +84,7 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
}
/**
* Implementes JsonSerializable interface.
* Implements JsonSerializable interface.
*
* @return array
*/

View File

@@ -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;
@@ -79,12 +79,49 @@ class CsvFormatter extends AbstractFormatter
// Get the field names
$header = str_getcsv(array_shift($lines), $delimiter);
// Allow for replacing a null string with null/empty value
$null_replace = $this->getConfig('null');
// Get the data
$list = [];
foreach ($lines as $line) {
$list[] = array_combine($header, str_getcsv($line, $delimiter));
$line = null;
try {
foreach ($lines as $line) {
if (!empty($line)) {
$csv_line = str_getcsv($line, $delimiter);
if ($null_replace) {
array_walk($csv_line, function(&$el) use ($null_replace) {
$el = str_replace($null_replace, null, $el);
});
}
$list[] = array_combine($header, $csv_line);
}
}
} catch (\Exception $e) {
throw new \Exception('Badly formatted CSV line: ' . $line);
}
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;
}
}

View File

@@ -97,7 +97,7 @@ class YamlFormatter extends AbstractFormatter
@ini_set('yaml.decode_php', $saved);
if ($decoded !== false) {
return $decoded;
return (array) $decoded;
}
}

View File

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

View File

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

View File

@@ -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);
@@ -332,5 +342,5 @@ trait FlexMediaTrait
abstract public function getFlexDirectory(): FlexDirectory;
abstract public function getStorageKey();
abstract public function getStorageKey(): string;
}

View File

@@ -10,12 +10,18 @@
namespace Grav\Framework\Form;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Framework\Form\Interfaces\FormFlashInterface;
use Psr\Http\Message\UploadedFileInterface;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class FormFlash implements \JsonSerializable
class FormFlash implements FormFlashInterface
{
/** @var bool */
protected $exists;
/** @var string */
protected $sessionId;
/** @var string */
@@ -26,6 +32,10 @@ class FormFlash implements \JsonSerializable
protected $url;
/** @var array */
protected $user;
/** @var int */
protected $createdTimestamp;
/** @var int */
protected $updatedTimestamp;
/** @var array */
protected $data;
/** @var array */
@@ -34,29 +44,35 @@ class FormFlash implements \JsonSerializable
protected $uploadedFiles;
/** @var string[] */
protected $uploadObjects;
/** @var bool */
protected $exists;
/** @var string */
protected $folder;
/**
* @param string $sessionId
* @return string
* @inheritDoc
*/
public static function getSessionTmpDir(string $sessionId): string
public function __construct($config)
{
return "tmp://forms/{$sessionId}";
}
// Backwards compatibility with Grav 1.6 plugins.
if (!is_array($config)) {
user_error(__CLASS__ . '::' . __FUNCTION__ . '($sessionId, $uniqueId, $formName) is deprecated since Grav 1.6.11, use $config parameter instead', E_USER_DEPRECATED);
/**
* FormFlashObject constructor.
* @param string $sessionId
* @param string $uniqueId
* @param string|null $formName
*/
public function __construct(string $sessionId, string $uniqueId, string $formName = null)
{
$this->sessionId = $sessionId;
$this->uniqueId = $uniqueId;
$args = func_get_args();
$config = [
'session_id' => $args[0],
'unique_id' => $args[1] ?? null,
'form_name' => $args[2] ?? null,
];
}
$this->sessionId = $config['session_id'] ?? 'no-session';
$this->uniqueId = $config['unique_id'] ?? '';
$folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : '');
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$this->folder = $folder && $locator->isStream($folder) ? $locator->findResource($folder, true, true) : $folder;
$file = $this->getTmpIndex();
$this->exists = $file->exists();
@@ -66,65 +82,58 @@ class FormFlash implements \JsonSerializable
} catch (\Exception $e) {
$data = [];
}
$this->formName = null !== $formName ? $content['form'] ?? '' : '';
$this->formName = $content['form'] ?? $config['form_name'] ?? '';
$this->url = $data['url'] ?? '';
$this->user = $data['user'] ?? null;
$this->updatedTimestamp = $data['timestamps']['updated'] ?? time();
$this->createdTimestamp = $data['timestamps']['created'] ?? $this->updatedTimestamp;
$this->data = $data['data'] ?? null;
$this->files = $data['files'] ?? [];
} else {
$this->formName = $formName;
$this->formName = $config['form_name'] ?? '';
$this->url = '';
$this->createdTimestamp = $this->updatedTimestamp = time();
$this->files = [];
}
}
/**
* @return string
* @inheritDoc
*/
public function getSessionId(): string
{
return $this->sessionId;
}
/**
* @inheritDoc
*/
public function getUniqueId(): string
{
return $this->uniqueId;
}
/**
* @deprecated 1.6.11 Use '->getUniqueId()' method instead.
*/
public function getUniqieId(): string
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6.11, use ->getUniqueId() method instead', E_USER_DEPRECATED);
return $this->getUniqueId();
}
/**
* @inheritDoc
*/
public function getFormName(): string
{
return $this->formName;
}
/**
* @return string
*/
public function getUniqieId(): string
{
return $this->uniqueId;
}
/**
* @return bool
*/
public function exists(): bool
{
return $this->exists;
}
/**
* @return $this
*/
public function save(): self
{
$file = $this->getTmpIndex();
$file->save($this->jsonSerialize());
$this->exists = true;
return $this;
}
public function delete(): self
{
$this->removeTmpDir();
$this->files = [];
$this->exists = false;
return $this;
}
/**
* @return string
* @inheritDoc
*/
public function getUrl(): string
{
@@ -132,18 +141,7 @@ class FormFlash implements \JsonSerializable
}
/**
* @param string $url
* @return $this
*/
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
/**
* @return string
* @inheritDoc
*/
public function getUsername(): string
{
@@ -151,7 +149,7 @@ class FormFlash implements \JsonSerializable
}
/**
* @return string
* @inheritDoc
*/
public function getUserEmail(): string
{
@@ -159,40 +157,84 @@ class FormFlash implements \JsonSerializable
}
/**
* @param string|null $username
* @return $this
* @inheritDoc
*/
public function setUserName(string $username = null): self
public function getCreatedTimestamp(): int
{
$this->user['username'] = $username;
return $this;
return $this->createdTimestamp;
}
/**
* @param string|null $email
* @return $this
* @inheritDoc
*/
public function setUserEmail(string $email = null): self
public function getUpdatedTimestamp(): int
{
$this->user['email'] = $email;
return $this;
return $this->updatedTimestamp;
}
/**
* @inheritDoc
*/
public function getData(): ?array
{
return $this->data;
}
/**
* @inheritDoc
*/
public function setData(?array $data): void
{
$this->data = $data;
}
/**
* @param string $field
* @return array
* @inheritDoc
*/
public function exists(): bool
{
return $this->exists;
}
/**
* @inheritDoc
*/
public function save(): self
{
if (!($this->folder && $this->uniqueId)) {
return $this;
}
if ($this->data || $this->files) {
// Only save if there is data or files to be saved.
$file = $this->getTmpIndex();
$file->save($this->jsonSerialize());
$this->exists = true;
} elseif ($this->exists) {
// Delete empty form flash if it exists (it carries no information).
return $this->delete();
}
return $this;
}
/**
* @inheritDoc
*/
public function delete(): self
{
if ($this->folder && $this->uniqueId) {
$this->removeTmpDir();
$this->files = [];
$this->exists = false;
}
return $this;
}
/**
* @inheritDoc
*/
public function getFilesByField(string $field): array
{
@@ -208,8 +250,7 @@ class FormFlash implements \JsonSerializable
}
/**
* @param bool $includeOriginal
* @return array
* @inheritDoc
*/
public function getFilesByFields($includeOriginal = false): array
{
@@ -225,12 +266,7 @@ class FormFlash implements \JsonSerializable
}
/**
* Add uploaded file to the form flash.
*
* @param UploadedFileInterface $upload
* @param string|null $field
* @param array|null $crop
* @return string Return name of the file
* @inheritDoc
*/
public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string
{
@@ -256,12 +292,7 @@ class FormFlash implements \JsonSerializable
/**
* Add existing file to the form flash.
*
* @param string $filename
* @param string $field
* @param array $crop
* @return bool
* @inheritDoc
*/
public function addFile(string $filename, string $field, array $crop = null): bool
{
@@ -282,11 +313,7 @@ class FormFlash implements \JsonSerializable
}
/**
* Remove any file from form flash.
*
* @param string $name
* @param string $field
* @return bool
* @inheritDoc
*/
public function removeFile(string $name, string $field = null): bool
{
@@ -318,7 +345,7 @@ class FormFlash implements \JsonSerializable
}
/**
* Clear form flash from all uploaded files.
* @inheritDoc
*/
public function clearFiles()
{
@@ -332,7 +359,7 @@ class FormFlash implements \JsonSerializable
}
/**
* @return array
* @inheritDoc
*/
public function jsonSerialize(): array
{
@@ -341,17 +368,72 @@ class FormFlash implements \JsonSerializable
'unique_id' => $this->uniqueId,
'url' => $this->url,
'user' => $this->user,
'timestamps' => [
'created' => $this->createdTimestamp,
'updated' => time(),
],
'data' => $this->data,
'files' => $this->files
];
}
/**
* @param string $url
* @return $this
*/
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
/**
* @param UserInterface|null $user
* @return $this
*/
public function setUser(UserInterface $user = null)
{
if ($user && $user->username) {
$this->user = [
'username' => $user->username,
'email' => $user->email ?? ''
];
} else {
$this->user = null;
}
return $this;
}
/**
* @param string|null $username
* @return $this
*/
public function setUserName(string $username = null): self
{
$this->user['username'] = $username;
return $this;
}
/**
* @param string|null $email
* @return $this
*/
public function setUserEmail(string $email = null): self
{
$this->user['email'] = $email;
return $this;
}
/**
* @return string
*/
public function getTmpDir(): string
{
return static::getSessionTmpDir($this->sessionId) . '/' . $this->uniqueId;
return $this->folder && $this->uniqueId ? "{$this->folder}/{$this->uniqueId}" : '';
}
/**
@@ -368,8 +450,9 @@ class FormFlash implements \JsonSerializable
*/
protected function removeTmpFile(string $name): void
{
$filename = $this->getTmpDir() . '/' . $name;
if ($name && is_file($filename)) {
$tmpDir = $this->getTmpDir();
$filename = $tmpDir ? $tmpDir . '/' . $name : '';
if ($name && $filename && is_file($filename)) {
unlink($filename);
}
}
@@ -377,7 +460,7 @@ class FormFlash implements \JsonSerializable
protected function removeTmpDir(): void
{
$tmpDir = $this->getTmpDir();
if (file_exists($tmpDir)) {
if ($tmpDir && file_exists($tmpDir)) {
Folder::delete($tmpDir);
}
}
@@ -390,6 +473,10 @@ class FormFlash implements \JsonSerializable
*/
protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void
{
if (!($this->folder && $this->uniqueId)) {
throw new \RuntimeException('Cannot upload files: form flash folder not defined');
}
$field = $field ?: 'undefined';
if (!isset($this->files[$field])) {
$this->files[$field] = [];

View File

@@ -0,0 +1,165 @@
<?php
/**
* @package Grav\Framework\Form
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Form\Interfaces;
use Psr\Http\Message\UploadedFileInterface;
interface FormFlashInterface extends \JsonSerializable
{
/**
* @param array $config Available configuration keys: session_id, unique_id, form_name
*/
public function __construct($config);
/**
* Get session Id associated to this form instance.
*
* @return string
*/
public function getSessionId(): string;
/**
* Get unique identifier associated to this form instance.
*
* @return string
*/
public function getUniqueId(): string;
/**
* Get form name associated to this form instance.
*
* @return string
*/
public function getFormName(): string;
/**
* Get URL associated to this form instance.
*
* @return string
*/
public function getUrl(): string;
/**
* Get username from the user who was associated to this form instance.
*
* @return string
*/
public function getUsername(): string;
/**
* Get email from the user who was associated to this form instance.
*
* @return string
*/
public function getUserEmail(): string;
/**
* Get creation timestamp for this form flash.
*
* @return int
*/
public function getCreatedTimestamp(): int;
/**
* Get last updated timestamp for this form flash.
*
* @return int
*/
public function getUpdatedTimestamp(): int;
/**
* Get raw form data.
*
* @return array|null
*/
public function getData(): ?array;
/**
* Set raw form data.
*
* @param array|null $data
*/
public function setData(?array $data): void;
/**
* Check if this form flash exists.
*
* @return bool
*/
public function exists(): bool;
/**
* Save this form flash.
*
* @return $this
*/
public function save();
/**
* Delete this form flash.
*/
public function delete();
/**
* Get all files associated to a form field.
*
* @param string $field
* @return array
*/
public function getFilesByField(string $field): array;
/**
* Get all files grouped by the associated form fields.
*
* @param bool $includeOriginal
* @return array
*/
public function getFilesByFields($includeOriginal = false): array;
/**
* Add uploaded file to the form flash.
*
* @param UploadedFileInterface $upload
* @param string|null $field
* @param array|null $crop
* @return string Return name of the file
*/
public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string;
/**
* Add existing file to the form flash.
*
* @param string $filename
* @param string $field
* @param array $crop
* @return bool
*/
public function addFile(string $filename, string $field, array $crop = null): bool;
/**
* Remove any file from form flash.
*
* @param string $name
* @param string $field
* @return bool
*/
public function removeFile(string $name, string $field = null): bool;
/**
* Clear form flash from all uploaded files.
*/
public function clearFiles();
/**
* @return array
*/
public function jsonSerialize(): array;
}

View File

@@ -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;
@@ -296,12 +298,12 @@ trait FormTrait
public function getButtons(): array
{
return $this->getBlueprint()['form']['buttons'] ?? [];
return $this->getBlueprint()->get('form/buttons') ?? [];
}
public function getTasks(): array
{
return $this->getBlueprint()['form']['tasks'] ?? [];
return $this->getBlueprint()->get('form/tasks') ?? [];
}
abstract public function getBlueprint(): Blueprint;
@@ -336,35 +338,56 @@ trait FormTrait
public function getFlash(): FormFlash
{
if (null === $this->flash) {
/** @var Grav $grav */
$grav = Grav::instance();
$id = null;
$user = $grav['user'] ?? null;
if (isset($user)) {
$rememberState = $this->getBlueprint()->get('form/remember_state');
if ($rememberState === 'user') {
$id = $user->username;
}
}
// Session Required for flash form
$session = $grav['session'] ?? null;
if (isset($session)) {
// By default store flash by the session id.
if (null === $id) {
$id = $session->getId();
}
$config = [
'session_id' => $this->getSessionId(),
'unique_id' => $this->getUniqueId(),
'form_name' => $this->getName(),
'folder' => $this->getFlashFolder()
];
$this->flash = new FormFlash($id, $this->getUniqueId(), $this->getName());
$this->flash->setUrl($grav['uri']->url)->setUser($user);
}
$this->flash = new FormFlash($config);
$this->flash->setUrl($grav['uri']->url)->setUser($grav['user'] ?? null);
}
return $this->flash;
}
/**
* Get all available form flash objects for this form.
*
* @return FormFlash[]
*/
public function getAllFlashes(): array
{
$folder = $this->getFlashFolder();
if (!$folder || !is_dir($folder)) {
return [];
}
$name = $this->getName();
$list = [];
/** @var \SplFileInfo $file */
foreach (new \FilesystemIterator($folder) as $file) {
$uniqueId = $file->getFilename();
$config = [
'session_id' => $this->getSessionId(),
'unique_id' => $uniqueId,
'form_name' => $name,
'folder' => $this->getFlashFolder()
];
$flash = new FormFlash($config);
if ($flash->exists() && $flash->getFormName() === $name) {
$list[] = $flash;
}
}
return $list;
}
/**
* {@inheritdoc}
* @see FormInterface::render()
@@ -389,11 +412,51 @@ trait FormTrait
return $block;
}
protected function getSessionId(): string
{
/** @var Grav $grav */
$grav = Grav::instance();
/** @var SessionInterface $session */
$session = $grav['session'] ?? null;
return $session ? ($session->getId() ?? '') : '';
}
protected function unsetFlash(): void
{
$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.
*
@@ -577,7 +640,7 @@ trait FormTrait
foreach ($data as $key => &$value) {
if (\is_array($value)) {
$value = $this->jsonDecode($value);
} elseif ($value === '') {
} elseif (trim($value) === '') {
unset($data[$key]);
} else {
$value = json_decode($value, true);

View File

@@ -154,16 +154,11 @@ class Route
* If the parameter exists in both, return Grav parameter.
*
* @param string $param
* @return string|null
* @return string|array|null
*/
public function getParam($param)
{
$value = $this->getGravParam($param);
if ($value === null) {
$value = $this->getQueryParam($param);
}
return $value;
return $this->getGravParam($param) ?? $this->getQueryParam($param);
}
/**
@@ -177,7 +172,7 @@ class Route
/**
* @param string $param
* @return string|null
* @return string|array|null
*/
public function getQueryParam($param)
{
@@ -313,7 +308,8 @@ class Route
*/
protected function withParam($type, $param, $value)
{
$oldValue = $this->{$type}[$param] ?? null;
$values = $this->{$type} ?? [];
$oldValue = $values[$param] ?? null;
if ($oldValue === $value) {
return $this;
@@ -321,11 +317,13 @@ class Route
$new = $this->copy();
if ($value === null) {
unset($new->{$type}[$param]);
unset($values[$param]);
} else {
$new->{$type}[$param] = $value;
$values[$param] = $value;
}
$new->{$type} = $values;
return $new;
}
@@ -376,8 +374,8 @@ class Route
$this->language = $gravParts['language'];
$this->route = $gravParts['route'];
$this->extension = $gravParts['extension'] ?? '';
$this->gravParams = $gravParts['params'];
$this->queryParams = $parts['query_params'];
$this->gravParams = $gravParts['params'] ?: [];
$this->queryParams = $parts['query_params'] ?: [];
} else {
$this->root = RouteFactory::getRoot();

View File

@@ -110,6 +110,7 @@ class ParsedownTest extends \Codeception\TestCase\Test
public function testImagesSubDir()
{
$this->config->set('system.images.cache_all', false);
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/item2/item2-2', '/subdir')->init();
$this->assertRegexp('|<p><img alt="" src="\/subdir\/images\/.*-home-cache-image.jpe?g" \/><\/p>|',

View File

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

View File

@@ -2,7 +2,7 @@
# You can override ddev's configuration by placing an edited copy
# of this config (or one of the other ones) in .ddev/nginx-site.conf
# See https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#providing-custom-nginx-configuration
# See https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/#providing-custom-nginx-configuration
# Set https to 'on' if x-forwarded-proto is https
map $http_x_forwarded_proto $fcgi_https {
@@ -11,11 +11,16 @@ map $http_x_forwarded_proto $fcgi_https {
}
server {
listen 80; ## listen for ipv4; this line is default and implied
listen [::]:80 default ipv6only=on; ## listen for ipv6
# The NGINX_DOCROOT variable is substituted with
listen 80;
listen [::]:80 default ipv6only=on;
# The WEBSERVER_DOCROOT variable is substituted with
# its value when the container is started.
root $NGINX_DOCROOT;
root $WEBSERVER_DOCROOT;
include /etc/nginx/monitoring.conf;
index index.php index.htm index.html;
# Make site accessible from http://localhost/
@@ -23,15 +28,20 @@ server {
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;
error_log /var/log/nginx/error.log info;
error_log /dev/stdout info;
access_log /var/log/nginx/access.log;
## Begin - Index
# for subfolders, simply adjust:
# `location /subfolder {`
# and the rewrite to use `/subfolder/index.php`
location / {
absolute_redirect off;
try_files $uri $uri/ /index.php?$query_string;
}
## End - Index
# pass the PHP scripts to FastCGI server listening on socket
# pass the PHP scripts to FastCGI server listening on socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
@@ -42,38 +52,78 @@ server {
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_intercept_errors off;
# fastcgi_read_timeout should match max_execution_time in php.ini
fastcgi_read_timeout 10m;
fastcgi_param SERVER_NAME $host;
fastcgi_param HTTPS $fcgi_https;
}
# Expire rules for static content
# Feed
location ~* \.(?:rss|atom|cache)$ {
expires 1h;
}
## 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|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|yml|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
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
include /mnt/ddev_config/nginx/*.conf;
}
# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
# Access to `/.well-known/` is allowed.
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
location ~* /\.(?!well-known\/) {
deny all;
}
# Prevent clients from accessing to backup/config/source files
location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ {
deny all;
server {
listen 443 ssl;
listen [::]:443 default ipv6only=on;
# The WEBSERVER_DOCROOT variable is substituted with
# its value when the container is started.
root $WEBSERVER_DOCROOT;
ssl_certificate /etc/ssl/certs/master.crt;
ssl_certificate_key /etc/ssl/certs/master.key;
include /etc/nginx/monitoring.conf;
index index.php index.htm index.html;
# Make site accessible from http://localhost/
server_name _;
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;
error_log /dev/stdout info;
access_log /var/log/nginx/access.log;
## Begin - Index
# for subfolders, simply adjust:
# `location /subfolder {`
# and the rewrite to use `/subfolder/index.php`
location / {
try_files $uri $uri/ /index.php?$query_string;
}
## End - Index
# pass the PHP scripts to FastCGI server listening on socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_intercept_errors off;
# fastcgi_read_timeout should match max_execution_time in php.ini
fastcgi_read_timeout 10m;
fastcgi_param SERVER_NAME $host;
fastcgi_param HTTPS $fcgi_https;
}
## Begin - Security
@@ -88,31 +138,5 @@ server {
## End - Security
## provide a health check endpoint
location /healthcheck {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
return 200;
}
error_page 400 401 /40x.html;
location = /40x.html {
root /usr/share/nginx/html;
}
location ~ ^/(fpmstatus|ping)$ {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
allow 127.0.0.1;
allow all;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm.sock;
}
include /mnt/ddev_config/nginx/*.conf;
}