Files
chat/api/groups.php
T
Ty Clifford 2881a3b338 - Groups are now globally configurable under Admin > Plans & Platform > Group Availability.
When disabled:

Group navigation and channel selector are hidden.
Group API operations are blocked.
Active sessions update automatically.
Existing groups and messages remain preserved.
Static and browser tests passed with no console errors.
2026-06-08 16:21:33 -04:00

110 lines
6.0 KiB
PHP

<?php
define('CYBERCHAT_API', true);
require_once __DIR__ . '/../bootstrap.php';
sendCorsHeaders();
$user = authRequired();
$action = $_POST['action'] ?? $_GET['action'] ?? 'list';
if (!groupsEnabled()) {
if ($action === 'list') {
jsonResponse(['groups' => [], 'member_limit' => 0, 'enabled' => false]);
}
jsonResponse(['error' => 'Private groups are currently disabled', 'groups_enabled' => false], 403);
}
switch ($action) {
case 'list':
jsonResponse([
'groups' => userGroups((int)$user['id']),
'member_limit' => (int)featureValue($user, 'group_member_limit', 2),
'enabled' => true,
]);
case 'create':
$name = trim((string)($_POST['name'] ?? ''));
$names = array_values(array_unique(array_filter(array_map('trim', explode(',', (string)($_POST['members'] ?? ''))))));
if ($name === '' || strlen($name) > 40) jsonResponse(['error' => 'Group name must be 1-40 characters'], 400);
$limit = max(2, (int)featureValue($user, 'group_member_limit', 2));
if (count($names) + 1 > $limit) jsonResponse(['error' => "Your plan allows $limit group members including you"], 403);
$memberIds = [];
$lookup = getDB()->prepare("SELECT id FROM users WHERE username=? COLLATE NOCASE");
foreach ($names as $username) {
if (strcasecmp($username, $user['username']) === 0) continue;
$lookup->execute([$username]);
$id = (int)$lookup->fetchColumn();
if (!$id) jsonResponse(['error' => "User '$username' was not found"], 404);
$memberIds[$id] = $id;
}
if (!$memberIds) jsonResponse(['error' => 'Add at least one other user to create a group'], 400);
if (count($memberIds) + 1 > $limit) jsonResponse(['error' => "Your plan allows $limit group members including you"], 403);
$db = getDB();
$db->beginTransaction();
$db->prepare("INSERT INTO chat_groups (owner_id,name,created_at,updated_at) VALUES (?,?,?,?)")
->execute([$user['id'], $name, time(), time()]);
$groupId = (int)$db->lastInsertId();
$insert = $db->prepare("INSERT INTO group_members (group_id,user_id,role,joined_at) VALUES (?,?,?,?)");
$insert->execute([$groupId, $user['id'], 'owner', time()]);
foreach ($memberIds as $id) $insert->execute([$groupId, $id, 'member', time()]);
$db->commit();
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
case 'add_member':
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
$username = trim((string)($_POST['username'] ?? ''));
$groupStmt = getDB()->prepare("SELECT * FROM chat_groups WHERE id=? AND owner_id=?");
$groupStmt->execute([$groupId, $user['id']]);
$group = $groupStmt->fetch();
if (!$group) jsonResponse(['error' => 'Only the group owner can add members'], 403);
$limit = max(2, (int)featureValue($user, 'group_member_limit', 2));
$countStmt = getDB()->prepare("SELECT COUNT(*) FROM group_members WHERE group_id=?");
$countStmt->execute([$groupId]);
if ((int)$countStmt->fetchColumn() >= $limit) jsonResponse(['error' => "This group has reached your $limit-member limit"], 403);
$lookup = getDB()->prepare("SELECT id FROM users WHERE username=? COLLATE NOCASE");
$lookup->execute([$username]);
$memberId = (int)$lookup->fetchColumn();
if (!$memberId) jsonResponse(['error' => 'User not found'], 404);
getDB()->prepare("INSERT OR IGNORE INTO group_members (group_id,user_id,role,joined_at) VALUES (?,?,'member',?)")
->execute([$groupId, $memberId, time()]);
getDB()->prepare("UPDATE chat_groups SET updated_at=? WHERE id=?")->execute([time(), $groupId]);
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
case 'remove_member':
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
$memberId = (int)($_POST['user_id'] ?? 0);
$stmt = getDB()->prepare("SELECT owner_id FROM chat_groups WHERE id=?");
$stmt->execute([$groupId]);
$ownerId = (int)$stmt->fetchColumn();
if (!$ownerId) jsonResponse(['error' => 'Group not found'], 404);
if ($ownerId !== (int)$user['id'] && $memberId !== (int)$user['id']) jsonResponse(['error' => 'Not allowed'], 403);
if ($memberId === $ownerId) jsonResponse(['error' => 'The owner must delete the group instead'], 400);
getDB()->prepare("DELETE FROM group_members WHERE group_id=? AND user_id=?")->execute([$groupId, $memberId]);
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
case 'delete':
$groupId = normalizeGroupId($_POST['group_id'] ?? null);
$db = getDB();
$owner = $db->prepare("SELECT 1 FROM chat_groups WHERE id=? AND owner_id=?");
$owner->execute([$groupId, $user['id']]);
if (!$owner->fetchColumn()) jsonResponse(['error' => 'Only the group owner can delete this group'], 403);
$voice = $db->prepare("SELECT voice_file FROM messages WHERE group_id=? AND voice_file IS NOT NULL");
$voice->execute([$groupId]);
$voiceFiles = $voice->fetchAll();
$db->beginTransaction();
try {
$db->prepare("DELETE FROM recording_status WHERE group_key=?")->execute([groupKey($groupId)]);
$db->prepare("DELETE FROM messages WHERE group_id=?")->execute([$groupId]);
$db->prepare("DELETE FROM group_members WHERE group_id=?")->execute([$groupId]);
$db->prepare("DELETE FROM chat_groups WHERE id=?")->execute([$groupId]);
$db->commit();
} catch (Throwable $e) {
if ($db->inTransaction()) $db->rollBack();
throw $e;
}
foreach ($voiceFiles as $row) deleteVoiceFile($row['voice_file'] ?? null);
jsonResponse(['success' => true, 'groups' => userGroups((int)$user['id'])]);
default:
jsonResponse(['error' => 'Unknown action'], 400);
}