mahnung/class/mahnungntfy.class.php
Eduard Wisch d1db85322b
All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
Initiales Release: Mahnung-Modul v0.1.0 [deploy]
Vollstaendiges 3-stufiges Mahnwesen nach BGB §288:
- SQL-Schema (llx_mahnung_mahnung, llx_mahnung_stufe)
- CRUD-Klassen (Mahnung, MahnungStufe, MahnungVorschlag)
- TCPDF DIN-5008 PDF-Generierung
- Verzugszinsberechnung B2C/B2B + §288 Abs.5 Pauschale
- Trigger: offene Mahnungen bei Zahlungseingang schliessen
- Hook: Tab + Button auf Rechnungs-/Kundenkarte
- Cron: taegl. Vorschlagsliste + Ntfy-Push
- Deploy-Pipeline (.forgejo/workflows/deploy.yml)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:09:37 +02:00

94 lines
2.9 KiB
PHP

<?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, version 3.
*/
/**
* \file htdocs/custom/mahnung/class/mahnungntfy.class.php
* \ingroup mahnung
* \brief Schmaler Ntfy-Push fuer das Mahnwesen-Modul.
*
* Phase 3: standalone Ntfy.
* Phase 7: wird durch GlobalNotify-Integration ergaenzt/ersetzt (notify-Skill).
*
* Konfig-Konstanten (Setup-Seite):
* MAHNUNG_NTFY_TOPIC Default 'vk-builds'
* MAHNUNG_NTFY_URL Default 'https://notify.data-it-solution.de'
* MAHNUNG_NTFY_AUTH Optional: 'Basic ...'
*/
class MahnungNtfy
{
/**
* Sendet einen Push. Nie blockierend — Fehler nur ins Syslog.
*
* @param string $title Titel (wird header-sanitisiert)
* @param string $message Body
* @param string $clickUrl Optional: Click-URL
* @param array $tags Optional: Emoji-Tags (z.B. ['envelope_with_arrow'])
* @return bool true wenn HTTP 2xx, sonst false
*/
public static function send($title, $message, $clickUrl = '', array $tags = array())
{
$url = trim((string) getDolGlobalString('MAHNUNG_NTFY_URL', 'https://notify.data-it-solution.de'));
$topic = trim((string) getDolGlobalString('MAHNUNG_NTFY_TOPIC', 'vk-builds'));
$auth = trim((string) getDolGlobalString('MAHNUNG_NTFY_AUTH', ''));
if (empty($url) || empty($topic)) {
dol_syslog('MahnungNtfy: URL oder Topic nicht konfiguriert', LOG_WARNING);
return false;
}
$endpoint = rtrim($url, '/').'/'.rawurlencode($topic);
$headers = array(
'Content-Type: text/plain; charset=utf-8',
'Title: '.self::sanitizeHeader($title),
'Priority: default',
);
if (!empty($tags)) {
$headers[] = 'Tags: '.self::sanitizeHeader(implode(',', $tags));
}
if (!empty($clickUrl)) {
$headers[] = 'Click: '.self::sanitizeHeader($clickUrl);
}
if (!empty($auth)) {
$headers[] = 'Authorization: '.$auth;
}
if (!function_exists('curl_init')) {
dol_syslog('MahnungNtfy: cURL nicht verfuegbar', LOG_WARNING);
return false;
}
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $message);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
return true;
}
dol_syslog('MahnungNtfy: Push fehlgeschlagen ('.$httpCode.') '.$error, LOG_WARNING);
return false;
}
/**
* Header-Werte muessen Single-Line sein. Newlines + Steuerzeichen entfernen.
*
* @param string $value
* @return string
*/
private static function sanitizeHeader($value)
{
return trim(preg_replace('/[\r\n\x00-\x1F\x7F]/', ' ', (string) $value));
}
}