From 0690d02baec86c87f242e8523f4e23ea2306fa1a Mon Sep 17 00:00:00 2001 From: Esenjin Date: Sun, 16 Feb 2025 12:22:19 +0100 Subject: [PATCH] ajout d'une fonction import/export de romans --- admin/api/export-stories.php | 166 ++++++++++++++++++++++++++++++ admin/api/import-stories.php | 179 +++++++++++++++++++++++++++++++++ admin/export-import.php | 190 +++++++++++++++++++++++++++++++++++ admin/index.php | 1 + 4 files changed, 536 insertions(+) create mode 100644 admin/api/export-stories.php create mode 100644 admin/api/import-stories.php create mode 100644 admin/export-import.php diff --git a/admin/api/export-stories.php b/admin/api/export-stories.php new file mode 100644 index 0000000..866e30d --- /dev/null +++ b/admin/api/export-stories.php @@ -0,0 +1,166 @@ + date('Y-m-d H:i:s'), + 'stories' => [] + ]; + + foreach ($selectedIds as $storyId) { + $story = Stories::get($storyId); + if (!$story) continue; + + // Créer un dossier pour ce roman + $storyDir = $tempDir . '/' . $storyId; + mkdir($storyDir); + mkdir($storyDir . '/images'); + + // Copier l'image de couverture si elle existe + if (!empty($story['cover'])) { + $coverPath = __DIR__ . '/../../' . $story['cover']; + if (file_exists($coverPath)) { + copy($coverPath, $storyDir . '/cover' . pathinfo($coverPath, PATHINFO_EXTENSION)); + $story['cover'] = 'cover' . pathinfo($coverPath, PATHINFO_EXTENSION); + } + } + + // Extraire et copier les images des chapitres + foreach ($story['chapters'] as &$chapter) { + if (!empty($chapter['content'])) { + $content = $chapter['content']; + if (is_string($content) && isJson($content)) { + $content = json_decode($content, true); + } + + // Traiter le contenu pour les images + if (is_array($content) && isset($content['ops'])) { + foreach ($content['ops'] as &$op) { + if (is_array($op['insert']) && isset($op['insert']['image'])) { + $imgUrl = $op['insert']['image']; + $imgPath = __DIR__ . '/../../' . preg_replace('/^(?:\.\.\/)+/', '', $imgUrl); + if (file_exists($imgPath)) { + $newImgName = 'image_' . uniqid() . pathinfo($imgPath, PATHINFO_EXTENSION); + copy($imgPath, $storyDir . '/images/' . $newImgName); + $op['insert']['image'] = 'images/' . $newImgName; + } + } + } + $chapter['content'] = json_encode($content); + } + } + } + + // Sauvegarder les données du roman + file_put_contents($storyDir . '/story.json', json_encode($story, JSON_PRETTY_PRINT)); + + // Ajouter au manifeste + $manifest['stories'][] = [ + 'id' => $story['id'], + 'title' => $story['title'] + ]; + } + + // Sauvegarder le manifeste + file_put_contents($tempDir . '/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT)); + + // Créer l'archive ZIP + $zipFile = sys_get_temp_dir() . '/romans_' . date('Y-m-d_His') . '.zip'; + $zip = new ZipArchive(); + + if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) { + addDirToZip($zip, $tempDir, ''); + $zip->close(); + + // Nettoyer le dossier temporaire + deleteDir($tempDir); + + // Envoyer l'archive + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . basename($zipFile) . '"'); + header('Content-Length: ' . filesize($zipFile)); + readfile($zipFile); + unlink($zipFile); + } else { + throw new Exception('Impossible de créer l\'archive ZIP'); + } + +} catch (Exception $e) { + if (isset($tempDir) && file_exists($tempDir)) { + deleteDir($tempDir); + } + if (isset($zipFile) && file_exists($zipFile)) { + unlink($zipFile); + } + + http_response_code(500); + echo 'Erreur lors de l\'export : ' . $e->getMessage(); +} + +// Fonction pour vérifier si une chaîne est du JSON valide +function isJson($string) { + json_decode($string); + return json_last_error() === JSON_ERROR_NONE; +} + +// Fonction récursive pour ajouter un dossier à une archive ZIP +function addDirToZip($zip, $dir, $relativePath) { + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $file) { + if (!$file->isDir()) { + $filePath = $file->getRealPath(); + $zipPath = $relativePath . substr($filePath, strlen($dir)); + + // Normaliser les séparateurs de chemin pour Windows + $zipPath = str_replace('\\', '/', $zipPath); + + $zip->addFile($filePath, $zipPath); + } + } +} + +// Fonction récursive pour supprimer un dossier et son contenu +function deleteDir($dir) { + if (!file_exists($dir)) return; + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + + rmdir($dir); +} \ No newline at end of file diff --git a/admin/api/import-stories.php b/admin/api/import-stories.php new file mode 100644 index 0000000..099b570 --- /dev/null +++ b/admin/api/import-stories.php @@ -0,0 +1,179 @@ +open($_FILES['importFile']['tmp_name']) !== true) { + throw new Exception('Impossible d\'ouvrir l\'archive ZIP'); + } + + $zip->extractTo($tempDir); + $zip->close(); + + // Vérifier la présence du manifeste + $manifestFile = $tempDir . '/manifest.json'; + if (!file_exists($manifestFile)) { + throw new Exception('Archive invalide : manifeste manquant'); + } + + $manifest = json_decode(file_get_contents($manifestFile), true); + if (!$manifest || !isset($manifest['stories'])) { + throw new Exception('Manifeste invalide'); + } + + $importedCount = 0; + $skippedCount = 0; + $errors = []; + + // Parcourir chaque roman dans le manifeste + foreach ($manifest['stories'] as $storyInfo) { + try { + $storyId = $storyInfo['id']; + $storyDir = $tempDir . '/' . $storyId; + + if (!file_exists($storyDir . '/story.json')) { + throw new Exception("Fichier story.json manquant pour {$storyInfo['title']}"); + } + + $story = json_decode(file_get_contents($storyDir . '/story.json'), true); + if (!$story) { + throw new Exception("Données JSON invalides pour {$storyInfo['title']}"); + } + + // Vérifier si le roman existe déjà + $existingStory = Stories::get($storyId); + if ($existingStory && !$overwrite) { + $skippedCount++; + continue; + } + + // Créer les dossiers nécessaires + $coverDir = __DIR__ . '/../../assets/images/covers/'; + $chaptersImgDir = __DIR__ . '/../../assets/images/chapters/' . $storyId; + + if (!file_exists($coverDir)) mkdir($coverDir, 0755, true); + if (!file_exists($chaptersImgDir)) mkdir($chaptersImgDir, 0755, true); + + // Gérer l'image de couverture + if (!empty($story['cover'])) { + $coverFile = $storyDir . '/' . $story['cover']; + if (file_exists($coverFile)) { + $newCoverPath = 'assets/images/covers/' . $storyId . '.' . pathinfo($coverFile, PATHINFO_EXTENSION); + copy($coverFile, __DIR__ . '/../../' . $newCoverPath); + $story['cover'] = $newCoverPath; + } + } + + // Gérer les images des chapitres + foreach ($story['chapters'] as &$chapter) { + if (!empty($chapter['content'])) { + $content = $chapter['content']; + if (is_string($content) && isJson($content)) { + $content = json_decode($content, true); + } + + if (is_array($content) && isset($content['ops'])) { + foreach ($content['ops'] as &$op) { + if (is_array($op['insert']) && isset($op['insert']['image'])) { + $imgPath = $storyDir . '/' . $op['insert']['image']; + if (file_exists($imgPath)) { + $newImgName = 'image_' . uniqid() . '.' . pathinfo($imgPath, PATHINFO_EXTENSION); + copy($imgPath, $chaptersImgDir . '/' . $newImgName); + $op['insert']['image'] = 'assets/images/chapters/' . $storyId . '/' . $newImgName; + } + } + } + $chapter['content'] = json_encode($content); + } + } + } + + // Sauvegarder le roman + Stories::save($story); + $importedCount++; + + } catch (Exception $e) { + $errors[] = "Erreur pour {$storyInfo['title']}: " . $e->getMessage(); + } + } + + // Nettoyer + deleteDir($tempDir); + + // Préparer le message de résultat + $messages = []; + if ($importedCount > 0) { + $messages[] = "$importedCount roman(s) importé(s)"; + } + if ($skippedCount > 0) { + $messages[] = "$skippedCount roman(s) ignoré(s) (déjà existants)"; + } + if (!empty($errors)) { + $messages[] = "Erreurs : " . implode(", ", $errors); + } + + $_SESSION['import_result'] = [ + 'success' => implode(", ", $messages), + 'error' => !empty($errors) ? implode("\n", $errors) : '' + ]; + + header('Location: ../export-import.php'); + exit; + +} catch (Exception $e) { + if (isset($tempDir) && file_exists($tempDir)) { + deleteDir($tempDir); + } + + $_SESSION['import_result'] = [ + 'error' => 'Erreur lors de l\'import : ' . $e->getMessage() + ]; + + header('Location: ../export-import.php'); + exit; +} + +// Fonction pour vérifier si une chaîne est du JSON valide +function isJson($string) { + json_decode($string); + return json_last_error() === JSON_ERROR_NONE; +} + +// Fonction récursive pour supprimer un dossier et son contenu +function deleteDir($dir) { + if (!file_exists($dir)) return; + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + + rmdir($dir); +} \ No newline at end of file diff --git a/admin/export-import.php b/admin/export-import.php new file mode 100644 index 0000000..cdf43a4 --- /dev/null +++ b/admin/export-import.php @@ -0,0 +1,190 @@ + + + + + + + Import/Export des romans - Administration + + + + + + + + +
+

Import/Export des romans

+ + +
+ + + +
+ + + +
+

Exporter des romans

+
+
+ +
+ +
+ +
+ +
+ + +
+
+
+ + +
+

Importer des romans

+
+
+ + +
+ +
+ +
+ + +
+
+
+ + + + + + \ No newline at end of file diff --git a/admin/index.php b/admin/index.php index cd37725..fea6fe6 100644 --- a/admin/index.php +++ b/admin/index.php @@ -37,6 +37,7 @@ $stories = Stories::getAll(); Profil Nouveau roman Options + Import/Export