163 lines
6.5 KiB
PHP
163 lines
6.5 KiB
PHP
<?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];
|
||
}
|