Files
copymailrpi/web/index.php
2026-05-05 23:46:40 +02:00

247 lines
9.6 KiB
PHP

<?php
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/layout.php';
auth_check();
// Suppression
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_source'])) {
$id = (int)$_POST['delete_source'];
$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]);
$row = $st->fetch();
if ($row) {
if ($row['fpaths']) {
foreach (explode(',', $row['fpaths']) as $path) {
if (file_exists($path)) unlink($path);
}
}
db()->prepare('DELETE FROM source_file WHERE id = ?')->execute([$id]);
flash('success', "Entrée supprimée.");
}
header('Location: /');
exit;
}
// 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');
?>
<div class="container-fluid">
<?php flash_render(); ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0"><i class="bi bi-envelope-fill me-2"></i>Courriers traités</h5>
<div class="d-flex align-items-center gap-2">
<span id="mail-count" class="badge bg-secondary">—</span>
<span class="text-muted small"><i class="bi bi-arrow-clockwise"></i> <span id="refresh-countdown">10</span>s</span>
</div>
</div>
<div class="card shadow-sm" id="mail-card">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Compte</th>
<th>Sujet</th>
<th>Expéditeur</th>
<th>Pièces jointes</th>
<th class="text-center" style="width:110px">Actions</th>
</tr>
</thead>
<tbody id="mail-tbody">
<tr><td colspan="6" class="text-center text-muted py-4">Chargement…</td></tr>
</tbody>
</table>
</div>
</div>
<div id="empty-msg" class="alert alert-info mt-3" style="display:none">
<i class="bi bi-info-circle me-2"></i>Aucun courrier traité pour l'instant.
</div>
</div>
<!-- Menu transfert flottant (hors tableau) -->
<div id="transfer-menu" style="display:none;position:fixed;z-index:9998;min-width:200px" class="card shadow border-0">
<div class="card-header py-2 small fw-semibold text-muted"><i class="bi bi-share me-1"></i>Transférer vers…</div>
<ul class="list-group list-group-flush" id="transfer-menu-list"></ul>
</div>
<!-- Toast -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index:9999">
<div id="toast-el" class="toast align-items-center border-0" role="alert">
<div class="d-flex">
<div class="toast-body" id="toast-body"></div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
</div>
<script>
const ALL_ACCOUNTS = <?= json_encode($otherAccounts) ?>;
let knownIds = null;
let countdown = 10;
let countdownTimer = null;
function showToast(msg, type = 'success') {
const el = document.getElementById('toast-el');
el.className = `toast align-items-center text-bg-${type} border-0`;
document.getElementById('toast-body').textContent = msg;
bootstrap.Toast.getOrCreateInstance(el, {delay: 4000}).show();
}
function buildTransferDropdown(sourceId, currentUsername) {
const others = ALL_ACCOUNTS.filter(a => a.username !== currentUsername);
if (!others.length) {
return `<button class="btn btn-sm btn-outline-secondary" disabled title="Aucun autre compte disponible"><i class="bi bi-share"></i></button>`;
}
return `<button class="btn btn-sm btn-outline-primary" title="Transférer vers…" onclick="openTransferMenu(event,${sourceId},'${currentUsername}')"><i class="bi bi-share"></i></button>`;
}
function openTransferMenu(e, sourceId, currentUsername) {
e.stopPropagation();
const btn = e.currentTarget;
const rect = btn.getBoundingClientRect();
const menu = document.getElementById('transfer-menu');
const list = document.getElementById('transfer-menu-list');
const others = ALL_ACCOUNTS.filter(a => a.username !== currentUsername);
list.innerHTML = others.map(a =>
`<li class="list-group-item list-group-item-action py-2 px-3" style="cursor:pointer"
onclick="doTransfer(event,${sourceId},${a.id},'${escHtml(a.username)}')">
<i class="bi bi-arrow-right-circle me-2 text-primary"></i>${escHtml(a.username)}
${a.display_name ? '<span class="text-muted small"> — '+escHtml(a.display_name)+'</span>' : ''}
</li>`
).join('');
// Positionne sous le bouton, recadre si déborde à droite
menu.style.display = 'block';
const menuW = menu.offsetWidth;
let left = rect.left;
if (left + menuW > window.innerWidth - 8) left = window.innerWidth - menuW - 8;
menu.style.top = (rect.bottom + 4) + 'px';
menu.style.left = left + 'px';
}
// Ferme le menu si on clique ailleurs
document.addEventListener('click', () => {
document.getElementById('transfer-menu').style.display = 'none';
});
function doTransfer(e, sourceId, targetId, targetName) {
e.preventDefault();
const fd = new FormData();
fd.append('source_id', sourceId);
fd.append('target_id', targetId);
fetch('/api/transfer.php', {method:'POST', body: fd})
.then(r => r.json())
.then(data => {
if (data.success) {
showToast(`Transféré vers ${data.target} (${data.moved} fichier(s))`);
loadMails();
} else {
showToast(data.error || 'Erreur lors du transfert', 'danger');
}
})
.catch(() => showToast('Erreur réseau', 'danger'));
}
function renderMails(rows) {
const tbody = document.getElementById('mail-tbody');
const card = document.getElementById('mail-card');
const empty = document.getElementById('empty-msg');
document.getElementById('mail-count').textContent = rows.length + ' entrée(s)';
// Détecte nouveaux mails
const newIds = new Set(rows.map(r => r.id));
if (knownIds !== null && [...newIds].some(id => !knownIds.has(id))) {
showToast('Nouveau courrier reçu !');
}
knownIds = newIds;
if (!rows.length) {
tbody.innerHTML = '';
card.style.display = 'none';
empty.style.display = '';
return;
}
card.style.display = '';
empty.style.display = 'none';
tbody.innerHTML = rows.map(r => {
const date = new Date(r.date_processing).toLocaleString('fr-FR', {
day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'
});
let filesHtml = '<span class="text-muted small">Aucune pièce jointe</span>';
if (r.files_list && r.files_list.length) {
filesHtml = r.files_list.map(f => {
const label = shortName(f.name);
return f.exists
? `<a href="/mail/download.php?id=${f.id}" class="badge bg-info text-decoration-none me-1" title="${escHtml(f.name)}"><i class="bi bi-download me-1"></i>${escHtml(label)}</a>`
: `<span class="badge bg-secondary me-1" title="${escHtml(f.name)}">⚠ ${escHtml(label)}</span>`;
}).join('');
}
const transferBtn = buildTransferDropdown(r.id, r.username);
return `<tr id="row-${r.id}">
<td class="text-nowrap text-muted small">${date}</td>
<td>${r.username ? `<span class="badge bg-primary">${escHtml(r.username)}</span>` : '<span class="text-muted">—</span>'}</td>
<td>${escHtml(r.title || '(sans sujet)')}</td>
<td class="small text-muted">${escHtml(r.sender || '')}</td>
<td>${filesHtml}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
${transferBtn}
<form method="post" class="d-inline" onsubmit="return confirm('Supprimer cette entrée ?')">
<input type="hidden" name="delete_source" value="${r.id}">
<button class="btn btn-sm btn-outline-danger" title="Supprimer"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>`;
}).join('');
}
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function shortName(name) {
const dot = name.lastIndexOf('.');
const ext = dot >= 0 ? name.substring(dot) : '';
const base = dot >= 0 ? name.substring(0, dot) : name;
if (name.length <= 24) return name;
return base.substring(0, 14) + '…' + ext;
}
function loadMails() {
fetch('/api/mails.php')
.then(r => r.json())
.then(renderMails)
.catch(() => {});
}
function startCountdown() {
if (countdownTimer) clearInterval(countdownTimer);
countdown = 10;
const el = document.getElementById('refresh-countdown');
countdownTimer = setInterval(() => {
countdown--;
if (el) el.textContent = countdown;
if (countdown <= 0) {
clearInterval(countdownTimer);
loadMails();
startCountdown();
}
}, 1000);
}
loadMails();
startCountdown();
</script>
<?php html_foot(); ?>