dolibarr.globalnotify/class/globalnotify.class.php
data b4a6f534ba fix: Auto-dismiss cron notifications for disabled jobs
- Add cleanupDisabledCronNotifications() method
- Automatically mark notifications as read when cronjob is disabled
- Fixes issue where "Cron-Job verpasst" kept reappearing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-03 12:09:42 +01:00

372 lines
9.8 KiB
PHP
Executable file

<?php
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
*
* 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 '%_LASTCHECK' 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 '%_LASTCHECK' 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);
}
}