diff --git a/processor/process_mail.php b/processor/process_mail.php
index cbcf087..7dd7fc2 100644
--- a/processor/process_mail.php
+++ b/processor/process_mail.php
@@ -91,5 +91,29 @@ function process_account(array $account, MailMimeParser $parser): void {
// Déplace le mail vers cur/ (marque comme lu)
rename($filePath, "/var/mail/vhosts/{$domain}/{$account['username']}/Maildir/cur/{$fileName}:2,S");
+
+ // Webhook
+ if (!empty($account['webhook_url'])) {
+ fire_webhook($account['webhook_url'], [
+ 'account' => $account['username'],
+ 'email' => $account['email'],
+ 'subject' => $subject,
+ 'from' => $from,
+ 'source_id' => $sourceId,
+ 'files' => $attachments ? count($attachments) : 0,
+ 'received_at'=> date('c'),
+ ]);
+ }
}
}
+
+function fire_webhook(string $url, array $payload): void {
+ $ctx = stream_context_create(['http' => [
+ 'method' => 'POST',
+ 'header' => "Content-Type: application/json\r\n",
+ 'content' => json_encode($payload),
+ 'timeout' => 5,
+ 'ignore_errors' => true,
+ ]]);
+ @file_get_contents($url, false, $ctx);
+}
diff --git a/sql/schema.sql b/sql/schema.sql
index 9486b53..557532e 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS accounts (
email VARCHAR(200) NOT NULL UNIQUE,
display_name VARCHAR(100),
smb_path VARCHAR(255) NOT NULL,
+ webhook_url VARCHAR(500) NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT NOW()
);
diff --git a/web/admin/accounts.php b/web/admin/accounts.php
index 58369b2..2a81fb9 100644
--- a/web/admin/accounts.php
+++ b/web/admin/accounts.php
@@ -29,9 +29,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($st->fetch()) {
$error = "Le compte « {$username} » existe déjà.";
} else {
+ $webhook = trim($_POST['webhook_url'] ?? '');
db()->prepare(
- 'INSERT INTO accounts (username, email, display_name, smb_path) VALUES (?, ?, ?, ?)'
- )->execute([$username, $email, $display, $smbPath]);
+ 'INSERT INTO accounts (username, email, display_name, smb_path, webhook_url) VALUES (?, ?, ?, ?, ?)'
+ )->execute([$username, $email, $display, $smbPath, $webhook ?: null]);
// Crée le dossier, l'utilisateur Dovecot et le share Samba
$cmd = sprintf(
@@ -114,6 +115,11 @@ html_navbar('accounts');
+
+
+
+
POST JSON à chaque réception de mail.
+
diff --git a/web/api/mails.php b/web/api/mails.php
new file mode 100644
index 0000000..1ec3b2e
--- /dev/null
+++ b/web/api/mails.php
@@ -0,0 +1,34 @@
+query(
+ 'SELECT sf.id, sf.title, sf.sender, sf.date_processing, a.username,
+ GROUP_CONCAT(f.id, ":", f.file_name, ":", f.path ORDER BY f.id SEPARATOR "|") as files
+ FROM source_file sf
+ LEFT JOIN accounts a ON sf.account_id = a.id
+ LEFT JOIN files f ON sf.id = f.source_file_id
+ GROUP BY sf.id
+ ORDER BY sf.date_processing DESC'
+)->fetchAll();
+
+// Vérifie l'existence physique des fichiers
+foreach ($rows as &$r) {
+ $r['files_list'] = [];
+ if ($r['files']) {
+ foreach (explode('|', $r['files']) as $f) {
+ [$fid, $fname, $fpath] = array_pad(explode(':', $f, 3), 3, '');
+ $r['files_list'][] = [
+ 'id' => (int)$fid,
+ 'name' => $fname,
+ 'exists' => file_exists($fpath),
+ ];
+ }
+ }
+ unset($r['files']);
+}
+
+echo json_encode($rows);
diff --git a/web/api/transfer.php b/web/api/transfer.php
new file mode 100644
index 0000000..b738b91
--- /dev/null
+++ b/web/api/transfer.php
@@ -0,0 +1,60 @@
+ 'Method not allowed']); exit;
+}
+
+$sourceId = (int)($_POST['source_id'] ?? 0);
+$targetId = (int)($_POST['target_id'] ?? 0);
+
+if (!$sourceId || !$targetId) {
+ http_response_code(400); echo json_encode(['error' => 'Paramètres manquants']); exit;
+}
+
+// Récupère le compte destination
+$target = db()->prepare('SELECT * FROM accounts WHERE id = ? AND active = 1');
+$target->execute([$targetId]);
+$targetAccount = $target->fetch();
+if (!$targetAccount) {
+ http_response_code(404); echo json_encode(['error' => 'Compte destination introuvable']); exit;
+}
+
+// Récupère les fichiers à transférer
+$files = db()->prepare('SELECT * FROM files WHERE source_file_id = ?');
+$files->execute([$sourceId]);
+$fileList = $files->fetchAll();
+
+$destDir = $targetAccount['smb_path'];
+if (!is_dir($destDir)) mkdir($destDir, 0750, true);
+
+$moved = 0;
+foreach ($fileList as $file) {
+ $newPath = $destDir . '/' . basename($file['path']);
+ // Évite collision
+ if (file_exists($newPath)) {
+ $ext = pathinfo($newPath, PATHINFO_EXTENSION);
+ $base = pathinfo($newPath, PATHINFO_FILENAME);
+ $newPath = $destDir . '/' . $base . '_' . time() . '.' . $ext;
+ }
+ if (file_exists($file['path'])) {
+ rename($file['path'], $newPath);
+ db()->prepare('UPDATE files SET path = ? WHERE id = ?')
+ ->execute([$newPath, $file['id']]);
+ $moved++;
+ }
+}
+
+// Met à jour le compte source_file
+db()->prepare('UPDATE source_file SET account_id = ? WHERE id = ?')
+ ->execute([$targetId, $sourceId]);
+
+echo json_encode([
+ 'success' => true,
+ 'moved' => $moved,
+ 'target' => $targetAccount['username'],
+]);
diff --git a/web/index.php b/web/index.php
index caadb48..0ffe376 100644
--- a/web/index.php
+++ b/web/index.php
@@ -5,12 +5,10 @@ require_once __DIR__ . '/includes/layout.php';
auth_check();
-$message = '';
-
-// Suppression d'un email traité
+// Suppression
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_source'])) {
$id = (int)$_POST['delete_source'];
- $st = db()->prepare('SELECT sf.title, CONCAT(sf.file_name) as fname, GROUP_CONCAT(f.path) as fpaths
+ $st = db()->prepare('SELECT sf.title, GROUP_CONCAT(f.path) as fpaths
FROM source_file sf LEFT JOIN files f ON sf.id = f.source_file_id
WHERE sf.id = ? GROUP BY sf.id');
$st->execute([$id]);
@@ -22,21 +20,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_source'])) {
}
}
db()->prepare('DELETE FROM source_file WHERE id = ?')->execute([$id]);
- flash('success', "Entrée « " . htmlspecialchars($row['title']) . " » supprimée.");
+ flash('success', "Entrée supprimée.");
}
header('Location: /');
exit;
}
-$rows = db()->query(
- 'SELECT sf.id, sf.title, sf.sender, sf.date_processing, a.username,
- GROUP_CONCAT(f.id, ":", f.file_name, ":", f.path ORDER BY f.id SEPARATOR "|") as files
- FROM source_file sf
- LEFT JOIN accounts a ON sf.account_id = a.id
- LEFT JOIN files f ON sf.id = f.source_file_id
- GROUP BY sf.id
- ORDER BY sf.date_processing DESC'
-)->fetchAll();
+// Comptes pour le dropdown transfert
+$otherAccounts = db()->query('SELECT id, username, display_name FROM accounts WHERE active = 1 ORDER BY username')->fetchAll();
html_head('Courriers');
html_navbar('mail');
@@ -45,15 +36,13 @@ html_navbar('mail');
Courriers traités
-
= count($rows) ?> entrée(s)
+
+ —
+ 10s
+
-
-
- Aucun courrier traité pour l'instant.
-
-
-
+
@@ -63,60 +52,158 @@ html_navbar('mail');
| Sujet |
Expéditeur |
Pièces jointes |
- Action |
+ Actions |
-
-
-
- |
- = htmlspecialchars(date('d/m/Y H:i', strtotime($r['date_processing']))) ?>
- |
-
-
- = htmlspecialchars($r['username']) ?>
-
- —
-
- |
- = htmlspecialchars($r['title'] ?? '(sans sujet)') ?> |
- = htmlspecialchars($r['sender'] ?? '') ?> |
-
-
-
-
-
-
- = htmlspecialchars(substr($fname, 0, 20)) ?>
-
-
-
- = htmlspecialchars(substr($fname, 0, 20)) ?>
-
-
-
-
- Aucune pièce jointe
-
- |
-
-
- |
-
-
+
+ | Chargement… |
-
+
+ Aucun courrier traité pour l'instant.
+
+
+
+
+