$type, 'message' => $message];
header("Location: {$url}");
exit;
};
if (!admin_validate_csrf('admin_users', $token)) {
$setFlashAndRedirect('error', 'Invalid session token. Please try again.', $redirectUrl);
}
if ($userId <= 0 || !admin_user_exists($pdo, $userId)) {
$setFlashAndRedirect('error', 'The selected user could not be found.', $redirectUrl);
}
$adminId = $_SESSION['ADMIN_ID'] ?? ($_SESSION['admin_id'] ?? null);
try {
switch ($action) {
case 'assign_role': {
$roleName = trim($_POST['role_name'] ?? '');
if ($roleName === '' || !isset($roleLookup[$roleName])) {
throw new InvalidArgumentException('Please select a valid role to assign.');
}
if (in_array($roleName, ['guest'], true)) {
throw new InvalidArgumentException('The selected role cannot be assigned manually.');
}
$existingRoles = admin_get_user_roles($pdo, $userId);
if (in_array($roleName, $existingRoles, true)) {
$message = sprintf('User already has the %s role.', $roleName);
} else {
$result = $rbac->assignRole($userId, $roleName, $adminId, 'Assigned via admin panel');
if (!$result) {
throw new RuntimeException('Failed to assign role. Please try again.');
}
$message = sprintf('Role %s assigned successfully.', $roleName);
}
$setFlashAndRedirect('success', $message, $redirectUrl);
break;
}
case 'remove_role': {
$roleName = trim($_POST['role_name'] ?? '');
if ($roleName === '' || !isset($roleLookup[$roleName])) {
throw new InvalidArgumentException('Please select a valid role to remove.');
}
if (in_array($roleName, ['guest', 'member', 'super_admin'], true)) {
throw new InvalidArgumentException('The selected role cannot be removed from this interface.');
}
$existingRoles = admin_get_user_roles($pdo, $userId);
if (!in_array($roleName, $existingRoles, true)) {
$message = sprintf('Role %s was already removed.', $roleName);
} else {
$result = $rbac->removeRole($userId, $roleName, $adminId, 'Removed via admin panel');
if (!$result) {
throw new RuntimeException('Failed to remove role. Please try again.');
}
$message = sprintf('Role %s removed successfully.', $roleName);
}
$setFlashAndRedirect('success', $message, $redirectUrl);
break;
}
case 'set_active': {
$activeValue = isset($_POST['active_value']) && (int) $_POST['active_value'] === 1;
if (!admin_set_user_active($pdo, $userId, $activeValue)) {
throw new RuntimeException('Unable to update user status. Please try again.');
}
$message = $activeValue ? 'User account activated.' : 'User account marked inactive.';
$setFlashAndRedirect('success', $message, $redirectUrl);
break;
}
case 'enable_streaming': {
$result = $rbac->assignRole($userId, VRBAC::ROLE_STREAMER, $adminId, 'Streaming access enabled via admin panel');
if (!$result) {
throw new RuntimeException('Unable to grant streaming access. Verify the user roles and try again.');
}
$setFlashAndRedirect('success', 'Streaming access enabled and stream key prepared.', $redirectUrl);
break;
}
case 'disable_streaming': {
$result = $rbac->removeRole($userId, VRBAC::ROLE_STREAMER, $adminId, 'Streaming access revoked via admin panel');
if (!$result) {
throw new RuntimeException('Unable to revoke streaming access. Verify the current roles and try again.');
}
$setFlashAndRedirect('success', 'Streaming access revoked for the user.', $redirectUrl);
break;
}
case 'regenerate_stream_key': {
VStreamKeyManager::getInstance()->regenerateStreamKey($userId);
$setFlashAndRedirect('success', 'Stream key regenerated successfully.', $redirectUrl);
break;
}
default:
throw new InvalidArgumentException('Unknown action requested.');
}
} catch (Throwable $e) {
$setFlashAndRedirect('error', $e->getMessage(), $redirectUrl);
}
}
$filters = [
'q' => trim($_GET['q'] ?? ''),
'status' => $_GET['status'] ?? '',
'role' => $_GET['role'] ?? '',
'verified' => $_GET['verified'] ?? '',
'streaming' => $_GET['streaming'] ?? '',
];
$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;
$userStats = admin_fetch_user_stats($pdo);
$recentUsers = admin_fetch_recent_users($pdo, 20);
$roleDistribution = admin_fetch_role_distribution($pdo);
$userGrowth = admin_fetch_user_growth($pdo, 14);
$topCreators = admin_fetch_top_creators($pdo, 12);
$userDirectory = admin_search_users($pdo, $filters, $perPage, $offset);
$totalUsersMatching = admin_count_users($pdo, $filters);
$totalPages = max(1, (int) ceil($totalUsersMatching / $perPage));
$assignableRoles = array_filter(
$availableRoles,
static fn(array $role): bool => !in_array($role['role_name'], ['guest', 'super_admin'], true)
);
$csrfToken = admin_csrf_token('admin_users');
admin_page_start('User Operations Center', 'users');
?>
= admin_escape($flash['message']) ?>
👥
Total Users
= admin_format_number($userStats['total']) ?>
= admin_format_number($userStats['today']) ?> joined today
= admin_format_number($userStats['active']) ?> active
✅
Verified Accounts
= admin_format_number($userStats['verified']) ?>
= admin_format_number($userStats['verified'] / max($userStats['total'], 1) * 100, 1) ?>% verification rate
🎯
Retention Focus
= admin_format_number($userStats['active'] - $userStats['today']) ?>
Active beyond first day
No users matched the current filters. Adjust your filters and try again.
| User |
Roles |
Streaming |
Engagement |
Account Controls |
|
#= admin_format_number($user['usr_id']) ?> · = admin_escape($user['usr_user']) ?>
Verified
Inactive
Email: = admin_escape($user['usr_email']) ?>
Joined: = admin_format_datetime($user['usr_joindate']) ?>
Last login: = admin_format_datetime($user['usr_lastlogin'], 'M j, Y H:i') ?>
|
No active roles
= admin_escape($roleName) ?>
|
Status: = $streamEnabled ? 'Enabled' : 'Disabled' ?>
Role gated: = $hasStreamerRole ? 'yes' : 'no' ?>
Streams: = $activeStreams ?>/= $totalStreams ?>
Key: = $streamEnabled && $currentStreamKey !== '' ? admin_escape($keyPreview) : '—' ?>
Updated: = admin_format_datetime($user['stream_key_regenerated_at'], 'M j, Y H:i') ?>
|
Token balance: = admin_format_number($user['usr_tokencount'] ?? 0, 2) ?>
Total logins: = admin_format_number($user['usr_logins'] ?? 0) ?>
|
|
1): ?>
No role data available yet.
| Role |
Total Users |
Share |
0 ? ($total / $userStats['total']) * 100 : 0;
?>
| = admin_escape($row['role'] ?: 'undefined') ?> |
= admin_format_number($total) ?> |
= admin_format_number($share, 1) ?>% |
Insufficient data to chart growth.
| Date |
New Accounts |
| = admin_escape($row['date']) ?> |
= admin_format_number((int) ($row['total'] ?? 0)) ?> |
No recent registrations found.
| User |
Email |
Joined |
Status |
Tokens |
| #= admin_escape((string) $recent['usr_id']) ?> · = admin_escape($recent['usr_user']) ?> |
= admin_escape($recent['usr_email']) ?> |
= admin_format_datetime($recent['usr_joindate']) ?> |
= $recent['usr_active'] ? 'Active' : 'Inactive' ?>
Verified
|
= admin_format_number($recent['token_balance'], 2) ?> |
No creator metrics ready yet.
| User |
Videos |
Tokens Earned |
Token Balance |
| #= admin_escape((string) ($creator['usr_id'] ?? $creator['user_id'] ?? '')) ?> · = admin_escape($creator['usr_user'] ?? ($creator['username'] ?? 'unknown')) ?> |
= admin_format_number($creator['usr_v_count'] ?? 0) ?> |
= admin_format_number($creator['total_tokens_earned'] ?? 0, 2) ?> |
= admin_format_number($creator['token_balance'] ?? 0, 2) ?> |