From 596a233898366465a8bc68e38766cb04bfd332e8 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Mar 2026 01:02:23 +0700 Subject: [PATCH] refactor: replace global WAHA notifications with granular user and group-specific templates and dispatch logic --- db.php | 142 ++++++++++++++--------- db.sqlite | 0 index.php | 22 +++- views/admin.php | 277 +++++++++++++++++++++++++++++++++++++++----- views/notes.php | 52 +++++++-- views/passwords.php | 50 +++++--- views/projects.php | 46 ++++++-- views/tasks.php | 44 +++++-- 8 files changed, 502 insertions(+), 131 deletions(-) create mode 100644 db.sqlite diff --git a/db.php b/db.php index 9e7b362..ff299ee 100644 --- a/db.php +++ b/db.php @@ -29,74 +29,108 @@ function get_user_groups($user_id) { return $stmt->fetchAll(); } -function notify_user($user_id, $message) { +function compile_waha_template($template, $context) { + if (!$template) return ""; + $action = $context['action'] ?? 'Update'; + $module = $context['module'] ?? 'System'; + $item = $context['item'] ?? ''; + + $user = $context['user'] ?? null; + if (!$user && isset($_SESSION['user_id'])) { + $u = get_user($_SESSION['user_id']); + $user = $u ? $u['username'] : 'System'; + } elseif (!$user) { + $user = 'System'; + } + + $attachment_count = $context['attachment_count'] ?? '0'; + $due_date = $context['due_date'] ?? 'None'; + $created_date = $context['created_date'] ?? date('Y-m-d H:i'); + $updated_date = $context['updated_date'] ?? date('Y-m-d H:i'); + $assignor = $context['assignor'] ?? 'N/A'; + $assignee = $context['assignee'] ?? 'N/A'; + + return str_replace( + ['{action}', '{module}', '{item}', '{user}', '{attachment_count}', '{due_date}', '{created_date}', '{updated_date}', '{assignor}', '{assignee}'], + [$action, $module, $item, $user, $attachment_count, $due_date, $created_date, $updated_date, $assignor, $assignee], + $template + ); +} + +function dispatch_waha_message($endpoint, $chat_id, $session, $api_key, $text) { + try { + $payload = json_encode([ + "chatId" => $chat_id, + "text" => $text, + "session" => $session ?: 'default' + ]); + $ch = curl_init($endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + $headers = ['Content-Type: application/json']; + if (!empty($api_key)) $headers[] = 'X-Api-Key: ' . $api_key; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_TIMEOUT, 2); + curl_exec($ch); + curl_close($ch); + } 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]); - // Virtual hook for Email/Telegram/WhatsApp based on user features $user = get_user($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. + 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) { +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) { - notify_user($u['user_id'], $message); + $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); + } } } - -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']); - if ($u) $current_user_name = $u['username']; - } - - foreach ($wa_users as $wu) { - $endpoint = $wu['waha_endpoint']; - $chat_id = $wu['waha_chat_id']; - if (!$endpoint || !$chat_id) continue; - - $template = $wu['waha_template'] ?: "*Update:* {action} on {module} '{item}' by {user}"; - $text = str_replace( - ['{action}', '{module}', '{item}', '{user}'], - [$action, $module, $item_name, $current_user_name], - $template - ); - - $payload = json_encode([ - "chatId" => $chat_id, - "text" => $text, - "session" => $wu['waha_session'] ?: 'default' - ]); - - $ch = curl_init($endpoint); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - $headers = ['Content-Type: application/json']; - if (!empty($wu['waha_api_key'])) { - $headers[] = 'X-Api-Key: ' . $wu['waha_api_key']; - } - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_TIMEOUT, 2); - curl_exec($ch); - curl_close($ch); - } - } catch (Exception $e) {} -} diff --git a/db.sqlite b/db.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/index.php b/index.php index c432e94..01c10db 100644 --- a/index.php +++ b/index.php @@ -57,8 +57,15 @@ if ($action === 'api') { $stmt = $pdo->prepare("UPDATE tasks SET $set_clause WHERE id = ?"); $stmt->execute($params); - $task_to_upd = query("SELECT title FROM tasks WHERE id=?", [$task_id])->fetch(); - if($task_to_upd) notify_waha('Moved to '.$status, 'Task Status', $task_to_upd['title']); + $t = query("SELECT title, assignee_type, assignee_id FROM tasks WHERE id=?", [$task_id])->fetch(); + 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']); exit; } @@ -71,8 +78,15 @@ if ($action === 'api') { if($task_id && $comment) { $stmt = $pdo->prepare("INSERT INTO task_comments (task_id, comment, created_by) VALUES (?, ?, ?)"); $stmt->execute([$task_id, $comment, $user_id]); - $t = query("SELECT title FROM tasks WHERE id=?", [$task_id])->fetch(); - if($t) notify_waha('Commented on', 'Task', $t['title']); + $t = query("SELECT title, assignee_type, assignee_id FROM tasks WHERE id=?", [$task_id])->fetch(); + 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']); exit; diff --git a/views/admin.php b/views/admin.php index af0495a..b968872 100644 --- a/views/admin.php +++ b/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 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_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_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 { $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')"); @@ -53,11 +67,18 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { if(isset($_POST['create_group'])) { try { - $stmt = $pdo->prepare("INSERT INTO groups (name) VALUES (?)"); - $stmt->execute([$_POST['group_name']]); - $success_msg = "Group '{$_POST['group_name']}' created!"; + $name = trim($_POST['group_name']); + $pic_path = null; + 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) { - $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) : ''; $waha_ep = trim($_POST['waha_endpoint'] ?? ''); $waha_cid = trim($_POST['waha_chat_id'] ?? ''); - $waha_tpl = trim($_POST['waha_template'] ?? ''); $waha_ses = trim($_POST['waha_session'] ?? '') ?: 'default'; $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=?"); - $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_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'] ?? ''); + + $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!"; } + 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'])) { $stmt = $pdo->prepare("UPDATE system_config SET v=? WHERE k=?"); $stmt->execute([$_POST['completed_days'] ?? 7, 'completed_days']); @@ -100,7 +166,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { } // ── 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(); $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; @@ -292,10 +358,17 @@ $active_tab = $_GET['tab'] ?? 'overview';
-

