diff --git a/app/Console/Commands/UpdateMailboxStats.php b/app/Console/Commands/UpdateMailboxStats.php index efb3a9d..3d2f7f9 100644 --- a/app/Console/Commands/UpdateMailboxStats.php +++ b/app/Console/Commands/UpdateMailboxStats.php @@ -16,7 +16,6 @@ class UpdateMailboxStats extends Command { $q = MailUser::query() ->where('is_active', true) - // sichere Filter: E-Mail vorhanden und enthält genau ein "@" ->whereNotNull('email') ->where('email', 'like', '%@%'); @@ -33,14 +32,14 @@ class UpdateMailboxStats extends Command foreach ($users as $u) { $email = trim($u->email); if (!preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { - // still und leise überspringen – kein „Überspringe @“ mehr + // ungültig → überspringen continue; } [$local, $domain] = explode('@', $email, 2); $maildir = "/var/mail/vhosts/{$domain}/{$local}"; - // 1) Größe in Bytes (rekursiv; ohne "du") + // 1) Größe (rekursiv, ohne `du`) $usedBytes = 0; if (is_dir($maildir)) { $it = new RecursiveIteratorIterator( @@ -53,29 +52,17 @@ class UpdateMailboxStats extends Command } } - // 2) Message-Count = Dateien in cur/ + new/ - $messageCount = 0; - foreach (['cur', 'new'] as $sub) { - $dir = "{$maildir}/{$sub}"; - if (is_dir($dir)) { - $dh = opendir($dir); - if ($dh) { - while (($fn = readdir($dh)) !== false) { - // echte Maildir-Dateien haben keinen führenden Punkt - if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') { - $messageCount++; - } - } - closedir($dh); - } - } + // 2) Nachrichtenzahl – bevorzugt via doveadm (sudo -u vmail), Fallback: Dateien zählen + $messageCount = $this->messageCountViaDoveadm($email); + if ($messageCount === null) { + $messageCount = $this->messageCountViaFilesystem($maildir); } // Update DB $u->forceFill([ - 'used_bytes' => $usedBytes, - 'message_count' => $messageCount, - 'stats_refreshed_at'=> now(), + 'used_bytes' => $usedBytes, + 'message_count' => (int) $messageCount, + 'stats_refreshed_at' => now(), ])->save(); $this->line(sprintf( @@ -89,4 +76,61 @@ class UpdateMailboxStats extends Command $this->info('Mailbox-Statistiken aktualisiert.'); return self::SUCCESS; } + + /** + * Holt die Anzahl der Nachrichten in INBOX über doveadm als vmail (sudo). + * Gibt int bei Erfolg, oder null bei Fehlern zurück. + */ + private function messageCountViaDoveadm(string $email): ?int + { + // bevorzugtes Format: tabellarisch → "INBOX\t123" + $cmd = sprintf( + "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u %s messages INBOX 2>/dev/null", + escapeshellarg($email) + ); + $out = trim((string) shell_exec($cmd)); + + if ($out === '') { + // Fallback auf normales Format: "messages=123" + $cmd2 = sprintf( + "sudo -n -u vmail /usr/bin/doveadm mailbox status -u %s messages INBOX 2>/dev/null", + escapeshellarg($email) + ); + $out = trim((string) shell_exec($cmd2)); + } + + if ($out === '') { + return null; + } + + // Match "INBOX123" oder "messages=123" + if (preg_match('/\t(\d+)\s*$/', $out, $m) || preg_match('/messages=(\d+)/', $out, $m)) { + return (int) $m[1]; + } + + return null; + } + + /** + * Fallback: Zählt Dateien in cur/ + new/ (Maildir). + */ + private function messageCountViaFilesystem(string $maildir): int + { + $count = 0; + foreach (['cur', 'new'] as $sub) { + $dir = "{$maildir}/{$sub}"; + if (is_dir($dir) && is_readable($dir)) { + $dh = opendir($dir); + if ($dh) { + while (($fn = readdir($dh)) !== false) { + if ($fn !== '.' && $fn !== '..' && $fn[0] !== '.') { + $count++; + } + } + closedir($dh); + } + } + } + return $count; + } }