date(d.administered_on))", '/dashboard', ], ]; foreach ($queries as [$key, $severity, $label, $sql, $url]) { try { $count = (int) $db->query($sql)->fetchColumn(); if ($count) { $alerts[] = compact('key', 'severity', 'label', 'count', 'url'); } } catch (Throwable) { } } if (class_exists(InventoryService::class)) { try { $stock = InventoryService::alerts(); if ($stock['low']) { $alerts[] = [ 'key' => 'inventory-low', 'severity' => 'warning', 'label' => t('notification.inventory_low'), 'count' => count($stock['low']), 'url' => '/settings/inventory', ]; } if ($stock['expired']) { $alerts[] = [ 'key' => 'inventory-expiry', 'severity' => 'danger', 'label' => t('notification.inventory_expiry'), 'count' => count($stock['expired']), 'url' => '/settings/inventory', ]; } } catch (Throwable) { } } if (class_exists(AgendaService::class)) { try { $due = AgendaService::due(); if ($due) { $alerts[] = [ 'key' => 'agenda', 'severity' => 'warning', 'label' => t('notification.agenda_due'), 'count' => count($due), 'url' => '/agenda', ]; } } catch (Throwable) { } } $backups = BackupService::list(); $days = 999; if ( $backups && ($date = DateTimeImmutable::createFromFormat( 'd/m/Y H:i:s', (string) $backups[0]['date'], new DateTimeZone('Europe/Paris'), )) ) { $days = (int) $date->diff(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('%a'); } if ($days > 7) { $alerts[] = [ 'key' => 'backup', 'severity' => $days > 30 ? 'danger' : 'warning', 'label' => t('notification.backup_old'), 'count' => $days, 'url' => '/settings/backups', ]; } return $alerts; } public static function send(bool $force = false): array { if (AppSettings::get('notification_email_enabled') !== '1' && !$force) { return ['sent' => false, 'reason' => 'disabled']; } $recipient = AppSettings::get('notification_email_recipient') ?: AppSettings::get('association_email'); if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) { return ['sent' => false, 'reason' => 'recipient']; } $frequency = AppSettings::get('notification_email_frequency'); $last = AppSettings::get('notification_last_sent_at'); if (!$force && $last !== '') { $elapsed = time() - (strtotime($last) ?: 0); $minimum = $frequency === 'daily' ? 20 * 3600 : 6 * 86400; if ($elapsed < $minimum) { return ['sent' => false, 'reason' => 'not_due']; } } $alerts = self::alerts(); $association = AppSettings::get('association_name') ?: 'Globinours'; $subject = '[' . $association . '] ' . t('notification.email_subject'); $lines = [t('notification.email_intro', ['association' => $association]), '']; if (!$alerts) { $lines[] = t('notification.no_alert'); } else { foreach ($alerts as $a) { $lines[] = '- ' . $a['label'] . ' : ' . $a['count']; } } $lines[] = ''; $lines[] = t('notification.email_footer'); $sent = @mail($recipient, $subject, implode("\n", $lines), 'Content-Type: text/plain; charset=UTF-8'); if ($sent) { AppSettings::save(['notification_last_sent_at' => date('Y-m-d H:i:s')], Auth::id()); } return ['sent' => $sent, 'reason' => $sent ? 'sent' : 'mail_failed', 'count' => count($alerts)]; } }