diff --git a/admin/api/clean-media.php b/admin/api/clean-media.php new file mode 100644 index 0000000..52b59d9 --- /dev/null +++ b/admin/api/clean-media.php @@ -0,0 +1,125 @@ + 'Non autorisé'])); +} + +function extractImagePaths($content) { + $paths = []; + + // Si le contenu est du JSON (format Delta de Quill) + if (is_string($content) && isJson($content)) { + $delta = json_decode($content, true); + if (isset($delta['ops'])) { + foreach ($delta['ops'] as $op) { + if (isset($op['insert']['image'])) { + $paths[] = normalizeImagePath($op['insert']['image']); + } + } + } + } else { + // Si le contenu est du HTML + preg_match_all('/src=["\']([^"\']+)["\']/', $content, $matches); + if (!empty($matches[1])) { + foreach ($matches[1] as $path) { + $paths[] = normalizeImagePath($path); + } + } + } + + return $paths; +} + +function normalizeImagePath($path) { + // Supprimer les "../" au début du chemin + $path = preg_replace('/^(?:\.\.\/)+/', '', $path); + return $path; +} + +function isJson($string) { + json_decode($string); + return json_last_error() === JSON_ERROR_NONE; +} + +try { + $unusedFiles = []; + $usedFiles = []; + $totalSpace = 0; + $freedSpace = 0; + + // Collecter tous les fichiers dans le dossier chapters + $chaptersDir = __DIR__ . '/../../assets/images/chapters/'; + $allFiles = []; + + foreach (glob($chaptersDir . '*', GLOB_ONLYDIR) as $storyDir) { + $storyId = basename($storyDir); + foreach (glob($storyDir . '/*') as $file) { + if (is_file($file)) { + $relativePath = 'assets/images/chapters/' . $storyId . '/' . basename($file); + $allFiles[$relativePath] = $file; + $totalSpace += filesize($file); + } + } + } + + // Parcourir tous les romans et leurs chapitres + $stories = Stories::getAll(); + foreach ($stories as $story) { + // Vérifier la description du roman + if (!empty($story['description'])) { + $usedFiles = array_merge($usedFiles, extractImagePaths($story['description'])); + } + + // Vérifier les chapitres + if (!empty($story['chapters'])) { + foreach ($story['chapters'] as $chapter) { + if (!empty($chapter['content'])) { + $usedFiles = array_merge($usedFiles, extractImagePaths($chapter['content'])); + } + } + } + } + + // Identifier les fichiers non utilisés + foreach ($allFiles as $relativePath => $fullPath) { + if (!in_array($relativePath, $usedFiles)) { + $unusedFiles[] = [ + 'path' => $relativePath, + 'size' => filesize($fullPath) + ]; + $freedSpace += filesize($fullPath); + + // Supprimer le fichier + unlink($fullPath); + } + } + + // Nettoyer les dossiers vides + foreach (glob($chaptersDir . '*', GLOB_ONLYDIR) as $storyDir) { + if (count(glob("$storyDir/*")) === 0) { + rmdir($storyDir); + } + } + + echo json_encode([ + 'success' => true, + 'stats' => [ + 'filesRemoved' => count($unusedFiles), + 'totalSpace' => $totalSpace, + 'freedSpace' => $freedSpace, + 'details' => $unusedFiles + ] + ]); + +} catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); +} \ No newline at end of file diff --git a/admin/options.php b/admin/options.php index ce1b86f..036b734 100644 --- a/admin/options.php +++ b/admin/options.php @@ -212,8 +212,14 @@ $config = Config::load(); ?> - +
+ +

Maintenance

+
+ + Supprime les images qui ne sont plus utilisées dans les romans et chapitres. +
diff --git a/assets/css/forms.css b/assets/css/forms.css index daa31c2..6c8f0e9 100644 --- a/assets/css/forms.css +++ b/assets/css/forms.css @@ -187,6 +187,23 @@ margin-top: var(--spacing-xl); } +/* Nettoyage des médias */ +.maintenance-actions { + margin: var(--spacing-lg) 0; + padding: var(--spacing-md); + background: var(--bg-secondary); + border-radius: var(--radius-sm); +} + +.maintenance-actions button { + margin-bottom: var(--spacing-xs); +} + +.maintenance-actions small { + display: block; + color: var(--text-secondary); +} + /* Responsive */ @media (max-width: 768px) { .options-section { diff --git a/assets/js/options.js b/assets/js/options.js index 87dedd2..d41a7c3 100644 --- a/assets/js/options.js +++ b/assets/js/options.js @@ -192,4 +192,36 @@ document.addEventListener('DOMContentLoaded', function() { setTimeout(() => notification.remove(), 300); }, 3000); } + + // Gestion du nettoyage des médias + const cleanMediaBtn = document.getElementById('cleanMedia'); + if (cleanMediaBtn) { + cleanMediaBtn.addEventListener('click', async () => { + confirmDialog.show({ + title: 'Nettoyage des médias', + message: 'Voulez-vous vraiment supprimer toutes les images qui ne sont plus utilisées ? Cette action est irréversible.', + confirmText: 'Nettoyer', + confirmClass: 'danger', + onConfirm: async () => { + try { + const response = await fetch('api/clean-media.php'); + if (!response.ok) throw new Error('Erreur réseau'); + + const result = await response.json(); + if (result.success) { + const stats = result.stats; + const mbFreed = (stats.freedSpace / (1024 * 1024)).toFixed(2); + const message = `${stats.filesRemoved} fichier(s) supprimé(s).\n${mbFreed} Mo d'espace libéré.`; + showNotification(message); + } else { + throw new Error(result.error || 'Une erreur est survenue'); + } + } catch (error) { + console.error('Erreur:', error); + showNotification(error.message, 'error'); + } + } + }); + }); + } }); \ No newline at end of file