Create Group

-
- - +

Create Group

+ +
+ + +
+
+ + +
+
@@ -333,18 +406,51 @@ $active_tab = $_GET['tab'] ?? 'overview'; $g_users = $stmt->fetchAll(); ?>
-
-
-
- +
+ + + +
+
+
+ + + + + +
+
+ +
member
+
+
+
+ + + + +
-
member
- - - -
-
+ +
+ + + + + + + + + + + +
+ +
@@ -456,10 +562,27 @@ $active_tab = $_GET['tab'] ?? 'overview';
-
- - -
Variables: {action} {module} {item} {user} {attachment_count} {due_date} {created_date} {updated_date} {assignor} {assignee}
+
+ +
Variables: {action} {module} {item} {user} {attachment_count} {due_date} {created_date} {updated_date} {assignor} {assignee}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
@@ -467,6 +590,96 @@ $active_tab = $_GET['tab'] ?? 'overview';
+ + +
+

Group Notification Channels

+

Configure notification channels for an entire group. WAHA settings appear when WhatsApp is enabled.

+ + +
+
+ + + + + +
+
+ + + + + +
+
+
+
Group
+
+ +
+ + +
+ +
+ + +
+
WAHA WhatsApp API Settings
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
Variables: {action} {module} {item} {user} {attachment_count} {due_date} {created_date} {updated_date} {assignor} {assignee}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ +
@@ -527,6 +740,16 @@ function toggleWAHA(uid, enabled) { 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'; + } +}