Upload via GUI (258 Dateien)

This commit is contained in:
2026-10-16 19:12:18 +00:00
parent 56deade877
commit 6cced3beda
50 changed files with 11148 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
<?php
/**
* includes/imap_spam.php
*
* Markiert per E-Mail eingegangene Tickets als Spam (und macht das rückgängig) –
* verschiebt dabei die Original-E-Mail im zugehörigen IMAP-Postfach in den
* konfigurierten Spam-/Junk-Ordner bzw. bei "Rückgängig" wieder zurück.
*
* set_ticket_spam() ist der gemeinsame Einstiegspunkt für Web (staff/ticket_view.php)
* und die mobile App (api/staff/v1/ticket_spam.php) – beide rufen dieselbe Funktion auf,
* damit sich das Verhalten nie auseinanderentwickelt.
*/
function imap_connect_account(array $account, string $folder): mixed
{
if (!function_exists('imap_open')) return false;
$sslFlag = match ($account['encryption']) {
'ssl' => '/imap/ssl',
'tls' => '/imap/tls',
default => '/imap/notls',
};
$mailbox = '{' . $account['host'] . ':' . $account['port'] . $sslFlag . '}' . $folder;
return @imap_open($mailbox, $account['username'], decrypt_secret($account['password_enc']), 0, 1);
}
function imap_normalize_message_id(?string $value): string
{
return trim(strtolower((string) $value), " \t\r\n<>");
}
/**
* Verschiebt EINE bereits als Ticket importierte Mail in einen Ziel-Ordner
* (Spam beim Markieren, ursprünglicher Posteingang beim Rückgängigmachen).
* Aktualisiert bei Erfolg current_folder in ticket_email_sources, damit
* nachfolgende Aufrufe (erneutes Markieren, Rückgängig machen) wissen, wo die
* Mail gerade tatsächlich liegt.
*/
function move_ticket_email_source(PDO $pdo, array $source, array $account, string $targetFolder): array
{
if (!function_exists('imap_open')) {
return ['ok' => false, 'message' => 'PHP-IMAP ist nicht installiert.'];
}
$sourceFolder = trim((string) ($source['current_folder'] ?? ''));
$targetFolder = trim($targetFolder);
if ($sourceFolder === '' || $targetFolder === '') {
return ['ok' => false, 'message' => 'IMAP-Quell- oder Zielordner fehlt.'];
}
if (strcasecmp($sourceFolder, $targetFolder) === 0) {
return ['ok' => true, 'message' => 'Die E-Mail befindet sich bereits in „' . $targetFolder . '“.'];
}
$mbox = imap_connect_account($account, $sourceFolder);
if (!$mbox) {
return ['ok' => false, 'message' => 'IMAP-Quellordner konnte nicht geöffnet werden: ' . (imap_last_error() ?: 'unbekannter Fehler')];
}
$emailNum = null;
$uid = (int) ($source['message_uid'] ?? 0);
if ($uid > 0) {
$candidate = @imap_msgno($mbox, $uid);
if ($candidate > 0) $emailNum = $candidate;
}
// Nach einem vorherigen Verschieben kann sich die UID geändert haben. Dann über Message-ID suchen.
if (!$emailNum && !empty($source['message_id'])) {
$targetId = imap_normalize_message_id($source['message_id']);
$hits = @imap_search($mbox, 'ALL');
if ($hits) {
foreach ($hits as $num) {
$h = @imap_headerinfo($mbox, $num);
$mid = imap_normalize_message_id($h->message_id ?? '');
if ($mid !== '' && $mid === $targetId) {
$emailNum = (int) $num;
break;
}
}
}
}
if (!$emailNum) {
imap_close($mbox);
return ['ok' => false, 'message' => 'Original-E-Mail wurde im IMAP-Ordner nicht gefunden.'];
}
if (!@imap_mail_move($mbox, (string) $emailNum, $targetFolder)) {
$err = imap_last_error() ?: 'unbekannter IMAP-Fehler';
imap_close($mbox);
return ['ok' => false, 'message' => 'Verschieben nach „' . $targetFolder . '“ fehlgeschlagen: ' . $err];
}
@imap_expunge($mbox);
imap_close($mbox);
if (!empty($source['id'])) {
$pdo->prepare('UPDATE ticket_email_sources SET current_folder = ? WHERE id = ?')
->execute([$targetFolder, (int) $source['id']]);
}
return ['ok' => true, 'message' => 'E-Mail nach „' . $targetFolder . '“ verschoben.'];
}
/**
* Markiert ein E-Mail-Ticket als Spam, oder macht das (mit $restore = true)
* wieder rückgängig. Gemeinsam genutzt von Web und App.
*
* @return array{error: ?string, moved: int, already: int, failed: string[]}
*/
function set_ticket_spam(PDO $pdo, array $ticket, int $staffId, bool $restore = false): array
{
if (($ticket['source'] ?? '') !== 'email') {
return ['error' => 'Nur per E-Mail eingegangene Tickets können als Spam markiert werden.', 'moved' => 0, 'already' => 0, 'failed' => []];
}
if ($restore && empty($ticket['is_spam'])) {
return ['error' => 'Dieses Ticket ist nicht als Spam markiert.', 'moved' => 0, 'already' => 0, 'failed' => []];
}
if (!$restore && !empty($ticket['is_spam'])) {
return ['error' => 'Dieses Ticket ist bereits als Spam markiert.', 'moved' => 0, 'already' => 0, 'failed' => []];
}
$srcStmt = $pdo->prepare(
'SELECT tes.*, ia.label, ia.spam_folder, ia.folder AS inbox_folder, ia.host, ia.port, ia.encryption, ia.username, ia.password_enc
FROM ticket_email_sources tes
JOIN imap_accounts ia ON ia.id = tes.imap_account_id
WHERE tes.ticket_id = ? ORDER BY tes.id'
);
$srcStmt->execute([(int) $ticket['id']]);
$sources = $srcStmt->fetchAll();
$moved = 0;
$already = 0;
$failed = [];
foreach ($sources as $source) {
$targetFolder = $restore
? ($source['inbox_folder'] ?: 'INBOX')
: ($source['spam_folder'] ?: 'Junk');
$result = move_ticket_email_source($pdo, $source, $source, $targetFolder);
if ($result['ok']) {
if (str_contains($result['message'], 'befindet sich bereits')) {
$already++;
} else {
$moved++;
}
} else {
$failed[] = ($source['label'] ?: 'Postfach') . ': ' . $result['message'];
}
}
$now = db_now();
if ($restore) {
$pdo->prepare('UPDATE tickets SET is_spam = 0, updated_at = ? WHERE id = ?')->execute([$now, $ticket['id']]);
log_activity($pdo, $ticket['id'], $staffId, 'Spam-Markierung aufgehoben');
} else {
$pdo->prepare(
"UPDATE tickets SET is_spam=1, status='closed', closed_at=COALESCE(closed_at, ?), updated_at=?, locked_by=NULL, locked_at=NULL WHERE id=?"
)->execute([$now, $now, $ticket['id']]);
finalize_work_session($pdo, (int) $ticket['id'], $staffId);
apply_tag_to_ticket($pdo, (int) $ticket['id'], 'Spam');
log_activity($pdo, $ticket['id'], $staffId, 'Ticket als Spam markiert');
}
return ['error' => null, 'moved' => $moved, 'already' => $already, 'failed' => $failed];
}