Enhance input validation, unicode handling, and sanitization across department goal criteria

- Add `validateAndSanitize` method to `goals_criteria` for input validation and defaulting incorrect values.
- Ensure JSON encoding in `response` and database interactions respects Unicode (`JSON_UNESCAPED_UNICODE`).
- Sanitize and validate input for `department_goals_o::add` and criteria usage.
- Fix header character encoding in response (`Content-Type: application/json; charset=utf-8`).
- Update criteria `label` sanitization with trimming, length limits, and safe character handling.
This commit is contained in:
Jeppe Bundgaard
2026-01-27 08:20:33 +01:00
parent f5e715d113
commit ad0277ff17
5 changed files with 52 additions and 9 deletions
+2 -2
View File
@@ -23,7 +23,7 @@ class response implements response_i
#[NoReturn] public function response(bool $success, mixed $data, int $status = null): void
{
global $DEBUG;
header('Content-Type: application/json');
header('Content-Type: application/json; charset=utf-8');
if ($status) {
http_response_code($status);
} else {
@@ -52,7 +52,7 @@ class response implements response_i
'data' => $data,
'meta' => $this->meta,
'includes' => $this->includes
]);
], JSON_UNESCAPED_UNICODE);
exit;
}
@@ -2,6 +2,7 @@
namespace goals\classes;
use classes\db;
use goals\helpers\goals_criteria_type;
use goals\helpers\goals_criteria_progress_alert_frequency;
use goals\interfaces\goals_criteria_i;
@@ -230,6 +231,7 @@ class goals_criteria implements goals_criteria_i
*/
public function toArray(): array
{
global /** @var db $db */ $db;
// Normalize lists to identifiers only
$users = $this->users?->listCustomerNumbers() ?? [];
$departments = $this->departments?->listIDs() ?? [];
@@ -243,7 +245,7 @@ class goals_criteria implements goals_criteria_i
return [
'type' => $this->type?->name ?? goals_criteria_type::NONE->name,
'target' => $this->target ?? 0,
'label' => $this->label,
'label' => (string)$this->label,
'start' => ($this->start instanceof \DateTimeInterface) ? $this->start->format(DATE_ATOM) : null,
'end' => ($this->end instanceof \DateTimeInterface) ? $this->end->format(DATE_ATOM) : null,
'users' => $users,
@@ -335,4 +337,40 @@ class goals_criteria implements goals_criteria_i
{
return $this->getProgress();
}
public function validateAndSanitize(): void
{
// Ensure target is non-negative
if ($this->target < 0) {
$this->target = 0;
}
// Ensure timeframe is valid
if (($this->start instanceof \DateTimeInterface) && ($this->end instanceof \DateTimeInterface)) {
if ($this->end < $this->start) {
$tmp = $this->start;
$this->start = $this->end;
$this->end = $tmp;
}
}
// Ensure unknown enum values are reset to defaults
if (!in_array($this->type, goals_criteria_type::cases())) {
$this->type = goals_criteria_type::NONE;
}
if (!in_array($this->progress_alert_frequency, goals_criteria_progress_alert_frequency::cases())) {
$this->progress_alert_frequency = goals_criteria_progress_alert_frequency::NONE;
}
// Sanitize label
if (is_string($this->label)) {
$label = trim($this->label);
$label = strip_tags($label);
$label = preg_replace('/\s+/', ' ', $label);
if ($label !== null) {
$label = mb_substr($label, 0, 255);
$this->label = $label;
}
}
}
}
@@ -36,11 +36,18 @@ class department_goals_o extends db
*/
public function add(users_o $created_by, array $departments, goals_criteria $criteria): department_goals_o
{
// Sanitize the input
$created_by->requireSelected();
foreach ($departments as $dept) {
$dept->requireSelected();
}
// Validate and sanitize criteria object
$criteria->validateAndSanitize();
// Add the object
$tmp_id = self::add_object([
'created_by' => (int)$created_by->id,
'departments' => array_map(fn($dept) => (int)$dept->id, $departments),
'criteria' => $criteria->toArray(),
'criteria' => (array)$criteria->toArray(),
]);
$this->id = $tmp_id;
self::getObjectProperties();
@@ -116,10 +116,8 @@ class departmentGoalsRoute
// Parse criteria (preserve unicode characters like æ, ø, å)
$criteria = goals_criteria::fromJson(json_encode($criteriaInput, JSON_UNESCAPED_UNICODE));
// Create
// Create the goal
$goal = (new department_goals_o())->add($user, $departments, $criteria);
$response->success($goal->asArray(), 201);
}, [
'goals_department_create' => 'Create a new department goal'
+2 -2
View File
@@ -1101,7 +1101,7 @@ trait db_object_t
foreach ( $data as $key => $value ) {
// JSON encode objects and arrays
if (is_object($value) || is_array($value)) {
$value = json_encode($value);
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
if ($value === false) {
throw new Exception('Failed to encode value for key: ' . $key . ' - ' . json_last_error_msg());
}
@@ -1226,7 +1226,7 @@ trait db_object_t
foreach ( $data as $key => $value ) {
// JSON encode objects and arrays
if (is_object($value) || is_array($value)) {
$value = json_encode($value);
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
if ($value === false) {
throw new Exception('Failed to encode value for key: ' . $key . ' - ' . json_last_error_msg());
}