' . htmlspecialchars((string)($translations['admin_access_denied'] ?? 'Accès refusé.'), ENT_QUOTES, 'UTF-8') . '
'; return; } // reauth (10 min) $mustReauth=function(int $ttl=600){$t=(int)($_SESSION['admin_reauth_at']??0);return ($t===0||(time()-$t)>$ttl);}; if ($mustReauth()){ $qs=http_build_query(['page'=>'reauth','redir'=>($_SERVER['REQUEST_URI']??'index.php?page=maintenance')]); header('Location:?'.$qs); exit; } // -- CSRF if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } $CSRF = $_SESSION['csrf_token']; // ----------------------- // Paramètres utilisateur // ----------------------- $uploadsSubdir = 'messages/uploads'; // relatif à la racine du site $minAgeDays = isset($_REQUEST['min_age']) ? max(0, (int)$_REQUEST['min_age']) : 2; $fixDbRefs = isset($_REQUEST['fix_db']) ? ($_REQUEST['fix_db'] === '1') : true; $doLive = false; if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'purge') { // Vérif CSRF if (!hash_equals($CSRF, $_POST['csrf'] ?? '')) { echo '
' . htmlspecialchars((string)($translations['csrf_invalid'] ?? 'Token CSRF invalide.'), ENT_QUOTES, 'UTF-8') . '
'; return; } $doLive = true; } // ----------------------- // Résolution des chemins // ----------------------- $siteRoot = realpath(__DIR__ . '/../../'); // racine du site $uploadsDir = $siteRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $uploadsSubdir); // ----------------------- // Garde-fous fichiers // ----------------------- $errors = []; if (!$siteRoot || !is_dir($siteRoot)) { $errors[] = htmlspecialchars((string)($translations['purge_mess_root_missing'] ?? 'Racine du site introuvable.'), ENT_QUOTES, 'UTF-8'); } if (!is_dir($uploadsDir)) { $tpl = (string)($translations['purge_mess_uploads_dir_missing'] ?? 'Dossier uploads introuvable: {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 // ----------------------- ?>


  • s