= 2 && (($value[0] === '"' && $value[-1] === '"') || ($value[0] === "'" && $value[-1] === "'")) ) { $value = substr($value, 1, -1); } $_ENV[$key] = $value; putenv($key . '=' . $value); } } function env_value(string $key, ?string $default = null): string { $value = $_ENV[$key] ?? getenv($key); if ($value === false || $value === null || $value === '') { if ($default !== null) return $default; throw new RuntimeException('Missing required environment value: ' . $key); } return (string)$value; } function env_bool(string $key, bool $default = false): bool { $value = strtolower(env_value($key, $default ? 'true' : 'false')); return in_array($value, ['1', 'true', 'yes', 'on'], true); } function env_list(string $key): array { $value = env_value($key, ''); return array_values(array_filter(array_map('trim', explode(',', $value)), fn($v) => $v !== '')); } load_env_file(__DIR__ . '/.env'); define('DEV_MODE', env_bool('DEV_MODE')); define('CLIENT_ID', env_value('CLIENT_ID')); define('REDIRECT_URI', env_value('REDIRECT_URI')); define('AUTHORIZE_URI', env_value('AUTHORIZE_URI')); define('TOKEN_URI', env_value('TOKEN_URI')); define('GRAPHQL_URI', env_value('GRAPHQL_URI')); define('ADMIN_USER_IDS', env_list('ADMIN_USER_IDS')); function session_start_safe(): void { if (session_status() === PHP_SESSION_NONE) { session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', 'secure' => false, // set to true in production (HTTPS) 'httponly' => true, 'samesite' => 'Lax', ]); session_start(); } } function default_return_path(): string { $script = str_replace('\\', '/', $_SERVER['SCRIPT_NAME'] ?? '/index.php'); $base = rtrim(dirname(dirname($script)), '/'); return ($base === '' ? '' : $base) . '/index.php'; } function safe_return_path(?string $value): string { $value = trim((string)$value); if ($value === '') return default_return_path(); $parts = parse_url($value); if ($parts === false) return default_return_path(); if (isset($parts['host'])) { $currentHost = strtolower(explode(':', $_SERVER['HTTP_HOST'] ?? '')[0]); if (strtolower($parts['host']) !== $currentHost) return default_return_path(); } elseif (str_starts_with($value, '//')) { return default_return_path(); } $path = $parts['path'] ?? ''; if ($path === '') $path = default_return_path(); if ($path[0] !== '/') $path = '/' . ltrim($path, '/'); $query = isset($parts['query']) ? '?' . $parts['query'] : ''; return $path . $query; } function current_return_path(): string { return safe_return_path($_SERVER['REQUEST_URI'] ?? null); } function auth_start_href(?string $returnPath = null): string { return 'auth/start.php?return=' . rawurlencode($returnPath ?? current_return_path()); } function fflogs_graphql_user_query(string $query, string $token): array { $payload = json_encode(['query' => $query]); if ($payload === false) { return ['error' => 'Could not encode GraphQL payload']; } if (function_exists('curl_init')) { $ch = curl_init(GRAPHQL_URI); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $token, ], CURLOPT_SSL_VERIFYPEER => !DEV_MODE, ]); $body = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($err) return ['error' => $err, 'status' => $code]; return ['body' => $body, 'status' => $code]; } $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'content' => $payload, 'header' => implode("\r\n", [ 'Content-Type: application/json', 'Authorization: Bearer ' . $token, ]), 'timeout' => 30, ], 'ssl' => [ 'verify_peer' => !DEV_MODE, 'verify_peer_name' => !DEV_MODE, ], ]); $body = file_get_contents(GRAPHQL_URI, false, $context); if ($body === false) return ['error' => 'GraphQL request failed']; return ['body' => $body, 'status' => 200]; } function fetch_current_fflogs_user(string $token): ?array { $result = fflogs_graphql_user_query('query { userData { currentUser { id name } } }', $token); if (!empty($result['error'])) { $_SESSION['fflogs_user_error'] = $result['error']; return null; } $data = json_decode((string)($result['body'] ?? ''), true); $user = $data['data']['userData']['currentUser'] ?? null; if (!is_array($user) || empty($user['id'])) { $_SESSION['fflogs_user_error'] = $data['errors'][0]['message'] ?? 'Could not read current FFLogs user'; return null; } return [ 'id' => (string)$user['id'], 'name' => (string)($user['name'] ?? ''), ]; } function current_fflogs_user(bool $forceRefresh = false): ?array { if (!$forceRefresh && !empty($_SESSION['fflogs_user']) && is_array($_SESSION['fflogs_user'])) { return $_SESSION['fflogs_user']; } if (empty($_SESSION['access_token']) || (($_SESSION['token_expires'] ?? 0) <= time())) { return null; } $user = fetch_current_fflogs_user($_SESSION['access_token']); if ($user !== null) { $_SESSION['fflogs_user'] = $user; unset($_SESSION['fflogs_user_error']); } return $user; } function is_admin_user(): bool { $user = current_fflogs_user(); return $user !== null && in_array((string)$user['id'], ADMIN_USER_IDS, true); }