$type, 'message' => $message, ]; } function pull_flashes(): array { $flashes = $_SESSION['_flash'] ?? []; unset($_SESSION['_flash']); return is_array($flashes) ? $flashes : []; } function csrf_token(): string { if (empty($_SESSION['_csrf'])) { $_SESSION['_csrf'] = bin2hex(random_bytes(32)); } return (string) $_SESSION['_csrf']; } function csrf_field(): string { return ''; } function verify_csrf(?string $token): bool { if (!is_string($token) || $token === '') { return false; } return hash_equals(csrf_token(), $token); } function is_active_path(string $path): bool { return request_path() === $path; } function format_points(float $value): string { $rounded = round($value, 1); if (abs($rounded - round($rounded)) < 0.05) { return (string) (int) round($rounded); } return number_format($rounded, 1, ',', '.'); } function normalize_username(string $username): string { return strtolower(trim($username)); } function today(): string { return date('Y-m-d'); } function decode_json_file(string $path, array $fallback = []): array { if (!is_file($path)) { return $fallback; } $decoded = json_decode((string) file_get_contents($path), true); return is_array($decoded) ? $decoded : $fallback; } function encode_payload(array $payload): string { return base64_encode((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } function shift_date(string $date, int $days): string { $current = DateTimeImmutable::createFromFormat('Y-m-d', $date); if ($current === false) { return today(); } return $current->modify(($days >= 0 ? '+' : '') . $days . ' day')->format('Y-m-d'); } function format_display_date(string $date, bool $withWeekday = true): string { $current = DateTimeImmutable::createFromFormat('Y-m-d', $date); if ($current === false) { return $date; } $weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; $months = [ 1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April', 5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember', ]; $label = $current->format('j.') . ' ' . $months[(int) $current->format('n')] . ' ' . $current->format('Y'); if (!$withWeekday) { return $label; } return $weekdays[(int) $current->format('w')] . ', ' . $label; } function icon_path(string $name): string { return '/assets/icons/' . $name . '.svg'; } function mood_icon_path(string $sentiment): string { return icon_path('mood-' . $sentiment); } function request_is_secure(): bool { $forwardedProto = strtolower((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')); $forwardedSsl = strtolower((string) ($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '')); return ( (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $forwardedProto === 'https' || $forwardedSsl === 'on' ); } function remember_me_lifetime(): int { return 60 * 60 * 24 * 30; } function session_cookie_params_for(int $lifetime = 0): array { return [ 'lifetime' => $lifetime, 'path' => '/', 'domain' => '', 'secure' => request_is_secure(), 'httponly' => true, 'samesite' => 'Lax', ]; } function session_cookie_options_for(int $expires = 0): array { return [ 'expires' => $expires, 'path' => '/', 'domain' => '', 'secure' => request_is_secure(), 'httponly' => true, 'samesite' => 'Lax', ]; } function sport_icon_path(string $icon): string { return icon_path('sport-' . $icon); } function sport_icon_options(): array { return [ 'strength' => 'Krafttraining', 'bike' => 'Radfahren', 'run' => 'Joggen', 'hike' => 'Wandern', 'swim' => 'Schwimmen', 'yoga' => 'Yoga', 'hiit' => 'HIIT / Workout', 'row' => 'Rudergerät', 'dance' => 'Tanzen', 'core' => 'Core', 'strength-home' => 'Krafttraining Zuhause', 'strength-gym' => 'Krafttraining Auswärts', ]; } function normalize_sport_type_id(string $value): string { $value = trim(strtr($value, [ 'Ä' => 'ae', 'Ö' => 'oe', 'Ü' => 'ue', 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss', ])); $value = strtolower($value); $value = strtr($value, [ '--' => '-', ]); $value = preg_replace('/[^a-z0-9]+/u', '-', $value) ?? ''; return trim($value, '-'); } function normalized_sport_types(array $settings): array { $types = $settings['sport_types'] ?? []; $normalized = []; $usedIds = []; $iconOptions = sport_icon_options(); foreach ($types as $index => $type) { if (!is_array($type)) { continue; } $label = trim((string) ($type['label'] ?? '')); if ($label === '') { continue; } $candidateId = trim((string) ($type['id'] ?? '')); if ($candidateId === '') { $candidateId = normalize_sport_type_id($label); } if ($candidateId === '') { $candidateId = 'sportart'; } $id = $candidateId; $suffix = 2; while (isset($usedIds[$id])) { $id = $candidateId . '-' . $suffix; $suffix++; } $usedIds[$id] = true; $icon = trim((string) ($type['icon'] ?? 'run')); if (!array_key_exists($icon, $iconOptions)) { $icon = 'run'; } $group = trim((string) ($type['recovery_group'] ?? '')); if ($group === '') { $group = $id; } $normalized[] = [ 'id' => $id, 'label' => $label, 'icon' => $icon, 'recovery_group' => normalize_sport_type_id($group) ?: $id, 'bonus_points' => max(0, min(20, (int) ($type['bonus_points'] ?? 0))), 'allow_consecutive' => !empty($type['allow_consecutive']), ]; } return $normalized; } function normalize_sport_type_selection(mixed $value): array { if (is_string($value)) { $value = trim($value); if ($value === '') { return []; } if (str_contains($value, ',')) { $value = array_map('trim', explode(',', $value)); } else { $value = [$value]; } } if (!is_array($value)) { return []; } $normalized = []; foreach ($value as $item) { if (!is_string($item)) { continue; } $id = trim($item); if ($id === '' || isset($normalized[$id])) { continue; } $normalized[$id] = true; } return array_keys($normalized); } function find_sport_type(array $settings, ?string $id): ?array { if (!is_string($id) || trim($id) === '') { return null; } foreach (normalized_sport_types($settings) as $type) { if (($type['id'] ?? '') === $id) { return $type; } } return null; } function find_sport_types(array $settings, array $ids): array { $types = []; foreach (normalize_sport_type_selection($ids) as $id) { $type = find_sport_type($settings, $id); if ($type !== null) { $types[] = $type; } } return $types; }