<?php require_once '../includes/config.php'; require_once '../includes/auth.php'; require_once '../includes/stories.php'; require_once 'upload-handler.php'; if (!Auth::check()) { header('Location: login.php'); exit; } $story = null; $error = ''; $success = ''; // Chargement du roman existant si ID fourni if (isset($_GET['id'])) { $story = Stories::get($_GET['id']); if (!$story) { header('Location: index.php'); exit; } // Vérification des permissions d'accès if (!Auth::isAdmin() && !Auth::canAccessStory($_GET['id'])) { header('Location: index.php'); exit; } } // Traitement de la sauvegarde if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $storyData = [ 'id' => $_POST['id'] ?? generateSlug($_POST['title']), 'title' => $_POST['title'], 'description' => $_POST['description'], 'cover' => $story['cover'] ?? '', 'created' => $story['created'] ?? date('Y-m-d'), 'updated' => date('Y-m-d'), 'chapters' => $story['chapters'] ?? [] ]; // Gestion des permissions d'accès if ((Auth::isAdmin() || Auth::hasAdminRole()) && isset($_POST['story_access'])) { try { $storyData['access'] = json_decode($_POST['story_access'], true) ?? []; } catch (Exception $e) { // En cas d'erreur de parsing, on utilise un tableau vide $storyData['access'] = []; } } // Gestion de l'upload de couverture if (isset($_FILES['cover']) && $_FILES['cover']['error'] !== UPLOAD_ERR_NO_FILE) { $uploadHandler = new CoverUploadHandler(); $storyData['cover'] = $uploadHandler->handleUpload($_FILES['cover'], $storyData['id']); } Stories::save($storyData); $success = 'Roman sauvegardé avec succès'; $story = $storyData; } catch (Exception $e) { $error = 'Erreur lors de la sauvegarde : ' . $e->getMessage(); } } function generateSlug($title) { $slug = strtolower($title); $slug = preg_replace('/[^a-z0-9-]/', '-', $slug); $slug = preg_replace('/-+/', '-', $slug); return trim($slug, '-'); } ?> <!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?= $story ? 'Modifier' : 'Nouveau' ?> roman - Administration</title> <?php if (file_exists(__DIR__ . '/../assets/images/site/favicon.png')): ?> <link rel="icon" type="image/png" href="../assets/images/site/favicon.png"> <?php endif; ?> <link rel="stylesheet" href="../assets/css/main.css"> <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"> <?php if (Auth::isAdmin() || Auth::hasAdminRole()): ?> <script> // Cette variable sera utilisée par la modale de gestion des accès const storyAccess = <?= json_encode($story['access'] ?? []) ?>; </script> <?php endif; ?> </head> <body> <nav class="admin-nav"> <div class="nav-brand"> <?php $config = Config::load(); if (!empty($config['site']['logo'])): ?> <img src="<?= htmlspecialchars('../' . $config['site']['logo']) ?>" alt="<?= htmlspecialchars($config['site']['name']) ?>"> <?php endif; ?> <span>Administration</span> </div> <div class="nav-menu"> <a href="index.php" class="button tooltip" data-tooltip="Retour"> <i class="fas fa-arrow-left"></i> </a> </div> </nav> <main class="admin-main"> <h1><?= $story ? 'Modifier le' : 'Nouveau' ?> roman</h1> <?php if ($error): ?> <div class="error-message"><?= htmlspecialchars($error) ?></div> <?php endif; ?> <?php if ($success): ?> <div class="success-message"><?= htmlspecialchars($success) ?></div> <?php endif; ?> <form method="POST" enctype="multipart/form-data" class="story-form"> <?php if ($story): ?> <input type="hidden" name="id" value="<?= htmlspecialchars($story['id']) ?>"> <?php endif; ?> <div class="form-group"> <label for="title">Titre</label> <input type="text" id="title" name="title" required value="<?= htmlspecialchars($story['title'] ?? '') ?>"> </div> <div class="form-group"> <label for="description">Description</label> <input type="hidden" id="description" name="description"> <div id="descriptionEditor"></div> </div> <div class="form-group"> <label for="cover">Image de couverture</label> <?php if (isset($story['cover'])): ?> <img src="<?= htmlspecialchars('../' . $story['cover']) ?>" alt="Couverture actuelle" class="current-cover"> <?php endif; ?> <input type="file" id="cover" name="cover" accept="image/*"> </div> <button type="submit" class="button tooltip" data-tooltip="Enregistrer"> <i class="fas fa-save"></i> <span class="tooltip-text">Enregistrer</span> </button> </form> <?php if ($story): ?> <section class="chapters-section"> <h2>Chapitres</h2> <button type="button" id="addChapter" class="button"> <i class="fas fa-plus"></i> <span class="button-text">Ajouter un chapitre</span> </button> <div id="chaptersList" class="chapters-list"> <?php foreach ($story['chapters'] ?? [] as $index => $chapter): ?> <div class="chapter-item" data-id="<?= htmlspecialchars($chapter['id']) ?>"> <span class="chapter-number"><?= $index + 1 ?></span> <h3 class="chapter-title"><?= htmlspecialchars($chapter['title']) ?></h3> <div class="chapter-actions"> <button type="button" class="edit-chapter"> <i class="fas fa-edit"></i> <span class="button-text">Éditer</span> </button> <button type="button" class="edit-cover"> <i class="fas fa-image"></i> <span class="button-text">Couverture</span> </button> <button type="button" class="delete-chapter"> <i class="fas fa-trash-alt"></i> <span class="button-text">Supprimer</span> </button> </div> </div> <?php endforeach; ?> </div> </section> <div id="chapterEditor" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>Éditer un chapitre</h2> <input type="text" id="chapterTitle" placeholder="Titre du chapitre"> <label class="draft-toggle"> <input type="checkbox" id="chapterDraft"> <span class="toggle-label">Mode brouillon</span> </label> </div> <div class="editor-container"> <div id="editor"></div> </div> <div class="modal-footer"> <button type="button" id="cancelEdit" class="button dark"> <i class="fas fa-times"></i> <span class="button-text">Annuler</span> </button> <button type="button" id="saveChapter" class="button"> <i class="fas fa-save"></i> <span class="button-text">Enregistrer</span> </button> </div> </div> </div> <?php endif; ?> <!-- Modale pour la couverture de chapitre --> <div id="chapterCoverEditor" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2>Couverture du chapitre</h2> <p class="chapter-title-display"></p> </div> <div class="cover-preview-container"> <div class="current-cover-preview"></div> <div class="form-group"> <label for="chapterCover">Nouvelle image de couverture</label> <input type="file" id="chapterCover" accept="image/*"> <small>Formats acceptés : JPG, PNG, GIF. Taille maximum : 5MB</small> </div> </div> <div class="modal-footer"> <button type="button" id="cancelCoverEdit" class="button dark"> <i class="fas fa-times"></i> <span class="button-text">Annuler</span> </button> <button type="button" id="deleteCover" class="button delete-story" style="display: none;"> <i class="fas fa-trash-alt"></i> <span class="button-text">Supprimer</span> </button> <button type="button" id="saveCover" class="button"> <i class="fas fa-save"></i> <span class="button-text">Enregistrer</span> </button> </div> </div> </div> </main> <script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script> <script src="../assets/js/story-edit.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { // Configuration de l'éditeur de description // Récupérer le storyId depuis l'input caché ou l'URL const urlParams = new URLSearchParams(window.location.search); const currentStoryId = document.querySelector('input[name="id"]')?.value || urlParams.get('id'); // Configuration des handlers pour les images const imageUploadHandler = (editor) => { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', 'image/*'); input.click(); input.onchange = async () => { const file = input.files[0]; if (file) { const formData = new FormData(); formData.append('image', file); formData.append('storyId', currentStoryId); try { const response = await fetch('api/upload-image.php', { method: 'POST', body: formData }); if (!response.ok) throw new Error('Upload failed'); const result = await response.json(); if (result.success) { const range = editor.getSelection(true); editor.insertEmbed(range.index, 'image', result.url); editor.setSelection(range.index + 1); } else { showNotification(result.error || 'Erreur lors de l\'upload', 'error'); } } catch (error) { console.error('Error:', error); showNotification('Erreur lors de l\'upload de l\'image', 'error'); } } }; }; const descriptionEditor = new Quill('#descriptionEditor', { theme: 'snow', modules: { toolbar: { container: [ [{ 'header': [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'strike'], [{ 'color': [] }, { 'background': [] }], [{ 'font': [] }], [{ 'align': [] }], ['blockquote', 'code-block'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'script': 'sub'}, { 'script': 'super' }], [{ 'indent': '-1'}, { 'indent': '+1' }], [{ 'direction': 'rtl' }], ['link', 'image', 'video'], ['divider'], ['clean'] ], handlers: { image: function() { imageUploadHandler(descriptionEditor); } } }, keyboard: { bindings: { tab: false, 'indent backwards': false } } }, placeholder: 'Commencez à écrire la description du roman...' }); // Initialiser le contenu si on édite un roman existant <?php if (isset($story['description'])): ?> descriptionEditor.root.innerHTML = <?= json_encode($story['description']) ?>; <?php endif; ?> // Mettre à jour le champ caché avant la soumission const form = document.querySelector('form'); form.addEventListener('submit', function() { const description = document.querySelector('#description'); description.value = descriptionEditor.root.innerHTML; }); // Fonction de notification pour l'éditeur function showNotification(message, type = 'success') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }, 10); setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-100%)'; setTimeout(() => notification.remove(), 300); }, 3000); } }); </script> <link rel="stylesheet" href="../assets/css/dialog.css"> <script src="../assets/js/dialog.js"></script> </body> </html>