refactor: replace global WAHA notifications with granular user and group-specific templates and dispatch logic
This commit is contained in:
130
db.php
130
db.php
@@ -29,74 +29,108 @@ function get_user_groups($user_id) {
|
|||||||
return $stmt->fetchAll();
|
return $stmt->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function notify_user($user_id, $message) {
|
function compile_waha_template($template, $context) {
|
||||||
global $pdo;
|
if (!$template) return "";
|
||||||
$stmt = $pdo->prepare("INSERT INTO notifications (user_id, message) VALUES (?, ?)");
|
$action = $context['action'] ?? 'Update';
|
||||||
$stmt->execute([$user_id, $message]);
|
$module = $context['module'] ?? 'System';
|
||||||
|
$item = $context['item'] ?? '';
|
||||||
|
|
||||||
// Virtual hook for Email/Telegram/WhatsApp based on user features
|
$user = $context['user'] ?? null;
|
||||||
$user = get_user($user_id);
|
if (!$user && isset($_SESSION['user_id'])) {
|
||||||
if($user && isset($user['notify_channels'])) {
|
|
||||||
$channels = explode(',', $user['notify_channels']);
|
|
||||||
// Here we would actually send the message to the third party API.
|
|
||||||
// For MVP we just assume it's sent.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify_group($group_id, $message) {
|
|
||||||
global $pdo;
|
|
||||||
$stmt = $pdo->prepare("SELECT user_id FROM user_groups WHERE group_id = ?");
|
|
||||||
$stmt->execute([$group_id]);
|
|
||||||
$users = $stmt->fetchAll();
|
|
||||||
foreach ($users as $u) {
|
|
||||||
notify_user($u['user_id'], $message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify_waha($action, $module, $item_name) {
|
|
||||||
global $pdo;
|
|
||||||
try {
|
|
||||||
// Fetch all users with WhatsApp channel enabled and WAHA configured
|
|
||||||
$stmt = $pdo->query("SELECT id, username, waha_endpoint, waha_chat_id, waha_template, waha_session, waha_api_key FROM users WHERE notify_channels LIKE '%whatsapp%' AND waha_endpoint IS NOT NULL AND waha_endpoint != ''");
|
|
||||||
$wa_users = $stmt->fetchAll();
|
|
||||||
if (empty($wa_users)) return;
|
|
||||||
|
|
||||||
$current_user_name = "System";
|
|
||||||
if (isset($_SESSION['user_id'])) {
|
|
||||||
$u = get_user($_SESSION['user_id']);
|
$u = get_user($_SESSION['user_id']);
|
||||||
if ($u) $current_user_name = $u['username'];
|
$user = $u ? $u['username'] : 'System';
|
||||||
|
} elseif (!$user) {
|
||||||
|
$user = 'System';
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($wa_users as $wu) {
|
$attachment_count = $context['attachment_count'] ?? '0';
|
||||||
$endpoint = $wu['waha_endpoint'];
|
$due_date = $context['due_date'] ?? 'None';
|
||||||
$chat_id = $wu['waha_chat_id'];
|
$created_date = $context['created_date'] ?? date('Y-m-d H:i');
|
||||||
if (!$endpoint || !$chat_id) continue;
|
$updated_date = $context['updated_date'] ?? date('Y-m-d H:i');
|
||||||
|
$assignor = $context['assignor'] ?? 'N/A';
|
||||||
|
$assignee = $context['assignee'] ?? 'N/A';
|
||||||
|
|
||||||
$template = $wu['waha_template'] ?: "*Update:* {action} on {module} '{item}' by {user}";
|
return str_replace(
|
||||||
$text = str_replace(
|
['{action}', '{module}', '{item}', '{user}', '{attachment_count}', '{due_date}', '{created_date}', '{updated_date}', '{assignor}', '{assignee}'],
|
||||||
['{action}', '{module}', '{item}', '{user}'],
|
[$action, $module, $item, $user, $attachment_count, $due_date, $created_date, $updated_date, $assignor, $assignee],
|
||||||
[$action, $module, $item_name, $current_user_name],
|
|
||||||
$template
|
$template
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatch_waha_message($endpoint, $chat_id, $session, $api_key, $text) {
|
||||||
|
try {
|
||||||
$payload = json_encode([
|
$payload = json_encode([
|
||||||
"chatId" => $chat_id,
|
"chatId" => $chat_id,
|
||||||
"text" => $text,
|
"text" => $text,
|
||||||
"session" => $wu['waha_session'] ?: 'default'
|
"session" => $session ?: 'default'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$ch = curl_init($endpoint);
|
$ch = curl_init($endpoint);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||||
$headers = ['Content-Type: application/json'];
|
$headers = ['Content-Type: application/json'];
|
||||||
if (!empty($wu['waha_api_key'])) {
|
if (!empty($api_key)) $headers[] = 'X-Api-Key: ' . $api_key;
|
||||||
$headers[] = 'X-Api-Key: ' . $wu['waha_api_key'];
|
|
||||||
}
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
|
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
|
||||||
curl_exec($ch);
|
curl_exec($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
}
|
|
||||||
} catch(Exception $e) {}
|
} catch(Exception $e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notify_user($user_id, $message, $context = []) {
|
||||||
|
global $pdo;
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO notifications (user_id, message) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$user_id, $message]);
|
||||||
|
|
||||||
|
$user = get_user($user_id);
|
||||||
|
if($user && isset($user['notify_channels'])) {
|
||||||
|
$channels = explode(',', $user['notify_channels']);
|
||||||
|
if (in_array('whatsapp', $channels) && $user['waha_endpoint'] && $user['waha_chat_id']) {
|
||||||
|
$module = $context['module'] ?? '';
|
||||||
|
$template = '';
|
||||||
|
if (stripos($module, 'Note') !== false) $template = $user['waha_template_note'];
|
||||||
|
elseif (stripos($module, 'Password') !== false) $template = $user['waha_template_password'];
|
||||||
|
elseif (stripos($module, 'File') !== false || stripos($module, 'Folder') !== false) $template = $user['waha_template_file'];
|
||||||
|
else $template = $user['waha_template_task'];
|
||||||
|
|
||||||
|
if (!$template) $template = "*Alert:* {action} on {module} '{item}' by {user}";
|
||||||
|
|
||||||
|
$text = compile_waha_template($template, $context);
|
||||||
|
dispatch_waha_message($user['waha_endpoint'], $user['waha_chat_id'], $user['waha_session'], $user['waha_api_key'], $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify_group($group_id, $message, $context = []) {
|
||||||
|
global $pdo;
|
||||||
|
// Notify all individual group members in their UI notifications
|
||||||
|
$stmt = $pdo->prepare("SELECT user_id FROM user_groups WHERE group_id = ?");
|
||||||
|
$stmt->execute([$group_id]);
|
||||||
|
$users = $stmt->fetchAll();
|
||||||
|
foreach ($users as $u) {
|
||||||
|
$s = $pdo->prepare("INSERT INTO notifications (user_id, message) VALUES (?, ?)");
|
||||||
|
$s->execute([$u['user_id'], $message]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the group itself has WAHA configured
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM groups WHERE id = ?");
|
||||||
|
$stmt->execute([$group_id]);
|
||||||
|
$group = $stmt->fetch();
|
||||||
|
|
||||||
|
if($group && isset($group['notify_channels'])) {
|
||||||
|
$channels = explode(',', $group['notify_channels']);
|
||||||
|
if (in_array('whatsapp', $channels) && $group['waha_endpoint'] && $group['waha_chat_id']) {
|
||||||
|
$module = $context['module'] ?? '';
|
||||||
|
$template = '';
|
||||||
|
if (stripos($module, 'Note') !== false) $template = $group['waha_template_note'];
|
||||||
|
elseif (stripos($module, 'Password') !== false) $template = $group['waha_template_password'];
|
||||||
|
elseif (stripos($module, 'File') !== false || stripos($module, 'Folder') !== false) $template = $group['waha_template_file'];
|
||||||
|
else $template = $group['waha_template_task'];
|
||||||
|
|
||||||
|
if (!$template) $template = "*Group Alert:* {action} on {module} '{item}' by {user}";
|
||||||
|
|
||||||
|
$text = compile_waha_template($template, $context);
|
||||||
|
dispatch_waha_message($group['waha_endpoint'], $group['waha_chat_id'], $group['waha_session'], $group['waha_api_key'], $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
22
index.php
22
index.php
@@ -57,8 +57,15 @@ if ($action === 'api') {
|
|||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE tasks SET $set_clause WHERE id = ?");
|
$stmt = $pdo->prepare("UPDATE tasks SET $set_clause WHERE id = ?");
|
||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
$task_to_upd = query("SELECT title FROM tasks WHERE id=?", [$task_id])->fetch();
|
$t = query("SELECT title, assignee_type, assignee_id FROM tasks WHERE id=?", [$task_id])->fetch();
|
||||||
if($task_to_upd) notify_waha('Moved to '.$status, 'Task Status', $task_to_upd['title']);
|
if($t) {
|
||||||
|
$ctx = ['action' => 'Moved to '.$status, 'module' => 'Task', 'item' => $t['title']];
|
||||||
|
if($t['assignee_type'] === 'user' && $t['assignee_id']) {
|
||||||
|
notify_user($t['assignee_id'], "Task '{$t['title']}' moved to $status", $ctx);
|
||||||
|
} elseif($t['assignee_type'] === 'group' && $t['assignee_id']) {
|
||||||
|
notify_group($t['assignee_id'], "Group task '{$t['title']}' moved to $status", $ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
echo json_encode(['status'=>'ok']);
|
echo json_encode(['status'=>'ok']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -71,8 +78,15 @@ if ($action === 'api') {
|
|||||||
if($task_id && $comment) {
|
if($task_id && $comment) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO task_comments (task_id, comment, created_by) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO task_comments (task_id, comment, created_by) VALUES (?, ?, ?)");
|
||||||
$stmt->execute([$task_id, $comment, $user_id]);
|
$stmt->execute([$task_id, $comment, $user_id]);
|
||||||
$t = query("SELECT title FROM tasks WHERE id=?", [$task_id])->fetch();
|
$t = query("SELECT title, assignee_type, assignee_id FROM tasks WHERE id=?", [$task_id])->fetch();
|
||||||
if($t) notify_waha('Commented on', 'Task', $t['title']);
|
if($t) {
|
||||||
|
$ctx = ['action' => 'New Comment', 'module' => 'Task', 'item' => $t['title']];
|
||||||
|
if($t['assignee_type'] === 'user' && $t['assignee_id']) {
|
||||||
|
notify_user($t['assignee_id'], "New comment on '{$t['title']}'", $ctx);
|
||||||
|
} elseif($t['assignee_type'] === 'group' && $t['assignee_id']) {
|
||||||
|
notify_group($t['assignee_id'], "New comment on group task '{$t['title']}'", $ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
echo json_encode(['status'=>'ok']);
|
echo json_encode(['status'=>'ok']);
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
273
views/admin.php
273
views/admin.php
@@ -5,9 +5,23 @@ if(empty($_SESSION['is_admin'])) die("Access Denied");
|
|||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_endpoint TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_endpoint TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_chat_id TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_chat_id TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_session TEXT DEFAULT 'default'"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_session TEXT DEFAULT 'default'"); } catch(Exception $e) {}
|
||||||
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_api_key TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_api_key TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template_task TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template_note TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template_password TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE users ADD COLUMN waha_template_file TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN notify_channels TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_endpoint TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_chat_id TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_api_key TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_session TEXT DEFAULT 'default'"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_template_task TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_template_note TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_template_password TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN waha_template_file TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
|
try { $pdo->exec("ALTER TABLE groups ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||||||
try {
|
try {
|
||||||
$pdo->exec("CREATE TABLE IF NOT EXISTS system_config (k TEXT PRIMARY KEY, v TEXT)");
|
$pdo->exec("CREATE TABLE IF NOT EXISTS system_config (k TEXT PRIMARY KEY, v TEXT)");
|
||||||
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('completed_days', '7')");
|
$pdo->exec("INSERT OR IGNORE INTO system_config (k, v) VALUES ('completed_days', '7')");
|
||||||
@@ -53,11 +67,18 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
if(isset($_POST['create_group'])) {
|
if(isset($_POST['create_group'])) {
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("INSERT INTO groups (name) VALUES (?)");
|
$name = trim($_POST['group_name']);
|
||||||
$stmt->execute([$_POST['group_name']]);
|
$pic_path = null;
|
||||||
$success_msg = "Group '{$_POST['group_name']}' created!";
|
if(isset($_FILES['group_pic']) && $_FILES['group_pic']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$ext = pathinfo($_FILES['group_pic']['name'], PATHINFO_EXTENSION);
|
||||||
|
$pic_path = 'uploads/group_' . time() . '_' . uniqid() . '.' . $ext;
|
||||||
|
move_uploaded_file($_FILES['group_pic']['tmp_name'], $pic_path);
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO groups (name, profile_pic) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$name, $pic_path]);
|
||||||
|
$success_msg = "Group '{$name}' created!";
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
$error_msg = "Group name may already exist.";
|
$error_msg = "Error: Group name may already exist.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,14 +104,59 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$ch_str = is_array($ch) ? implode(',', $ch) : '';
|
$ch_str = is_array($ch) ? implode(',', $ch) : '';
|
||||||
$waha_ep = trim($_POST['waha_endpoint'] ?? '');
|
$waha_ep = trim($_POST['waha_endpoint'] ?? '');
|
||||||
$waha_cid = trim($_POST['waha_chat_id'] ?? '');
|
$waha_cid = trim($_POST['waha_chat_id'] ?? '');
|
||||||
$waha_tpl = trim($_POST['waha_template'] ?? '');
|
|
||||||
$waha_ses = trim($_POST['waha_session'] ?? '') ?: 'default';
|
$waha_ses = trim($_POST['waha_session'] ?? '') ?: 'default';
|
||||||
$waha_key = trim($_POST['waha_api_key'] ?? '');
|
$waha_key = trim($_POST['waha_api_key'] ?? '');
|
||||||
$stmt = $pdo->prepare("UPDATE users SET notify_channels=?, telegram_id=?, email=?, whatsapp_number=?, waha_endpoint=?, waha_chat_id=?, waha_template=?, waha_session=?, waha_api_key=? WHERE id=?");
|
$tpl_task = trim($_POST['waha_template_task'] ?? '');
|
||||||
$stmt->execute([$ch_str, $_POST['telegram_id'], $_POST['email'], $_POST['whatsapp_number'], $waha_ep ?: null, $waha_cid ?: null, $waha_tpl ?: null, $waha_ses, $waha_key ?: null, $_POST['user_id']]);
|
$tpl_note = trim($_POST['waha_template_note'] ?? '');
|
||||||
|
$tpl_pass = trim($_POST['waha_template_password'] ?? '');
|
||||||
|
$tpl_file = trim($_POST['waha_template_file'] ?? '');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("UPDATE users SET notify_channels=?, telegram_id=?, email=?, whatsapp_number=?, waha_endpoint=?, waha_chat_id=?, waha_template_task=?, waha_template_note=?, waha_template_password=?, waha_template_file=?, waha_session=?, waha_api_key=? WHERE id=?");
|
||||||
|
$stmt->execute([$ch_str, $_POST['telegram_id'], $_POST['email'], $_POST['whatsapp_number'], $waha_ep ?: null, $waha_cid ?: null, $tpl_task ?: null, $tpl_note ?: null, $tpl_pass ?: null, $tpl_file ?: null, $waha_ses, $waha_key ?: null, $_POST['user_id']]);
|
||||||
$success_msg = "User notification preferences updated!";
|
$success_msg = "User notification preferences updated!";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isset($_POST['update_group_channels'])) {
|
||||||
|
$ch = $_POST['channels'] ?? [];
|
||||||
|
$ch_str = is_array($ch) ? implode(',', $ch) : '';
|
||||||
|
$waha_ep = trim($_POST['waha_endpoint'] ?? '');
|
||||||
|
$waha_cid = trim($_POST['waha_chat_id'] ?? '');
|
||||||
|
$waha_ses = trim($_POST['waha_session'] ?? '') ?: 'default';
|
||||||
|
$waha_key = trim($_POST['waha_api_key'] ?? '');
|
||||||
|
$tpl_task = trim($_POST['waha_template_task'] ?? '');
|
||||||
|
$tpl_note = trim($_POST['waha_template_note'] ?? '');
|
||||||
|
$tpl_pass = trim($_POST['waha_template_password'] ?? '');
|
||||||
|
$tpl_file = trim($_POST['waha_template_file'] ?? '');
|
||||||
|
$group_id = $_POST['group_id'];
|
||||||
|
$name = trim($_POST['group_name'] ?? '');
|
||||||
|
|
||||||
|
// Handle profile pic update
|
||||||
|
$pic_sql = "";
|
||||||
|
$pic_params = [];
|
||||||
|
if(isset($_POST['remove_pic']) && $_POST['remove_pic'] == '1') {
|
||||||
|
$old = query("SELECT profile_pic FROM groups WHERE id=?", [$group_id])->fetch();
|
||||||
|
if($old && $old['profile_pic'] && file_exists($old['profile_pic'])) @unlink($old['profile_pic']);
|
||||||
|
$pic_sql = ", profile_pic = NULL";
|
||||||
|
} elseif(isset($_FILES['group_pic']) && $_FILES['group_pic']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$old = query("SELECT profile_pic FROM groups WHERE id=?", [$group_id])->fetch();
|
||||||
|
if($old && $old['profile_pic'] && file_exists($old['profile_pic'])) @unlink($old['profile_pic']);
|
||||||
|
$ext = pathinfo($_FILES['group_pic']['name'], PATHINFO_EXTENSION);
|
||||||
|
$new_path = 'uploads/group_' . time() . '_' . uniqid() . '.' . $ext;
|
||||||
|
move_uploaded_file($_FILES['group_pic']['tmp_name'], $new_path);
|
||||||
|
$pic_sql = ", profile_pic = ?";
|
||||||
|
$pic_params[] = $new_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE groups SET notify_channels=?, waha_endpoint=?, waha_chat_id=?, waha_template_task=?, waha_template_note=?, waha_template_password=?, waha_template_file=?, waha_session=?, waha_api_key=?, name=? $pic_sql WHERE id=?";
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute(array_merge([$ch_str, $waha_ep ?: null, $waha_cid ?: null, $tpl_task ?: null, $tpl_note ?: null, $tpl_pass ?: null, $tpl_file ?: null, $waha_ses, $waha_key ?: null, $name], $pic_params, [$group_id]));
|
||||||
|
$success_msg = "Group settings updated!";
|
||||||
|
} catch(Exception $e) {
|
||||||
|
$error_msg = "Error: Group name may already exist.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($_POST['update_sys_config'])) {
|
if(isset($_POST['update_sys_config'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE system_config SET v=? WHERE k=?");
|
$stmt = $pdo->prepare("UPDATE system_config SET v=? WHERE k=?");
|
||||||
$stmt->execute([$_POST['completed_days'] ?? 7, 'completed_days']);
|
$stmt->execute([$_POST['completed_days'] ?? 7, 'completed_days']);
|
||||||
@@ -100,7 +166,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Fetch Data ────────────────────────────────────────────────────────────────
|
// ── Fetch Data ────────────────────────────────────────────────────────────────
|
||||||
$users = query("SELECT id, username, is_admin, notify_channels, telegram_id, email, whatsapp_number, profile_pic, waha_endpoint, waha_chat_id, waha_template, waha_session, waha_api_key FROM users ORDER BY is_admin DESC, username ASC")->fetchAll();
|
$users = query("SELECT id, username, is_admin, notify_channels, telegram_id, email, whatsapp_number, profile_pic, waha_endpoint, waha_chat_id, waha_template_task, waha_template_note, waha_template_password, waha_template_file, waha_session, waha_api_key FROM users ORDER BY is_admin DESC, username ASC")->fetchAll();
|
||||||
$all_groups = query("SELECT * FROM groups ORDER BY name")->fetchAll();
|
$all_groups = query("SELECT * FROM groups ORDER BY name")->fetchAll();
|
||||||
$conf_comp = query("SELECT v FROM system_config WHERE k='completed_days'")->fetchColumn() ?: 7;
|
$conf_comp = query("SELECT v FROM system_config WHERE k='completed_days'")->fetchColumn() ?: 7;
|
||||||
$conf_arch = query("SELECT v FROM system_config WHERE k='archived_days'")->fetchColumn() ?: 90;
|
$conf_arch = query("SELECT v FROM system_config WHERE k='archived_days'")->fetchColumn() ?: 90;
|
||||||
@@ -292,10 +358,17 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
|||||||
<!-- Create Group + Assign -->
|
<!-- Create Group + Assign -->
|
||||||
<div>
|
<div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 style="font-size:1rem; font-weight:700; margin-bottom:1rem;"><i class="fa fa-folder-plus"></i> Create Group</h3>
|
<h3 style="font-size:1rem; font-weight:700; margin-bottom:1.25rem;"><i class="fa fa-folder-plus"></i> Create Group</h3>
|
||||||
<form method="POST" style="display:flex; gap:0.5rem;">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
<input type="text" name="group_name" class="form-control" placeholder="Group name" required>
|
<div class="form-group">
|
||||||
<button type="submit" name="create_group" class="btn btn-sm"><i class="fa fa-plus"></i> Create</button>
|
<label>Group Name</label>
|
||||||
|
<input type="text" name="group_name" class="form-control" placeholder="Enter group name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Profile Picture</label>
|
||||||
|
<input type="file" name="group_pic" class="form-control" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="create_group" class="btn" style="width:100%"><i class="fa fa-plus"></i> Create Group</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -333,18 +406,51 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
|||||||
$g_users = $stmt->fetchAll();
|
$g_users = $stmt->fetchAll();
|
||||||
?>
|
?>
|
||||||
<div class="card" style="margin-bottom:0;">
|
<div class="card" style="margin-bottom:0;">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.75rem;">
|
<form method="POST" enctype="multipart/form-data">
|
||||||
<div>
|
<input type="hidden" name="update_group_channels" value="1">
|
||||||
<div style="font-weight:700; font-size:1rem; color:var(--primary);">
|
<input type="hidden" name="group_id" value="<?= $g['id'] ?>">
|
||||||
<i class="fa fa-people-group"></i> <?= htmlspecialchars($g['name']) ?>
|
<input type="hidden" name="group_name" value="<?= htmlspecialchars($g['name']) ?>">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:0.75rem;">
|
||||||
|
<div style="display:flex; align-items:center; gap:0.75rem;">
|
||||||
|
<div class="admin-user-avatar" style="width:42px; height:42px; font-size:1rem;">
|
||||||
|
<?php if($g['profile_pic'] && file_exists($g['profile_pic'])): ?>
|
||||||
|
<img src="<?= htmlspecialchars($g['profile_pic']) ?>" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">
|
||||||
|
<?php else: ?>
|
||||||
|
<?= strtoupper(mb_substr($g['name'],0,1)) ?>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" name="group_name" value="<?= htmlspecialchars($g['name']) ?>" class="form-control" style="font-weight:700; font-size:1rem; color:var(--primary); background:transparent; border:none; padding:0; height:auto; width:100%;" onchange="this.form.submit()">
|
||||||
<div style="font-size:0.75rem; color:var(--text-light);"><?= count($g_users) ?> member<?= count($g_users)!=1?'s':'' ?></div>
|
<div style="font-size:0.75rem; color:var(--text-light);"><?= count($g_users) ?> member<?= count($g_users)!=1?'s':'' ?></div>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" onsubmit="event.preventDefault(); appConfirm('Delete group <?= htmlspecialchars(addslashes($g['name'])) ?>?').then(y => { if(y) { this.onsubmit=null; this.submit(); } })">
|
|
||||||
<input type="hidden" name="del_group_id" value="<?= $g['id'] ?>">
|
|
||||||
<button type="submit" name="delete_group" class="btn btn-sm btn-danger"><i class="fa fa-trash"></i></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display:flex; gap:0.4rem;">
|
||||||
|
<label class="btn btn-sm btn-secondary" style="cursor:pointer;" title="Change Picture">
|
||||||
|
<i class="fa fa-camera"></i>
|
||||||
|
<input type="file" name="group_pic" style="display:none;" onchange="this.form.submit()">
|
||||||
|
</label>
|
||||||
|
<?php if($g['profile_pic']): ?>
|
||||||
|
<button type="submit" name="remove_pic" value="1" class="btn btn-sm btn-secondary" title="Remove Picture" onclick="return confirm('Remove profile picture?')"><i class="fa fa-image-slash"></i></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" onclick="appConfirm('Delete group <?= htmlspecialchars(addslashes($g['name'])) ?>?').then(y => { if(y) { document.getElementById('del-group-<?= $g['id'] ?>').submit(); } })"><i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Hidden inputs for templates to preserve them when updating from here -->
|
||||||
|
<div style="display:none;">
|
||||||
|
<input type="text" name="waha_endpoint" value="<?= htmlspecialchars($g['waha_endpoint']??'') ?>">
|
||||||
|
<input type="text" name="waha_chat_id" value="<?= htmlspecialchars($g['waha_chat_id']??'') ?>">
|
||||||
|
<input type="text" name="waha_session" value="<?= htmlspecialchars($g['waha_session']??'default') ?>">
|
||||||
|
<input type="text" name="waha_api_key" value="<?= htmlspecialchars($g['waha_api_key']??'') ?>">
|
||||||
|
<textarea name="waha_template_task"><?= htmlspecialchars($g['waha_template_task']??'') ?></textarea>
|
||||||
|
<textarea name="waha_template_note"><?= htmlspecialchars($g['waha_template_note']??'') ?></textarea>
|
||||||
|
<textarea name="waha_template_password"><?= htmlspecialchars($g['waha_template_password']??'') ?></textarea>
|
||||||
|
<textarea name="waha_template_file"><?= htmlspecialchars($g['waha_template_file']??'') ?></textarea>
|
||||||
|
<?php $channels = explode(',', $g['notify_channels']??''); foreach($channels as $c): if(!$c) continue; ?>
|
||||||
|
<input type="checkbox" name="channels[]" value="<?= $c ?>" checked>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form method="POST" id="del-group-<?= $g['id'] ?>" style="display:none;"><input type="hidden" name="del_group_id" value="<?= $g['id'] ?>"><input type="hidden" name="delete_group" value="1"></form>
|
||||||
<?php if($g_users): ?>
|
<?php if($g_users): ?>
|
||||||
<div style="display:flex; flex-wrap:wrap; gap:0.5rem;">
|
<div style="display:flex; flex-wrap:wrap; gap:0.5rem;">
|
||||||
<?php foreach($g_users as $gu): ?>
|
<?php foreach($g_users as $gu): ?>
|
||||||
@@ -456,10 +562,27 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
|||||||
<input type="password" name="waha_api_key" value="<?= htmlspecialchars($u['waha_api_key']??'') ?>" class="form-control" style="padding:0.35rem; font-family:monospace;" placeholder="Leave blank if not required">
|
<input type="password" name="waha_api_key" value="<?= htmlspecialchars($u['waha_api_key']??'') ?>" class="form-control" style="padding:0.35rem; font-family:monospace;" placeholder="Leave blank if not required">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-top:0.6rem;">
|
<div style="margin-top:0.6rem;">
|
||||||
<label style="font-size:0.75rem;">Message Template</label>
|
<label style="font-size:0.75rem;">Message Templates</label>
|
||||||
<textarea name="waha_template" class="form-control" rows="2" style="padding:0.35rem; font-size:0.8rem;" placeholder="*Alert:* {action} on {module} '{item}' by {user}"><?= htmlspecialchars($u['waha_template']??'') ?></textarea>
|
<div style="font-size:0.72rem; color:var(--text-light); margin-bottom:8px;">Variables: <code>{action}</code> <code>{module}</code> <code>{item}</code> <code>{user}</code> <code>{attachment_count}</code> <code>{due_date}</code> <code>{created_date}</code> <code>{updated_date}</code> <code>{assignor}</code> <code>{assignee}</code></div>
|
||||||
<div style="font-size:0.72rem; color:var(--text-light); margin-top:3px;">Variables: <code>{action}</code> <code>{module}</code> <code>{item}</code> <code>{user}</code> <code>{attachment_count}</code> <code>{due_date}</code> <code>{created_date}</code> <code>{updated_date}</code> <code>{assignor}</code> <code>{assignee}</code></div>
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Task</label>
|
||||||
|
<textarea name="waha_template_task" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Task Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($u['waha_template_task']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Note</label>
|
||||||
|
<textarea name="waha_template_note" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Note Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($u['waha_template_note']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Password</label>
|
||||||
|
<textarea name="waha_template_password" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Password Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($u['waha_template_password']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">File/Folder</label>
|
||||||
|
<textarea name="waha_template_file" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*File Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($u['waha_template_file']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -467,6 +590,96 @@ $active_tab = $_GET['tab'] ?? 'overview';
|
|||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Group Notification Channels -->
|
||||||
|
<div class="card" style="margin-top:2rem;">
|
||||||
|
<h3 style="font-size:1rem; font-weight:700; margin-bottom:0.5rem;"><i class="fa fa-users"></i> Group Notification Channels</h3>
|
||||||
|
<p style="font-size:0.85rem; color:var(--text-light); margin-bottom:1.25rem;">Configure notification channels for an entire group. WAHA settings appear when <strong>WhatsApp</strong> is enabled.</p>
|
||||||
|
|
||||||
|
<?php foreach($all_groups as $g):
|
||||||
|
$chs = explode(',', $g['notify_channels'] ?? '');
|
||||||
|
$gid = $g['id'];
|
||||||
|
$hasWA = in_array('whatsapp', $chs);
|
||||||
|
?>
|
||||||
|
<div class="card" style="margin-bottom:1rem; padding:1rem; border:1px solid var(--border);">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="update_group_channels" value="1">
|
||||||
|
<input type="hidden" name="group_id" value="<?= $gid ?>">
|
||||||
|
<input type="hidden" name="group_name" value="<?= htmlspecialchars($g['name']) ?>">
|
||||||
|
|
||||||
|
<!-- Group header row -->
|
||||||
|
<div style="display:flex; align-items:center; gap:0.75rem; flex-wrap:wrap;">
|
||||||
|
<div style="width:36px;height:36px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:0.85rem;font-weight:700;flex-shrink:0;overflow:hidden;">
|
||||||
|
<?php if(!empty($g['profile_pic']) && file_exists($g['profile_pic'])): ?>
|
||||||
|
<img src="<?= htmlspecialchars($g['profile_pic']) ?>" style="width:100%;height:100%;object-fit:cover;">
|
||||||
|
<?php else: ?>
|
||||||
|
<?= strtoupper(mb_substr($g['name'],0,1)) ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;">
|
||||||
|
<div style="font-weight:700; font-size:0.95rem;"><?= htmlspecialchars($g['name']) ?></div>
|
||||||
|
<div><span style="font-size:0.7rem; color:var(--text-light);">Group</span></div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" name="update_group_channels" class="btn btn-sm"><i class="fa fa-save"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Channel checkboxes -->
|
||||||
|
<div style="display:flex; gap:1rem; flex-wrap:wrap; margin-top:0.85rem; padding-top:0.75rem; border-top:1px dashed var(--border);">
|
||||||
|
<label style="display:flex; align-items:center; gap:5px; font-size:0.85rem; cursor:pointer; font-weight:500;">
|
||||||
|
<input type="checkbox" name="channels[]" value="whatsapp" <?= $hasWA?'checked':'' ?> onchange="toggleWAHAGroup(<?= $gid ?>, this.checked)">
|
||||||
|
📱 WhatsApp
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- WAHA expandable section -->
|
||||||
|
<div id="waha-group-section-<?= $gid ?>" style="<?= $hasWA ? '' : 'display:none;' ?> margin-top:0.75rem; padding:0.85rem; background:rgba(37,211,102,0.06); border:1px solid rgba(37,211,102,0.25); border-radius:8px;">
|
||||||
|
<div style="font-size:0.78rem; font-weight:700; color:#25D366; margin-bottom:0.65rem;"><i class="fab fa-whatsapp"></i> WAHA WhatsApp API Settings</div>
|
||||||
|
<div style="display:flex; gap:1rem; flex-wrap:wrap; align-items:flex-end;">
|
||||||
|
<div class="form-group" style="margin:0; flex:2; min-width:200px;">
|
||||||
|
<label style="font-size:0.75rem;">WAHA Endpoint URL</label>
|
||||||
|
<input type="text" name="waha_endpoint" value="<?= htmlspecialchars($g['waha_endpoint']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="http://localhost:3000/api/sendText">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0; flex:1; min-width:120px;">
|
||||||
|
<label style="font-size:0.75rem;">Session Name</label>
|
||||||
|
<input type="text" name="waha_session" value="<?= htmlspecialchars($g['waha_session'] ?? 'default') ?>" class="form-control" style="padding:0.35rem;" placeholder="default">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0; flex:1; min-width:160px;">
|
||||||
|
<label style="font-size:0.75rem;">Chat ID</label>
|
||||||
|
<input type="text" name="waha_chat_id" value="<?= htmlspecialchars($g['waha_chat_id']??'') ?>" class="form-control" style="padding:0.35rem;" placeholder="12345678@g.us">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0; flex:1; min-width:160px;">
|
||||||
|
<label style="font-size:0.75rem;">API Key <span style="font-weight:400; color:var(--text-light);">(X-Api-Key)</span></label>
|
||||||
|
<input type="password" name="waha_api_key" value="<?= htmlspecialchars($g['waha_api_key']??'') ?>" class="form-control" style="padding:0.35rem; font-family:monospace;" placeholder="Leave blank if not required">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top:0.6rem;">
|
||||||
|
<label style="font-size:0.75rem;">Message Templates</label>
|
||||||
|
<div style="font-size:0.72rem; color:var(--text-light); margin-bottom:8px;">Variables: <code>{action}</code> <code>{module}</code> <code>{item}</code> <code>{user}</code> <code>{attachment_count}</code> <code>{due_date}</code> <code>{created_date}</code> <code>{updated_date}</code> <code>{assignor}</code> <code>{assignee}</code></div>
|
||||||
|
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Task</label>
|
||||||
|
<textarea name="waha_template_task" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Task Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($g['waha_template_task']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Note</label>
|
||||||
|
<textarea name="waha_template_note" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Note Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($g['waha_template_note']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">Password</label>
|
||||||
|
<textarea name="waha_template_password" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*Password Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($g['waha_template_password']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin:0;">
|
||||||
|
<label style="font-size:0.7rem; color:var(--text-light);">File/Folder</label>
|
||||||
|
<textarea name="waha_template_file" class="form-control" rows="2" style="padding:0.35rem; font-size:0.75rem;" placeholder="*File Alert:* {action} on '{item}' by {user}"><?= htmlspecialchars($g['waha_template_file']??'') ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── System Config Tab ─────────────────────────────────────────────────── -->
|
<!-- ── System Config Tab ─────────────────────────────────────────────────── -->
|
||||||
@@ -527,6 +740,16 @@ function toggleWAHA(uid, enabled) {
|
|||||||
section.style.display = 'none';
|
section.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function toggleWAHAGroup(gid, enabled) {
|
||||||
|
const section = document.getElementById('waha-group-section-' + gid);
|
||||||
|
if (!section) return;
|
||||||
|
if (enabled) {
|
||||||
|
section.style.display = 'block';
|
||||||
|
section.style.animation = 'fadeIn 0.25s ease';
|
||||||
|
} else {
|
||||||
|
section.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@keyframes fadeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:none; } }
|
@keyframes fadeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:none; } }
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO notes (title, content, owner_id, color, image_path, label_name) VALUES (?, ?, ?, ?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO notes (title, content, owner_id, color, image_path, label_name) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
$stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $user_id, $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null]);
|
$stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $user_id, $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null]);
|
||||||
notify_waha('Created', 'Note', $_POST['title'] ?? 'Untitled');
|
// Note creation only notifies owner in UI, but we can trigger a WAHA self-alert if they have it enabled
|
||||||
|
notify_user($user_id, "You created a new note: " . ($_POST['title'] ?? 'Untitled'), ['action'=>'Created', 'module'=>'Note', 'item'=>$_POST['title']??'Untitled']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_POST['update_note'])) {
|
if(isset($_POST['update_note'])) {
|
||||||
@@ -68,7 +69,16 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("UPDATE notes SET title=?, content=?, color=?, image_path=?, label_name=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND owner_id=?");
|
$stmt = $pdo->prepare("UPDATE notes SET title=?, content=?, color=?, image_path=?, label_name=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND owner_id=?");
|
||||||
$stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null, $_POST['note_id'], $user_id]);
|
$stmt->execute([$_POST['title'] ?? 'Untitled', $_POST['content'], $_POST['color'] ?? null, $image_path, trim($_POST['label_name'] ?? '') ?: null, $_POST['note_id'], $user_id]);
|
||||||
notify_waha('Updated', 'Note', $_POST['title'] ?? 'Untitled');
|
|
||||||
|
$ctx = ['action'=>'Updated', 'module'=>'Note', 'item'=>$_POST['title']??'Untitled'];
|
||||||
|
// Notify owner
|
||||||
|
notify_user($user_id, "Note '{$_POST['title']}' updated.", $ctx);
|
||||||
|
// Notify anyone this note is shared with
|
||||||
|
$shares = query("SELECT share_type, share_id FROM note_shares WHERE note_id=?", [$_POST['note_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared note '{$_POST['title']}' updated.", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group note '{$_POST['title']}' updated.", $ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +86,15 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$n = query("SELECT owner_id, image_path, title FROM notes WHERE id=?", [$_POST['note_id']])->fetch();
|
$n = query("SELECT owner_id, image_path, title FROM notes WHERE id=?", [$_POST['note_id']])->fetch();
|
||||||
if($n && $n['owner_id'] == $user_id) {
|
if($n && $n['owner_id'] == $user_id) {
|
||||||
if($n['image_path'] && file_exists($n['image_path'])) @unlink($n['image_path']);
|
if($n['image_path'] && file_exists($n['image_path'])) @unlink($n['image_path']);
|
||||||
|
$ctx = ['action'=>'Deleted', 'module'=>'Note', 'item'=>$n['title']??'Untitled'];
|
||||||
|
// Notify anyone it was shared with before deleting
|
||||||
|
$shares = query("SELECT share_type, share_id FROM note_shares WHERE note_id=?", [$_POST['note_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared note '{$n['title']}' has been deleted.", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group note '{$n['title']}' has been deleted.", $ctx);
|
||||||
|
}
|
||||||
query("DELETE FROM notes WHERE id=? AND owner_id=?", [$_POST['note_id'], $user_id]);
|
query("DELETE FROM notes WHERE id=? AND owner_id=?", [$_POST['note_id'], $user_id]);
|
||||||
notify_waha('Deleted', 'Note', $n['title'] ?? 'Untitled');
|
notify_user($user_id, "Note '{$n['title']}' has been deleted.", $ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +107,9 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$stmt->execute([$n_id, $parts[0], $parts[1]]);
|
$stmt->execute([$n_id, $parts[0], $parts[1]]);
|
||||||
$n = query("SELECT title FROM notes WHERE id=?", [$n_id])->fetch();
|
$n = query("SELECT title FROM notes WHERE id=?", [$n_id])->fetch();
|
||||||
$msg = "A note '{$n['title']}' has been shared with you.";
|
$msg = "A note '{$n['title']}' has been shared with you.";
|
||||||
if($parts[0] === 'user') notify_user($parts[1], $msg);
|
$ctx = ['action'=>'Shared', 'module'=>'Note', 'item'=>$n['title']??'Untitled', 'user'=>$_SESSION['username']??'System'];
|
||||||
else notify_group($parts[1], $msg);
|
if($parts[0] === 'user') notify_user($parts[1], $msg, $ctx);
|
||||||
notify_waha('Shared', 'Note', $n['title'] ?? 'Untitled');
|
else notify_group($parts[1], $msg, $ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +415,7 @@ $note_colors = [
|
|||||||
$preview = mb_substr($preview, 0, 120) . (mb_strlen($preview) > 120 ? '...' : '');
|
$preview = mb_substr($preview, 0, 120) . (mb_strlen($preview) > 120 ? '...' : '');
|
||||||
$note_shares = [];
|
$note_shares = [];
|
||||||
if($n['is_mine']) {
|
if($n['is_mine']) {
|
||||||
$note_shares = query("SELECT ns.id, ns.share_type, ns.share_id, u.username, g.name as gname FROM note_shares ns LEFT JOIN users u ON (ns.share_type='user' AND ns.share_id=u.id) LEFT JOIN groups g ON (ns.share_type='group' AND ns.share_id=g.id) WHERE ns.note_id=?", [$n['id']])->fetchAll();
|
$note_shares = query("SELECT ns.id, ns.share_type, ns.share_id, u.username, u.profile_pic as upic, g.name as gname, g.profile_pic as gpic FROM note_shares ns LEFT JOIN users u ON (ns.share_type='user' AND ns.share_id=u.id) LEFT JOIN groups g ON (ns.share_type='group' AND ns.share_id=g.id) WHERE ns.note_id=?", [$n['id']])->fetchAll();
|
||||||
}
|
}
|
||||||
$shares_json = htmlspecialchars(json_encode($note_shares), ENT_QUOTES, 'UTF-8');
|
$shares_json = htmlspecialchars(json_encode($note_shares), ENT_QUOTES, 'UTF-8');
|
||||||
?>
|
?>
|
||||||
@@ -623,17 +640,28 @@ function renderNoteShares(jsonShares) {
|
|||||||
try { shares = JSON.parse(jsonShares || '[]'); } catch(e) { shares = []; }
|
try { shares = JSON.parse(jsonShares || '[]'); } catch(e) { shares = []; }
|
||||||
if(shares.length === 0) return;
|
if(shares.length === 0) return;
|
||||||
shares.forEach(sh => {
|
shares.forEach(sh => {
|
||||||
const label = sh.share_type === 'user' ? ('👤 ' + (sh.username || 'User')) : ('👥 ' + (sh.gname || 'Group'));
|
const pic = sh.share_type === 'user' ? sh.upic : sh.gpic;
|
||||||
|
const name = sh.share_type === 'user' ? (sh.username || 'User') : (sh.gname || 'Group');
|
||||||
|
const icon = sh.share_type === 'user' ? '👤' : '👥';
|
||||||
|
|
||||||
const chip = document.createElement('div');
|
const chip = document.createElement('div');
|
||||||
|
chip.className = 'share-chip';
|
||||||
chip.style.display = 'inline-flex';
|
chip.style.display = 'inline-flex';
|
||||||
chip.style.alignItems = 'center';
|
chip.style.alignItems = 'center';
|
||||||
chip.style.gap = '0.35rem';
|
chip.style.gap = '0.4rem';
|
||||||
chip.style.fontSize = '0.72rem';
|
chip.style.fontSize = '0.72rem';
|
||||||
chip.style.padding = '3px 7px';
|
chip.style.padding = '2px 8px 2px 2px';
|
||||||
chip.style.borderRadius = '999px';
|
chip.style.borderRadius = '999px';
|
||||||
chip.style.background = 'rgba(var(--primary-rgb),0.12)';
|
chip.style.background = 'rgba(var(--primary-rgb),0.1)';
|
||||||
chip.style.color = 'var(--primary)';
|
chip.style.color = 'var(--primary)';
|
||||||
chip.innerHTML = `<span>${label}</span><button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;" title="Unshare" onclick="removeNoteShare(${Number(sh.id)})">×</button>`;
|
chip.style.border = '1px solid rgba(var(--primary-rgb),0.2)';
|
||||||
|
|
||||||
|
let avatarHtml = `<div style="width:20px; height:20px; border-radius:50%; background:var(--primary); color:white; display:flex; align-items:center; justify-content:center; font-size:0.6rem; font-weight:700; overflow:hidden;">${name.charAt(0).toUpperCase()}</div>`;
|
||||||
|
if(pic) {
|
||||||
|
avatarHtml = `<img src="${pic}" style="width:20px; height:20px; border-radius:50%; object-fit:cover;">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip.innerHTML = `${avatarHtml}<span style="font-weight:600;">${name}</span><button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;margin-left:2px;font-size:0.9rem;" title="Unshare" onclick="removeNoteShare(${Number(sh.id)})">×</button>`;
|
||||||
list.appendChild(chip);
|
list.appendChild(chip);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
if(isset($_POST['create_password'])) {
|
if(isset($_POST['create_password'])) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO passwords (title, username, password, url, notes, owner_id, folder_id, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO passwords (title, username, password, url, notes, owner_id, folder_id, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
$stmt->execute([$_POST['title'], $_POST['username'], $_POST['password'], $_POST['url'], $_POST['notes'], $user_id, $_POST['folder_id'] ?: null, trim($_POST['tags'] ?? '')]);
|
$stmt->execute([$_POST['title'], $_POST['username'], $_POST['password'], $_POST['url'], $_POST['notes'], $user_id, $_POST['folder_id'] ?: null, trim($_POST['tags'] ?? '')]);
|
||||||
notify_waha('Created', 'Password Entry', $_POST['title']);
|
notify_user($user_id, "You added a new password: " . $_POST['title'], ['action'=>'Created', 'module'=>'Password', 'item'=>$_POST['title']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_POST['update_password'])) {
|
if(isset($_POST['update_password'])) {
|
||||||
@@ -36,16 +36,29 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
if($p && $p['owner_id'] == $user_id) {
|
if($p && $p['owner_id'] == $user_id) {
|
||||||
$stmt = $pdo->prepare("UPDATE passwords SET title=?, username=?, password=?, url=?, notes=?, folder_id=?, tags=? WHERE id=? AND owner_id=?");
|
$stmt = $pdo->prepare("UPDATE passwords SET title=?, username=?, password=?, url=?, notes=?, folder_id=?, tags=? WHERE id=? AND owner_id=?");
|
||||||
$stmt->execute([$_POST['title'], $_POST['username'], $_POST['password'], $_POST['url'], $_POST['notes'], $_POST['folder_id'] ?: null, trim($_POST['tags'] ?? ''), $_POST['password_id'], $user_id]);
|
$stmt->execute([$_POST['title'], $_POST['username'], $_POST['password'], $_POST['url'], $_POST['notes'], $_POST['folder_id'] ?: null, trim($_POST['tags'] ?? ''), $_POST['password_id'], $user_id]);
|
||||||
notify_waha('Updated', 'Password Entry', $_POST['title']);
|
|
||||||
|
$ctx = ['action'=>'Updated', 'module'=>'Password', 'item'=>$_POST['title']];
|
||||||
|
notify_user($user_id, "Password '{$_POST['title']}' updated.", $ctx);
|
||||||
|
// Notify shared users/groups
|
||||||
|
$shares = query("SELECT share_type, share_id FROM password_shares WHERE password_id=?", [$_POST['password_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared password '{$_POST['title']}' updated.", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group password '{$_POST['title']}' updated.", $ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_POST['delete_password'])) {
|
if(isset($_POST['delete_password'])) {
|
||||||
$p = query("SELECT owner_id, title FROM passwords WHERE id=?", [$_POST['password_id']])->fetch();
|
$p = query("SELECT owner_id, title FROM passwords WHERE id=?", [$_POST['password_id']])->fetch();
|
||||||
if($p && $p['owner_id'] == $user_id) {
|
$ctx = ['action'=>'Deleted', 'module'=>'Password', 'item'=>$p['title']];
|
||||||
query("DELETE FROM passwords WHERE id=? AND owner_id=?", [$_POST['password_id'], $user_id]);
|
// Notify shared recipients before deletion
|
||||||
notify_waha('Deleted', 'Password Entry', $p['title']);
|
$shares = query("SELECT share_type, share_id FROM password_shares WHERE password_id=?", [$_POST['password_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared password '{$p['title']}' has been deleted.", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group password '{$p['title']}' has been deleted.", $ctx);
|
||||||
}
|
}
|
||||||
|
query("DELETE FROM passwords WHERE id=? AND owner_id=?", [$_POST['password_id'], $user_id]);
|
||||||
|
notify_user($user_id, "Password '{$p['title']}' deleted.", $ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unshare a specific share record
|
// Unshare a specific share record
|
||||||
@@ -76,9 +89,9 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$stmt = $pdo->prepare("INSERT INTO password_shares (password_id, share_type, share_id) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO password_shares (password_id, share_type, share_id) VALUES (?, ?, ?)");
|
||||||
$stmt->execute([$p_id, $parts[0], $parts[1]]);
|
$stmt->execute([$p_id, $parts[0], $parts[1]]);
|
||||||
$msg = "A password '{$p['title']}' has been shared with you.";
|
$msg = "A password '{$p['title']}' has been shared with you.";
|
||||||
if($parts[0] === 'user') notify_user($parts[1], $msg);
|
$ctx = ['action'=>'Shared', 'module'=>'Password', 'item'=>$p['title'], 'user'=>$_SESSION['username']??'System'];
|
||||||
else notify_group($parts[1], $msg);
|
if($parts[0] === 'user') notify_user($parts[1], $msg, $ctx);
|
||||||
notify_waha('Shared', 'Password Entry', $p['title']);
|
else notify_group($parts[1], $msg, $ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,7 +378,7 @@ $active_folder = $_GET['vault_folder'] ?? 'all';
|
|||||||
$favicon = $domain ? "https://www.google.com/s2/favicons?domain={$domain}&sz=32" : '';
|
$favicon = $domain ? "https://www.google.com/s2/favicons?domain={$domain}&sz=32" : '';
|
||||||
$pw_shares = [];
|
$pw_shares = [];
|
||||||
if($p['is_mine']) {
|
if($p['is_mine']) {
|
||||||
$pw_shares = query("SELECT ps.id, ps.share_type, ps.share_id, u.username, g.name as gname FROM password_shares ps LEFT JOIN users u ON (ps.share_type='user' AND ps.share_id=u.id) LEFT JOIN groups g ON (ps.share_type='group' AND ps.share_id=g.id) WHERE ps.password_id=?", [$p['id']])->fetchAll();
|
$pw_shares = query("SELECT ps.id, ps.share_type, ps.share_id, u.username, u.profile_pic as upic, g.name as gname, g.profile_pic as gpic FROM password_shares ps LEFT JOIN users u ON (ps.share_type='user' AND ps.share_id=u.id) LEFT JOIN groups g ON (ps.share_type='group' AND ps.share_id=g.id) WHERE ps.password_id=?", [$p['id']])->fetchAll();
|
||||||
}
|
}
|
||||||
$shares_json = htmlspecialchars(json_encode($pw_shares), ENT_QUOTES, 'UTF-8');
|
$shares_json = htmlspecialchars(json_encode($pw_shares), ENT_QUOTES, 'UTF-8');
|
||||||
?>
|
?>
|
||||||
@@ -589,17 +602,26 @@ function renderPasswordShares(jsonShares) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
shares.forEach(sh => {
|
shares.forEach(sh => {
|
||||||
const label = sh.share_type === 'user' ? ('👤 ' + (sh.username || 'User')) : ('👥 ' + (sh.gname || 'Group'));
|
const pic = sh.share_type === 'user' ? sh.upic : sh.gpic;
|
||||||
|
const name = sh.share_type === 'user' ? (sh.username || 'User') : (sh.gname || 'Group');
|
||||||
|
|
||||||
const chip = document.createElement('div');
|
const chip = document.createElement('div');
|
||||||
chip.style.display = 'inline-flex';
|
chip.style.display = 'inline-flex';
|
||||||
chip.style.alignItems = 'center';
|
chip.style.alignItems = 'center';
|
||||||
chip.style.gap = '0.35rem';
|
chip.style.gap = '0.4rem';
|
||||||
chip.style.fontSize = '0.72rem';
|
chip.style.fontSize = '0.72rem';
|
||||||
chip.style.padding = '3px 7px';
|
chip.style.padding = '2px 8px 2px 2px';
|
||||||
chip.style.borderRadius = '999px';
|
chip.style.borderRadius = '999px';
|
||||||
chip.style.background = 'rgba(var(--primary-rgb),0.12)';
|
chip.style.background = 'rgba(var(--primary-rgb),0.1)';
|
||||||
chip.style.color = 'var(--primary)';
|
chip.style.color = 'var(--primary)';
|
||||||
chip.innerHTML = `<span>${label}</span><button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;" title="Unshare" onclick="removePasswordShare(${Number(sh.id)})">×</button>`;
|
chip.style.border = '1px solid rgba(var(--primary-rgb),0.2)';
|
||||||
|
|
||||||
|
let avatarHtml = `<div style="width:20px; height:20px; border-radius:50%; background:var(--primary); color:white; display:flex; align-items:center; justify-content:center; font-size:0.6rem; font-weight:700; overflow:hidden;">${name.charAt(0).toUpperCase()}</div>`;
|
||||||
|
if(pic) {
|
||||||
|
avatarHtml = `<img src="${pic}" style="width:20px; height:20px; border-radius:50%; object-fit:cover;">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip.innerHTML = `${avatarHtml}<span style="font-weight:600;">${name}</span><button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;margin-left:2px;font-size:0.9rem;" title="Unshare" onclick="removePasswordShare(${Number(sh.id)})">×</button>`;
|
||||||
list.appendChild(chip);
|
list.appendChild(chip);
|
||||||
});
|
});
|
||||||
sec.style.display = 'block';
|
sec.style.display = 'block';
|
||||||
|
|||||||
@@ -4,20 +4,35 @@ $parent_id = $_GET['folder'] ?? null;
|
|||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_folder'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_folder'])) {
|
||||||
$stmt = $pdo->prepare("INSERT INTO projects (name, is_folder, parent_id, owner_id) VALUES (?, 1, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO projects (name, is_folder, parent_id, owner_id) VALUES (?, 1, ?, ?)");
|
||||||
$stmt->execute([$_POST['name'], $parent_id, $user_id]);
|
$stmt->execute([$_POST['name'], $parent_id, $user_id]);
|
||||||
notify_waha('Created', 'Folder', $_POST['name']);
|
notify_user($user_id, "New folder created: {$_POST['name']}", ['action'=>'Created', 'module'=>'Folder', 'item'=>$_POST['name']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_item'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_item'])) {
|
||||||
$item = query("SELECT name FROM projects WHERE id=?", [$_POST['item_id']])->fetch();
|
$item = query("SELECT name FROM projects WHERE id=?", [$_POST['item_id']])->fetch();
|
||||||
|
$ctx = ['action'=>'Deleted', 'module'=>'File/Folder', 'item'=>$item['name']??'Item'];
|
||||||
|
// Notify shared recipients before deletion
|
||||||
|
$shares = query("SELECT share_type, share_id FROM project_shares WHERE project_id=?", [$_POST['item_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared item '{$item['name']}' deleted.", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group item '{$item['name']}' deleted.", $ctx);
|
||||||
|
}
|
||||||
$stmt = $pdo->prepare("DELETE FROM projects WHERE id=? AND owner_id=?");
|
$stmt = $pdo->prepare("DELETE FROM projects WHERE id=? AND owner_id=?");
|
||||||
$stmt->execute([$_POST['item_id'], $user_id]);
|
$stmt->execute([$_POST['item_id'], $user_id]);
|
||||||
if($item) notify_waha('Deleted', 'File/Folder', $item['name']);
|
notify_user($user_id, "Item deleted: " . ($item['name']??'Item'), $ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_item'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_item'])) {
|
||||||
$stmt = $pdo->prepare("UPDATE projects SET name=? WHERE id=? AND owner_id=?");
|
$stmt = $pdo->prepare("UPDATE projects SET name=? WHERE id=? AND owner_id=?");
|
||||||
$stmt->execute([$_POST['new_name'], $_POST['item_id'], $user_id]);
|
$stmt->execute([$_POST['new_name'], $_POST['item_id'], $user_id]);
|
||||||
notify_waha('Renamed', 'File/Folder', $_POST['new_name']);
|
|
||||||
|
$ctx = ['action'=>'Renamed', 'module'=>'File/Folder', 'item'=>$_POST['new_name']];
|
||||||
|
notify_user($user_id, "Item renamed to '{$_POST['new_name']}'", $ctx);
|
||||||
|
// Notify shared
|
||||||
|
$shares = query("SELECT share_type, share_id FROM project_shares WHERE project_id=?", [$_POST['item_id']])->fetchAll();
|
||||||
|
foreach($shares as $sh) {
|
||||||
|
if($sh['share_type'] === 'user') notify_user($sh['share_id'], "Shared item renamed to '{$_POST['new_name']}'", $ctx);
|
||||||
|
else notify_group($sh['share_id'], "Shared group item renamed to '{$_POST['new_name']}'", $ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_file']) && isset($_FILES['file'])) {
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_file']) && isset($_FILES['file'])) {
|
||||||
@@ -28,7 +43,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_file']) && isse
|
|||||||
move_uploaded_file($tmp_name, $path);
|
move_uploaded_file($tmp_name, $path);
|
||||||
$stmt = $pdo->prepare("INSERT INTO projects (name, is_folder, parent_id, file_path, owner_id) VALUES (?, 0, ?, ?, ?)");
|
$stmt = $pdo->prepare("INSERT INTO projects (name, is_folder, parent_id, file_path, owner_id) VALUES (?, 0, ?, ?, ?)");
|
||||||
$stmt->execute([$name, $parent_id, $path, $user_id]);
|
$stmt->execute([$name, $parent_id, $path, $user_id]);
|
||||||
notify_waha('Uploaded', 'File', $name);
|
notify_user($user_id, "File uploaded: $name", ['action'=>'Uploaded', 'module'=>'File', 'item'=>$name]);
|
||||||
echo "OK"; exit;
|
echo "OK"; exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,9 +56,9 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['share_project'])) {
|
|||||||
$stmt->execute([$pr_id, $parts[0], $parts[1]]);
|
$stmt->execute([$pr_id, $parts[0], $parts[1]]);
|
||||||
$pr = query("SELECT name FROM projects WHERE id=?", [$pr_id])->fetch();
|
$pr = query("SELECT name FROM projects WHERE id=?", [$pr_id])->fetch();
|
||||||
$msg = "A project/file '{$pr['name']}' has been shared with you.";
|
$msg = "A project/file '{$pr['name']}' has been shared with you.";
|
||||||
if($parts[0] === 'user') notify_user($parts[1], $msg);
|
$ctx = ['action'=>'Shared', 'module'=>'File/Folder', 'item'=>$pr['name'], 'user'=>$_SESSION['username']??'System'];
|
||||||
else notify_group($parts[1], $msg);
|
if($parts[0] === 'user') notify_user($parts[1], $msg, $ctx);
|
||||||
notify_waha('Shared', 'File/Folder', $pr['name']);
|
else notify_group($parts[1], $msg, $ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,12 +261,21 @@ function get_file_icon($name, $is_folder) {
|
|||||||
<td style="font-size:0.8rem; color:var(--text-light);">
|
<td style="font-size:0.8rem; color:var(--text-light);">
|
||||||
<?php
|
<?php
|
||||||
// Show shares
|
// Show shares
|
||||||
$shares = query("SELECT ps.id, ps.share_type, ps.share_id, u.username, g.name as gname FROM project_shares ps LEFT JOIN users u ON (ps.share_type='user' AND ps.share_id=u.id) LEFT JOIN groups g ON (ps.share_type='group' AND ps.share_id=g.id) WHERE ps.project_id=?", [$item['id']])->fetchAll();
|
$shares = query("SELECT ps.id, ps.share_type, ps.share_id, u.username, u.profile_pic as upic, g.name as gname, g.profile_pic as gpic FROM project_shares ps LEFT JOIN users u ON (ps.share_type='user' AND ps.share_id=u.id) LEFT JOIN groups g ON (ps.share_type='group' AND ps.share_id=g.id) WHERE ps.project_id=?", [$item['id']])->fetchAll();
|
||||||
foreach($shares as $sh) {
|
foreach($shares as $sh) {
|
||||||
echo '<span style="font-size:0.72rem; background:rgba(var(--primary-rgb),0.1); color:var(--primary); padding:1px 5px; border-radius:8px; margin:1px; display:inline-flex; align-items:center; gap:3px;">';
|
$pic = ($sh['share_type'] === 'user' ? ($sh['upic']??'') : ($sh['gpic']??''));
|
||||||
echo $sh['share_type']==='user' ? ('👤 '.htmlspecialchars($sh['username']??'')) : ('👥 '.htmlspecialchars($sh['gname']??''));
|
$name = ($sh['share_type'] === 'user' ? ($sh['username']??'User') : ($sh['gname']??'Group'));
|
||||||
|
$init = strtoupper(mb_substr($name, 0, 1));
|
||||||
|
|
||||||
|
echo '<span style="font-size:0.72rem; background:rgba(var(--primary-rgb),0.1); color:var(--primary); padding:1px 5px 1px 1px; border-radius:12px; margin:1px; display:inline-flex; align-items:center; gap:3px; border:1px solid rgba(var(--primary-rgb),0.15);">';
|
||||||
|
if($pic && file_exists($pic)) {
|
||||||
|
echo '<img src="'.htmlspecialchars($pic).'" style="width:16px; height:16px; border-radius:50%; object-fit:cover;">';
|
||||||
|
} else {
|
||||||
|
echo '<div style="width:16px; height:16px; border-radius:50%; background:var(--primary); color:white; display:flex; align-items:center; justify-content:center; font-size:0.55rem; font-weight:700;">'.$init.'</div>';
|
||||||
|
}
|
||||||
|
echo '<span>'.htmlspecialchars($name).'</span>';
|
||||||
if($item['is_mine']) {
|
if($item['is_mine']) {
|
||||||
echo '<button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;" title="Unshare" onclick="removeProjectShare('.$sh['id'].')">×</button>';
|
echo '<button type="button" style="border:none;background:transparent;color:inherit;cursor:pointer;line-height:1;margin-left:2px;font-size:0.8rem;" title="Unshare" onclick="removeProjectShare('.$sh['id'].')">×</button>';
|
||||||
}
|
}
|
||||||
echo '</span>';
|
echo '</span>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,16 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_task'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($can_delete) {
|
if($can_delete) {
|
||||||
$task_to_del = query("SELECT title FROM tasks WHERE id=?", [$del_id])->fetch();
|
$task_to_del = query("SELECT title, assignee_type, assignee_id FROM tasks WHERE id=?", [$del_id])->fetch();
|
||||||
query("DELETE FROM tasks WHERE id=?", [$del_id]);
|
query("DELETE FROM tasks WHERE id=?", [$del_id]);
|
||||||
if($task_to_del) notify_waha('Deleted', 'Task', $task_to_del['title']);
|
if($task_to_del) {
|
||||||
|
$ctx = ['action' => 'Deleted', 'module' => 'Task', 'item' => $task_to_del['title']];
|
||||||
|
if($task_to_del['assignee_type'] === 'user' && $task_to_del['assignee_id']) {
|
||||||
|
notify_user($task_to_del['assignee_id'], "Task '{$task_to_del['title']}' has been deleted.", $ctx);
|
||||||
|
} elseif($task_to_del['assignee_type'] === 'group' && $task_to_del['assignee_id']) {
|
||||||
|
notify_group($task_to_del['assignee_id'], "Group task '{$task_to_del['title']}' has been deleted.", $ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,17 +155,16 @@ if($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_task'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle notifications
|
// Handle notifications
|
||||||
|
$ctx = ['action' => 'Created', 'module' => 'Task', 'item' => $title, 'assignor' => $_SESSION['username'] ?? 'System', 'due_date' => $due_date ?: 'None'];
|
||||||
foreach($to_list as $rec) {
|
foreach($to_list as $rec) {
|
||||||
$u = query("SELECT id FROM users WHERE username=?", [$rec])->fetch();
|
$u = query("SELECT id FROM users WHERE username=?", [$rec])->fetch();
|
||||||
if($u) notify_user($u['id'], "You have been assigned a new task: $title");
|
if($u) notify_user($u['id'], "You have been assigned a new task: $title", $ctx);
|
||||||
else {
|
else {
|
||||||
$g = query("SELECT id FROM groups WHERE name=?", [$rec])->fetch();
|
$g = query("SELECT id FROM groups WHERE name=?", [$rec])->fetch();
|
||||||
if($g) notify_group($g['id'], "Your group has been assigned a new task: $title");
|
if($g) notify_group($g['id'], "Your group has been assigned a new task: $title", $ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_waha('Created', 'Task', $title);
|
|
||||||
|
|
||||||
if(isset($_POST['ajax_submit'])) {
|
if(isset($_POST['ajax_submit'])) {
|
||||||
echo json_encode(['success'=>true, 'redirect'=>'?action=tasks&msg_title='.urlencode('Task Created').'&msg_body='.urlencode('Task successfully created.')]);
|
echo json_encode(['success'=>true, 'redirect'=>'?action=tasks&msg_title='.urlencode('Task Created').'&msg_body='.urlencode('Task successfully created.')]);
|
||||||
exit;
|
exit;
|
||||||
@@ -194,7 +200,12 @@ $custom_like = $custom_tokens ? implode(' OR ', array_fill(0, count($custom_toke
|
|||||||
|
|
||||||
$params = array_merge([$user_id], $my_group_ids, $my_group_ids, [$user_id], $custom_tokens, $custom_tokens);
|
$params = array_merge([$user_id], $my_group_ids, $my_group_ids, [$user_id], $custom_tokens, $custom_tokens);
|
||||||
|
|
||||||
$sql = "SELECT t.*, u.username as assignor_user_name, u.profile_pic as assignor_profile_pic, g.name as assignor_group_name, au.username as assignee_user_name, au.profile_pic as assignee_profile_pic, ag.name as assignee_group_name, cu.username as completed_by_username, aru.username as archived_by_username
|
$sql = "SELECT t.*,
|
||||||
|
u.username as assignor_user_name, u.profile_pic as assignor_user_pic,
|
||||||
|
g.name as assignor_group_name, g.profile_pic as assignor_group_pic,
|
||||||
|
au.username as assignee_user_name, au.profile_pic as assignee_user_pic,
|
||||||
|
ag.name as assignee_group_name, ag.profile_pic as assignee_group_pic,
|
||||||
|
cu.username as completed_by_username, aru.username as archived_by_username
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
LEFT JOIN users u ON t.assignor_id = u.id
|
LEFT JOIN users u ON t.assignor_id = u.id
|
||||||
LEFT JOIN groups g ON t.assignor_group_id = g.id
|
LEFT JOIN groups g ON t.assignor_group_id = g.id
|
||||||
@@ -237,7 +248,7 @@ if($t_ids) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-fetch profile pics for all potential assignees in the view
|
// Pre-fetch profile pics for users
|
||||||
$all_usernames = [];
|
$all_usernames = [];
|
||||||
foreach($tasks as $t) {
|
foreach($tasks as $t) {
|
||||||
if($t['assignee_type'] == 'user' || $t['assignee_type'] == 'custom') {
|
if($t['assignee_type'] == 'user' || $t['assignee_type'] == 'custom') {
|
||||||
@@ -256,6 +267,21 @@ if($all_usernames) {
|
|||||||
$user_pics[$row['username']] = $row['profile_pic'];
|
$user_pics[$row['username']] = $row['profile_pic'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Pre-fetch group pics
|
||||||
|
$all_groupnames = [];
|
||||||
|
foreach($tasks as $t) {
|
||||||
|
if($t['assignee_type'] == 'group') $all_groupnames[] = $t['assignee_group_name'];
|
||||||
|
if($t['assignor_type'] == 'group') $all_groupnames[] = $t['assignor_group_name'];
|
||||||
|
}
|
||||||
|
if($all_groupnames) {
|
||||||
|
$all_groupnames = array_unique($all_groupnames);
|
||||||
|
$in_g = str_repeat('?,', count($all_groupnames) - 1) . '?';
|
||||||
|
$stmt_g = $pdo->prepare("SELECT name, profile_pic FROM groups WHERE name IN ($in_g)");
|
||||||
|
$stmt_g->execute(array_values($all_groupnames));
|
||||||
|
foreach($stmt_g->fetchAll() as $row) {
|
||||||
|
$user_pics[$row['name']] = $row['profile_pic'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$kanban = ['pending' => [], 'in_progress' => [], 'completed' => [], 'archived' => []];
|
$kanban = ['pending' => [], 'in_progress' => [], 'completed' => [], 'archived' => []];
|
||||||
foreach($tasks as $t) {
|
foreach($tasks as $t) {
|
||||||
@@ -683,7 +709,7 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$from_profile_pic = ($t['assignor_type'] == 'user') ? ($user_pics[$from_name] ?? '') : '';
|
$from_profile_pic = $user_pics[$from_name] ?? '';
|
||||||
?>
|
?>
|
||||||
<div style="font-size:0.8rem; display:flex; justify-content:space-between; align-items:center;">
|
<div style="font-size:0.8rem; display:flex; justify-content:space-between; align-items:center;">
|
||||||
<span style="color:var(--text-light);">Assignments:</span>
|
<span style="color:var(--text-light);">Assignments:</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user