- Configurable tier-based Auto Play with a saved user toggle.
Admin tier assignment without payment via Users > Edit > Tier Override. Fixed live group membership refresh, polling, unread indicators, and incoming voice clip population. Added asset cache busting so clients receive the fixes immediately.
This commit is contained in:
@@ -350,12 +350,14 @@ if ($isLoggedIn && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = trim($_POST['username']);
|
||||
$color = trim($_POST['color']);
|
||||
$newpass = trim($_POST['new_password']);
|
||||
$manualPlanId = max(0, (int)($_POST['manual_plan_id'] ?? 0));
|
||||
$errors = [];
|
||||
|
||||
if ($username === '') $errors[] = 'Username required.';
|
||||
if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $username)) $errors[] = 'Username: letters, numbers, _ and - only.';
|
||||
if (strlen($username) > (int)cfgVal('chat.max_username_length', 12)) $errors[] = 'Username too long.';
|
||||
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) $errors[] = 'Invalid color hex.';
|
||||
if ($manualPlanId > 0 && !planById($manualPlanId)) $errors[] = 'Invalid tier override.';
|
||||
|
||||
if ($errors) { flash(implode(' ', $errors), 'err'); redirect('tab=users'); }
|
||||
|
||||
@@ -366,7 +368,8 @@ if ($isLoggedIn && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
flash('Username already taken.', 'err'); redirect('tab=users');
|
||||
}
|
||||
|
||||
db()->prepare("UPDATE users SET username = ?, color = ? WHERE id = ?")->execute([$username, $color, $id]);
|
||||
db()->prepare("UPDATE users SET username = ?, color = ?, manual_plan_id = ? WHERE id = ?")
|
||||
->execute([$username, $color, $manualPlanId ?: null, $id]);
|
||||
// Also update username in messages (denormalised)
|
||||
db()->prepare("UPDATE messages SET username = ?, color = ? WHERE user_id = ?")->execute([$username, $color, $id]);
|
||||
|
||||
@@ -374,7 +377,7 @@ if ($isLoggedIn && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$hash = password_hash($newpass, PASSWORD_BCRYPT, ['cost' => (int)cfgVal('security.bcrypt_cost', 10)]);
|
||||
db()->prepare("UPDATE users SET password_hash = ? WHERE id = ?")->execute([$hash, $id]);
|
||||
}
|
||||
flash("User #$id updated.");
|
||||
flash("User #$id updated, including tier assignment.");
|
||||
redirect('tab=users');
|
||||
}
|
||||
|
||||
@@ -1502,10 +1505,13 @@ td.actions{white-space:nowrap;width:1px}
|
||||
|
||||
<?php
|
||||
$allUsers = db()->query("
|
||||
SELECT u.*,
|
||||
SELECT u.*,p.name AS billing_plan_name,mp.name AS manual_plan_name,
|
||||
(SELECT COUNT(*) FROM messages m WHERE m.user_id = u.id) as msg_count,
|
||||
(SELECT COUNT(*) FROM sessions s WHERE s.user_id = u.id) as sess_count
|
||||
FROM users u ORDER BY u.created_at DESC
|
||||
FROM users u
|
||||
LEFT JOIN plans p ON p.id=u.plan_id
|
||||
LEFT JOIN plans mp ON mp.id=u.manual_plan_id
|
||||
ORDER BY u.created_at DESC
|
||||
")->fetchAll();
|
||||
?>
|
||||
|
||||
@@ -1520,7 +1526,7 @@ td.actions{white-space:nowrap;width:1px}
|
||||
<div class="tbl-wrap">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>ID</th><th>Username</th><th>Color</th><th>Messages</th>
|
||||
<th>ID</th><th>Username</th><th>Tier</th><th>Color</th><th>Messages</th>
|
||||
<th>Sessions</th><th>Last Seen</th><th>Joined</th><th>Actions</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
@@ -1530,6 +1536,12 @@ td.actions{white-space:nowrap;width:1px}
|
||||
<td style="color:<?= esc($u['color']) ?>;font-family:var(--mono);font-weight:600">
|
||||
<?= esc($u['username']) ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?= $u['manual_plan_id'] ? 'badge-o' : 'badge-b' ?>">
|
||||
<?= esc($u['manual_plan_name'] ?: $u['billing_plan_name'] ?: 'Free') ?>
|
||||
</span>
|
||||
<div class="dim"><?= $u['manual_plan_id'] ? 'ADMIN' : 'BILLING' ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:6px">
|
||||
<span style="background:<?= esc($u['color']) ?>;width:14px;height:14px;border-radius:50%;display:inline-block;flex-shrink:0"></span>
|
||||
@@ -1543,7 +1555,7 @@ td.actions{white-space:nowrap;width:1px}
|
||||
<td class="actions">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-g"
|
||||
onclick="openEditUser(<?= $u['id'] ?>,'<?= esc(addslashes($u['username'])) ?>','<?= esc($u['color']) ?>')">
|
||||
onclick='openEditUser(<?= json_encode((int)$u['id']) ?>,<?= json_encode($u['username']) ?>,<?= json_encode($u['color']) ?>,<?= json_encode((int)($u['manual_plan_id'] ?? 0)) ?>)'>
|
||||
EDIT
|
||||
</button>
|
||||
<form method="post" style="display:inline">
|
||||
@@ -1780,6 +1792,16 @@ td.actions{white-space:nowrap;width:1px}
|
||||
<label>New Password <small style="color:var(--t2);text-transform:none">(leave blank to keep current)</small></label>
|
||||
<input type="password" name="new_password" autocomplete="new-password" placeholder="leave blank to keep…">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Tier Override</label>
|
||||
<select name="manual_plan_id" id="edit-user-plan">
|
||||
<option value="0">Follow Stripe / billing status</option>
|
||||
<?php foreach (plans(false) as $plan): ?>
|
||||
<option value="<?= (int)$plan['id'] ?>"><?= esc($plan['name']) ?> (admin assigned)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="dim" style="margin-top:5px">An admin tier grants its features without payment and takes precedence over Stripe.</div>
|
||||
</div>
|
||||
<div class="alert warn" style="font-size:10px;margin-top:4px;margin-bottom:0">
|
||||
⚠ Changing username also updates it in all their messages.
|
||||
</div>
|
||||
@@ -1818,11 +1840,12 @@ function openEditMsg(id, src, text, username, rqs) {
|
||||
}
|
||||
|
||||
// ── Edit user ──────────────────────────────────────────────────────────────
|
||||
function openEditUser(id, username, color) {
|
||||
function openEditUser(id, username, color, manualPlanId) {
|
||||
document.getElementById('edit-user-id').value = id;
|
||||
document.getElementById('edit-user-name').value = username;
|
||||
document.getElementById('edit-color-txt').value = color;
|
||||
document.getElementById('edit-color-pick').value = color;
|
||||
document.getElementById('edit-user-plan').value = String(manualPlanId || 0);
|
||||
updateSwatch('edit-color-swatch', color);
|
||||
openModal('modal-edit-user');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user