getMessage()); } jsonResponse(true, [ 'id' => $newUser['id'], 'username' => $newUser['username'], 'email' => $newUser['email'], 'role' => $newUser['role'], ], 'User created'); break; case 'update-user': if (!isAdmin()) { jsonResponse(false, null, 'Forbidden'); } if (!validateCsrfToken()) { jsonResponse(false, null, 'Invalid CSRF token'); } $editUserId = $_POST['user_id'] ?? ''; $editUser = findUserById($editUserId); if (!$editUser) { jsonResponse(false, null, 'User not found'); } $fields = []; if (isset($_POST['username'])) { $uname = trim($_POST['username']); if (strlen($uname) < 3) { jsonResponse(false, null, 'Username must be at least 3 characters'); } $existing = findUserByUsername($uname); if ($existing && $existing['id'] !== $editUserId) { jsonResponse(false, null, 'Username already exists'); } $fields['username'] = $uname; } if (isset($_POST['email'])) { $uemail = trim($_POST['email']); if (!empty($uemail)) { $existing = findUserByEmail($uemail); if ($existing && $existing['id'] !== $editUserId) { jsonResponse(false, null, 'Email already in use'); } } $fields['email'] = $uemail; } if (isset($_POST['role'])) { $newRole = $_POST['role']; if (!in_array($newRole, ['admin', 'editor'])) { jsonResponse(false, null, 'Invalid role'); } // Prevent demoting the last admin if ($editUser['role'] === 'admin' && $newRole === 'editor' && countUsersByRole('admin') <= 1) { jsonResponse(false, null, 'Cannot demote the last admin'); } $fields['role'] = $newRole; } if (!empty($fields)) { if (!updateUser($editUserId, $fields)) jsonResponse(false, null, 'Could not update user'); // Update session if editing self if ($editUserId === ($_SESSION['admin_user_id'] ?? '')) { if (isset($fields['username'])) $_SESSION['admin_username'] = $fields['username']; if (isset($fields['role'])) $_SESSION['admin_role'] = $fields['role']; } } jsonResponse(true, null, 'User updated'); break; case 'delete-user': if (!isAdmin()) { jsonResponse(false, null, 'Forbidden'); } if (!validateCsrfToken()) { jsonResponse(false, null, 'Invalid CSRF token'); } $delUserId = $_POST['user_id'] ?? ''; if ($delUserId === ($_SESSION['admin_user_id'] ?? '')) { jsonResponse(false, null, 'Cannot delete yourself'); } $delUser = findUserById($delUserId); if (!$delUser) { jsonResponse(false, null, 'User not found'); } if ($delUser['role'] === 'admin' && countUsersByRole('admin') <= 1) { jsonResponse(false, null, 'Cannot delete the last admin'); } if (!deleteUser($delUserId)) jsonResponse(false, null, 'Could not delete user'); jsonResponse(true, null, 'User deleted'); break; case 'admin-reset-password': if (!isAdmin()) { jsonResponse(false, null, 'Forbidden'); } if (!validateCsrfToken()) { jsonResponse(false, null, 'Invalid CSRF token'); } $resetUserId = $_POST['user_id'] ?? ''; $resetNewPw = $_POST['password'] ?? ''; $resetUser = findUserById($resetUserId); if (!$resetUser) { jsonResponse(false, null, 'User not found'); } if (empty($resetNewPw)) { jsonResponse(false, null, 'Password is required'); } if (strlen($resetNewPw) < 8 || !preg_match('/[A-Z]/', $resetNewPw) || !preg_match('/[a-z]/', $resetNewPw) || !preg_match('/[0-9]/', $resetNewPw) || !preg_match('/[^A-Za-z0-9]/', $resetNewPw)) { jsonResponse(false, null, 'Password does not meet requirements'); } $resetHash = password_hash($resetNewPw, PASSWORD_DEFAULT); if (!updateUserPassword($resetUserId, $resetHash)) jsonResponse(false, null, 'Could not save password'); jsonResponse(true, null, 'Password reset successfully'); break; // ============================================================ // MENU ORDER // ============================================================ }