Add optional agent user limit set in config.php ($config_agent_user_limit, unset or 0 = unlimited): blocks adding, activating or restoring agent users once the limit is reached, and shows X of Y agent users on the Users page with New User disabled at the limit

This commit is contained in:
johnnyq
2026-10-13 15:34:10 -04:00
parent 714ea86d39
commit 40ca4a6e6c
3 changed files with 84 additions and 4 deletions

View File

@@ -10,6 +10,11 @@ if (isset($_POST['add_user'])) {
validateCSRFToken();
if (agentUserLimitReached()) {
flashAlert(agentUserLimitMessage(), 'error');
redirect();
}
require_once 'user_model.php';
$password = password_hash(trim($_POST['password']), PASSWORD_DEFAULT);
@@ -113,6 +118,7 @@ if (isset($_POST['edit_user'])) {
require_once 'user_model.php';
$user_id = intval($_POST['user_id']);
$new_password = trim($_POST['new_password']);
// Update Client Access (allow / deny per client)
@@ -194,6 +200,13 @@ if (isset($_GET['activate_user'])) {
$user_id = intval($_GET['activate_user']);
// Only a user who isn't already using a seat can push the count over the limit
$seat_row = mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(user_id) FROM users WHERE user_id = $user_id AND user_type = 1 AND user_status = 1 AND user_archived_at IS NULL"));
if (intval($seat_row[0]) === 0 && agentUserLimitReached()) {
flashAlert(agentUserLimitMessage(), 'error');
redirect();
}
$user_name = escapeSql(getFieldById('users', $user_id, 'user_name'));
mysqli_query($mysqli, "UPDATE users SET user_status = 1 WHERE user_id = $user_id");
@@ -303,6 +316,13 @@ if (isset($_POST['restore_user'])) {
validateCSRFToken();
$user_id = intval($_POST['user_id']);
// Only a user who isn't already using a seat can push the count over the limit
$seat_row = mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(user_id) FROM users WHERE user_id = $user_id AND user_type = 1 AND user_status = 1 AND user_archived_at IS NULL"));
if (intval($seat_row[0]) === 0 && agentUserLimitReached()) {
flashAlert(agentUserLimitMessage(), 'error');
redirect();
}
$new_password = trim($_POST['new_password']);
$role = intval($_POST['role']);

View File

@@ -20,16 +20,33 @@ $sql = mysqli_query(
$num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
// Agent user limit (set in config.php, 0 / unset = unlimited)
$agent_user_limit = getAgentUserLimit();
$agent_user_count = getActiveAgentUserCount();
$agent_user_limit_reached = $agent_user_limit > 0 && $agent_user_count >= $agent_user_limit;
?>
<div class="card">
<div class="card-header bg-dark py-2">
<h3 class="card-title mt-2"><i class="fas fa-fw fa-users me-2"></i>Users</h3>
<h3 class="card-title mt-2"><i class="fas fa-fw fa-users me-2"></i>Users
<?php if ($agent_user_limit > 0) { ?>
<span class="badge text-bg-<?= $agent_user_limit_reached ? 'danger' : 'secondary' ?> ms-2" title="Active agent users / agent user limit">
<?= $agent_user_count ?> of <?= $agent_user_limit ?> agent users
</span>
<?php } ?>
</h3>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/user/user_add.php">
<i class="fas fa-fw fa-user-plus me-2"></i>New User
</button>
<?php if ($agent_user_limit_reached) { ?>
<button type="button" class="btn btn-primary" disabled title="Agent user limit reached">
<i class="fas fa-fw fa-user-plus me-2"></i>New User
</button>
<?php } else { ?>
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/user/user_add.php">
<i class="fas fa-fw fa-user-plus me-2"></i>New User
</button>
<?php } ?>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown"></button>
<div class="dropdown-menu">
<!--<a class="dropdown-item text-dark ajax-modal" href="#" data-modal-url="modals/user/user_invite.php"><i class="fas fa-paper-plane me-2"></i>Invite User</a>-->

View File

@@ -914,3 +914,46 @@ function getProductsForAutocomplete($mysqli): string
return json_encode($products) ?: '[]';
}
/*
* Agent user limit
*
* Optional cap on the number of active agent users, set in config.php:
* $config_agent_user_limit = 5;
* Unset or 0 means unlimited, which is the default for every self-hosted install.
* It lives in config.php rather than the settings table on purpose: on a hosted
* instance the provider controls config.php and the customer's admins do not, so
* nobody inside the app can raise their own limit.
*
* An active agent is user_type 1, enabled and not archived. Disabled and archived
* users do not use a seat.
*/
function getAgentUserLimit(): int
{
global $config_agent_user_limit;
return max(0, intval($config_agent_user_limit ?? 0));
}
function getActiveAgentUserCount(): int
{
global $mysqli;
$row = mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(user_id) FROM users WHERE user_type = 1 AND user_status = 1 AND user_archived_at IS NULL"));
return intval($row[0]);
}
function agentUserLimitReached(): bool
{
$limit = getAgentUserLimit();
return $limit > 0 && getActiveAgentUserCount() >= $limit;
}
function agentUserLimitMessage(): string
{
$limit = getAgentUserLimit();
return "Agent user limit reached - this installation allows <strong>$limit</strong> active agent " . ($limit == 1 ? "user" : "users") . ". Archive or disable a user first, or contact your provider to add more.";
}