* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. */ /** * \file globalnotify/class/globalnotify.class.php * \ingroup globalnotify * \brief Global notification manager class */ /** * Class GlobalNotify * Manages global notifications from all modules */ class GlobalNotify { /** * @var DoliDB Database handler */ public $db; /** * Notification types with styling */ const TYPE_ERROR = 'error'; const TYPE_WARNING = 'warning'; const TYPE_INFO = 'info'; const TYPE_SUCCESS = 'success'; const TYPE_ACTION = 'action'; // Requires user action /** * Constructor * * @param DoliDB $db Database handler */ public function __construct($db) { $this->db = $db; } /** * Add a notification * * @param string $module Module name (e.g., 'bankimport', 'importzugferd') * @param string $type Notification type (error, warning, info, success, action) * @param string $title Short title * @param string $message Detailed message * @param string $actionUrl URL for action button (optional) * @param string $actionLabel Label for action button (optional) * @param int $priority Priority 1-10 (10 = highest) * @param int $userId Specific user ID or 0 for all admins * @return int Notification ID or -1 on error */ public function addNotification($module, $type, $title, $message, $actionUrl = '', $actionLabel = '', $priority = 5, $userId = 0) { global $conf; // Store in llx_const as JSON array $key = 'GLOBALNOTIFY_'.strtoupper($module); $notification = array( 'id' => uniqid($module.'_'), 'module' => $module, 'type' => $type, 'title' => $title, 'message' => $message, 'action_url' => $actionUrl, 'action_label' => $actionLabel, 'priority' => $priority, 'user_id' => $userId, 'created' => time(), 'read' => false ); // Get existing notifications for this module $existing = $this->getModuleNotifications($module); $existing[] = $notification; // Keep only last 50 notifications per module if (count($existing) > 50) { $existing = array_slice($existing, -50); } $result = dolibarr_set_const($this->db, $key, json_encode($existing), 'chaine', 0, '', $conf->entity); return $result > 0 ? $notification['id'] : -1; } /** * Get notifications for a specific module * * @param string $module Module name * @return array Notifications */ public function getModuleNotifications($module) { $key = 'GLOBALNOTIFY_'.strtoupper($module); $data = getDolGlobalString($key); if (empty($data)) { return array(); } $notifications = json_decode($data, true); return is_array($notifications) ? $notifications : array(); } /** * Get all active notifications for current user * * @param int $userId User ID * @param bool $unreadOnly Only return unread notifications * @return array All notifications sorted by priority and date */ public function getAllNotifications($userId = 0, $unreadOnly = true) { global $user; if ($userId == 0) { $userId = $user->id; } $allNotifications = array(); // Get all notification constants (exclude internal settings) $sql = "SELECT name, value FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%' AND name NOT LIKE 'GLOBALNOTIFY_CRON%' AND value != '' AND value LIKE '[%'"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $notifications = json_decode($obj->value, true); if (is_array($notifications)) { foreach ($notifications as $notif) { // Filter by user if specified in notification if (!empty($notif['user_id']) && $notif['user_id'] != $userId) { continue; } // Filter by read status if ($unreadOnly && !empty($notif['read'])) { continue; } $allNotifications[] = $notif; } } } } // Sort by priority (desc) and date (desc) usort($allNotifications, function($a, $b) { if ($a['priority'] != $b['priority']) { return $b['priority'] - $a['priority']; } return $b['created'] - $a['created']; }); return $allNotifications; } /** * Get read notifications for history display * * @param int $userId User ID * @param int $limit Maximum number to return * @return array Read notifications sorted by date (newest first) */ public function getReadNotifications($userId = 0, $limit = 20) { global $user; if ($userId == 0) { $userId = $user->id; } $readNotifications = array(); // Get all notification constants (exclude internal settings) $sql = "SELECT name, value FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%' AND name NOT LIKE 'GLOBALNOTIFY_CRON%' AND value != '' AND value LIKE '[%'"; $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $notifications = json_decode($obj->value, true); if (is_array($notifications)) { foreach ($notifications as $notif) { // Filter by user if specified if (!empty($notif['user_id']) && $notif['user_id'] != $userId) { continue; } // Only read notifications if (!empty($notif['read'])) { $readNotifications[] = $notif; } } } } } // Sort by date (newest first) usort($readNotifications, function($a, $b) { return $b['created'] - $a['created']; }); // Limit results return array_slice($readNotifications, 0, $limit); } /** * Mark notification as read * * @param string $notificationId Notification ID * @return bool Success */ public function markAsRead($notificationId) { global $conf; // Find which module this notification belongs to $sql = "SELECT name, value FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%' AND value LIKE '%".addslashes($notificationId)."%'"; $resql = $this->db->query($sql); if ($resql && $obj = $this->db->fetch_object($resql)) { $notifications = json_decode($obj->value, true); if (is_array($notifications)) { foreach ($notifications as &$notif) { if ($notif['id'] == $notificationId) { $notif['read'] = true; break; } } dolibarr_set_const($this->db, $obj->name, json_encode($notifications), 'chaine', 0, '', $conf->entity); return true; } } return false; } /** * Mark all notifications as read * * @param string $module Optional: only for specific module * @return int Number of notifications marked as read */ public function markAllAsRead($module = '') { global $conf; $count = 0; $sql = "SELECT name, value FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%'"; if (!empty($module)) { $sql .= " AND name = 'GLOBALNOTIFY_".strtoupper($module)."'"; } $resql = $this->db->query($sql); if ($resql) { while ($obj = $this->db->fetch_object($resql)) { $notifications = json_decode($obj->value, true); if (is_array($notifications)) { $changed = false; foreach ($notifications as &$notif) { if (empty($notif['read'])) { $notif['read'] = true; $changed = true; $count++; } } if ($changed) { dolibarr_set_const($this->db, $obj->name, json_encode($notifications), 'chaine', 0, '', $conf->entity); } } } } return $count; } /** * Delete a notification * * @param string $notificationId Notification ID * @return bool Success */ public function deleteNotification($notificationId) { global $conf; $sql = "SELECT name, value FROM ".MAIN_DB_PREFIX."const WHERE name LIKE 'GLOBALNOTIFY_%' AND value LIKE '%".addslashes($notificationId)."%'"; $resql = $this->db->query($sql); if ($resql && $obj = $this->db->fetch_object($resql)) { $notifications = json_decode($obj->value, true); if (is_array($notifications)) { $notifications = array_filter($notifications, function($n) use ($notificationId) { return $n['id'] != $notificationId; }); dolibarr_set_const($this->db, $obj->name, json_encode(array_values($notifications)), 'chaine', 0, '', $conf->entity); return true; } } return false; } /** * Clear all notifications for a module * * @param string $module Module name * @return bool Success */ public function clearModuleNotifications($module) { global $conf; $key = 'GLOBALNOTIFY_'.strtoupper($module); return dolibarr_del_const($this->db, $key, $conf->entity) >= 0; } /** * Get unread count for badge display * * @return int Number of unread notifications */ public function getUnreadCount() { return count($this->getAllNotifications(0, true)); } /** * Helper: Add error notification */ public static function error($module, $title, $message, $actionUrl = '', $actionLabel = '') { global $db; $notify = new self($db); return $notify->addNotification($module, self::TYPE_ERROR, $title, $message, $actionUrl, $actionLabel, 10); } /** * Helper: Add warning notification */ public static function warning($module, $title, $message, $actionUrl = '', $actionLabel = '') { global $db; $notify = new self($db); return $notify->addNotification($module, self::TYPE_WARNING, $title, $message, $actionUrl, $actionLabel, 7); } /** * Helper: Add info notification */ public static function info($module, $title, $message, $actionUrl = '', $actionLabel = '') { global $db; $notify = new self($db); return $notify->addNotification($module, self::TYPE_INFO, $title, $message, $actionUrl, $actionLabel, 3); } /** * Helper: Add action required notification */ public static function actionRequired($module, $title, $message, $actionUrl, $actionLabel = 'Aktion erforderlich') { global $db; $notify = new self($db); return $notify->addNotification($module, self::TYPE_ACTION, $title, $message, $actionUrl, $actionLabel, 9); } }