requireLogin(); $user_id = $_SESSION['user_id']; $bookModel = new Book($this->pdo); $books = $bookModel->findByUser($user_id); $this->render('books/index', [ 'books' => $books, 'page_title' => 'Мои книги' ]); } public function create() { $this->requireLogin(); $seriesModel = new Series($this->pdo); $series = $seriesModel->findByUser($_SESSION['user_id']); $error = ''; $cover_error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!verify_csrf_token($_POST['csrf_token'] ?? '')) { $_SESSION['error'] = "Ошибка безопасности"; $this->redirect('/books/create'); } $title = trim($_POST['title'] ?? ''); if (empty($title)) { $_SESSION['error'] = "Название книги обязательно"; $this->redirect('/books/create'); } $is_short_story = isset($_POST['is_short_story']) ? 1 : 0; $content = $is_short_story ? ($_POST['content'] ?? '') : null; // Валидация: для рассказа проверяем, что есть хоть какое-то содержание if ($is_short_story && empty(trim(strip_tags($content)))) { $_SESSION['error'] = "Для рассказа необходимо заполнить содержание"; $_SESSION['form_data'] = $_POST; $this->redirect('/books/create'); } $bookModel = new Book($this->pdo); $data = [ 'title' => $title, 'description' => trim($_POST['description'] ?? ''), 'genre' => trim($_POST['genre'] ?? ''), 'user_id' => $_SESSION['user_id'], 'series_id' => !empty($_POST['series_id']) ? (int)$_POST['series_id'] : null, 'sort_order_in_series' => !empty($_POST['sort_order_in_series']) ? (int)$_POST['sort_order_in_series'] : null, 'published' => isset($_POST['published']) ? 1 : 0, 'is_short_story' => $is_short_story, 'content' => $content ]; if ($bookModel->create($data)) { $new_book_id = $this->pdo->lastInsertId(); // Обработка загрузки обложки if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) { $cover_result = handleCoverUpload($_FILES['cover_image'], $new_book_id); if ($cover_result['success']) { $bookModel->updateCover($new_book_id, $cover_result['filename']); } else { $cover_error = $cover_result['error']; // Сохраняем ошибку в сессии, чтобы показать после редиректа $_SESSION['cover_error'] = $cover_error; } } // Если это рассказ с содержанием - редиректим на редактирование // Если рассказ без содержания - тоже на редактирование (чтобы добавить текст) if ($is_short_story) { $_SESSION['success'] = "Рассказ успешно создан! Теперь можно добавить содержание."; $this->redirect("/books/{$new_book_id}/edit"); } else { $_SESSION['success'] = "Книга успешно создана" . ($cover_error ? ", но возникла ошибка с обложкой: " . $cover_error : ""); $this->redirect("/books/{$new_book_id}/chapters/create"); } } else { $_SESSION['error'] = "Ошибка при создании книги"; } } $this->render('books/create', [ 'series' => $series, 'error' => $error, 'cover_error' => $cover_error, 'page_title' => 'Создание новой книги' ]); } public function edit($id) { $this->requireLogin(); $bookModel = new Book($this->pdo); $book = $bookModel->findById($id); if (!$book || $book['user_id'] != $_SESSION['user_id']) { $_SESSION['error'] = "Книга не найдена или у вас нет доступа"; $this->redirect('/books'); } $seriesModel = new Series($this->pdo); $series = $seriesModel->findByUser($_SESSION['user_id']); $error = ''; $cover_error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Обработка запроса автосохранения if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') { header('Content-Type: application/json'); // Подавляем ошибки для автосейва error_reporting(0); if (!verify_csrf_token($_POST['csrf_token'] ?? '')) { echo json_encode(['success' => false, 'error' => 'Ошибка безопасности']); exit; } $book = $bookModel->findById($id); if (!$book || $book['user_id'] != $_SESSION['user_id']) { echo json_encode(['success' => false, 'error' => 'Книга не найдена']); exit; } $is_short_story = isset($_POST['is_short_story']) ? 1 : 0; $content = $is_short_story ? ($_POST['content'] ?? '') : null; // Для autosave обновляем только title и user_id из сессии, остальное получаем из базы $title = trim($_POST['title'] ?? $book['title']); $description = trim($_POST['description'] ?? ($book['description'] ?? '')); $genre = trim($_POST['genre'] ?? ($book['genre'] ?? '')); $user_id = $_SESSION['user_id']; $series_id = !empty($_POST['series_id']) ? (int)$_POST['series_id'] : $book['series_id']; $sort_order_in_series = !empty($_POST['sort_order_in_series']) ? (int)$_POST['sort_order_in_series'] : $book['sort_order_in_series']; $published = isset($_POST['published']) ? 1 : $book['published']; $data = [ 'title' => $title, 'description' => $description, 'genre' => $genre, 'user_id' => $user_id, 'series_id' => $series_id, 'sort_order_in_series' => $sort_order_in_series, 'published' => $published, 'is_short_story' => $is_short_story, 'content' => $content ]; $success = $bookModel->update($id, $data); header('Content-Type: application/json'); if ($success) { echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'error' => 'Ошибка при сохранении']); } exit; } if (!verify_csrf_token($_POST['csrf_token'] ?? '')) { $error = "Ошибка безопасности"; } else { $title = trim($_POST['title'] ?? ''); if (empty($title)) { $error = "Название книги обязательно"; } else { $is_short_story = isset($_POST['is_short_story']) ? 1 : 0; $content = $is_short_story ? ($_POST['content'] ?? '') : null; $data = [ 'title' => $title, 'description' => trim($_POST['description'] ?? ''), 'genre' => trim($_POST['genre'] ?? ''), 'user_id' => $_SESSION['user_id'], 'series_id' => !empty($_POST['series_id']) ? (int)$_POST['series_id'] : null, 'sort_order_in_series' => !empty($_POST['sort_order_in_series']) ? (int)$_POST['sort_order_in_series'] : null, 'published' => isset($_POST['published']) ? 1 : 0, 'is_short_story' => $is_short_story, 'content' => $content ]; // Обработка обложки if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) { $cover_result = handleCoverUpload($_FILES['cover_image'], $id); if ($cover_result['success']) { $bookModel->updateCover($id, $cover_result['filename']); } else { $cover_error = $cover_result['error']; } } // Удаление обложки if (isset($_POST['delete_cover']) && $_POST['delete_cover'] == '1') { $bookModel->deleteCover($id); } // Обновление книги $success = $bookModel->update($id, $data); if ($success) { $success_message = "Книга успешно обновлена"; $_SESSION['success'] = $success_message; $this->redirect("/books/{$id}/edit"); } else { $error = "Ошибка при обновлении книги"; } } } } // Получаем статистику по главам для отображения в шаблоне $chapterModel = new Chapter($this->pdo); $chapters = $chapterModel->findByBook($id); $this->render('books/edit', [ 'book' => $book, 'series' => $series, 'chapters' => $chapters, 'error' => $error, 'cover_error' => $cover_error, 'page_title' => 'Редактирование книги' ]); } public function delete($id) { $this->requireLogin(); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $_SESSION['error'] = "Неверный метод запроса"; $this->redirect('/books'); } if (!verify_csrf_token($_POST['csrf_token'] ?? '')) { $_SESSION['error'] = "Ошибка безопасности"; $this->redirect('/books'); } $user_id = $_SESSION['user_id']; $bookModel = new Book($this->pdo); if (!$bookModel->userOwnsBook($id, $user_id)) { $_SESSION['error'] = "У вас нет доступа к этой книге"; $this->redirect('/books'); } if ($bookModel->delete($id, $user_id)) { $_SESSION['success'] = "Книга успешно удалена"; } else { $_SESSION['error'] = "Ошибка при удалении книги"; } $this->redirect('/books'); } public function deleteAll() { $this->requireLogin(); $user_id = $_SESSION['user_id']; if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !verify_csrf_token($_POST['csrf_token'] ?? '')) { $_SESSION['error'] = "Ошибка безопасности"; $this->redirect('/books'); } $bookModel = new Book($this->pdo); // Получаем все книги пользователя $books = $bookModel->findByUser($user_id); if (empty($books)) { $_SESSION['info'] = "У вас нет книг для удаления"; $this->redirect('/books'); } try { $this->pdo->beginTransaction(); $deleted_count = 0; $deleted_covers = 0; foreach ($books as $book) { // Удаляем обложку если она есть if (!empty($book['cover_image'])) { $cover_path = COVERS_PATH . $book['cover_image']; if (file_exists($cover_path) && unlink($cover_path)) { $deleted_covers++; } } // Удаляем главы книги $stmt = $this->pdo->prepare("DELETE FROM chapters WHERE book_id = ?"); $stmt->execute([$book['id']]); // Удаляем саму книгу $stmt = $this->pdo->prepare("DELETE FROM books WHERE id = ? AND user_id = ?"); $stmt->execute([$book['id'], $user_id]); $deleted_count++; } $this->pdo->commit(); $message = "Все книги успешно удалены ($deleted_count книг"; if ($deleted_covers > 0) { $message .= ", удалено $deleted_covers обложек"; } $message .= ")"; $_SESSION['success'] = $message; } catch (Exception $e) { $this->pdo->rollBack(); error_log("Ошибка при массовом удалении: " . $e->getMessage()); $_SESSION['error'] = "Произошла ошибка при удалении книг: " . $e->getMessage(); } $this->redirect('/books'); } public function viewPublic($share_token) { $bookModel = new Book($this->pdo); $chapterModel = new Chapter($this->pdo); $book = $bookModel->findByShareToken($share_token); if (!$book) { http_response_code(404); $this->render('errors/404'); return; } // Если это рассказ - не загружаем главы if ($book['is_short_story']) { $chapters = []; $is_short_story = true; } else { $chapters = $chapterModel->getPublishedChapters($book['id']); $is_short_story = false; } // Получаем информацию об авторе $stmt = $this->pdo->prepare("SELECT id, username, display_name FROM users WHERE id = ?"); $stmt->execute([$book['user_id']]); $author = $stmt->fetch(PDO::FETCH_ASSOC); $this->render('books/view_public', [ 'book' => $book, 'chapters' => $chapters, 'author' => $author, 'is_short_story' => $is_short_story, 'page_title' => $book['title'] ]); } public function viewAll($id) { $this->requireLogin(); $user_id = $_SESSION['user_id']; $bookModel = new Book($this->pdo); $chapterModel = new Chapter($this->pdo); $book = $bookModel->findByUserBook($id, $user_id); if (!$book) { http_response_code(404); $this->render('errors/404'); return; } // Если это рассказ - не загружаем главы if ($book['is_short_story']) { $chapters = []; $is_short_story = true; } else { $chapters = $chapterModel->findByBook($book['id']); $is_short_story = false; } // Получаем информацию об авторе $stmt = $this->pdo->prepare("SELECT id, username, display_name FROM users WHERE id = ?"); $stmt->execute([$book['user_id']]); $author = $stmt->fetch(PDO::FETCH_ASSOC); $this->render('books/view_public', [ 'book' => $book, 'chapters' => $chapters, 'author' => $author, 'is_short_story' => $is_short_story, 'page_title' => $book['title'] ]); } public function regenerateToken($id) { $this->requireLogin(); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $_SESSION['error'] = "Неверный метод запроса"; $this->redirect("/books/{$id}/edit"); } if (!verify_csrf_token($_POST['csrf_token'] ?? '')) { $_SESSION['error'] = "Ошибка безопасности"; $this->redirect("/books/{$id}/edit"); } $user_id = $_SESSION['user_id']; $bookModel = new Book($this->pdo); if (!$bookModel->userOwnsBook($id, $user_id)) { $_SESSION['error'] = "У вас нет доступа к этой книге"; $this->redirect('/books'); } $new_token = $bookModel->generateNewShareToken($id); if ($new_token) { $_SESSION['success'] = "Ссылка успешно обновлена"; } else { $_SESSION['error'] = "Ошибка при обновлении ссылки"; } $this->redirect("/books/{$id}/edit"); } } ?>