opsdash-app/opsdash/lib/Service/PersistDeckSanitizer.php
2026-03-07 11:57:03 +07:00

135 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Opsdash\Service;
final class PersistDeckSanitizer {
private const MAX_DECK_BOARD_ID = 100000;
/**
* @param mixed $value
* @param array<string,mixed> $defaults
* @return array<string,mixed>
*/
public function sanitize($value, array $defaults): array {
if (!is_array($value)) {
return $defaults;
}
$allowedFilters = [
'all',
'mine',
'open_all',
'open_mine',
'done_all',
'done_mine',
'archived_all',
'archived_mine',
];
$defaultFilter = in_array(($value['defaultFilter'] ?? 'all'), $allowedFilters, true)
? $value['defaultFilter']
: 'all';
$hiddenBoards = [];
if (!empty($value['hiddenBoards']) && is_array($value['hiddenBoards'])) {
foreach ($value['hiddenBoards'] as $id) {
$validated = filter_var($id, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => self::MAX_DECK_BOARD_ID],
]);
if ($validated === false) {
continue;
}
$clamped = $this->clampDeckBoardId((int)$validated);
if ($clamped !== null) {
$hiddenBoards[] = $clamped;
}
}
$hiddenBoards = array_values(array_unique($hiddenBoards));
}
$mineMode = 'assignee';
if (isset($value['mineMode']) && is_string($value['mineMode'])) {
$mode = $value['mineMode'];
if (in_array($mode, ['assignee', 'creator', 'both'], true)) {
$mineMode = $mode;
}
}
$ticker = $defaults['ticker'];
if (isset($value['ticker']) && is_array($value['ticker'])) {
$ticker = [
'autoScroll' => array_key_exists('autoScroll', $value['ticker'])
? $this->normalizeBool($value['ticker']['autoScroll'], $defaults['ticker']['autoScroll'])
: $defaults['ticker']['autoScroll'],
'intervalSeconds' => $this->clampInt(
$value['ticker']['intervalSeconds'] ?? $defaults['ticker']['intervalSeconds'],
3,
10,
$defaults['ticker']['intervalSeconds']
),
'showBoardBadges' => array_key_exists('showBoardBadges', $value['ticker'])
? $this->normalizeBool($value['ticker']['showBoardBadges'], $defaults['ticker']['showBoardBadges'])
: $defaults['ticker']['showBoardBadges'],
];
}
return [
'enabled' => array_key_exists('enabled', $value) ? $this->normalizeBool($value['enabled'], $defaults['enabled']) : $defaults['enabled'],
'filtersEnabled' => array_key_exists('filtersEnabled', $value) ? $this->normalizeBool($value['filtersEnabled'], $defaults['filtersEnabled']) : $defaults['filtersEnabled'],
'defaultFilter' => $defaultFilter,
'hiddenBoards' => $hiddenBoards,
'mineMode' => $mineMode,
'solvedIncludesArchived' => array_key_exists('solvedIncludesArchived', $value)
? $this->normalizeBool($value['solvedIncludesArchived'], $defaults['solvedIncludesArchived'])
: $defaults['solvedIncludesArchived'],
'ticker' => $ticker,
];
}
private function normalizeBool(mixed $value, bool $default): bool {
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
$filtered = filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE);
if ($filtered !== null) {
return $filtered;
}
}
if (is_int($value)) {
if ($value === 1) {
return true;
}
if ($value === 0) {
return false;
}
return $default;
}
if (is_float($value)) {
if ($value === 1.0) {
return true;
}
if ($value === 0.0) {
return false;
}
return $default;
}
return $default;
}
private function clampDeckBoardId(int $id): ?int {
if ($id <= 0 || $id > self::MAX_DECK_BOARD_ID) {
return null;
}
return $id;
}
private function clampInt(mixed $value, int $min, int $max, int $fallback): int {
if (!is_numeric($value)) {
return $fallback;
}
$n = (int)floor((float)$value);
if ($n < $min) {
return $min;
}
if ($n > $max) {
return $max;
}
return $n;
}
}