false, 'error' => 'Could not upload profile picture.' ], 400); } if (($file['size'] ?? 0) > 10 * 1024 * 1024) { jsonResponse([ 'success' => false, 'error' => 'Profile picture must be 10 MB or smaller.' ], 400); } $tmpName = (string)($file['tmp_name'] ?? ''); $picture = $tmpName !== '' ? file_get_contents($tmpName) : false; if ($picture === false) { jsonResponse([ 'success' => false, 'error' => 'Could not read profile picture.' ], 400); } $info = @getimagesizefromstring($picture); $allowedTypes = [ IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_WEBP, IMAGETYPE_GIF ]; if (!is_array($info) || !in_array($info[2] ?? null, $allowedTypes, true)) { jsonResponse([ 'success' => false, 'error' => 'Profile picture must be a PNG, JPG, WEBP, or GIF image.' ], 400); } return $picture; } function getUserSetting(int $userId, string $settingName): ?string { $stmt = db()->prepare( 'SELECT setting_value FROM user_settings WHERE user_id = ? AND setting_name = ? LIMIT 1' ); $stmt->execute([$userId, $settingName]); $value = $stmt->fetchColumn(); return $value !== false ? (string)$value : null; } function setUserSetting(int $userId, string $settingName, string $settingValue): void { $stmt = db()->prepare( 'SELECT id FROM user_settings WHERE user_id = ? AND setting_name = ? LIMIT 1' ); $stmt->execute([$userId, $settingName]); $settingId = $stmt->fetchColumn(); if ($settingId) { $updateStmt = db()->prepare( 'UPDATE user_settings SET setting_value = ? WHERE id = ?' ); $updateStmt->execute([$settingValue, $settingId]); return; } $insertStmt = db()->prepare( 'INSERT INTO user_settings (user_id, setting_name, setting_value) VALUES (?, ?, ?)' ); $insertStmt->execute([$userId, $settingName, $settingValue]); } $user = requireApiAuth(); $api = $_GET['api'] ?? ''; switch ($api) { case 'ListUsers': { $stmt = db()->query( 'SELECT id, name, email, picture FROM users ORDER BY name ASC, email ASC' ); $users = array_map(static function (array $targetUser): array { $targetUser['picture'] = pictureDataUri($targetUser['picture']); $targetUser['id'] = (int)$targetUser['id']; return $targetUser; }, $stmt->fetchAll()); jsonResponse([ 'success' => true, 'users' => $users ]); } case 'UserInfo': { $userId = (int)($_GET['user_id'] ?? 0); if ($userId <= 0) { jsonResponse([ 'success' => false, 'error' => 'Missing or invalid user_id.' ], 400); } $stmt = db()->prepare( 'SELECT id, name, email, picture FROM users WHERE id = ? LIMIT 1' ); $stmt->execute([$userId]); $targetUser = $stmt->fetch(); if (!$targetUser) { jsonResponse([ 'success' => false, 'error' => 'User not found.' ], 404); } $targetUser['picture'] = pictureDataUri($targetUser['picture']); $targetUser['settings'] = [ 'theme' => getUserSetting((int)$targetUser['id'], 'theme') ?? 'dark' ]; jsonResponse([ 'success' => true, 'user' => $targetUser ]); } case 'Edit': { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse([ 'success' => false, 'error' => 'Edit requires POST.' ], 405); } $userId = (int)($_GET['user_id'] ?? 0); if ($userId <= 0) { jsonResponse([ 'success' => false, 'error' => 'Missing or invalid user_id.' ], 400); } if ($userId !== (int)$user['id']) { jsonResponse([ 'success' => false, 'error' => 'You can only edit your own user.' ], 403); } $data = getInputData(); $fields = []; $values = []; if (array_key_exists('name', $data)) { $name = trim((string)$data['name']); if ($name === '') { jsonResponse([ 'success' => false, 'error' => 'Name cannot be empty.' ], 400); } if (strlen($name) > 128) { jsonResponse([ 'success' => false, 'error' => 'Name is too long.' ], 400); } $fields[] = 'name = ?'; $values[] = $name; } if (array_key_exists('email', $data)) { $email = strtolower(trim((string)$data['email'])); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { jsonResponse([ 'success' => false, 'error' => 'Invalid email.' ], 400); } if (strlen($email) > 128) { jsonResponse([ 'success' => false, 'error' => 'Email is too long.' ], 400); } $emailStmt = db()->prepare( 'SELECT id FROM users WHERE email = ? AND id <> ? LIMIT 1' ); $emailStmt->execute([$email, $userId]); if ($emailStmt->fetch()) { jsonResponse([ 'success' => false, 'error' => 'Email is already in use.' ], 400); } $fields[] = 'email = ?'; $values[] = $email; } if (array_key_exists('password', $data)) { $password = (string)$data['password']; if (strlen($password) < 8) { jsonResponse([ 'success' => false, 'error' => 'Password must be at least 8 characters.' ], 400); } $fields[] = 'passwd = ?'; $values[] = password_hash($password, PASSWORD_ARGON2ID); } $removePicture = ($data['remove_picture'] ?? '') === '1'; $picture = $removePicture ? null : uploadedPicture(); if ($picture !== null) { $fields[] = 'picture = ?'; $values[] = $picture; } if ($removePicture) { $fields[] = 'picture = NULL'; } $theme = null; if (array_key_exists('theme', $data)) { $theme = strtolower(trim((string)$data['theme'])); if (!in_array($theme, VALID_THEMES, true)) { jsonResponse([ 'success' => false, 'error' => 'Theme must be valid.' ], 400); } } if (empty($fields) && $theme === null) { jsonResponse([ 'success' => false, 'error' => 'No editable fields provided.' ], 400); } try { if (!empty($fields)) { $values[] = $userId; $stmt = db()->prepare( 'UPDATE users SET ' . implode(', ', $fields) . ' WHERE id = ?' ); $stmt->execute($values); } if ($theme !== null) { setUserSetting($userId, 'theme', $theme); } jsonResponse([ 'success' => true, 'user_id' => $userId ]); } catch (PDOException $e) { jsonResponse([ 'success' => false, 'error' => 'Database error.' ], 500); } } default: { jsonResponse([ 'success' => false, 'error' => 'Unknown API action.' ], 404); } }