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