prepare('SELECT layout FROM user_ui_prefs WHERE user_id = ? AND page = ?'); $st->execute([$userId, $page]); $j = $st->fetchColumn(); $d = $j ? json_decode($j, true) : null; return is_array($d) ? self::clean($d) : []; } catch (Throwable $e) { return []; } } public static function save(PDO $pdo, int $userId, string $page, array $layout): bool { if ($userId <= 0 || !self::validPage($page)) return false; $json = json_encode(self::clean($layout)); $st = $pdo->prepare('INSERT INTO user_ui_prefs (user_id, page, layout, updated_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE layout = VALUES(layout), updated_at = NOW()'); return $st->execute([$userId, $page, $json]); } public static function reset(PDO $pdo, int $userId, string $page): bool { if ($userId <= 0 || !self::validPage($page)) return false; $st = $pdo->prepare('DELETE FROM user_ui_prefs WHERE user_id = ? AND page = ?'); return $st->execute([$userId, $page]); } /** Attributi HTML per il contenitore dei riquadri: data-layout + data-prefs. */ public static function attr(PDO $pdo, $userId, string $page): string { $p = self::get($pdo, (int)$userId, $page); return 'data-layout="' . htmlspecialchars($page) . '" data-prefs="' . htmlspecialchars(json_encode($p ?: new stdClass()), ENT_QUOTES) . '"'; } private static function clean(array $d): array { $key = function ($k) { return is_string($k) && preg_match('/^[a-z0-9_]{1,40}$/', $k); }; $order = array_values(array_unique(array_filter((array)($d['order'] ?? []), $key))); $hidden = array_values(array_unique(array_filter((array)($d['hidden'] ?? []), $key))); $sizes = []; foreach ((array)($d['sizes'] ?? []) as $k => $v) { if ($key($k) && in_array((int)$v, self::SIZES, true)) $sizes[$k] = (int)$v; } return [ 'order' => array_slice($order, 0, 60), 'hidden' => array_slice($hidden, 0, 60), 'sizes' => $sizes ?: new stdClass(), ]; } }