diff --git a/CHANGELOG.md b/CHANGELOG.md index 5babb09b9..fb744b44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Added `route` and `request` to `onPagesInitialized` event * Improved page cloning, added method `Page::initialize()` * Improved `FlexObject::getChanges()`: return changed lists and arrays as whole instead of just changed keys/values + * Improved form validation JSON responses to contain list of failed fields with their error messages 3. [](#bugfix) * Fixed path traversal vulnerability when using `bin/grav server` * Fixed unescaped error messages in JSON error responses diff --git a/system/src/Grav/Common/Data/ValidationException.php b/system/src/Grav/Common/Data/ValidationException.php index 2d94ab816..be6a674d7 100644 --- a/system/src/Grav/Common/Data/ValidationException.php +++ b/system/src/Grav/Common/Data/ValidationException.php @@ -10,16 +10,18 @@ namespace Grav\Common\Data; use Grav\Common\Grav; +use JsonSerializable; use RuntimeException; /** * Class ValidationException * @package Grav\Common\Data */ -class ValidationException extends RuntimeException +class ValidationException extends RuntimeException implements JsonSerializable { /** @var array */ protected $messages = []; + protected $escape = true; /** * @param array $messages @@ -32,21 +34,34 @@ class ValidationException extends RuntimeException $language = Grav::instance()['language']; $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message; - foreach ($messages as $variable => &$list) { + foreach ($messages as $list) { $list = array_unique($list); foreach ($list as $message) { - $this->message .= "
$message"; + $this->message .= '
' . htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } } return $this; } + public function setSimpleMessage(bool $escape = true): void + { + $first = reset($this->messages); + $message = reset($first); + + $this->message = $escape ? htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $message; + } + /** * @return array */ - public function getMessages() + public function getMessages(): array { return $this->messages; } + + public function jsonSerialize(): array + { + return ['validation' => $this->messages]; + } } diff --git a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php index 5b4863573..afa08aa42 100644 --- a/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php +++ b/system/src/Grav/Framework/Controller/Traits/ControllerResponseTrait.php @@ -19,6 +19,7 @@ use Grav\Common\Utils; use Grav\Framework\Psr7\Response; use Grav\Framework\RequestHandler\Exception\RequestException; use Grav\Framework\Route\Route; +use JsonSerializable; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; @@ -209,6 +210,9 @@ trait ControllerResponseTrait } else { $message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); } + + $extra = $e instanceof JsonSerializable ? $e->jsonSerialize() : []; + $response = [ 'code' => $code, 'status' => 'error', @@ -216,7 +220,7 @@ trait ControllerResponseTrait 'error' => [ 'code' => $code, 'message' => $message - ] + ] + $extra ]; /** @var Debugger $debugger */ diff --git a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php index e09f1b257..9eadf72e3 100644 --- a/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php +++ b/system/src/Grav/Framework/RequestHandler/Middlewares/Exceptions.php @@ -16,6 +16,7 @@ use Grav\Common\Debugger; use Grav\Common\Grav; use Grav\Framework\Psr7\Response; use JsonException; +use JsonSerializable; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -46,6 +47,9 @@ class Exceptions implements MiddlewareInterface } else { $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8'); } + + $extra = $exception instanceof JsonSerializable ? $exception->jsonSerialize() : []; + $response = [ 'code' => $code, 'status' => 'error', @@ -53,7 +57,7 @@ class Exceptions implements MiddlewareInterface 'error' => [ 'code' => $code, 'message' => $message, - ] + ] + $extra ]; /** @var Debugger $debugger */