Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
declare (strict_types = 1);
|
||||
/**
|
||||
* clean_uploads.php
|
||||
* Compatible:
|
||||
* - accès direct /admin/tools/clean_uploads.php
|
||||
* - inclusion via /admin/index.php?page=clean_uploads
|
||||
*/
|
||||
|
||||
// Si APP_HTDOCS n'existe pas, c'est qu'on n'est pas passé par le bootstrap admin
|
||||
if (! defined('APP_HTDOCS')) {
|
||||
require_once __DIR__ . '/../../public/includes/init_admin.php';
|
||||
}
|
||||
|
||||
// Dossier uploads (chez toi: htdocs/public/uploads/)
|
||||
$uploadDir = rtrim(APP_HTDOCS, '/\\') . '/public/uploads/';
|
||||
if (! is_dir($uploadDir)) {
|
||||
http_response_code(500);
|
||||
exit("Uploads introuvable: " . $uploadDir);
|
||||
}
|
||||
$uploadDir = rtrim($uploadDir, "/\\") . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Assurer PDO
|
||||
if (! isset($pdo) || ! ($pdo instanceof PDO)) {
|
||||
http_response_code(500);
|
||||
exit("PDO introuvable (DB non initialisée).");
|
||||
}
|
||||
|
||||
// ⚙️ Sources DB : tu peux en ajouter plusieurs (plusieurs tables/colonnes)
|
||||
$dbSources = [
|
||||
[
|
||||
'sql' => "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];
|
||||
}
|
||||
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Nettoyage uploads (orphelins)</title>
|
||||
<style>
|
||||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;margin:20px;line-height:1.35}
|
||||
.box{padding:12px 14px;border:1px solid #ddd;border-radius:10px;margin:12px 0}
|
||||
.ok{background:#f2fff5;border-color:#bde8c7}
|
||||
.err{background:#fff4f4;border-color:#f0b9b9}
|
||||
table{border-collapse:collapse;width:100%;margin-top:10px}
|
||||
th,td{border-bottom:1px solid #eee;padding:8px 6px;text-align:left;font-size:14px}
|
||||
th{background:#fafafa}
|
||||
code{background:#f6f6f6;padding:2px 6px;border-radius:6px}
|
||||
.muted{color:#666}
|
||||
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
|
||||
.btn{display:inline-block;padding:10px 14px;border-radius:10px;border:1px solid #ccc;background:#fff;cursor:pointer}
|
||||
.btn-danger{border-color:#e3a4a4;background:#fff1f1}
|
||||
.btn:disabled{opacity:.5;cursor:not-allowed}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Nettoyage du dossier uploads</h2>
|
||||
<div class="box">
|
||||
<div class="muted">Dossier :</div>
|
||||
<div><code><?php echo h($uploadDir) ?></code></div>
|
||||
</div>
|
||||
|
||||
<?php if (! empty($deleted)): ?>
|
||||
<div class="box ok">
|
||||
✅ Supprimés : <b><?php echo count($deleted) ?></b>
|
||||
<div class="muted" style="margin-top:6px;">
|
||||
Exemple : <?php echo h($deleted[0]) ?><?php echo count($deleted) > 1 ? ' …' : '' ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (! empty($errors)): ?>
|
||||
<div class="box err">
|
||||
❌ Erreurs :
|
||||
<ul>
|
||||
<?php foreach ($errors as $e): ?>
|
||||
<li><?php echo $e ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div>Fichiers orphelins : <b><?php echo count($orphans) ?></b></div>
|
||||
<div>Taille totale : <b><?php echo h(format_bytes((int) $totalOrphanSize)) ?></b></div>
|
||||
<div class="muted">(triés par taille décroissante)</div>
|
||||
</div>
|
||||
|
||||
<?php if (count($orphans) === 0): ?>
|
||||
<p class="muted" style="margin-top:10px;">Aucun fichier orphelin trouvé ✅</p>
|
||||
<?php else: ?>
|
||||
<form method="post" onsubmit="return confirm('Confirmer la suppression des fichiers orphelins ?');" style="margin-top:12px;">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="csrf" value="<?php echo h($csrf) ?>">
|
||||
|
||||
<label style="display:flex;gap:8px;align-items:center;margin:10px 0;">
|
||||
<input type="checkbox" name="confirm_delete" value="1">
|
||||
<span>Je confirme la suppression de <b><?php echo count($orphans) ?></b> fichiers orphelins (<?php echo h(format_bytes((int) $totalOrphanSize)) ?>).</span>
|
||||
</label>
|
||||
|
||||
<button class="btn btn-danger" type="submit">Supprimer maintenant</button>
|
||||
<span class="muted" style="margin-left:10px;">(Tu as déjà une copie sur ton PC, parfait.)</span>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Fichier</th>
|
||||
<th>Taille</th>
|
||||
<th>Dernière modif</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$max = min(count($orphans), $limitList);
|
||||
for ($i = 0; $i < $max; $i++):
|
||||
$o = $orphans[$i];
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo $i + 1 ?></td>
|
||||
<td><?php echo h($o['name']) ?></td>
|
||||
<td><?php echo h(format_bytes((int) $o['size'])) ?></td>
|
||||
<td><?php echo $o['mtime'] ? h(date('Y-m-d H:i:s', (int) $o['mtime'])) : '-' ?></td>
|
||||
</tr>
|
||||
<?php endfor; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if (count($orphans) > $limitList): ?>
|
||||
<p class="muted">Affichage limité à <?php echo (int) $limitList ?> fichiers. (Mais la suppression concerne tous les orphelins trouvés.)</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user