{PATH}');
$errors[] = str_replace('{PATH}', htmlspecialchars((string)$uploadsDir, ENT_QUOTES, 'UTF-8'), $tpl);
}
if (!is_readable($uploadsDir)) {
$errors[] = htmlspecialchars((string)($translations['purge_mess_uploads_not_readable'] ?? 'Dossier uploads non lisible.'), ENT_QUOTES, 'UTF-8');
}
if ($doLive && !is_writable($uploadsDir)) {
$errors[] = htmlspecialchars((string)($translations['purge_mess_uploads_not_writable_live'] ?? 'Dossier uploads non inscriptible alors que purge LIVE demandée.'), ENT_QUOTES, 'UTF-8');
}
// -----------------------
// Utilitaires
// -----------------------
$logs = [];
function logln(string $msg){ global $logs; $logs[] = $msg; }
function ageDays(int $mtime): float { return (time() - $mtime) / 86400.0; }
function safeWithinUploads(string $path, string $uploadsRoot): bool {
$rp = realpath($path);
$ur = realpath($uploadsRoot);
return $rp && $ur && strpos($rp, $ur) === 0;
}
// -----------------------
// Exécution (si OK)
// -----------------------
$start = microtime(true);
$stats = [
'seen' => 0,
'deleted' => 0,
'skipped' => 0,
'fixed' => 0,
];
$deletedList = [];
$dry = !$doLive;
if (empty($errors)) {
// 1) Chemins référencés en DB
$dbPaths = [];
if (isset($pdo) && $pdo instanceof PDO) {
$stmt = $pdo->query("SELECT attachment_path FROM messages WHERE attachment_path IS NOT NULL");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$p = trim((string)$row['attachment_path']);
if ($p === '') continue;
if (strpos($p, $uploadsSubdir . '/') === 0 || $p === $uploadsSubdir) {
$dbPaths[$p] = true;
}
}
$tpl = (string)($translations['purge_mess_db_refs_loaded'] ?? 'Références DB chargées: {N}');
logln(str_replace('{N}', (string)count($dbPaths), $tpl));
} else {
logln($translations['purge_mess_pdo_unavailable_info'] ?? "[INFO] PDO indisponible: l'étape DB sera limitée.");
}
// 2) Balayage disque
$rii = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($uploadsDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_FILEINFO)
);
foreach ($rii as $fi) {
/** @var SplFileInfo $fi */
if (!$fi->isFile()) continue;
$stats['seen']++;
$basename = $fi->getFilename();
if ($basename === '.htaccess' || $basename[0] === '.') continue;
$absPath = $fi->getRealPath();
if (!$absPath) continue;
$relFromUploads = ltrim(str_replace($uploadsDir, '', $absPath), DIRECTORY_SEPARATOR);
$webRel = rtrim($uploadsSubdir, '/').'/'.str_replace(DIRECTORY_SEPARATOR, '/', $relFromUploads);
// référencé ?
if (isset($dbPaths[$webRel])) continue;
// âge
$age = ageDays($fi->getMTime());
if ($age < $minAgeDays) {
$stats['skipped']++;
continue;
}
// garde-fou
if (!safeWithinUploads($absPath, $uploadsDir)) {
$tpl = (string)($translations['purge_mess_alert_outside_scope_skip'] ?? '[ALERTE] Hors périmètre uploads (skip): {PATH}');
logln(str_replace('{PATH}', $absPath, $tpl));
continue;
}
if ($dry) {
$deletedList[] = [$webRel, $age, 'simulation'];
$tpl = (string)($translations['purge_mess_log_dry_orphan_sim'] ?? '[DRY] Orphelin: {FILE} ({DAYS} j) -> suppression simulée');
logln(str_replace(
['{FILE}','{DAYS}'],
[$webRel, number_format($age,1)],
$tpl
));
} else {
if (@unlink($absPath)) {
$stats['deleted']++;
$deletedList[] = [$webRel, $age, 'supprimé'];
$tpl = (string)($translations['purge_mess_log_ok_deleted'] ?? '[OK] Supprimé: {FILE} ({DAYS} j)');
logln(str_replace(
['{FILE}','{DAYS}'],
[$webRel, number_format($age,1)],
$tpl
));
} else {
$tpl = (string)($translations['purge_mess_log_warn_delete_failed'] ?? '[WARN] Échec suppression: {FILE}');
logln(str_replace('{FILE}', $webRel, $tpl));
}
}
}
// 3) Normaliser DB si fichier manquant
if ($fixDbRefs && isset($pdo) && $pdo instanceof PDO) {
$stmt = $pdo->query("SELECT id, attachment_path FROM messages WHERE attachment_path IS NOT NULL");
$update = $pdo->prepare("UPDATE messages SET attachment_path = NULL, attachment_name = NULL WHERE id = ?");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$id = (int)$row['id'];
$p = (string)$row['attachment_path'];
if ($p === '' || strpos($p, $uploadsSubdir . '/') !== 0) continue;
$abs = $siteRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $p);
$absReal = realpath($abs);
if (!$absReal || !is_file($absReal)) {
if ($dry) {
$tpl = (string)($translations['purge_mess_log_dry_db_fix'] ?? '[DRY] DB FIX id={ID} : {PATH} -> NULL (fichier manquant)');
logln(str_replace(['{ID}','{PATH}'], [(string)$id, $p], $tpl));
} else {
$update->execute([$id]);
$stats['fixed'] += $update->rowCount();
$tpl = (string)($translations['purge_mess_log_ok_db_fix'] ?? '[OK] DB FIX id={ID} : {PATH} -> NULL');
logln(str_replace(['{ID}','{PATH}'], [(string)$id, $p], $tpl));
}
}
}
}
}
$dur = microtime(true) - $start;
// -----------------------
// Rendu Bootstrap
// -----------------------
?>
= htmlspecialchars($uploadsDir, ENT_QUOTES, 'UTF-8') ?>
| = htmlspecialchars((string)($translations['th_file'] ?? 'Fichier'), ENT_QUOTES, 'UTF-8') ?> | = htmlspecialchars((string)($translations['th_age_days'] ?? 'Âge (j)'), ENT_QUOTES, 'UTF-8') ?> | = htmlspecialchars((string)($translations['th_action'] ?? 'Action'), ENT_QUOTES, 'UTF-8') ?> |
|---|---|---|
= htmlspecialchars($webRel, ENT_QUOTES, 'UTF-8') ?> |
= number_format($age, 1) ?> | = htmlspecialchars((string)($translations[$act === 'supprimé' ? 'status_deleted' : 'status_simulation'] ?? $act), ENT_QUOTES, 'UTF-8') ?> |
= htmlspecialchars(implode("\n", $logs), ENT_QUOTES, 'UTF-8') ?>