Upload via GUI (346 Dateien)
This commit is contained in:
@@ -12,6 +12,7 @@ require_once __DIR__ . '/time_tracking.php';
|
||||
require_once __DIR__ . '/pseudo_cron.php';
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/smtp_mailer.php';
|
||||
require_once __DIR__ . '/mail_queue.php';
|
||||
require_once __DIR__ . '/mailer.php';
|
||||
require_once __DIR__ . '/email_addresses.php';
|
||||
require_once __DIR__ . '/health.php';
|
||||
|
||||
@@ -188,7 +188,7 @@ function email_address_send_test(PDO $pdo, int $id, string $recipient): string
|
||||
$from = $addr['label'] ? "{$addr['label']} <{$addr['email']}>" : $addr['email'];
|
||||
$via = $addr['smtp_enabled'] ? 'eigenen SMTP-Server' : 'globalen SMTP-Server';
|
||||
$html = '<p>Dies ist eine Test-Mail von DeskHub.</p><p>Absender: ' . e($addr['email']) . '<br>Versand über: ' . e($via) . '</p>';
|
||||
if (!send_mail($recipient, 'DeskHub – Test-Mail von ' . $addr['email'], $html, $from)) {
|
||||
if (!send_mail($recipient, 'DeskHub – Test-Mail von ' . $addr['email'], $html, $from, false)) {
|
||||
throw new EmailAddressException('Versand fehlgeschlagen. Bitte SMTP-Daten prüfen. ' . smtp_last_error(), 'MAIL_FAILED', 502);
|
||||
}
|
||||
return "Test-Mail von {$addr['email']} an {$recipient} gesendet (über {$via}).";
|
||||
|
||||
@@ -109,6 +109,12 @@ function system_health(PDO $pdo): array
|
||||
$issues[] = ['level' => 'warning', 'code' => 'send_failed', 'message' => 'Der letzte E-Mail-Versand ist fehlgeschlagen (' . date('d.m.Y H:i', strtotime($at)) . '): ' . $msg];
|
||||
}
|
||||
}
|
||||
// Mails, die nicht rausgingen: warten in der Warteschlange oder wurden aufgegeben
|
||||
if (function_exists('mail_queue_counts')) {
|
||||
$q = mail_queue_counts($pdo);
|
||||
if ($q['failed'] > 0) $issues[] = ['level' => 'error', 'code' => 'mail_queue_failed', 'message' => $q['failed'] . ' Mail(s) konnten auch nach mehreren Versuchen nicht gesendet werden. Unter Verwalten → Mail-Warteschlange nachsenden oder löschen.'];
|
||||
elseif ($q['pending'] > 0) $issues[] = ['level' => 'warning', 'code' => 'mail_queue_pending', 'message' => $q['pending'] . ' Mail(s) warten in der Warteschlange und werden erneut versucht (Verwalten → Mail-Warteschlange).'];
|
||||
}
|
||||
return $issues;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,15 +95,80 @@ function i18n_current(): string
|
||||
* Wörterbuch einer Sprache: exakte Texte und Muster mit "{}".
|
||||
* @return array{exact:array<string,string>,patterns:list<array{0:string,1:string}>,count:int}
|
||||
*/
|
||||
function i18n_dictionary(string $code): array
|
||||
function i18n_dictionary(string $code, bool $reload = false): array
|
||||
{
|
||||
static $cache = [];
|
||||
if ($reload) unset($cache[$code]);
|
||||
if (isset($cache[$code])) return $cache[$code];
|
||||
$dict = ['exact' => [], 'patterns' => [], 'count' => 0];
|
||||
$file = i18n_languages()[$code]['strings'] ?? '';
|
||||
if ($file === '' || !is_file($file) || filesize($file) > I18N_MAX_STRINGS_BYTES) return $cache[$code] = $dict;
|
||||
$files = [];
|
||||
// Zuerst die eigenen Übersetzungen der aktiven Plugins (lang/<code>.json), danach das Sprachpaket: Es gewinnt bei gleichem Text,
|
||||
// damit sich ein Plugin-Text im Sprachpaket verbessern lässt.
|
||||
foreach (i18n_plugin_string_files($code) as $f) $files[] = $f;
|
||||
$packFile = i18n_languages()[$code]['strings'] ?? '';
|
||||
if ($packFile !== '') $files[] = $packFile;
|
||||
foreach ($files as $file) i18n_load_strings_file($file, $dict);
|
||||
return $cache[$code] = $dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Übersetzungsdateien der aktiven Plugins für eine Sprache: <Plugin>/lang/<code>.json (gleiches Format wie strings.json eines Sprachpakets).
|
||||
* So bringt jedes Plugin seine Texte selbst mit; eine neue Sprache braucht dann nur die Datei lang/<code>.json im Plugin.
|
||||
* @return array<string,string> Plugin-Kennung => Dateipfad
|
||||
*/
|
||||
function i18n_plugin_string_files(string $code): array
|
||||
{
|
||||
if (!preg_match('/^[a-z]{2,3}(-[a-z]{2,4})?$/', $code) || $code === 'de') return [];
|
||||
$slugs = [];
|
||||
try {
|
||||
if (function_exists('plugins_globally_disabled') && plugins_globally_disabled()) return [];
|
||||
foreach (get_db()->query('SELECT slug FROM plugins WHERE is_active = 1 ORDER BY slug')->fetchAll(PDO::FETCH_COLUMN) as $slug) {
|
||||
if (plugin_valid_slug((string) $slug)) $slugs[] = (string) $slug;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($slugs as $slug) {
|
||||
$file = plugins_dir() . '/' . $slug . '/lang/' . $code . '.json';
|
||||
if (is_file($file)) $out[$slug] = $file;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welche Plugins bringen für welche Sprachen eigene Übersetzungen mit? (Anzeige unter Sprachen)
|
||||
* @return array<string,array<string,int>> Plugin-Kennung => [Sprachcode => Anzahl Texte]
|
||||
*/
|
||||
function i18n_plugin_language_overview(): array
|
||||
{
|
||||
$out = [];
|
||||
try {
|
||||
$slugs = get_db()->query('SELECT slug FROM plugins WHERE is_active = 1 ORDER BY slug')->fetchAll(PDO::FETCH_COLUMN);
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
foreach ($slugs as $slug) {
|
||||
$slug = (string) $slug;
|
||||
if (!plugin_valid_slug($slug)) continue;
|
||||
foreach (glob(plugins_dir() . '/' . $slug . '/lang/*.json') ?: [] as $f) {
|
||||
$code = strtolower(basename($f, '.json'));
|
||||
if (!preg_match('/^[a-z]{2,3}(-[a-z]{2,4})?$/', $code) || filesize($f) > I18N_MAX_STRINGS_BYTES) continue;
|
||||
$data = json_decode((string) file_get_contents($f), true);
|
||||
$n = 0;
|
||||
if (is_array($data)) foreach ($data as $k => $v) if (is_string($v) && $v !== '' && (string) $k !== '' && ((string) $k)[0] !== '_') $n++;
|
||||
$out[$slug][$code] = $n;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Liest eine strings.json (Schlüssel = deutscher Text) in ein Wörterbuch ein; spätere Dateien überschreiben frühere. */
|
||||
function i18n_load_strings_file(string $file, array &$dict): void
|
||||
{
|
||||
if ($file === '' || !is_file($file) || filesize($file) > I18N_MAX_STRINGS_BYTES) return;
|
||||
$data = json_decode((string) file_get_contents($file), true);
|
||||
if (!is_array($data)) return $cache[$code] = $dict;
|
||||
if (!is_array($data)) return;
|
||||
foreach ($data as $key => $value) {
|
||||
$key = (string) $key;
|
||||
if (!is_string($value) || $value === '' || $key === '' || $key[0] === '_') continue;
|
||||
@@ -114,13 +179,18 @@ function i18n_dictionary(string $code): array
|
||||
$regex = '/^';
|
||||
foreach ($pieces as $k => $piece) $regex .= $k % 2 === 0 ? preg_quote($piece, '/') : ($piece === '{#}' ? '(\d+)' : '(.+?)');
|
||||
$regex .= '$/su';
|
||||
// gleicher Schlüssel in einer späteren Datei ersetzt das frühere Muster
|
||||
foreach ($dict['patterns'] as $i => $p) if (($dict['pkeys'][$i] ?? null) === $norm) { unset($dict['patterns'][$i], $dict['pkeys'][$i]); $dict['count']--; }
|
||||
$dict['patterns'] = array_values($dict['patterns']);
|
||||
$dict['pkeys'] = array_values($dict['pkeys'] ?? []);
|
||||
$dict['patterns'][] = [$regex, $value];
|
||||
$dict['pkeys'][] = $norm;
|
||||
$dict['count']++;
|
||||
} else {
|
||||
if (!isset($dict['exact'][$norm])) $dict['count']++;
|
||||
$dict['exact'][$norm] = $value;
|
||||
}
|
||||
$dict['count']++;
|
||||
}
|
||||
return $cache[$code] = $dict;
|
||||
}
|
||||
|
||||
/** Entities auflösen, Leerraum zusammenfassen: so werden Schlüssel und Seitentexte verglichen. */
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* includes/mail_queue.php
|
||||
*
|
||||
* Warteschlange für ausgehende Mails. Scheitert der Versand (SMTP-Server nicht erreichbar, falsche Zugangsdaten, Port gesperrt …),
|
||||
* wird die Mail hier gespeichert und vom Cron-Job "process_mail_queue" mit wachsenden Abständen erneut versucht. So gehen
|
||||
* Antworten, Eingangsbestätigungen und Passwort-Mails bei einer Störung nicht verloren.
|
||||
*
|
||||
* Tabelle mail_queue (Migration php_0064): status = pending (wartet), sent (nachträglich gesendet), failed (aufgegeben).
|
||||
*/
|
||||
|
||||
const MAIL_QUEUE_MAX_ATTEMPTS = 10;
|
||||
const MAIL_QUEUE_MAX_AGE_HOURS = 48;
|
||||
const MAIL_QUEUE_MAX_PENDING = 2000;
|
||||
/** Minuten bis zum nächsten Versuch nach dem 1., 2., 3. … Fehlversuch (der letzte Wert gilt für alle weiteren). */
|
||||
const MAIL_QUEUE_BACKOFF_MINUTES = [5, 10, 20, 40, 60, 120, 240, 360];
|
||||
|
||||
/** Ist die Warteschlange eingeschaltet? (Standard: ja; Einstellung mail_queue_enabled = 0 schaltet sie ab) */
|
||||
function mail_queue_enabled(): bool
|
||||
{
|
||||
return get_setting('mail_queue_enabled', '1') === '1';
|
||||
}
|
||||
|
||||
/** Merkt sich pro Anfrage, ob der letzte send_mail()-Aufruf wirklich versucht hat zu senden (nicht nur "Mailversand aus"). */
|
||||
function mail_attempted(?bool $set = null): bool
|
||||
{
|
||||
static $v = false;
|
||||
if ($set !== null) $v = $set;
|
||||
return $v;
|
||||
}
|
||||
|
||||
/** Wurde die zuletzt fehlgeschlagene Mail in die Warteschlange gelegt? */
|
||||
function mail_last_queued(?bool $set = null): bool
|
||||
{
|
||||
static $v = false;
|
||||
if ($set !== null) $v = $set;
|
||||
return $v;
|
||||
}
|
||||
|
||||
/** Legt eine Mail in die Warteschlange. @return int|null ID, null wenn nicht möglich (Tabelle fehlt, Warteschlange voll) */
|
||||
function mail_queue_add(PDO $pdo, string $to, string $subject, string $htmlBody, ?string $fromHeader, string $error): ?int
|
||||
{
|
||||
try {
|
||||
$pending = (int) $pdo->query("SELECT COUNT(*) FROM mail_queue WHERE status = 'pending'")->fetchColumn();
|
||||
if ($pending >= MAIL_QUEUE_MAX_PENDING) return null;
|
||||
$now = db_now();
|
||||
$pdo->prepare("INSERT INTO mail_queue (to_email, from_header, subject, body, status, attempts, next_attempt_at, last_error, created_at) VALUES (?,?,?,?, 'pending', 1, ?, ?, ?)")
|
||||
->execute([mb_substr($to, 0, 255), $fromHeader !== null && $fromHeader !== '' ? mb_substr($fromHeader, 0, 255) : null, mb_substr($subject, 0, 500), $htmlBody,
|
||||
date('Y-m-d H:i:s', time() + MAIL_QUEUE_BACKOFF_MINUTES[0] * 60), mb_substr($error, 0, 400), $now]);
|
||||
return (int) $pdo->lastInsertId();
|
||||
} catch (Throwable $e) {
|
||||
error_log('mail_queue_add: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versucht fällige Mails erneut zu senden (höchstens $limit pro Lauf). Jede Mail wird vorher atomar "reserviert", damit zwei
|
||||
* gleichzeitige Läufe sie nicht doppelt senden.
|
||||
* @return array{sent:int, failed:int, retry:int, gave_up:int}
|
||||
*/
|
||||
function mail_queue_run(PDO $pdo, int $limit = 20): array
|
||||
{
|
||||
$res = ['sent' => 0, 'failed' => 0, 'retry' => 0, 'gave_up' => 0];
|
||||
try {
|
||||
$now = db_now();
|
||||
$due = $pdo->prepare("SELECT id FROM mail_queue WHERE status = 'pending' AND next_attempt_at <= ? ORDER BY id LIMIT " . max(1, $limit));
|
||||
$due->execute([$now]);
|
||||
$ids = $due->fetchAll(PDO::FETCH_COLUMN);
|
||||
} catch (Throwable $e) {
|
||||
return $res;
|
||||
}
|
||||
foreach ($ids as $id) {
|
||||
// Reservieren: nur wer die Zeile verschiebt, darf senden
|
||||
$claim = $pdo->prepare("UPDATE mail_queue SET next_attempt_at = ? WHERE id = ? AND status = 'pending' AND next_attempt_at <= ?");
|
||||
$claim->execute([date('Y-m-d H:i:s', time() + 600), $id, db_now()]);
|
||||
if ($claim->rowCount() !== 1) continue;
|
||||
$s = $pdo->prepare('SELECT * FROM mail_queue WHERE id = ?');
|
||||
$s->execute([$id]);
|
||||
$m = $s->fetch();
|
||||
if (!$m) continue;
|
||||
$ok = send_mail($m['to_email'], $m['subject'], $m['body'], $m['from_header'] ?: null, false);
|
||||
$attempts = (int) $m['attempts'] + 1;
|
||||
if ($ok) {
|
||||
$pdo->prepare("UPDATE mail_queue SET status = 'sent', attempts = ?, sent_at = ?, last_error = NULL WHERE id = ?")->execute([$attempts, db_now(), $id]);
|
||||
$res['sent']++;
|
||||
continue;
|
||||
}
|
||||
$err = mb_substr(smtp_last_error() !== '' ? smtp_last_error() : 'Versand fehlgeschlagen', 0, 400);
|
||||
$tooOld = time() - (int) strtotime((string) $m['created_at']) > MAIL_QUEUE_MAX_AGE_HOURS * 3600;
|
||||
if ($attempts >= MAIL_QUEUE_MAX_ATTEMPTS || $tooOld) {
|
||||
$pdo->prepare("UPDATE mail_queue SET status = 'failed', attempts = ?, last_error = ? WHERE id = ?")->execute([$attempts, $err, $id]);
|
||||
$res['gave_up']++;
|
||||
} else {
|
||||
$wait = MAIL_QUEUE_BACKOFF_MINUTES[min($attempts - 1, count(MAIL_QUEUE_BACKOFF_MINUTES) - 1)];
|
||||
$pdo->prepare("UPDATE mail_queue SET attempts = ?, last_error = ?, next_attempt_at = ? WHERE id = ?")->execute([$attempts, $err, date('Y-m-d H:i:s', time() + $wait * 60), $id]);
|
||||
$res['retry']++;
|
||||
}
|
||||
$res['failed']++;
|
||||
}
|
||||
// Aufräumen: Erledigtes und Aufgegebenes nach 30 Tagen löschen
|
||||
try {
|
||||
$pdo->prepare("DELETE FROM mail_queue WHERE status IN ('sent','failed') AND created_at < ?")->execute([date('Y-m-d H:i:s', time() - 30 * 86400)]);
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/** @return array{pending:int, failed:int, sent:int} Anzahl nach Status (für Anzeige und Gesundheitsprüfung) */
|
||||
function mail_queue_counts(PDO $pdo): array
|
||||
{
|
||||
$out = ['pending' => 0, 'failed' => 0, 'sent' => 0];
|
||||
try {
|
||||
foreach ($pdo->query('SELECT status, COUNT(*) AS n FROM mail_queue GROUP BY status')->fetchAll() as $r) {
|
||||
if (isset($out[$r['status']])) $out[$r['status']] = (int) $r['n'];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
@@ -15,7 +15,27 @@ function smtp_last_error(): string
|
||||
return func_num_args() > 0 ? ($lastError = func_get_arg(0)) : $lastError;
|
||||
}
|
||||
|
||||
function send_mail(string $to, string $subject, string $htmlBody, ?string $fromOverride = null): bool
|
||||
/**
|
||||
* Sendet eine Mail. Scheitert der Versand, obwohl ein Versandweg eingerichtet ist, kommt die Mail in die Warteschlange
|
||||
* (includes/mail_queue.php) und wird später erneut versucht; der Rückgabewert bleibt dann false, mail_last_queued() ist true.
|
||||
* $queue = false für Test-Mails und für die Wiederholungsläufe der Warteschlange selbst.
|
||||
*/
|
||||
function send_mail(string $to, string $subject, string $htmlBody, ?string $fromOverride = null, bool $queue = true): bool
|
||||
{
|
||||
mail_attempted(false);
|
||||
mail_last_queued(false);
|
||||
$ok = send_mail_now($to, $subject, $htmlBody, $fromOverride);
|
||||
if (!$ok && $queue && mail_attempted() && mail_queue_enabled()) {
|
||||
try {
|
||||
if (mail_queue_add(get_db(), $to, $subject, $htmlBody, $fromOverride, smtp_last_error()) !== null) mail_last_queued(true);
|
||||
} catch (Throwable $e) {
|
||||
error_log('send_mail queue: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
function send_mail_now(string $to, string $subject, string $htmlBody, ?string $fromOverride = null): bool
|
||||
{
|
||||
$cfg = [
|
||||
'enabled' => get_setting('mail_enabled', config()['mail']['enabled'] ? '1' : '0') === '1',
|
||||
@@ -45,6 +65,7 @@ function send_mail(string $to, string $subject, string $htmlBody, ?string $fromO
|
||||
(string) $own['smtp_username'],
|
||||
$own['smtp_password_enc'] ? decrypt_secret($own['smtp_password_enc']) : '',
|
||||
);
|
||||
mail_attempted(true);
|
||||
$ok = $mailer->send($to, $fromHeader, $subject, $htmlBody);
|
||||
if (!$ok) {
|
||||
smtp_last_error($mailer->getLastError());
|
||||
@@ -62,6 +83,7 @@ function send_mail(string $to, string $subject, string $htmlBody, ?string $fromO
|
||||
get_setting('smtp_username', ''),
|
||||
decrypt_secret(get_setting('smtp_password', '')),
|
||||
);
|
||||
mail_attempted(true);
|
||||
$ok = $mailer->send($to, $fromHeader, $subject, $htmlBody);
|
||||
if (!$ok) {
|
||||
smtp_last_error($mailer->getLastError());
|
||||
@@ -75,6 +97,7 @@ function send_mail(string $to, string $subject, string $htmlBody, ?string $fromO
|
||||
$headers .= "Content-type: text/html; charset=UTF-8\r\n";
|
||||
$headers .= "From: {$fromHeader}\r\n";
|
||||
|
||||
mail_attempted(true);
|
||||
return @mail($to, $subject, $htmlBody, $headers);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ function pseudo_cron_job_definitions(): array
|
||||
'anonymize_data' => ['setting' => 'cron_anonymize_data_last_run', 'interval' => 86400, 'file' => 'anonymize_data.php', 'fn' => 'run_anonymization'],
|
||||
'auto_close_tickets'=> ['setting' => 'cron_auto_close_tickets_last_run','interval' => 86400, 'file' => 'auto_close_tickets.php','fn' => 'run_auto_close_tickets'],
|
||||
'process_snoozes' => ['setting' => 'cron_process_snoozes_last_run', 'interval' => 120, 'file' => 'process_snoozes.php', 'fn' => 'run_process_snoozes'],
|
||||
'process_mail_queue' => ['setting' => 'cron_process_mail_queue_last_run', 'interval' => 120, 'file' => 'process_mail_queue.php', 'fn' => 'run_process_mail_queue'],
|
||||
'remind_customers' => ['setting' => 'cron_remind_customers_last_run', 'interval' => 3600, 'file' => 'remind_customers.php', 'fn' => 'run_remind_customers'],
|
||||
'send_csat' => ['setting' => 'cron_send_csat_last_run', 'interval' => 300, 'file' => 'send_csat.php', 'fn' => 'run_send_csat'],
|
||||
'status_checks' => ['setting' => 'cron_status_checks_last_run', 'interval' => 60, 'file' => 'status_checks.php', 'fn' => 'run_status_checks'],
|
||||
|
||||
@@ -86,7 +86,19 @@ class SmtpMailer
|
||||
STREAM_CLIENT_CONNECT, stream_context_create(['ssl' => ['verify_peer' => true, 'verify_peer_name' => true]])
|
||||
);
|
||||
if (!$this->socket) {
|
||||
throw new \RuntimeException("Verbindung zu {$this->host}:{$this->port} fehlgeschlagen: {$errstr} ({$errno})");
|
||||
// Bei SSL-Fehlern bleibt $errstr oft leer; der eigentliche Grund steht dann in der letzten PHP-Warnung
|
||||
$reason = trim((string) $errstr);
|
||||
if ($reason === '') {
|
||||
$last = (string) (error_get_last()['message'] ?? '');
|
||||
$reason = trim(preg_replace('/^stream_socket_client\(\):\s*/', '', $last));
|
||||
}
|
||||
if ($reason === '') $reason = $this->encryption === 'ssl'
|
||||
? 'keine Antwort. Prüfen, ob Port und Verschlüsselung zusammenpassen (SSL = 465, STARTTLS = 587) und ob der Server ausgehend verbinden darf'
|
||||
: 'keine Antwort (Zeitüberschreitung oder Sperre durch eine Firewall)';
|
||||
if ($this->encryption === 'ssl' && $this->port === 587) $reason .= ' – Port 587 ist für STARTTLS gedacht, bei „SSL/TLS“ gehört Port 465 dazu';
|
||||
elseif ($this->encryption === 'tls' && $this->port === 465) $reason .= ' – Port 465 ist für SSL/TLS gedacht, bei „STARTTLS“ gehört Port 587 dazu';
|
||||
elseif (stripos($reason, 'certificate') !== false || stripos($reason, 'verify') !== false) $reason .= ' – möglicherweise fehlt auf diesem Server die Liste vertrauenswürdiger Zertifizierungsstellen (CA-Bundle: curl.cainfo/openssl.cafile in der PHP-Konfiguration)';
|
||||
throw new \RuntimeException("Verbindung zu {$this->host}:{$this->port} fehlgeschlagen: {$reason}" . ($errno ? " ({$errno})" : ''));
|
||||
}
|
||||
stream_set_timeout($this->socket, $this->timeout);
|
||||
$this->readResponse([220]);
|
||||
|
||||
@@ -87,11 +87,9 @@ function status_execute_component_check(PDO $pdo, array $check): array
|
||||
try { $pdo->query('SELECT 1'); $ok = true; } catch (Throwable $e) { $error = 'Datenbankabfrage fehlgeschlagen'; }
|
||||
} elseif (in_array($check['type'], ['android_app', 'windows_app'], true)) {
|
||||
$platform = $check['type'] === 'windows_app' ? 'windows' : 'android';
|
||||
$connection = status_app_connection($pdo, $platform);
|
||||
$ok = $connection['online'];
|
||||
if (!$ok) $error = $connection['last_seen'] === null
|
||||
? 'Noch keine Verbindung von ' . ($platform === 'windows' ? 'Windows' : 'Android') . ' gemeldet'
|
||||
: 'Keine aktuelle Verbindung von ' . ($platform === 'windows' ? 'Windows' : 'Android');
|
||||
// Erreichbar heißt: Der Server nimmt Anfragen der App an (Datenbank und Schnittstelle laufen). Ob gerade ein PC oder Handy
|
||||
// eingeschaltet ist, sagt darüber nichts: Ohne laufendes Programm ist der Dienst trotzdem erreichbar.
|
||||
try { $pdo->query('SELECT 1'); $ok = true; } catch (Throwable $e) { $error = 'Schnittstelle für die App nicht erreichbar (Datenbankabfrage fehlgeschlagen)'; }
|
||||
} elseif ($check['type'] === 'mail') {
|
||||
$endpoints = status_configured_mail_endpoints($pdo);
|
||||
$ok = count($endpoints) > 0;
|
||||
@@ -334,9 +332,9 @@ function status_component_detected_targets(PDO $pdo, array $check): array
|
||||
$platform = $check['type'] === 'windows_app' ? 'windows' : 'android';
|
||||
$label = $platform === 'windows' ? 'Windows' : 'Android';
|
||||
$connection = status_app_connection($pdo, $platform);
|
||||
if ($connection['last_seen'] === null) return ['Noch keine Verbindung von ' . $label . ' gemeldet'];
|
||||
if ($connection['last_seen'] === null) return ['Server erreichbar · noch kein Gerät mit ' . $label . ' angemeldet gewesen'];
|
||||
$when = format_dt($connection['last_seen']);
|
||||
return [$connection['online'] ? 'Verbunden · letzter Kontakt ' . $when : 'Letzter Kontakt ' . $when . ' · älter als ' . (int) $connection['timeout_minutes'] . ' Minuten'];
|
||||
return ['Server erreichbar · ' . ($connection['online'] ? 'Gerät gerade verbunden, letzter Kontakt ' . $when : 'kein Gerät gerade aktiv, letzter Kontakt ' . $when)];
|
||||
}
|
||||
if ($check['type'] === 'database') {
|
||||
$db = (array) (config()['db'] ?? []);
|
||||
|
||||
Reference in New Issue
Block a user