"SELECT photo FROM praticien WHERE photo IS NOT NULL AND photo <> ''", 'col' => 'photo', ], // Exemple si tu as d'autres tables : // ['sql' => "SELECT image FROM articles WHERE image IS NOT NULL AND image <> ''", 'col' => 'image'], ]; // --- CSRF token --- if (empty($_SESSION['csrf_clean_uploads'])) { $_SESSION['csrf_clean_uploads'] = bin2hex(random_bytes(32)); } $csrf = $_SESSION['csrf_clean_uploads']; // --- Paramètres --- $limitList = 400; // pour éviter une page énorme si milliers de fichiers $action = $_POST['action'] ?? ''; $confirmed = isset($_POST['confirm_delete']) && $_POST['confirm_delete'] === '1'; // --- 1) Charger noms d'images depuis la DB --- $dbNames = []; foreach ($dbSources as $src) { $stmt = $pdo->query($src['sql']); $rows = $stmt->fetchAll(PDO::FETCH_COLUMN); foreach ($rows as $name) { $name = trim((string) $name); if ($name !== '') { $dbNames[$name] = true; } // set } } // --- 2) Scanner le dossier uploads (fichiers seulement) --- $files = @scandir($uploadDir); if (! is_array($files)) { http_response_code(500); exit('Impossible de lire le dossier uploads.'); } $orphans = []; // [ ['name'=>..., 'size'=>..., 'mtime'=>...] ] $totalOrphanSize = 0; foreach ($files as $f) { if ($f === '.' || $f === '..') { continue; if ($f[0] === '.') { continue; } } // ignore fichiers système fréquents if ($f === '.DS_Store' || $f === 'Thumbs.db' || $f === '.htaccess') { continue; } $full = $uploadDir . $f; // uniquement les fichiers (pas sous-dossiers) if (! is_file($full)) { continue; } // si le fichier n'est pas dans la base => orphelin if (! isset($dbNames[$f])) { $size = filesize($full); $mtime = filemtime($full); $orphans[] = [ 'name' => $f, 'size' => is_int($size) ? $size : 0, 'mtime' => is_int($mtime) ? $mtime : 0, ]; $totalOrphanSize += (is_int($size) ? $size : 0); } } // Tri : plus gros d'abord (pratique pour voir vite les gains) usort($orphans, fn($a, $b) => ($b['size'] <=> $a['size'])); // --- 3) Suppression si demandé --- $deleted = []; $errors = []; if ($action === 'delete') { // CSRF $postedCsrf = (string) ($_POST['csrf'] ?? ''); if (! hash_equals($csrf, $postedCsrf)) { $errors[] = "CSRF invalide. Recharge la page."; } elseif (! $confirmed) { $errors[] = "Coche la confirmation avant de supprimer."; } else { // On supprime uniquement les orphelins trouvés à l’instant T foreach ($orphans as $o) { $name = $o['name']; // Sécurité anti traversal if (str_contains($name, '/') || str_contains($name, '\\') || $name === '') { $errors[] = "Nom de fichier suspect ignoré: " . h($name); continue; } $full = $uploadDir . $name; // recheck réel if (! is_file($full)) { continue; } // s'assurer qu'on reste dans uploadDir $real = realpath($full); if ($real === false || strncmp($real, $uploadDir, strlen($uploadDir)) !== 0) { $errors[] = "Chemin refusé: " . h($name); continue; } if (@unlink($real)) { $deleted[] = $name; } else { $errors[] = "Impossible de supprimer: " . h($name); } } // Re-générer un nouveau token après action destructive $_SESSION['csrf_clean_uploads'] = bin2hex(random_bytes(32)); $csrf = $_SESSION['csrf_clean_uploads']; } } // Helpers function format_bytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; $val = (float) $bytes; while ($val >= 1024 && $i < count($units) - 1) {$val /= 1024; $i++;} return ($i === 0 ? (string) $bytes : number_format($val, 2, '.', ' ')) . ' ' . $units[$i]; } ?> Nettoyage uploads (orphelins)

Nettoyage du dossier uploads

Dossier :
✅ Supprimés :
Exemple : 1 ? ' …' : '' ?>
❌ Erreurs :
Fichiers orphelins :
Taille totale :
(triés par taille décroissante)

Aucun fichier orphelin trouvé ✅

(Tu as déjà une copie sur ton PC, parfait.)
# Fichier Taille Dernière modif
$limitList): ?>

Affichage limité à fichiers. (Mais la suppression concerne tous les orphelins trouvés.)