diff --git a/333.txt b/333.txt
deleted file mode 100644
index cffdea3..0000000
--- a/333.txt
+++ /dev/null
@@ -1,6499 +0,0 @@
-// ./controllers/DashboardController.php
-requireLogin();
-
- $user_id = $_SESSION['user_id'];
-
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
- $seriesModel = new Series($this->pdo);
-
- // Получаем статистику
- $books = $bookModel->findByUser($user_id);
- $published_books = $bookModel->findByUser($user_id, true);
-
- $total_books = count($books);
- $published_books_count = count($published_books);
-
- // Общее количество слов и глав
- $total_words = 0;
- $total_chapters = 0;
- foreach ($books as $book) {
- $stats = $bookModel->getBookStats($book['id']);
- $total_words += $stats['total_words'] ?? 0;
- $total_chapters += $stats['chapter_count'] ?? 0;
- }
-
- // Последние книги
- $recent_books = array_slice($books, 0, 5);
-
- // Серии
- $series = $seriesModel->findByUser($user_id);
-
- $this->render('dashboard/index', [
- 'total_books' => $total_books,
- 'published_books_count' => $published_books_count,
- 'total_words' => $total_words,
- 'total_chapters' => $total_chapters,
- 'recent_books' => $recent_books,
- 'series' => $series,
- 'page_title' => 'Панель управления'
- ]);
- }
-}
-?>
-// ./controllers/AuthController.php
-redirect('/dashboard');
- }
-
- $error = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $username = trim($_POST['username'] ?? '');
- $password = $_POST['password'] ?? '';
-
- if (empty($username) || empty($password)) {
- $error = 'Пожалуйста, введите имя пользователя и пароль';
- } else {
- $userModel = new User($this->pdo);
- $user = $userModel->findByUsername($username);
-
- if ($user && $userModel->verifyPassword($password, $user['password_hash'])) {
- if (!$user['is_active']) {
- $error = 'Ваш аккаунт деактивирован или ожидает активации администратором.';
- } else {
- // Успешный вход
- session_regenerate_id(true);
- $_SESSION['user_id'] = $user['id'];
- $_SESSION['username'] = $user['username'];
- $_SESSION['display_name'] = $user['display_name'] ?: $user['username'];
- $_SESSION['avatar'] = $user['avatar'] ?? null;
-
- // Обновляем время последнего входа
- $userModel->updateLastLogin($user['id']);
-
- $_SESSION['success'] = 'Добро пожаловать, ' . e($user['display_name'] ?: $user['username']) . '!';
- $this->redirect('/dashboard');
- }
- } else {
- $error = 'Неверное имя пользователя или пароль';
- }
- }
- }
- }
-
- $this->render('auth/login', [
- 'error' => $error,
- 'page_title' => 'Вход в систему'
- ]);
- }
-
- public function logout() {
- // Очищаем все данные сессии
- $_SESSION = [];
-
- if (ini_get("session.use_cookies")) {
- $params = session_get_cookie_params();
- setcookie(session_name(), '', time() - 42000,
- $params["path"], $params["domain"],
- $params["secure"], $params["httponly"]
- );
- }
-
- session_destroy();
- $this->redirect('/login');
- }
-
- public function register() {
- // Если пользователь уже авторизован, перенаправляем на dashboard
- if (is_logged_in()) {
- $this->redirect('/dashboard');
- }
-
- $error = '';
- $success = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $username = trim($_POST['username'] ?? '');
- $password = $_POST['password'] ?? '';
- $password_confirm = $_POST['password_confirm'] ?? '';
- $email = trim($_POST['email'] ?? '');
- $display_name = trim($_POST['display_name'] ?? '');
-
- // Валидация
- if (empty($username) || empty($password)) {
- $error = 'Имя пользователя и пароль обязательны';
- } elseif ($password !== $password_confirm) {
- $error = 'Пароли не совпадают';
- } elseif (strlen($password) < 6) {
- $error = 'Пароль должен быть не менее 6 символов';
- } else {
- $userModel = new User($this->pdo);
-
- // Проверяем, не занят ли username
- if ($userModel->findByUsername($username)) {
- $error = 'Имя пользователя уже занято';
- } elseif ($email && $userModel->findByEmail($email)) {
- $error = 'Email уже используется';
- } else {
- $data = [
- 'username' => $username,
- 'password' => $password,
- 'email' => $email ?: null,
- 'display_name' => $display_name ?: $username,
- 'is_active' => 1 // Авто-активация для простоты
- ];
-
- if ($userModel->create($data)) {
- $success = 'Регистрация успешна! Теперь вы можете войти в систему.';
- // Можно автоматически войти после регистрации
- // $this->redirect('/login');
- } else {
- $error = 'Ошибка при создании аккаунта';
- }
- }
- }
- }
- }
-
- $this->render('auth/register', [
- 'error' => $error,
- 'success' => $success,
- 'page_title' => 'Регистрация'
- ]);
- }
-}
-?>
-// ./controllers/ChapterController.php
-requireLogin();
-
- $user_id = $_SESSION['user_id'];
-
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
-
- // Проверяем права доступа к книге
- if (!$bookModel->userOwnsBook($book_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой книге";
- $this->redirect('/books');
- }
-
- // Получаем информацию о книге и главах
- $book = $bookModel->findById($book_id);
- $chapters = $chapterModel->findByBook($book_id);
-
- $this->render('chapters/index', [
- 'book' => $book,
- 'chapters' => $chapters,
- 'page_title' => "Главы книги: " . e($book['title'])
- ]);
- }
-
- public function create($book_id) {
- $this->requireLogin();
-
- $user_id = $_SESSION['user_id'];
-
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
-
- // Проверяем права доступа к книге
- if (!$bookModel->userOwnsBook($book_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой книге";
- $this->redirect('/books');
- }
-
- $book = $bookModel->findById($book_id);
- $error = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $title = trim($_POST['title'] ?? '');
- $content = trim($_POST['content']) ?? '';
- $content = $this->cleanChapterContent($content);
- $status = $_POST['status'] ?? 'draft';
-
- if (empty($title)) {
- $error = "Название главы обязательно";
- } else {
- $data = [
- 'book_id' => $book_id,
- 'title' => $title,
- 'content' => $content,
- 'status' => $status
- ];
-
- if ($chapterModel->create($data)) {
- $_SESSION['success'] = "Глава успешно создана";
- $this->redirect("/books/{$book_id}/chapters");
- } else {
- $error = "Ошибка при создании главы";
- }
- }
- }
- }
-
- $this->render('chapters/create', [
- 'book' => $book,
- 'error' => $error,
- 'page_title' => "Новая глава для: " . e($book['title'])
- ]);
- }
-
- public function edit($id) {
- $this->requireLogin();
-
- $user_id = $_SESSION['user_id'];
-
- $chapterModel = new Chapter($this->pdo);
- $bookModel = new Book($this->pdo);
-
- // Получаем главу и книгу
- $chapter = $chapterModel->findById($id);
- if (!$chapter) {
- if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') {
- header('Content-Type: application/json');
- echo json_encode(['success' => false, 'error' => 'Глава не найдена']);
- exit;
- }
- $_SESSION['error'] = "Глава не найдена";
- $this->redirect('/books');
- }
-
- $book = $bookModel->findById($chapter['book_id']);
-
- // Проверяем права доступа
- if (!$chapterModel->userOwnsChapter($id, $user_id)) {
- if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') {
- header('Content-Type: application/json');
- echo json_encode(['success' => false, 'error' => 'Доступ запрещен']);
- exit;
- }
- $_SESSION['error'] = "У вас нет доступа к этой главе";
- $this->redirect('/books');
- }
-
- // Обработка POST запроса
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $title = trim($_POST['title'] ?? '');
- $content = $this->cleanChapterContent($_POST['content'] ?? '');
- $status = $_POST['status'] ?? 'draft';
-
- // Проверяем CSRF
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') {
- header('Content-Type: application/json');
- echo json_encode(['success' => false, 'error' => 'Ошибка безопасности']);
- exit;
- }
- $error = "Ошибка безопасности";
- }
-
- if (empty($title)) {
- $error = "Название главы обязательно";
- }
-
- $data = ['title' => $title, 'content' => $content, 'status' => $status];
-
- // Если это автосейв — возвращаем JSON сразу
- if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') {
- if (empty($error)) {
- $success = $chapterModel->update($id, $data);
- header('Content-Type: application/json');
- echo json_encode(['success' => $success, 'error' => $success ? null : 'Ошибка при сохранении']);
- } else {
- header('Content-Type: application/json');
- echo json_encode(['success' => false, 'error' => $error]);
- }
- exit;
- }
-
- // Обычное сохранение формы
- if (empty($error)) {
- if ($chapterModel->update($id, $data)) {
- $_SESSION['success'] = "Глава успешно обновлена";
- $this->redirect("/books/{$chapter['book_id']}/chapters");
- } else {
- $error = "Ошибка при обновлении главы";
- }
- }
- }
-
- // Рендер страницы
- $this->render('chapters/edit', [
- 'chapter' => $chapter,
- 'book' => $book,
- 'error' => $error ?? '',
- 'page_title' => "Редактирование главы: " . e($chapter['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'];
- $chapterModel = new Chapter($this->pdo);
-
- // Проверяем права доступа
- if (!$chapterModel->userOwnsChapter($id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой главе";
- $this->redirect('/books');
- }
-
- $chapter = $chapterModel->findById($id);
- $book_id = $chapter['book_id'];
-
- // Удаляем главу
- if ($chapterModel->delete($id)) {
- $_SESSION['success'] = "Глава успешно удалена";
- } else {
- $_SESSION['error'] = "Ошибка при удалении главы";
- }
-
- $this->redirect("/books/{$book_id}/chapters");
- }
- public function preview() {
- $this->requireLogin();
-
- $content = $_POST['content'] ?? '';
- $content = $this->cleanChapterContent($content);
- $title = $_POST['title'] ?? 'Предпросмотр';
-
- $this->render('chapters/preview', [
- 'content' => $content,
- 'title' => $title,
- 'page_title' => "Предпросмотр: " . e($title)
- ]);
- }
-
- // Добавьте эту функцию в начало файла
- function cleanChapterContent($content) {
- // Удаляем лишние пробелы в начале и конце
- $content = trim($content);
-
- // Удаляем пустые абзацы и параграфы, содержащие только пробелы
- $content = preg_replace('/
]*>\s*(?:
| )?\s*<\/p>/i', '', $content);
- $content = preg_replace('/
]*>\s*<\/p>/i', '', $content);
-
- // Удаляем последовательные пустые абзацы
- $content = preg_replace('/(<\/p>\s*
]*>)+/', '
', $content);
-
- // Удаляем лишние пробелы в начале и конце каждого параграфа
- $content = preg_replace('/(
]*>)\s+/', '$1', $content);
- $content = preg_replace('/\s+<\/p>/', '
', $content);
-
- // Удаляем лишние переносы строк
- $content = preg_replace('/\n{3,}/', "\n\n", $content);
-
- return $content;
- }
-
-}
-?>
-// ./controllers/ExportController.php
-requireLogin();
-
- $user_id = $_SESSION['user_id'];
-
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
-
- $book = $bookModel->findById($book_id);
- if (!$book || $book['user_id'] != $user_id) {
- $_SESSION['error'] = "Доступ запрещен";
- $this->redirect('/books');
- }
-
- // Для автора - все главы
- $chapters = $chapterModel->findByBook($book_id);
-
- // Получаем информацию об авторе
- $author_name = $this->getAuthorName($book['user_id']);
-
- $this->handleExport($book, $chapters, false, $author_name, $format);
- }
-
- public function exportShared($share_token, $format = 'pdf') {
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
-
- $book = $bookModel->findByShareToken($share_token);
- if (!$book) {
- $_SESSION['error'] = "Книга не найдена";
- $this->redirect('/');
- }
-
- // Для публичного доступа - только опубликованные главы
- $chapters = $chapterModel->getPublishedChapters($book['id']);
-
- // Получаем информацию об авторе
- $author_name = $this->getAuthorName($book['user_id']);
-
- $this->handleExport($book, $chapters, true, $author_name, $format);
- }
-
- private function getAuthorName($user_id) {
- $stmt = $this->pdo->prepare("SELECT display_name, username FROM users WHERE id = ?");
- $stmt->execute([$user_id]);
- $author_info = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if ($author_info && $author_info['display_name'] != "") {
- return $author_info['display_name'];
- } elseif ($author_info) {
- return $author_info['username'];
- }
-
- return "Неизвестный автор";
- }
-
- private function handleExport($book, $chapters, $is_public, $author_name, $format) {
-
-
- switch ($format) {
- case 'pdf':
- $this->exportPDF($book, $chapters, $is_public, $author_name);
- break;
- case 'docx':
- $this->exportDOCX($book, $chapters, $is_public, $author_name);
- break;
- case 'html':
- $this->exportHTML($book, $chapters, $is_public, $author_name);
- break;
- case 'txt':
- $this->exportTXT($book, $chapters, $is_public, $author_name);
- break;
- default:
- $_SESSION['error'] = "Неверный формат экспорта";
- $redirect_url = $is_public ?
- "/book/{$book['share_token']}" :
- "/books/{$book['id']}/edit";
- $this->redirect($redirect_url);
- }
- }
-
- function exportPDF($book, $chapters, $is_public, $author_name) {
-
-
- $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
-
- // Устанавливаем метаданные документа
- $pdf->SetCreator(APP_NAME);
- $pdf->SetAuthor($author_name);
- $pdf->SetTitle($book['title']);
- $pdf->SetSubject($book['genre'] ?? '');
-
- // Устанавливаем margins
- $pdf->SetMargins(15, 25, 15);
- $pdf->SetHeaderMargin(10);
- $pdf->SetFooterMargin(10);
-
- // Устанавливаем авто разрыв страниц
- $pdf->SetAutoPageBreak(TRUE, 15);
-
- // Добавляем страницу
- $pdf->AddPage();
-
- // Устанавливаем шрифт с поддержкой кириллицы
- $pdf->SetFont('dejavusans', '', 12);
-
- // Заголовок книги
- $pdf->SetFont('dejavusans', 'B', 18);
- $pdf->Cell(0, 10, $book['title'], 0, 1, 'C');
- $pdf->Ln(2);
-
- // Автор
- $pdf->SetFont('dejavusans', 'I', 14);
- $pdf->Cell(0, 10, $author_name, 0, 1, 'C');
- $pdf->Ln(5);
-
- // Обложка книги
- if (!empty($book['cover_image'])) {
- $cover_path = COVERS_PATH . $book['cover_image'];
- if (file_exists($cover_path)) {
- list($width, $height) = getimagesize($cover_path);
- $max_width = 80;
- $ratio = $width / $height;
- $new_height = $max_width / $ratio;
-
- $x = (210 - $max_width) / 2;
- $pdf->Image($cover_path, $x, $pdf->GetY(), $max_width, $new_height, '', '', 'N', false, 300, '', false, false, 0, false, false, false);
- $pdf->Ln($new_height + 5);
- }
- }
-
- // Жанр
- if (!empty($book['genre'])) {
- $pdf->SetFont('dejavusans', 'I', 12);
- $pdf->Cell(0, 10, 'Жанр: ' . $book['genre'], 0, 1, 'C');
- $pdf->Ln(5);
- }
-
- // Описание
- if (!empty($book['description'])) {
- $pdf->SetFont('dejavusans', '', 11);
- $pdf->MultiCell(0, 6, $book['description'], 0, 'J');
- $pdf->Ln(10);
- }
-
- // Интерактивное оглавление
- $chapterLinks = [];
- if (!empty($chapters)) {
- $pdf->SetFont('dejavusans', 'B', 14);
- $pdf->Cell(0, 10, 'Оглавление', 0, 1, 'C');
- $pdf->Ln(5);
-
- $toc_page = $pdf->getPage();
-
- $pdf->SetFont('dejavusans', '', 11);
- foreach ($chapters as $index => $chapter) {
- $chapter_number = $index + 1;
- $link = $pdf->AddLink();
- $chapterLinks[$chapter['id']] = $link; // Сохраняем ссылку для этой главы
- $pdf->Cell(0, 6, "{$chapter_number}. {$chapter['title']}", 0, 1, 'L', false, $link);
- }
- $pdf->Ln(10);
- }
-
- // Разделитель
- $pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
- $pdf->Ln(10);
-
- // Главы с закладками и правильными ссылками
- foreach ($chapters as $index => $chapter) {
- // Добавляем новую страницу для каждой главы
- $pdf->AddPage();
-
- // УСТАНАВЛИВАЕМ ЯКОРЬ ДЛЯ ССЫЛКИ
- if (isset($chapterLinks[$chapter['id']])) {
- $pdf->SetLink($chapterLinks[$chapter['id']]);
- }
-
- // Устанавливаем закладку для этой главы
- $pdf->Bookmark($chapter['title'], 0, 0, '', 'B', array(0, 0, 0));
-
- // Название главы
- $pdf->SetFont('dejavusans', 'B', 14);
- $pdf->Cell(0, 8, $chapter['title'], 0, 1);
- $pdf->Ln(2);
-
- // Контент главы
- $pdf->SetFont('dejavusans', '', 11);
-
- $htmlContent = $chapter['content'];
-
- $pdf->writeHTML($htmlContent, true, false, true, false, '');
-
- $pdf->Ln(8);
- }
-
- // Футер с информацией
- $pdf->SetY(-25);
- $pdf->SetFont('dejavusans', 'I', 8);
- $pdf->Cell(0, 6, 'Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i'), 0, 1, 'C');
- $pdf->Cell(0, 6, 'Автор: ' . $author_name . ' | Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), 0, 1, 'C');
-
- // Отправляем файл
- $filename = cleanFilename($book['title']) . '.pdf';
- $pdf->Output($filename, 'D');
- exit;
- }
-
- function exportDOCX($book, $chapters, $is_public, $author_name) {
-
- $phpWord = new PhpWord();
-
- // Стили документа
- $phpWord->setDefaultFontName('Times New Roman');
- $phpWord->setDefaultFontSize(12);
-
- // Секция документа
- $section = $phpWord->addSection();
-
- // Заголовок книги
- $section->addText($book['title'], ['bold' => true, 'size' => 16], ['alignment' => 'center']);
- $section->addTextBreak(1);
-
- // Автор
- $section->addText($author_name, ['italic' => true, 'size' => 14], ['alignment' => 'center']);
- $section->addTextBreak(2);
-
- // Обложка книги
- if (!empty($book['cover_image'])) {
- $cover_path = COVERS_PATH . $book['cover_image'];
- if (file_exists($cover_path)) {
- $section->addImage($cover_path, [
- 'width' => 150,
- 'height' => 225,
- 'alignment' => 'center'
- ]);
- $section->addTextBreak(2);
- }
- }
-
- // Жанр
- if (!empty($book['genre'])) {
- $section->addText('Жанр: ' . $book['genre'], ['italic' => true], ['alignment' => 'center']);
- $section->addTextBreak(1);
- }
-
- // Описание
- if (!empty($book['description'])) {
-
- $descriptionParagraphs = $this->htmlToParagraphs($book['description']);
-
- foreach ($descriptionParagraphs as $paragraph) {
- if (!empty(trim($paragraph))) {
- $section->addText($paragraph);
- }
- }
- $section->addTextBreak(2);
- }
-
- // Интерактивное оглавление
- if (!empty($chapters)) {
- $section->addText('Оглавление', ['bold' => true, 'size' => 14], ['alignment' => 'center']);
- $section->addTextBreak(1);
-
- foreach ($chapters as $index => $chapter) {
- $chapter_number = $index + 1;
- // Создаем гиперссылку на заголовок главы
- $section->addLink("chapter_{$chapter['id']}", "{$chapter_number}. {$chapter['title']}", null, null, true);
- $section->addTextBreak(1);
- }
- $section->addTextBreak(2);
- }
-
- // Разделитель
- $section->addPageBreak();
-
- // Главы с закладками
- foreach ($chapters as $index => $chapter) {
- // Добавляем закладку для главы
- $section->addBookmark("chapter_{$chapter['id']}");
-
- // Заголовок главы
- $section->addText($chapter['title'], ['bold' => true, 'size' => 14]);
- $section->addTextBreak(1);
-
- // Получаем очищенный текст и разбиваем на абзацы
-
- $cleanContent = strip_tags($chapter['content']);
- $paragraphs = $this->htmlToParagraphs($chapter['content']);
-
-
- // Добавляем каждый абзац
- foreach ($paragraphs as $paragraph) {
- if (!empty(trim($paragraph))) {
- $section->addText($paragraph);
- $section->addTextBreak(1);
- }
- }
-
- // Добавляем разрыв страницы между главами (кроме последней)
- if ($index < count($chapters) - 1) {
- $section->addPageBreak();
- }
- }
-
- // Футер
- $section->addTextBreak(2);
- $section->addText('Экспортировано из ' . APP_NAME . ' - ' . date('d.m.Y H:i'), ['italic' => true, 'size' => 9]);
- $section->addText('Автор: ' . $author_name . ' | Всего глав: ' . count($chapters) . ' | Всего слов: ' . array_sum(array_column($chapters, 'word_count')), ['italic' => true, 'size' => 9]);
-
- // Сохраняем и отправляем
- $filename = cleanFilename($book['title']) . '.docx';
- header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
- header('Content-Disposition: attachment; filename="' . $filename . '"');
-
- $objWriter = IOFactory::createWriter($phpWord, 'Word2007');
- $objWriter->save('php://output');
- exit;
- }
-
- function exportHTML($book, $chapters, $is_public, $author_name) {
-
- $html = '
-
-
-
-
- ' . htmlspecialchars($book['title']) . '
-
-
-
- ' . htmlspecialchars($book['title']) . '
- ' . htmlspecialchars($author_name) . '
';
-
- if (!empty($book['genre'])) {
- $html .= 'Жанр: ' . htmlspecialchars($book['genre']) . '
';
- }
-
- // Обложка книги
- if (!empty($book['cover_image'])) {
- $cover_url = COVERS_URL . $book['cover_image'];
- $html .= '';
- $html .= '
![' . htmlspecialchars($book['title']) . '](' . $cover_url . ')
';
- $html .= '
';
- }
-
- if (!empty($book['description'])) {
- $html .= '';
- $html .= $book['description'];
- $html .= '
';
- }
-
- // Интерактивное оглавление
- if (!empty($chapters)) {
- $html .= '';
- $html .= '
Оглавление
';
- $html .= '
';
- $html .= '
';
- }
-
- $html .= '
';
-
- foreach ($chapters as $index => $chapter) {
- $html .= '';
- $html .= '
' . htmlspecialchars($chapter['title']) . '
';
- $html .= '
' . $chapter['content']. '
';
- $html .= '
';
-
- if ($index < count($chapters) - 1) {
- $html .= '
';
- }
- }
-
- $html .= '
-
- ';
-
- $filename = cleanFilename($book['title']) . '.html';
- header('Content-Type: text/html; charset=utf-8');
- header('Content-Disposition: attachment; filename="' . $filename . '"');
- echo $html;
- exit;
- }
-
- function exportTXT($book, $chapters, $is_public, $author_name) {
- $content = "=" . str_repeat("=", 80) . "=\n";
- $content .= str_pad($book['title'], 80, " ", STR_PAD_BOTH) . "\n";
- $content .= str_pad($author_name, 80, " ", STR_PAD_BOTH) . "\n";
- $content .= "=" . str_repeat("=", 80) . "=\n\n";
-
- if (!empty($book['genre'])) {
- $content .= "Жанр: " . $book['genre'] . "\n\n";
- }
-
- if (!empty($book['description'])) {
- $content .= "ОПИСАНИЕ:\n";
-
- // Обрабатываем описание
- $descriptionText = strip_tags($book['description']);
- $content .= wordwrap($descriptionText, 144) . "\n\n";
- }
-
- // Оглавление
- if (!empty($chapters)) {
- $content .= "ОГЛАВЛЕНИЕ:\n";
- $content .= str_repeat("-", 60) . "\n";
- foreach ($chapters as $index => $chapter) {
- $chapter_number = $index + 1;
- $content .= "{$chapter_number}. {$chapter['title']}\n";
- }
- $content .= "\n";
- }
-
- $content .= str_repeat("-", 144) . "\n\n";
-
- foreach ($chapters as $index => $chapter) {
- $content .= $chapter['title'] . "\n";
- $content .= str_repeat("-", 60) . "\n\n";
-
- // Получаем очищенный текст
- $cleanContent = strip_tags($chapter['content']);
- $paragraphs = $this->htmlToPlainTextParagraphs($cleanContent);
-
- foreach ($paragraphs as $paragraph) {
- if (!empty(trim($paragraph))) {
- $content .= wordwrap($paragraph, 144) . "\n\n";
- }
- }
-
- if ($index < count($chapters) - 1) {
- $content .= str_repeat("-", 144) . "\n\n";
- }
- }
-
- $content .= "\n" . str_repeat("=", 144) . "\n";
- $content .= "Экспортировано из " . APP_NAME . " - " . date('d.m.Y H:i') . "\n";
- $content .= "Автор: " . $author_name . " | Всего глав: " . count($chapters) . " | Всего слов: " . array_sum(array_column($chapters, 'word_count')) . "\n";
- $content .= str_repeat("=", 144) . "\n";
-
- $filename = cleanFilename($book['title']) . '.txt';
- header('Content-Type: text/plain; charset=utf-8');
- header('Content-Disposition: attachment; filename="' . $filename . '"');
- echo $content;
- exit;
- }
-
- // Функция для разбивки HTML на абзацы
- function htmlToParagraphs($html) {
- // Убираем HTML теги и нормализуем пробелы
- $text = strip_tags($html);
- $text = preg_replace('/\s+/', ' ', $text);
-
- // Разбиваем на абзацы по точкам и переносам строк
- $paragraphs = preg_split('/(?<=[.!?])\s+/', $text);
-
- // Фильтруем пустые абзацы
- $paragraphs = array_filter($paragraphs, function($paragraph) {
- return !empty(trim($paragraph));
- });
-
- return $paragraphs;
- }
-
- function htmlToPlainTextParagraphs($html) {
- // Убираем HTML теги
- $text = strip_tags($html);
-
- // Заменяем HTML entities
- $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
-
- // Нормализуем переносы строк
- $text = str_replace(["\r\n", "\r"], "\n", $text);
-
- // Разбиваем на строки
- $lines = explode("\n", $text);
- $paragraphs = [];
- $currentParagraph = '';
-
- foreach ($lines as $line) {
- $trimmedLine = trim($line);
-
- // Пустая строка - конец абзаца
- if (empty($trimmedLine)) {
- if (!empty($currentParagraph)) {
- $paragraphs[] = $currentParagraph;
- $currentParagraph = '';
- }
- continue;
- }
-
- // Добавляем к текущему абзацу
- if (!empty($currentParagraph)) {
- $currentParagraph .= ' ' . $trimmedLine;
- } else {
- $currentParagraph = $trimmedLine;
- }
- }
-
- // Добавляем последний абзац
- if (!empty($currentParagraph)) {
- $paragraphs[] = $currentParagraph;
- }
-
- return $paragraphs;
- }
-}
-?>
-// ./controllers/BaseController.php
-pdo = $pdo;
- }
-
- protected function render($view, $data = []) {
- extract($data);
- include "views/$view.php";
- }
-
- protected function redirect($url) {
- header("Location: " . SITE_URL . $url);
- exit;
- }
-
- protected function requireLogin() {
- if (!is_logged_in()) {
- $this->redirect('/login');
- }
- }
-
- protected function requireAdmin() {
- if (!is_logged_in()) {
- $this->redirect('/login');
- return;
- }
-
- global $pdo;
- $userModel = new User($pdo);
- $user = $userModel->findById($_SESSION['user_id']);
-
- if (!$user || $user['id'] != 1) { // Предполагаем, что администратор имеет ID = 1
- $_SESSION['error'] = "У вас нет прав администратора";
- $this->redirect('/dashboard');
- exit;
- }
- }
-
- protected function jsonResponse($data) {
- header('Content-Type: application/json');
- echo json_encode($data);
- exit;
- }
-}
-?>
-// ./controllers/SeriesController.php
-requireLogin();
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
- $series = $seriesModel->findByUser($user_id);
-
- // Получаем статистику для каждой серии отдельно
- foreach ($series as &$ser) {
- $stats = $seriesModel->getSeriesStats($ser['id'], $user_id);
- $ser['book_count'] = $stats['book_count'] ?? 0;
- $ser['total_words'] = $stats['total_words'] ?? 0;
- }
- unset($ser);
-
- $this->render('series/index', [
- 'series' => $series,
- 'page_title' => "Мои серии книг"
- ]);
- }
-
- public function create() {
- $this->requireLogin();
-
- $error = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $title = trim($_POST['title'] ?? '');
- $description = trim($_POST['description'] ?? '');
-
- if (empty($title)) {
- $error = "Название серии обязательно";
- } else {
- $seriesModel = new Series($this->pdo);
- $data = [
- 'title' => $title,
- 'description' => $description,
- 'user_id' => $_SESSION['user_id']
- ];
-
- if ($seriesModel->create($data)) {
- $_SESSION['success'] = "Серия успешно создана";
- $new_series_id = $this->pdo->lastInsertId();
- $this->redirect("/series/{$new_series_id}/edit");
- } else {
- $error = "Ошибка при создании серии";
- }
- }
- }
- }
-
- $this->render('series/create', [
- 'error' => $error,
- 'page_title' => "Создание новой серии"
- ]);
- }
-
- public function edit($id) {
- $this->requireLogin();
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
- $series = $seriesModel->findById($id);
-
- if (!$series || !$seriesModel->userOwnsSeries($id, $user_id)) {
- $_SESSION['error'] = "Серия не найдена или у вас нет доступа";
- $this->redirect('/series');
- }
-
- $error = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $title = trim($_POST['title'] ?? '');
- $description = trim($_POST['description'] ?? '');
-
- if (empty($title)) {
- $error = "Название серии обязательно";
- } else {
- $data = [
- 'title' => $title,
- 'description' => $description,
- 'user_id' => $user_id
- ];
-
- if ($seriesModel->update($id, $data)) {
- $_SESSION['success'] = "Серия успешно обновлена";
- $this->redirect('/series');
- } else {
- $error = "Ошибка при обновлении серии";
- }
- }
- }
- }
-
- // Получаем книги в серии
- $bookModel = new Book($this->pdo);
- $books_in_series = $bookModel->findBySeries($id);
- $available_books = $bookModel->getBooksNotInSeries($user_id, $id);
-
- $this->render('series/edit', [
- 'series' => $series,
- 'books_in_series' => $books_in_series,
- 'available_books' => $available_books,
- 'error' => $error,
- 'page_title' => "Редактирование серии: " . e($series['title'])
- ]);
- }
-
- public function delete($id) {
- $this->requireLogin();
-
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- $_SESSION['error'] = "Неверный метод запроса";
- $this->redirect('/series');
- }
-
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Ошибка безопасности";
- $this->redirect('/series');
- }
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
-
- if (!$seriesModel->userOwnsSeries($id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой серии";
- $this->redirect('/series');
- }
-
- if ($seriesModel->delete($id, $user_id)) {
- $_SESSION['success'] = "Серия успешно удалена";
- } else {
- $_SESSION['error'] = "Ошибка при удалении серии";
- }
-
- $this->redirect('/series');
- }
-
- public function viewPublic($id) {
- $seriesModel = new Series($this->pdo);
- $series = $seriesModel->findById($id);
-
- if (!$series) {
- http_response_code(404);
- $this->render('errors/404');
- return;
- }
-
- // Получаем только опубликованные книги серии
- $books = $seriesModel->getBooksInSeries($id, true);
-
- // Получаем информацию об авторе
- $stmt = $this->pdo->prepare("SELECT id, username, display_name FROM users WHERE id = ?");
- $stmt->execute([$series['user_id']]);
- $author = $stmt->fetch(PDO::FETCH_ASSOC);
-
- // Получаем статистику по опубликованным книгам
- $bookModel = new Book($this->pdo);
- $total_words = 0;
- $total_chapters = 0;
-
- foreach ($books as $book) {
- $book_stats = $bookModel->getBookStats($book['id'], true);
- $total_words += $book_stats['total_words'] ?? 0;
- $total_chapters += $book_stats['chapter_count'] ?? 0;
- }
-
- $this->render('series/view_public', [
- 'series' => $series,
- 'books' => $books,
- 'author' => $author,
- 'total_words' => $total_words,
- 'total_chapters' => $total_chapters,
- 'page_title' => $series['title'] . ' — серия книг'
- ]);
- }
-
- public function addBook($series_id) {
- $this->requireLogin();
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
- $bookModel = new Book($this->pdo);
-
- if (!$seriesModel->userOwnsSeries($series_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой серии";
- $this->redirect('/series');
- }
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Ошибка безопасности";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- $book_id = (int)($_POST['book_id'] ?? 0);
- $sort_order = (int)($_POST['sort_order'] ?? 0);
-
- if (!$book_id) {
- $_SESSION['error'] = "Выберите книгу";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- // Проверяем, что книга принадлежит пользователю
- if (!$bookModel->userOwnsBook($book_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой книге";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- // Добавляем книгу в серию
- if ($bookModel->updateSeriesInfo($book_id, $series_id, $sort_order)) {
- $_SESSION['success'] = "Книга добавлена в серию";
- } else {
- $_SESSION['error'] = "Ошибка при добавлении книги в серию";
- }
-
- $this->redirect("/series/{$series_id}/edit");
- }
- }
-
- public function removeBook($series_id, $book_id) {
- $this->requireLogin();
-
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- $_SESSION['error'] = "Неверный метод запроса";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Ошибка безопасности";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
- $bookModel = new Book($this->pdo);
-
- if (!$seriesModel->userOwnsSeries($series_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой серии";
- $this->redirect('/series');
- }
-
- // Проверяем, что книга принадлежит пользователю
- if (!$bookModel->userOwnsBook($book_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой книге";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- // Удаляем книгу из серии
- if ($bookModel->removeFromSeries($book_id)) {
- $_SESSION['success'] = "Книга удалена из серии";
- } else {
- $_SESSION['error'] = "Ошибка при удалении книги из серии";
- }
-
- $this->redirect("/series/{$series_id}/edit");
- }
-
- public function updateBookOrder($series_id) {
- $this->requireLogin();
-
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- $_SESSION['error'] = "Неверный метод запроса";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Ошибка безопасности";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- $user_id = $_SESSION['user_id'];
- $seriesModel = new Series($this->pdo);
- $bookModel = new Book($this->pdo);
-
- if (!$seriesModel->userOwnsSeries($series_id, $user_id)) {
- $_SESSION['error'] = "У вас нет доступа к этой серии";
- $this->redirect('/series');
- }
-
- $order_data = $_POST['order'] ?? [];
-
- if (empty($order_data)) {
- $_SESSION['error'] = "Нет данных для обновления";
- $this->redirect("/series/{$series_id}/edit");
- }
-
- // Обновляем порядок книг
- if ($bookModel->reorderSeriesBooks($series_id, $order_data)) {
- $_SESSION['success'] = "Порядок книг обновлен";
- } else {
- $_SESSION['error'] = "Ошибка при обновлении порядка книг";
- }
-
- $this->redirect("/series/{$series_id}/edit");
- }
-}
-?>
-// ./controllers/UserController.php
-requireLogin();
-
- $user_id = $_SESSION['user_id'];
- $userModel = new User($this->pdo);
- $user = $userModel->findById($user_id);
-
- $message = '';
- $avatar_error = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $message = "Ошибка безопасности";
- } else {
- $display_name = trim($_POST['display_name'] ?? '');
- $email = trim($_POST['email'] ?? '');
- $bio = trim($_POST['bio'] ?? '');
-
- // Обработка загрузки аватарки
- if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] === UPLOAD_ERR_OK) {
- $avatar_result = handleAvatarUpload($_FILES['avatar'], $user_id);
- if ($avatar_result['success']) {
- $userModel->updateAvatar($user_id, $avatar_result['filename']);
- // Обновляем данные пользователя
- $user = $userModel->findById($user_id);
- } else {
- $avatar_error = $avatar_result['error'];
- }
- }
-
- // Обработка удаления аватарки
- if (isset($_POST['delete_avatar']) && $_POST['delete_avatar'] == '1') {
- deleteUserAvatar($user_id);
- $user = $userModel->findById($user_id);
- }
-
- // Обновляем основные данные
- $data = [
- 'display_name' => $display_name,
- 'email' => $email,
- 'bio' => $bio
- ];
-
- if ($userModel->updateProfile($user_id, $data)) {
- $_SESSION['display_name'] = $display_name ?: $user['username'];
- $message = "Профиль обновлен";
- // Обновляем данные пользователя
- $user = $userModel->findById($user_id);
- } else {
- $message = "Ошибка при обновлении профиля";
- }
- }
- }
-
- $this->render('user/profile', [
- 'user' => $user,
- 'message' => $message,
- 'avatar_error' => $avatar_error,
- 'page_title' => "Мой профиль"
- ]);
- }
-
- public function updateProfile() {
- $this->requireLogin();
-
- // Эта функция обрабатывает AJAX или прямые POST запросы для обновления профиля
- // Можно объединить с методом profile() или оставить отдельно для API-like операций
- $this->profile(); // Перенаправляем на основной метод
- }
-
- public function viewPublic($id) {
- $userModel = new User($this->pdo);
- $user = $userModel->findById($id);
-
- if (!$user) {
- http_response_code(404);
- $this->render('errors/404');
- return;
- }
-
- $bookModel = new Book($this->pdo);
- $books = $bookModel->findByUser($id, true); // только опубликованные
-
- // Получаем статистику автора
- $total_books = count($books);
- $total_words = 0;
- $total_chapters = 0;
-
- foreach ($books as $book) {
- $book_stats = $bookModel->getBookStats($book['id'], true);
- $total_words += $book_stats['total_words'] ?? 0;
- $total_chapters += $book_stats['chapter_count'] ?? 0;
- }
-
-
-
- $this->render('user/view_public', [
- 'user' => $user,
- 'books' => $books,
- 'total_books' => $total_books,
- 'total_words' => $total_words,
- 'total_chapters' => $total_chapters,
- 'page_title' => ($user['display_name'] ?: $user['username']) . ' — публичная страница'
- ]);
- }
-}
-?>
-// ./controllers/BookController.php
-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');
- }
-
- $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
- ];
-
- 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;
- }
- }
-
- $_SESSION['success'] = "Книга успешно создана" . ($cover_error ? ", но возникла ошибка с обложкой: " . $cover_error : "");
- $this->redirect("/books/{$new_book_id}/edit");
- } 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 (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $title = trim($_POST['title'] ?? '');
- if (empty($title)) {
- $error = "Название книги обязательно";
- } else {
- $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
- ];
-
- // Обработка обложки
- 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;
- }
- $chapters = $chapterModel->getPublishedChapters($book['id']);
-
- // Получаем информацию об авторе
- $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,
- 'page_title' => $book['title']
- ]);
- }
-
- public function viewAll($id) {
- $bookModel = new Book($this->pdo);
- $chapterModel = new Chapter($this->pdo);
- $book = $bookModel->findById($id);
- if (!$book) {
- http_response_code(404);
- $this->render('errors/404');
- return;
- }
- $chapters = $chapterModel->findByBook($book['id']);
-
- // Получаем информацию об авторе
- $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,
- '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");
- }
-}
-?>
-// ./controllers/AdminController.php
-requireAdmin();
- }
-
-
- public function users() {
- $userModel = new User($this->pdo);
- $users = $userModel->findAll();
-
- $this->render('admin/users', [
- 'users' => $users,
- 'page_title' => 'Управление пользователями'
- ]);
- }
-
- public function toggleUserStatus($user_id) {
- if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Неверный метод запроса или токен безопасности";
- $this->redirect('/admin/users');
- return;
- }
-
- if ($user_id == $_SESSION['user_id']) {
- $_SESSION['error'] = "Нельзя изменить статус собственного аккаунта";
- $this->redirect('/admin/users');
- return;
- }
-
- $userModel = new User($this->pdo);
- $user = $userModel->findById($user_id);
-
- if (!$user) {
- $_SESSION['error'] = "Пользователь не найден";
- $this->redirect('/admin/users');
- return;
- }
-
- $newStatus = $user['is_active'] ? 0 : 1;
- if ($userModel->updateStatus($user_id, $newStatus)) {
- $_SESSION['success'] = "Статус пользователя обновлен";
- } else {
- $_SESSION['error'] = "Ошибка при обновлении статуса";
- }
-
- $this->redirect('/admin/users');
- }
-
- public function deleteUser($user_id) {
- if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $_SESSION['error'] = "Неверный метод запроса или токен безопасности";
- $this->redirect('/admin/users');
- return;
- }
-
- if ($user_id == $_SESSION['user_id']) {
- $_SESSION['error'] = "Нельзя удалить собственный аккаунт";
- $this->redirect('/admin/users');
- return;
- }
-
- $userModel = new User($this->pdo);
- $user = $userModel->findById($user_id);
-
- if (!$user) {
- $_SESSION['error'] = "Пользователь не найден";
- $this->redirect('/admin/users');
- return;
- }
-
- if ($userModel->delete($user_id)) {
- $_SESSION['success'] = "Пользователь успешно удален";
- } else {
- $_SESSION['error'] = "Ошибка при удалении пользователя";
- }
-
- $this->redirect('/admin/users');
- }
-
- public function addUser() {
- $error = '';
- $success = '';
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
- $error = "Ошибка безопасности";
- } else {
- $username = trim($_POST['username'] ?? '');
- $password = $_POST['password'] ?? '';
- $password_confirm = $_POST['password_confirm'] ?? '';
- $email = trim($_POST['email'] ?? '');
- $display_name = trim($_POST['display_name'] ?? '');
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- if (empty($username) || empty($password)) {
- $error = 'Имя пользователя и пароль обязательны';
- } elseif ($password !== $password_confirm) {
- $error = 'Пароли не совпадают';
- } elseif (strlen($password) < 6) {
- $error = 'Пароль должен быть не менее 6 символов';
- } else {
- $userModel = new User($this->pdo);
- if ($userModel->findByUsername($username)) {
- $error = 'Имя пользователя уже занято';
- } elseif (!empty($email) && $userModel->findByEmail($email)) {
- $error = 'Email уже используется';
- } else {
- $data = [
- 'username' => $username,
- 'password' => $password,
- 'email' => $email ?: null,
- 'display_name' => $display_name ?: $username,
- 'is_active' => $is_active
- ];
-
- if ($userModel->create($data)) {
- $success = 'Пользователь успешно создан';
- // Очищаем поля формы
- $_POST = [];
- } else {
- $error = 'Ошибка при создании пользователя';
- }
- }
- }
- }
- }
-
- $this->render('admin/add_user', [
- 'error' => $error,
- 'success' => $success,
- 'page_title' => 'Добавление пользователя'
- ]);
- }
-}
-?>
-// ./composer.json
-{
- "require": {
- "phpoffice/phpword": "^1.0",
- "tecnickcom/tcpdf": "^6.6"
- }
-}
-
-// ./models/Chapter.php
-pdo = $pdo;
- }
-
- public function findById($id) {
- $stmt = $this->pdo->prepare("
- SELECT c.*, b.user_id, b.title as book_title
- FROM chapters c
- JOIN books b ON c.book_id = b.id
- WHERE c.id = ?
- ");
- $stmt->execute([$id]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByBook($book_id) {
- $stmt = $this->pdo->prepare("
- SELECT * FROM chapters
- WHERE book_id = ?
- ORDER BY sort_order, created_at
- ");
- $stmt->execute([$book_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function create($data) {
- $stmt = $this->pdo->prepare("SELECT MAX(sort_order) as max_order FROM chapters WHERE book_id = ?");
- $stmt->execute([$data['book_id']]);
- $result = $stmt->fetch();
- $next_order = ($result['max_order'] ?? 0) + 1;
-
- $word_count = $this->countWords($data['content']);
-
- $stmt = $this->pdo->prepare("
- INSERT INTO chapters (book_id, title, content, sort_order, word_count, status)
- VALUES (?, ?, ?, ?, ?, ?)
- ");
- return $stmt->execute([
- $data['book_id'],
- $data['title'],
- $data['content'],
- $next_order,
- $word_count,
- $data['status'] ?? 'draft'
- ]);
- }
-
- public function update($id, $data) {
- $word_count = $this->countWords($data['content']);
-
- $stmt = $this->pdo->prepare("
- UPDATE chapters
- SET title = ?, content = ?, word_count = ?, status = ?, updated_at = CURRENT_TIMESTAMP
- WHERE id = ?
- ");
- return $stmt->execute([
- $data['title'],
- $data['content'],
- $word_count,
- $data['status'] ?? 'draft',
- $id
- ]);
- }
-
- public function delete($id) {
- $stmt = $this->pdo->prepare("DELETE FROM chapters WHERE id = ?");
- return $stmt->execute([$id]);
- }
-
- public function updateSortOrder($chapter_id, $new_order) {
- $stmt = $this->pdo->prepare("UPDATE chapters SET sort_order = ? WHERE id = ?");
- return $stmt->execute([$new_order, $chapter_id]);
- }
-
- private function countWords($text) {
- $text = strip_tags($text);
- $text = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
- $words = preg_split('/\s+/', $text);
- $words = array_filter($words);
- return count($words);
- }
-
- public function userOwnsChapter($chapter_id, $user_id) {
- $stmt = $this->pdo->prepare("
- SELECT c.id
- FROM chapters c
- JOIN books b ON c.book_id = b.id
- WHERE c.id = ? AND b.user_id = ?
- ");
- $stmt->execute([$chapter_id, $user_id]);
- return $stmt->fetch() !== false;
- }
-
-
- public function getPublishedChapters($book_id) {
- $stmt = $this->pdo->prepare("
- SELECT * FROM chapters
- WHERE book_id = ? AND status = 'published'
- ORDER BY sort_order, created_at
- ");
- $stmt->execute([$book_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- // private function getAllChapters($book_id) {
- // $stmt = $this->pdo->prepare("SELECT id, content FROM chapters WHERE book_id = ?");
- // $stmt->execute([$book_id]);
- // return $stmt->fetchAll(PDO::FETCH_ASSOC);
- // }
-
- // private function updateChapterContent($chapter_id, $content) {
- // $word_count = $this->countWords($content);
- // $stmt = $this->pdo->prepare("
- // UPDATE chapters
- // SET content = ?, word_count = ?, updated_at = CURRENT_TIMESTAMP
- // WHERE id = ?
- // ");
- // return $stmt->execute([$content, $word_count, $chapter_id]);
- // }
-
-}
-?>
-// ./models/Series.php
-pdo = $pdo;
- }
-
- public function findById($id) {
- $stmt = $this->pdo->prepare("
- SELECT s.*,
- COUNT(b.id) as book_count,
- COALESCE((
- SELECT SUM(c.word_count)
- FROM chapters c
- JOIN books b2 ON c.book_id = b2.id
- WHERE b2.series_id = s.id AND b2.published = 1
- ), 0) as total_words
- FROM series s
- LEFT JOIN books b ON s.id = b.series_id AND b.published = 1
- WHERE s.id = ?
- GROUP BY s.id
- ");
- $stmt->execute([$id]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByUser($user_id, $include_stats = true) {
- if ($include_stats) {
- $sql = "
- SELECT s.*,
- COUNT(b.id) as book_count,
- COALESCE((
- SELECT SUM(c.word_count)
- FROM chapters c
- JOIN books b2 ON c.book_id = b2.id
- WHERE b2.series_id = s.id AND b2.user_id = ?
- ), 0) as total_words
- FROM series s
- LEFT JOIN books b ON s.id = b.series_id
- WHERE s.user_id = ?
- GROUP BY s.id
- ORDER BY s.created_at DESC
- ";
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$user_id, $user_id]);
- } else {
- $sql = "SELECT * FROM series WHERE user_id = ? ORDER BY created_at DESC";
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$user_id]);
- }
-
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function create($data) {
- $stmt = $this->pdo->prepare("
- INSERT INTO series (title, description, user_id)
- VALUES (?, ?, ?)
- ");
- return $stmt->execute([
- $data['title'],
- $data['description'] ?? null,
- $data['user_id']
- ]);
- }
-
- public function update($id, $data) {
- $stmt = $this->pdo->prepare("
- UPDATE series
- SET title = ?, description = ?, updated_at = CURRENT_TIMESTAMP
- WHERE id = ? AND user_id = ?
- ");
- return $stmt->execute([
- $data['title'],
- $data['description'] ?? null,
- $id,
- $data['user_id']
- ]);
- }
-
- public function delete($id, $user_id) {
- try {
- $this->pdo->beginTransaction();
-
- $stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE series_id = ? AND user_id = ?");
- $stmt->execute([$id, $user_id]);
-
- $stmt = $this->pdo->prepare("DELETE FROM series WHERE id = ? AND user_id = ?");
- $result = $stmt->execute([$id, $user_id]);
-
- $this->pdo->commit();
- return $result;
- } catch (Exception $e) {
- $this->pdo->rollBack();
- return false;
- }
- }
-
- public function userOwnsSeries($series_id, $user_id) {
- $stmt = $this->pdo->prepare("SELECT id FROM series WHERE id = ? AND user_id = ?");
- $stmt->execute([$series_id, $user_id]);
- return $stmt->fetch() !== false;
- }
-
- public function getBooksInSeries($series_id, $only_published = false) {
- $sql = "SELECT * FROM books WHERE series_id = ?";
- if ($only_published) {
- $sql .= " AND published = 1";
- }
- $sql .= " ORDER BY sort_order_in_series, created_at";
-
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$series_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function getNextSortOrder($series_id) {
- $stmt = $this->pdo->prepare("SELECT MAX(sort_order_in_series) as max_order FROM books WHERE series_id = ?");
- $stmt->execute([$series_id]);
- $result = $stmt->fetch();
- return ($result['max_order'] ?? 0) + 1;
- }
-
- public function getSeriesStats($series_id, $user_id = null) {
- $sql = "
- SELECT
- COUNT(b.id) as book_count,
- COALESCE(SUM(stats.chapter_count), 0) as chapter_count,
- COALESCE(SUM(stats.total_words), 0) as total_words
- FROM series s
- LEFT JOIN books b ON s.id = b.series_id
- LEFT JOIN (
- SELECT
- book_id,
- COUNT(id) as chapter_count,
- SUM(word_count) as total_words
- FROM chapters
- GROUP BY book_id
- ) stats ON b.id = stats.book_id
- WHERE s.id = ?
- ";
-
- $params = [$series_id];
-
- if ($user_id) {
- $sql .= " AND s.user_id = ?";
- $params[] = $user_id;
- }
-
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute($params);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-}
-?>
-// ./models/index.php
-
-// ./models/User.php
-pdo = $pdo;
- }
-
- public function findById($id) {
- $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
- $stmt->execute([$id]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByUsername($username) {
- $stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ?");
- $stmt->execute([$username]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByEmail($email) {
- $stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
- $stmt->execute([$email]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findAll() {
- $stmt = $this->pdo->prepare("SELECT id, username, display_name, email, created_at, last_login, is_active FROM users ORDER BY created_at DESC");
- $stmt->execute();
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function create($data) {
- $password_hash = password_hash($data['password'], PASSWORD_DEFAULT);
-
- $is_active = $data['is_active'] ?? 0;
-
- $stmt = $this->pdo->prepare("
- INSERT INTO users (username, display_name, email, password_hash, is_active)
- VALUES (?, ?, ?, ?, ?)
- ");
-
- return $stmt->execute([
- $data['username'],
- $data['display_name'] ?? $data['username'],
- $data['email'] ?? null,
- $password_hash,
- $is_active
- ]);
- }
-
- public function update($id, $data) {
- $sql = "UPDATE users SET display_name = ?, email = ?";
- $params = [$data['display_name'], $data['email']];
-
- if (!empty($data['password'])) {
- $sql .= ", password_hash = ?";
- $params[] = password_hash($data['password'], PASSWORD_DEFAULT);
- }
-
- $sql .= " WHERE id = ?";
- $params[] = $id;
-
- $stmt = $this->pdo->prepare($sql);
- return $stmt->execute($params);
- }
-
- public function delete($id) {
- $stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
- return $stmt->execute([$id]);
- }
-
- public function updateStatus($id, $is_active) {
- $stmt = $this->pdo->prepare("UPDATE users SET is_active = ? WHERE id = ?");
- return $stmt->execute([$is_active, $id]);
- }
-
- public function updateLastLogin($id) {
- $stmt = $this->pdo->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?");
- return $stmt->execute([$id]);
- }
-
- public function verifyPassword($password, $hash) {
- return password_verify($password, $hash);
- }
-
- public function updateAvatar($id, $filename) {
- $stmt = $this->pdo->prepare("UPDATE users SET avatar = ? WHERE id = ?");
- return $stmt->execute([$filename, $id]);
- }
-
- public function updateBio($id, $bio) {
- $stmt = $this->pdo->prepare("UPDATE users SET bio = ? WHERE id = ?");
- return $stmt->execute([$bio, $id]);
- }
-
- public function updateProfile($id, $data) {
- $sql = "UPDATE users SET display_name = ?, email = ?, bio = ?";
- $params = [
- $data['display_name'] ?? '',
- $data['email'] ?? null,
- $data['bio'] ?? null
- ];
-
- if (!empty($data['avatar'])) {
- $sql .= ", avatar = ?";
- $params[] = $data['avatar'];
- }
-
- $sql .= " WHERE id = ?";
- $params[] = $id;
-
- $stmt = $this->pdo->prepare($sql);
- return $stmt->execute($params);
- }
-}
-?>
-// ./models/Book.php
-pdo = $pdo;
- }
-
- public function findById($id) {
- $stmt = $this->pdo->prepare("SELECT * FROM books WHERE id = ?");
- $stmt->execute([$id]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByShareToken($share_token) {
- $stmt = $this->pdo->prepare("SELECT * FROM books WHERE share_token = ?");
- $stmt->execute([$share_token]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
- public function findByUser($user_id, $only_published = false) {
- $sql = "
- SELECT b.*,
- COUNT(c.id) as chapter_count,
- COALESCE(SUM(c.word_count), 0) as total_words
- FROM books b
- LEFT JOIN chapters c ON b.id = c.book_id
- WHERE b.user_id = ?
- ";
- if ($only_published) {
- $sql .= " AND b.published = 1 ";
- }
- $sql .= " GROUP BY b.id ORDER BY b.created_at DESC ";
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$user_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function create($data) {
- $share_token = bin2hex(random_bytes(16));
- $published = isset($data['published']) ? (int)$data['published'] : 0;
-
- $stmt = $this->pdo->prepare("
- INSERT INTO books (title, description, genre, user_id, series_id, sort_order_in_series, share_token, published)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
- ");
- return $stmt->execute([
- $data['title'],
- $data['description'] ?? null,
- $data['genre'] ?? null,
- $data['user_id'],
- $data['series_id'] ?? null,
- $data['sort_order_in_series'] ?? null,
- $share_token,
- $published
- ]);
- }
-
- public function update($id, $data) {
- $published = isset($data['published']) ? (int)$data['published'] : 0;
-
- // Преобразуем пустые строки в NULL для integer полей
- $series_id = !empty($data['series_id']) ? (int)$data['series_id'] : null;
- $sort_order_in_series = !empty($data['sort_order_in_series']) ? (int)$data['sort_order_in_series'] : null;
-
- $stmt = $this->pdo->prepare("
- UPDATE books
- SET title = ?, description = ?, genre = ?, series_id = ?, sort_order_in_series = ?, published = ?
- WHERE id = ? AND user_id = ?
- ");
- return $stmt->execute([
- $data['title'],
- $data['description'] ?? null,
- $data['genre'] ?? null,
- $series_id, // Теперь это либо integer, либо NULL
- $sort_order_in_series, // Теперь это либо integer, либо NULL
- $published,
- $id,
- $data['user_id']
- ]);
- }
-
-
- public function delete($id, $user_id) {
- try {
- $this->pdo->beginTransaction();
-
- // Удаляем главы книги
- $stmt = $this->pdo->prepare("DELETE FROM chapters WHERE book_id = ?");
- $stmt->execute([$id]);
-
- // Удаляем саму книгу
- $stmt = $this->pdo->prepare("DELETE FROM books WHERE id = ? AND user_id = ?");
- $result = $stmt->execute([$id, $user_id]);
-
- $this->pdo->commit();
- return $result;
- } catch (Exception $e) {
- $this->pdo->rollBack();
- return false;
- }
- }
-
- public function deleteAllByUser($user_id) {
- try {
- $this->pdo->beginTransaction();
-
- // Получаем ID всех книг пользователя
- $stmt = $this->pdo->prepare("SELECT id FROM books WHERE user_id = ?");
- $stmt->execute([$user_id]);
- $book_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
-
- if (empty($book_ids)) {
- $this->pdo->commit();
- return 0;
- }
-
- // Удаляем главы всех книг пользователя (одним запросом)
- $placeholders = implode(',', array_fill(0, count($book_ids), '?'));
- $stmt = $this->pdo->prepare("DELETE FROM chapters WHERE book_id IN ($placeholders)");
- $stmt->execute($book_ids);
-
- // Удаляем все книги пользователя (одним запросом)
- $stmt = $this->pdo->prepare("DELETE FROM books WHERE user_id = ?");
- $stmt->execute([$user_id]);
-
- $deleted_count = $stmt->rowCount();
- $this->pdo->commit();
-
- return $deleted_count;
- } catch (Exception $e) {
- $this->pdo->rollBack();
- throw $e;
- }
- }
-
- public function userOwnsBook($book_id, $user_id) {
- $stmt = $this->pdo->prepare("SELECT id FROM books WHERE id = ? AND user_id = ?");
- $stmt->execute([$book_id, $user_id]);
- return $stmt->fetch() !== false;
- }
-
- public function generateNewShareToken($book_id) {
- $new_token = bin2hex(random_bytes(16));
- $stmt = $this->pdo->prepare("UPDATE books SET share_token = ? WHERE id = ?");
- $success = $stmt->execute([$new_token, $book_id]);
- return $success ? $new_token : false;
- }
-
-
-
- public function updateCover($book_id, $filename) {
- $stmt = $this->pdo->prepare("UPDATE books SET cover_image = ? WHERE id = ?");
- return $stmt->execute([$filename, $book_id]);
- }
-
- public function deleteCover($book_id) {
-
- $book = $this->findById($book_id);
- $old_filename = $book['cover_image'];
-
- if ($old_filename) {
- $file_path = COVERS_PATH . $old_filename;
- if (file_exists($file_path)) {
- unlink($file_path);
- }
- }
-
- $stmt = $this->pdo->prepare("UPDATE books SET cover_image = NULL WHERE id = ?");
- return $stmt->execute([$book_id]);
- }
-
- public function updateSeriesInfo($book_id, $series_id, $sort_order) {
- $stmt = $this->pdo->prepare("UPDATE books SET series_id = ?, sort_order_in_series = ? WHERE id = ?");
- return $stmt->execute([$series_id, $sort_order, $book_id]);
- }
-
- public function removeFromSeries($book_id) {
- $stmt = $this->pdo->prepare("UPDATE books SET series_id = NULL, sort_order_in_series = NULL WHERE id = ?");
- return $stmt->execute([$book_id]);
- }
-
- public function findBySeries($series_id) {
- $stmt = $this->pdo->prepare("
- SELECT b.*
- FROM books b
- WHERE b.series_id = ?
- ORDER BY b.sort_order_in_series, b.created_at
- ");
- $stmt->execute([$series_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function getBookStats($book_id, $only_published_chapters = false) {
- $sql = "
- SELECT
- COUNT(c.id) as chapter_count,
- COALESCE(SUM(c.word_count), 0) as total_words
- FROM books b
- LEFT JOIN chapters c ON b.id = c.book_id
- WHERE b.id = ?
- ";
-
- if ($only_published_chapters) {
- $sql .= " AND c.status = 'published'";
- }
-
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$book_id]);
- return $stmt->fetch(PDO::FETCH_ASSOC);
- }
-
-
- public function getBooksNotInSeries($user_id, $series_id = null) {
- $sql = "SELECT * FROM books
- WHERE user_id = ?
- AND (series_id IS NULL OR series_id != ? OR series_id = 0)";
- $stmt = $this->pdo->prepare($sql);
- $stmt->execute([$user_id, $series_id]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
-
- public function reorderSeriesBooks($series_id, $new_order) {
- try {
- $this->pdo->beginTransaction();
-
- foreach ($new_order as $order => $book_id) {
- $stmt = $this->pdo->prepare("UPDATE books SET sort_order_in_series = ? WHERE id = ? AND series_id = ?");
- $stmt->execute([$order + 1, $book_id, $series_id]);
- }
-
- $this->pdo->commit();
- return true;
- } catch (Exception $e) {
- $this->pdo->rollBack();
- error_log("Ошибка при обновлении порядка книг: " . $e->getMessage());
- return false;
- }
- }
-
- private function countWords($text) {
- $text = strip_tags($text);
- $text = preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $text);
- $words = preg_split('/\s+/', $text);
- $words = array_filter($words);
- return count($words);
- }
-
-}
-?>
-// ./index.php
- 'text/css',
- 'js' => 'application/javascript',
- 'png' => 'image/png',
- 'jpg' => 'image/jpeg',
- 'jpeg' => 'image/jpeg',
- 'gif' => 'image/gif',
- 'webp' => 'image/webp',
- 'svg' => 'image/svg+xml',
- 'ico' => 'image/x-icon',
- 'json' => 'application/json',
- 'woff' => 'font/woff',
- 'woff2' => 'font/woff2',
- 'ttf' => 'font/ttf',
- 'eot' => 'application/vnd.ms-fontobject',
- ];
-
- $extension = strtolower(pathinfo($physicalPath, PATHINFO_EXTENSION));
- if (isset($mimeTypes[$extension])) {
- header('Content-Type: ' . $mimeTypes[$extension]);
- }
-
- // Запрещаем кэширование для разработки, в продакшене можно увеличить время
- header('Cache-Control: public, max-age=3600');
-
- // Отправляем файл
- readfile($physicalPath);
- exit;
- }
-}
-// Простой роутер
-class Router {
- private $routes = [];
-
- public function add($pattern, $handler) {
- $this->routes[$pattern] = $handler;
- }
-
- public function handle($uri) {
- // Убираем базовый URL если есть
- $basePath = parse_url(SITE_URL, PHP_URL_PATH) ?? '';
- $uri = str_replace($basePath, '', $uri);
- $uri = parse_url($uri, PHP_URL_PATH) ?? '/';
-
- foreach ($this->routes as $pattern => $handler) {
- if ($this->match($pattern, $uri)) {
- return $this->callHandler($handler, $this->params);
- }
- }
-
- // 404
- http_response_code(404);
- include 'views/errors/404.php';
- exit;
- }
-
- private function match($pattern, $uri) {
- $pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $pattern);
- $pattern = "#^$pattern$#";
-
- if (preg_match($pattern, $uri, $matches)) {
- $this->params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
- return true;
- }
- return false;
- }
-
- private function callHandler($handler, $params) {
- if (is_callable($handler)) {
- return call_user_func_array($handler, array_values($params));
- }
- if (is_string($handler)) {
- list($controller, $method) = explode('@', $handler);
- $controllerFile = "controllers/{$controller}.php";
- if (file_exists($controllerFile)) {
- require_once $controllerFile;
- $controllerInstance = new $controller();
- if (method_exists($controllerInstance, $method)) {
- return call_user_func_array([$controllerInstance, $method], array_values($params));
- }
- }
- }
- throw new Exception("Handler not found");
- }
-}
-
-// Инициализация роутера
-$router = new Router();
-
-// Маршруты
-$router->add('/', 'DashboardController@index');
-$router->add('/dashboard', 'DashboardController@index');
-$router->add('/index.php', 'DashboardController@index');
-$router->add('/login', 'AuthController@login');
-$router->add('/logout', 'AuthController@logout');
-$router->add('/register', 'AuthController@register');
-
-// Книги
-$router->add('/books', 'BookController@index');
-$router->add('/book/all/{id}', 'BookController@viewAll');
-$router->add('/books/create', 'BookController@create');
-$router->add('/books/{id}/edit', 'BookController@edit');
-$router->add('/books/{id}/delete', 'BookController@delete');
-$router->add('/books/delete-all', 'BookController@deleteAll');
-$router->add('/books/{id}/normalize', 'BookController@normalizeContent');
-$router->add('/books/{id}/regenerate-token', 'BookController@regenerateToken');
-
-// Главы
-$router->add('/books/{book_id}/chapters', 'ChapterController@index');
-$router->add('/books/{book_id}/chapters/create', 'ChapterController@create');
-$router->add('/chapters/{id}/edit', 'ChapterController@edit');
-$router->add('/chapters/{id}/delete', 'ChapterController@delete');
-$router->add('/chapters/preview', 'ChapterController@preview');
-
-// Серии
-$router->add('/series', 'SeriesController@index');
-$router->add('/series/create', 'SeriesController@create');
-$router->add('/series/{id}/edit', 'SeriesController@edit');
-$router->add('/series/{id}/delete', 'SeriesController@delete');
-$router->add('/series/{id}/add-book', 'SeriesController@addBook');
-$router->add('/series/{id}/remove-book/{book_id}', 'SeriesController@removeBook');
-$router->add('/series/{id}/update-order', 'SeriesController@updateBookOrder');
-
-// Профиль
-$router->add('/profile', 'UserController@profile');
-$router->add('/profile/update', 'UserController@updateProfile');
-
-// Экспорт с параметром формата
-//публичный экспорт
-$router->add('/export/shared/{share_token}/{format}', 'ExportController@exportShared');
-$router->add('/export/shared/{share_token}', 'ExportController@exportShared'); // по умолчанию pdf
-//авторсикй экспорт
-$router->add('/export/{book_id}/{format}', 'ExportController@export');
-$router->add('/export/{book_id}', 'ExportController@export'); // по умолчанию pdf
-
-// Публичные страницы
-$router->add('/book/{share_token}', 'BookController@viewPublic');
-$router->add('/author/{id}', 'UserController@viewPublic');
-$router->add('/series/{id}/view', 'SeriesController@viewPublic');
-
-
-// Администрирование
-$router->add('/admin/users', 'AdminController@users');
-$router->add('/admin/add-user', 'AdminController@addUser');
-$router->add('/admin/user/{user_id}/toggle-status', 'AdminController@toggleUserStatus');
-$router->add('/admin/user/{user_id}/delete', 'AdminController@deleteUser');
-
-
-// Обработка запроса
-$requestUri = $_SERVER['REQUEST_URI'];
-$router->handle($requestUri);
-
-// Редирект с корня на dashboard для авторизованных
-$router->add('/', function() {
- if (is_logged_in()) {
- header("Location: " . SITE_URL . "/dashboard");
- } else {
- header("Location: " . SITE_URL . "/login");
- }
- exit;
-});
-
-
-?>
-// ./includes/functions.php
- 100) {
- $filename = substr($filename, 0, 100);
- }
-
- return $filename;
-}
-
-function handleCoverUpload($file, $book_id) {
- global $pdo;
-
- // Проверяем папку для загрузок
- if (!file_exists(COVERS_PATH)) {
- mkdir(COVERS_PATH, 0755, true);
- }
-
- $allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
- $max_size = 5 * 1024 * 1024; // 5MB
-
- // Проверка типа файла
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $mime_type = finfo_file($finfo, $file['tmp_name']);
- finfo_close($finfo);
-
- if (!in_array($mime_type, $allowed_types)) {
- return ['success' => false, 'error' => 'Разрешены только JPG, PNG, GIF и WebP изображения'];
- }
-
- // Проверка размера
- if ($file['size'] > $max_size) {
- return ['success' => false, 'error' => 'Размер изображения не должен превышать 5MB'];
- }
-
- // Проверка на ошибки загрузки
- if ($file['error'] !== UPLOAD_ERR_OK) {
- return ['success' => false, 'error' => 'Ошибка загрузки файла: ' . $file['error']];
- }
-
- // Генерация уникального имени файла
- $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
- $filename = 'cover_' . $book_id . '_' . time() . '.' . $extension;
- $file_path = COVERS_PATH . $filename;
-
- // Удаляем старую обложку если есть
- $bookModel = new Book($pdo);
- $bookModel->deleteCover($book_id);
-
- // Сохраняем новую обложку
- if (move_uploaded_file($file['tmp_name'], $file_path)) {
- // Оптимизируем изображение
- optimizeImage($file_path);
- return ['success' => true, 'filename' => $filename];
- } else {
- return ['success' => false, 'error' => 'Не удалось сохранить файл'];
- }
-}
-
-function optimizeImage($file_path) {
- list($width, $height, $type) = getimagesize($file_path);
-
- $max_width = 800;
- $max_height = 1200;
-
- if ($width > $max_width || $height > $max_height) {
- // Вычисляем новые размеры
- $ratio = $width / $height;
- if ($max_width / $max_height > $ratio) {
- $new_width = $max_height * $ratio;
- $new_height = $max_height;
- } else {
- $new_width = $max_width;
- $new_height = $max_width / $ratio;
- }
-
- // Создаем новое изображение
- $new_image = imagecreatetruecolor($new_width, $new_height);
-
- // Загружаем исходное изображение в зависимости от типа
- switch ($type) {
- case IMAGETYPE_JPEG:
- $source = imagecreatefromjpeg($file_path);
- break;
- case IMAGETYPE_PNG:
- $source = imagecreatefrompng($file_path);
- // Сохраняем прозрачность для PNG
- imagecolortransparent($new_image, imagecolorallocatealpha($new_image, 0, 0, 0, 127));
- imagealphablending($new_image, false);
- imagesavealpha($new_image, true);
- break;
- case IMAGETYPE_GIF:
- $source = imagecreatefromgif($file_path);
- break;
- default:
- return; // Не поддерживаемый тип
- }
-
- // Ресайз и сохраняем
- imagecopyresampled($new_image, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
-
- switch ($type) {
- case IMAGETYPE_JPEG:
- imagejpeg($new_image, $file_path, 85);
- break;
- case IMAGETYPE_PNG:
- imagepng($new_image, $file_path, 8);
- break;
- case IMAGETYPE_GIF:
- imagegif($new_image, $file_path);
- break;
- }
-
- // Освобождаем память
- imagedestroy($source);
- imagedestroy($new_image);
- }
-}
-
-function handleAvatarUpload($file, $user_id) {
- global $pdo;
-
- // Проверяем папку для загрузок
- if (!file_exists(AVATARS_PATH)) {
- mkdir(AVATARS_PATH, 0755, true);
- }
-
- $allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
- $max_size = 2 * 1024 * 1024; // 2MB
-
- // Проверка типа файла
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $mime_type = finfo_file($finfo, $file['tmp_name']);
- finfo_close($finfo);
-
- if (!in_array($mime_type, $allowed_types)) {
- return ['success' => false, 'error' => 'Разрешены только JPG, PNG, GIF и WebP изображения'];
- }
-
- // Проверка размера
- if ($file['size'] > $max_size) {
- return ['success' => false, 'error' => 'Размер изображения не должен превышать 2MB'];
- }
-
- // Проверка на ошибки загрузки
- if ($file['error'] !== UPLOAD_ERR_OK) {
- return ['success' => false, 'error' => 'Ошибка загрузки файла: ' . $file['error']];
- }
-
- // Проверка реального типа файла по содержимому
- $allowed_signatures = [
- 'image/jpeg' => "\xFF\xD8\xFF",
- 'image/png' => "\x89\x50\x4E\x47",
- 'image/gif' => "GIF",
- 'image/webp' => "RIFF"
- ];
-
- $file_content = file_get_contents($file['tmp_name']);
- $signature = substr($file_content, 0, 4);
-
- $valid_signature = false;
- foreach ($allowed_signatures as $type => $sig) {
- if (strpos($signature, $sig) === 0) {
- $valid_signature = true;
- break;
- }
- }
-
- if (!$valid_signature) {
- return ['success' => false, 'error' => 'Неверный формат изображения'];
- }
-
- // Генерация уникального имени файла
- $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
- $filename = 'avatar_' . $user_id . '_' . time() . '.' . $extension;
- $file_path = AVATARS_PATH . $filename;
-
- // Удаляем старый аватар если есть
- $userModel = new User($pdo);
- $user = $userModel->findById($user_id);
- if (!empty($user['avatar'])) {
- $old_file_path = AVATARS_PATH . $user['avatar'];
- if (file_exists($old_file_path)) {
- unlink($old_file_path);
- }
- }
-
- // Сохраняем новую аватарку
- if (move_uploaded_file($file['tmp_name'], $file_path)) {
- // Оптимизируем изображение
- optimizeAvatar($file_path);
- return ['success' => true, 'filename' => $filename];
- } else {
- return ['success' => false, 'error' => 'Не удалось сохранить файл'];
- }
-}
-
-function optimizeAvatar($file_path) {
- // Оптимизация аватарки - ресайз до 200x200
- list($width, $height, $type) = getimagesize($file_path);
-
- $max_size = 200;
-
- if ($width > $max_size || $height > $max_size) {
- // Вычисляем новые размеры
- $ratio = $width / $height;
- if ($ratio > 1) {
- $new_width = $max_size;
- $new_height = $max_size / $ratio;
- } else {
- $new_width = $max_size * $ratio;
- $new_height = $max_size;
- }
-
- // Создаем новое изображение
- $new_image = imagecreatetruecolor($new_width, $new_height);
-
- // Загружаем исходное изображение в зависимости от типа
- switch ($type) {
- case IMAGETYPE_JPEG:
- $source = imagecreatefromjpeg($file_path);
- break;
- case IMAGETYPE_PNG:
- $source = imagecreatefrompng($file_path);
- // Сохраняем прозрачность для PNG
- imagecolortransparent($new_image, imagecolorallocatealpha($new_image, 0, 0, 0, 127));
- imagealphablending($new_image, false);
- imagesavealpha($new_image, true);
- break;
- case IMAGETYPE_GIF:
- $source = imagecreatefromgif($file_path);
- break;
- case IMAGETYPE_WEBP:
- $source = imagecreatefromwebp($file_path);
- break;
- default:
- return; // Не поддерживаемый тип
- }
-
- // Ресайз и сохраняем
- imagecopyresampled($new_image, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
-
- switch ($type) {
- case IMAGETYPE_JPEG:
- imagejpeg($new_image, $file_path, 85);
- break;
- case IMAGETYPE_PNG:
- imagepng($new_image, $file_path, 8);
- break;
- case IMAGETYPE_GIF:
- imagegif($new_image, $file_path);
- break;
- case IMAGETYPE_WEBP:
- imagewebp($new_image, $file_path, 85);
- break;
- }
-
- // Освобождаем память
- imagedestroy($source);
- imagedestroy($new_image);
- }
-}
-
-function deleteUserAvatar($user_id) {
- global $pdo;
-
- $userModel = new User($pdo);
- $user = $userModel->findById($user_id);
-
- if (!empty($user['avatar'])) {
- $file_path = AVATARS_PATH . $user['avatar'];
- if (file_exists($file_path)) {
- unlink($file_path);
- }
-
- // Обновляем запись в БД
- $stmt = $pdo->prepare("UPDATE users SET avatar = NULL WHERE id = ?");
- return $stmt->execute([$user_id]);
- }
-
- return true;
-}
-?>
-// ./includes/index.php
-
-// ./composer.lock
-{
- "_readme": [
- "This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
- "This file is @generated automatically"
- ],
- "content-hash": "493a3be12648bbe702ed126df05ead04",
- "packages": [
- {
- "name": "cybermonde/odtphp",
- "version": "v1.7",
- "source": {
- "type": "git",
- "url": "https://github.com/cybermonde/odtphp.git",
- "reference": "23aba70923ca3c07af15a5600f4072751c1b4a36"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/cybermonde/odtphp/zipball/23aba70923ca3c07af15a5600f4072751c1b4a36",
- "reference": "23aba70923ca3c07af15a5600f4072751c1b4a36",
- "shasum": ""
- },
- "require": {
- "php": ">=5.2.4"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "library"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL"
- ],
- "description": "ODT document generator",
- "homepage": "https://github.com/cybermonde/odtphp",
- "keywords": [
- "odt",
- "php"
- ],
- "support": {
- "issues": "https://github.com/cybermonde/odtphp/issues",
- "source": "https://github.com/cybermonde/odtphp/tree/v1.7"
- },
- "time": "2015-06-02T07:28:25+00:00"
- },
- {
- "name": "phpoffice/math",
- "version": "0.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/PHPOffice/Math.git",
- "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHPOffice/Math/zipball/fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a",
- "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-xml": "*",
- "php": "^7.1|^8.0"
- },
- "require-dev": {
- "phpstan/phpstan": "^0.12.88 || ^1.0.0",
- "phpunit/phpunit": "^7.0 || ^9.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "PhpOffice\\Math\\": "src/Math/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Progi1984",
- "homepage": "https://lefevre.dev"
- }
- ],
- "description": "Math - Manipulate Math Formula",
- "homepage": "https://phpoffice.github.io/Math/",
- "keywords": [
- "MathML",
- "officemathml",
- "php"
- ],
- "support": {
- "issues": "https://github.com/PHPOffice/Math/issues",
- "source": "https://github.com/PHPOffice/Math/tree/0.3.0"
- },
- "time": "2025-05-29T08:31:49+00:00"
- },
- {
- "name": "phpoffice/phpword",
- "version": "1.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/PHPOffice/PHPWord.git",
- "reference": "6d75328229bc93790b37e93741adf70646cea958"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/6d75328229bc93790b37e93741adf70646cea958",
- "reference": "6d75328229bc93790b37e93741adf70646cea958",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-gd": "*",
- "ext-json": "*",
- "ext-xml": "*",
- "ext-zip": "*",
- "php": "^7.1|^8.0",
- "phpoffice/math": "^0.3"
- },
- "require-dev": {
- "dompdf/dompdf": "^2.0 || ^3.0",
- "ext-libxml": "*",
- "friendsofphp/php-cs-fixer": "^3.3",
- "mpdf/mpdf": "^7.0 || ^8.0",
- "phpmd/phpmd": "^2.13",
- "phpstan/phpstan": "^0.12.88 || ^1.0.0",
- "phpstan/phpstan-phpunit": "^1.0 || ^2.0",
- "phpunit/phpunit": ">=7.0",
- "symfony/process": "^4.4 || ^5.0",
- "tecnickcom/tcpdf": "^6.5"
- },
- "suggest": {
- "dompdf/dompdf": "Allows writing PDF",
- "ext-xmlwriter": "Allows writing OOXML and ODF",
- "ext-xsl": "Allows applying XSL style sheet to headers, to main document part, and to footers of an OOXML template"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "PhpOffice\\PhpWord\\": "src/PhpWord"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-3.0-only"
- ],
- "authors": [
- {
- "name": "Mark Baker"
- },
- {
- "name": "Gabriel Bull",
- "email": "me@gabrielbull.com",
- "homepage": "http://gabrielbull.com/"
- },
- {
- "name": "Franck Lefevre",
- "homepage": "https://rootslabs.net/blog/"
- },
- {
- "name": "Ivan Lanin",
- "homepage": "http://ivan.lanin.org"
- },
- {
- "name": "Roman Syroeshko",
- "homepage": "http://ru.linkedin.com/pub/roman-syroeshko/34/a53/994/"
- },
- {
- "name": "Antoine de Troostembergh"
- }
- ],
- "description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)",
- "homepage": "https://phpoffice.github.io/PHPWord/",
- "keywords": [
- "ISO IEC 29500",
- "OOXML",
- "Office Open XML",
- "OpenDocument",
- "OpenXML",
- "PhpOffice",
- "PhpWord",
- "Rich Text Format",
- "WordprocessingML",
- "doc",
- "docx",
- "html",
- "odf",
- "odt",
- "office",
- "pdf",
- "php",
- "reader",
- "rtf",
- "template",
- "template processor",
- "word",
- "writer"
- ],
- "support": {
- "issues": "https://github.com/PHPOffice/PHPWord/issues",
- "source": "https://github.com/PHPOffice/PHPWord/tree/1.4.0"
- },
- "time": "2025-06-05T10:32:36+00:00"
- },
- {
- "name": "tecnickcom/tcpdf",
- "version": "6.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/tecnickcom/TCPDF.git",
- "reference": "ca5b6de294512145db96bcbc94e61696599c391d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/ca5b6de294512145db96bcbc94e61696599c391d",
- "reference": "ca5b6de294512145db96bcbc94e61696599c391d",
- "shasum": ""
- },
- "require": {
- "ext-curl": "*",
- "php": ">=7.1.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "config",
- "include",
- "tcpdf.php",
- "tcpdf_barcodes_1d.php",
- "tcpdf_barcodes_2d.php",
- "include/tcpdf_colors.php",
- "include/tcpdf_filters.php",
- "include/tcpdf_font_data.php",
- "include/tcpdf_fonts.php",
- "include/tcpdf_images.php",
- "include/tcpdf_static.php",
- "include/barcodes/datamatrix.php",
- "include/barcodes/pdf417.php",
- "include/barcodes/qrcode.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "LGPL-3.0-or-later"
- ],
- "authors": [
- {
- "name": "Nicola Asuni",
- "email": "info@tecnick.com",
- "role": "lead"
- }
- ],
- "description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
- "homepage": "http://www.tcpdf.org/",
- "keywords": [
- "PDFD32000-2008",
- "TCPDF",
- "barcodes",
- "datamatrix",
- "pdf",
- "pdf417",
- "qrcode"
- ],
- "support": {
- "issues": "https://github.com/tecnickcom/TCPDF/issues",
- "source": "https://github.com/tecnickcom/TCPDF/tree/6.10.0"
- },
- "funding": [
- {
- "url": "https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ",
- "type": "custom"
- }
- ],
- "time": "2025-05-27T18:02:28+00:00"
- }
- ],
- "packages-dev": [],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": {},
- "prefer-stable": false,
- "prefer-lowest": false,
- "platform": {},
- "platform-dev": {},
- "plugin-api-version": "2.9.0"
-}
-
-// ./README.md
-# Web Writer
-
-**Лицензия:** AGPLv3
-
-**Web Writer** — веб-приложение для написания, хранения и публикации книг и серий книг. Поддерживает Markdown с расширением для диалогов, автосохранение текста, экспорт и управление пользователями.
-
----
-
-## 🚀 Возможности
-
-- **Книги и серии:** создавайте серии и добавляйте книги с главами.
-- **Редактор книг:** Markdown, автосохранение текста, интерактивное содержание.
-- **Предпросмотр книг:**
- - **Автор:** видит все черновики и опубликованные главы.
- - **Публичный доступ:** только опубликованные главы по ссылке с `shared_token`.
-- **Обложки и аватары:** добавляйте изображения к книгам и профилям.
-- **Экспорт:** PDF, DOCX, HTML, TXT.
-- **Администрирование пользователей:**
- - Управление аккаунтами, активация/деактивация.
- - При удалении пользователя удаляются все его книги.
-- **Публичные ссылки:** делитесь `shared_token` для просмотра опубликованных глав.
-
----
-
-## ⚙️ Требования
-
-- **PHP:** 8.0 и выше
-- **MySQL** с InnoDB и внешними ключами
-- **PHP расширения:** `mbstring`, `json`, `PDO`
-- Веб-сервер с правами на запись в папки `config/` и `uploads/`
-
-> Все библиотеки уже включены в `vendor/`. Composer не нужен.
-
----
-
-## 🛠 Установка
-
-1. Скопируйте файлы на веб-сервер.
-2. Проверьте доступность папок `config/` и `uploads/` для записи.
-3. Перейдите в браузере на `install.php` и следуйте шагам:
-
- **Шаг 1: Настройки базы данных**
- - Хост БД
- - Имя базы данных
- - Пользователь и пароль
-
- **Шаг 2: Создание администратора**
- - Имя пользователя
- - Пароль
- - Email (по желанию)
- - Отображаемое имя (по желанию)
-
-4. После успешной установки файл `config/config.php` будет сгенерирован автоматически.
-5. Перейдите на главную страницу приложения (`index.php`) и войдите под админом.
-6. **Не забудьте удалить или переместить файл install.php!!!**
-
----
-
-## 📝 Конфигурация
-
-Файл `config/config.php` содержит:
-
-- Подключение к базе данных: `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_NAME`
-- Пути к файлам:
- - `UPLOAD_PATH` — корневая папка загрузок
- - `COVERS_PATH` / `COVERS_URL` — обложки книг
- - `AVATARS_PATH` / `AVATARS_URL` — аватары пользователей
-- Адрес сайта: `SITE_URL`
-- Имя приложения: `APP_NAME` = "Web Writer"
-
----
-
-## 🛠 Дальнейшее развитие
-
-- Планирую вынести работу с сущностями (книги, главы, серии, пользователи) в контроллеры.
-- Создать единую точку входа для приложения.
-
----
-
-## ❗ Поддержка
-
-Все ошибки и предложения шлите в issue
-
----
-
-## 📜 Лицензия
-
-Приложение распространяется под лицензией [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.html).
-// ./config/index.php
-
-// ./config/config.php
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-} catch(PDOException $e) {
- error_log("DB Error: " . $e->getMessage());
- die("Ошибка подключения к базе данных");
-}
-
-// Добавляем константы для новых путей
-define('CONTROLLERS_PATH', __DIR__ . '/../controllers/');
-define('VIEWS_PATH', __DIR__ . '/../views/');
-define('LAYOUTS_PATH', VIEWS_PATH . 'layouts/');
-
-// Автозагрузка контроллеров
-spl_autoload_register(function ($class_name) {
- $controller_file = CONTROLLERS_PATH . $class_name . '.php';
- if (file_exists($controller_file)) {
- require_once $controller_file;
- }
-});
-?>
-// ./install.php
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
- // Пытаемся создать базу данных если не существует
- $pdo->exec("CREATE DATABASE IF NOT EXISTS `$db_name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
- $pdo->exec("USE `$db_name`");
-
- // Сохраняем данные в сессии для следующего шага
- session_start();
- $_SESSION['install_data'] = [
- 'db_host' => $db_host,
- 'db_name' => $db_name,
- 'db_user' => $db_user,
- 'db_pass' => $db_pass
- ];
-
- header('Location: install.php?step=2');
- exit;
-
- } catch (PDOException $e) {
- $error = "Ошибка подключения к базе данных: " . $e->getMessage();
- }
-
- } elseif ($step === '2') {
- // Шаг 2: Создание администратора
- session_start();
- if (!isset($_SESSION['install_data'])) {
- header('Location: install.php?step=1');
- exit;
- }
-
- $admin_username = $_POST['admin_username'] ?? '';
- $admin_password = $_POST['admin_password'] ?? '';
- $admin_email = $_POST['admin_email'] ?? '';
- $admin_display_name = $_POST['admin_display_name'] ?? $admin_username;
-
- if (empty($admin_username) || empty($admin_password)) {
- $error = 'Имя пользователя и пароль администратора обязательны';
- } else {
- try {
- $db = $_SESSION['install_data'];
- $pdo = new PDO("mysql:host={$db['db_host']};dbname={$db['db_name']}", $db['db_user'], $db['db_pass']);
- $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
- // Создаем таблицы
- $pdo->exec($database_sql);
-
- // Создаем администратора
- $password_hash = password_hash($admin_password, PASSWORD_DEFAULT);
- $stmt = $pdo->prepare("
- INSERT INTO users (username, display_name, password_hash, email, is_active, created_at)
- VALUES (?, ?, ?, ?, 1, NOW())
- ");
- $stmt->execute([$admin_username, $admin_display_name, $password_hash, $admin_email]);
-
- // Создаем config.php
- $config_content = generate_config($db);
- if (file_put_contents('config/config.php', $config_content)) {
- // Создаем папки для загрузок
- if (!file_exists('uploads/covers')) {
- mkdir('uploads/covers', 0755, true);
- }
- if (!file_exists('uploads/avatars')) {
- mkdir('uploads/avatars', 0755, true);
- }
-
- $success = 'Установка завершена успешно!';
- session_destroy();
- } else {
- $error = 'Не удалось создать файл config.php. Проверьте права доступа к папке config/';
- }
-
- } catch (PDOException $e) {
- $error = "Ошибка при установке: " . $e->getMessage();
- }
- }
- }
-}
-
-function generate_config($db) {
- $site_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
- $base_path = str_replace('/install.php', '', $_SERVER['PHP_SELF']);
- $site_url .= $base_path;
-
- return <<setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-} catch(PDOException \$e) {
- error_log("DB Error: " . \$e->getMessage());
- die("Ошибка подключения к базе данных");
-}
-
-
-
-// Автозагрузка моделей
-spl_autoload_register(function (\$class_name) {
- \$model_file = __DIR__ . '/../models/' . \$class_name . '.php';
- if (file_exists(\$model_file)) {
- require_once \$model_file;
- }
-});
-?>
-EOT;
-}
-?>
-
-
-
-
-
- Установка Web Writer
-
-
-
-
-
-
-
Установка Web Writer
-
-
-
-
1. База данных
-
2. Администратор
-
3. Завершение
-
-
-
-
- = htmlspecialchars($error) ?>
-
-
-
-
-
- = htmlspecialchars($success) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Перед установкой убедитесь, что:
-
- - Сервер MySQL запущен и доступен
- - У вас есть данные для подключения к БД (хост, пользователь, пароль)
- - Папка
config/ доступна для записи
- - Папка
uploads/ доступна для записи
-
-
-
-
-
-
-
-// ./views/layouts/header.php
-
-
-
-
-
-
- = e($page_title ?? 'Web Writer') ?>
-
-
-
-
-
-
-
-
-
-
-
- = e($_SESSION['success']) ?>
-
-
-
-
-
-
- = e($_SESSION['error']) ?>
-
-
-
-
-
-
- = e($_SESSION['warning']) ?>
-
-
-
-
-
-
- = e($_SESSION['info']) ?>
-
-
-
-// ./views/layouts/footer.php
-
-
-
-
-
-
-
-
-// ./views/dashboard/index.php
-
-
-Панель управления
-
-
-
- 📚 Книги
-
- = $total_books ?>
-
- Всего книг
-
-
-
- 📑 Главы
-
- = $total_chapters ?>
-
- Всего глав
-
-
-
- 📝 Слова
-
- = number_format($total_words) ?>
-
- Всего слов
-
-
-
- 🌐 Публикации
-
- = $published_books_count ?>
-
- Опубликовано книг
-
-
-
-
-
-
Недавние книги
-
-
-
-
-
- = e($book['genre']) ?>
-
-
- = e($book['description']) ?>
-
-
-
-
-
-
-
-
-
-
- У вас пока нет книг.
- Создать первую книгу
-
-
-
-
-
-
Мои серии
-
-
-
-
-
- = e(mb_strimwidth($ser['description'], 0, 100, '...')) ?>
-
-
-
-
-
-
-
-
- У вас пока нет серий.
- Создать первую серию
-
-
-
-
Быстрые действия
-
-
-
-
-
-// ./views/auth/login.php
-
-
-
-
Вход в систему
-
-
-
- = e($error) ?>
-
-
-
-
-
-
-
-
-
-// ./views/auth/register.php
-
-
-
-
Регистрация
-
-
-
- = e($error) ?>
-
-
-
-
-
- = e($success) ?>
-
-
-
-
-
-
-
-
-
-// ./views/chapters/preview.php
-
-
-
-
-
- = e($page_title) ?>
-
-
-
-
-
-
-
- = $content ?>
-
-
-
-
-// ./views/chapters/index.php
-
-
-
-
Главы книги: = e($book['title']) ?>
-
-
-
-
-
-
-
-
-
-
- | № |
- Название главы |
- Статус |
- Слов |
- Обновлено |
- Действия |
-
-
-
- $chapter): ?>
-
- | = $index + 1 ?> |
-
- = e($chapter['title']) ?>
-
- = e(mb_strimwidth($chapter['content'], 0, 100, '...')) ?>
-
- |
-
-
- = $chapter['status'] == 'published' ? '✅ Опубликована' : '📝 Черновик' ?>
-
- |
- = $chapter['word_count'] ?> |
-
- = date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?>
- |
-
-
- |
-
-
-
-
-
-
-
- Статистика:
- Всего глав: = count($chapters) ?> |
- Всего слов: = array_sum(array_column($chapters, 'word_count')) ?> |
- Опубликовано: = count(array_filter($chapters, function($ch) { return $ch['status'] == 'published'; })) ?>
-
-
-
-
-// ./views/chapters/create.php
-
-
-Новая глава для: = e($book['title']) ?>
-
-
-
- = e($error) ?>
-
-
-
-
-
-
-
-
-
-
-// ./views/chapters/edit.php
-
-
-Редактирование главы: = e($chapter['title']) ?>
-
-
-
- = e($error) ?>
-
-
-
-
-
-
-
Информация о главе
-
Книга: = e($book['title']) ?>
-
Количество слов: = $chapter['word_count'] ?>
-
Создана: = date('d.m.Y H:i', strtotime($chapter['created_at'])) ?>
-
Обновлена: = date('d.m.Y H:i', strtotime($chapter['updated_at'])) ?>
-
-
-
-
-
-
-// ./views/series/index.php
-
-
-
-
-
-
- 📚
- Пока нет серий
-
- Создайте свою первую серию, чтобы организовать книги в циклы и сериалы.
-
-
-
-
-
-
-
-
-
-
-
- = e($ser['description']) ?>
-
-
-
-
-
- = $ser['book_count'] ?? 0 ?>
- книг
-
-
- = number_format($ser['total_words'] ?? 0) ?>
- слов
-
-
-
- 0 ? round($ser['total_words'] / $ser['book_count']) : 0;
- echo number_format($avg_words);
- ?>
-
- слов/книга
-
-
-
-
-
-
-
-
-
-
-// ./views/series/create.php
-
-
-Создание новой серии
-
-
-
- = e($error) ?>
-
-
-
-
-
-
-
Что такое серия?
-
Серия позволяет объединить несколько книг в одну тематическую коллекцию. Это полезно для:
-
- - Циклов книг с общим сюжетом
- - Книг в одном мире или вселенной
- - Организации книг по темам или жанрам
-
-
Вы сможете добавить книги в серию после её создания.
-
-
-
-// ./views/series/edit.php
-
-
-Редактирование серии: = e($series['title']) ?>
-
- Основная информация
-
-
-
-
-
-
-
- Добавить книгу в серию
-
-
-
- Все ваши книги уже добавлены в эту серию или у вас нет доступных книг.
- Создать новую книгу
-
-
-
-
-
-
- Книги в серии (= count($books_in_series) ?>)
-
-
-
-
-
-
- В этой серии пока нет книг. Добавьте книги с помощью формы слева.
-
-
-
-
-
-
-
-
-
-
-
-// ./views/series/view_public.php
-
-
-
-
-
-
-
-
-
В этой серии пока нет опубликованных книг
-
Автор еще не опубликовал книги из этой серии
-
-
-
-
Книги серии
-
-
-
-
-
-
![<?= e($book['title']) ?>](<?= COVERS_URL . e($book['cover_image']) ?>)
-
-
-
-
-
-
-
-
- Книга = $book['sort_order_in_series'] ?>
-
- = e($book['title']) ?>
-
-
-
-
= e($book['genre']) ?>
-
-
-
-
= nl2br(e($book['description'])) ?>
-
-
-
-
- Читать
-
-
- getBookStats($book['id'], true);
- ?>
-
-
- Глав: = $book_stats['chapter_count'] ?? 0 ?> | Слов: = $book_stats['total_words'] ?? 0 ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// ./views/errors/404.php
-
-
-
-
404 - Страница не найдена
-
- Запрашиваемая страница не существует или была перемещена.
-
-
-
-
-
-// ./views/admin/add_user.php
-
-
-
-
-
-// ./views/admin/users.php
-
-
-
-
Управление пользователями
-
-
-
- = e($_SESSION['success']) ?>
-
-
-
-
-
-
- = e($_SESSION['error']) ?>
-
-
-
-
-
-
-
-
- Пользователи не найдены
- Зарегистрируйте первого пользователя
- 📝 Добавить пользователя
-
-
-
-
-
-
- | ID |
- Имя пользователя |
- Отображаемое имя |
- Email |
- Дата регистрации |
- Статус |
- Действия |
-
-
-
-
-
- | = $user['id'] ?> |
-
- = e($user['username']) ?>
-
- (Вы)
-
- |
- = e($user['display_name']) ?> |
- = e($user['email']) ?> |
-
- = date('d.m.Y H:i', strtotime($user['created_at'])) ?>
-
- Вход: = date('d.m.Y H:i', strtotime($user['last_login'])) ?>
-
- |
-
-
- = $user['is_active'] ? '✅ Активен' : '❌ Неактивен' ?>
-
- |
-
-
-
-
-
-
-
- Текущий пользователь
-
- |
-
-
-
-
-
-
-
-
-
-// ./views/user/profile.php
-
-
-Мой профиль
-
-
-
- = e($message) ?>
-
-
-
-
-
- Основная информация
-
-
-
-
- Аватарка
-
-
-
-
 ?>)
-
-
- = mb_substr(e($user['display_name'] ?? $user['username']), 0, 1) ?>
-
-
-
-
-
-
-
-
-
- Примечание: Аватарка отображается на вашей публичной странице автора
-
-
-
-
-
-
-
- Информация об аккаунте
-
- 👁️ Посмотреть мою публичную страницу
-
- Дата регистрации: = date('d.m.Y H:i', strtotime($user['created_at'])) ?>
-
- Последний вход: = date('d.m.Y H:i', strtotime($user['last_login'])) ?>
-
-
-
-
-// ./views/user/view_public.php
-
-
-
-
-
-
- Публикации автора
-
-
-
-
У этого автора пока нет опубликованных книг
-
Следите за обновлениями, скоро здесь появятся новые произведения!
-
-
-
-
-
-
-
-
![<?= e($book['title']) ?>](<?= COVERS_URL . e($book['cover_image']) ?>)
-
-
-
-
-
-
-
= e($book['title']) ?>
-
-
-
= e($book['genre']) ?>
-
-
-
-
= nl2br(e($book['description'])) ?>
-
-
- getBookStats($book['id'], true);
- $chapter_count = $book_stats['chapter_count'] ?? 0;
- $word_count = $book_stats['total_words'] ?? 0;
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// ./views/books/index.php
-
-
-Мои книги (Всего книг: = count($books) ?>)
-
-
-
-
-
-
- У вас пока нет книг
- Создайте свою первую книгу и начните писать!
- 📖 Создать первую книгу
-
-
-
-
-
-
-
-
-
![<?= e($book['title']) ?>](<?= COVERS_URL . e($book['cover_image']) ?>)
-
- 📚
-
-
-
- 📚
-
-
-
-
-
- = $book['published'] ? '✅' : '📝' ?>
-
-
-
-
-
-
-
-
-
= e($book['genre']) ?>
-
-
-
-
- = e(mb_strimwidth($book['description'], 0, 120, '...')) ?>
-
-
-
-
-
-
- = $book['chapter_count'] ?? 0 ?> глав
-
-
- = number_format($book['total_words'] ?? 0) ?> слов
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// ./views/books/create.php
-
-Создание новой книги
-
-
- = e($_SESSION['error']) ?>
-
-
-
-
-
-
- = e($error) ?>
-
-
-
-
- Ошибка загрузки обложки: = e($cover_error) ?>
-
-
-
-
-// ./views/books/edit.php
-
-
-
- Ошибка загрузки обложки: = e($_SESSION['cover_error']) ?>
-
-
-
-Редактирование книги
-
-
-
-
-
Публичная ссылка для чтения
-
-
-
-
-
-
- Примечание: В публичном просмотре отображаются только главы со статусом "Опубликована"
-
-
-
-
Экспорт книги
-
Экспортируйте книгу в различные форматы:
-
-
- Примечание: Экспортируются все главы книги (включая черновики)
-
-
-
-
Главы этой книги
-
-
-
-
-
-
- | Название |
- Статус |
- Слов |
- Действия |
-
-
-
-
-
- | = e($chapter['title']) ?> |
-
-
- = $chapter['status'] == 'published' ? 'Опубликована' : 'Черновик' ?>
-
- |
- = $chapter['word_count'] ?> |
-
-
- Редактировать
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// ./views/books/view_public.php
-
-
-
-
-
-
-
-
![<?= e($book['title']) ?>](<?= COVERS_URL . e($book['cover_image']) ?>)
-
-
-
- = e($book['title']) ?>
-
-
- Автор: = e($author['display_name']??$author['username']) ?>
-
-
-
-
- = e($book['genre']) ?>
-
-
-
-
-
- = nl2br(e($book['description'])) ?>
-
-
-
-
-
Глав: = count($chapters) ?>
-
Слов: = array_sum(array_column($chapters, 'word_count')) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
В этой книге пока нет глав
-
Автор еще не опубликовал содержание книги
-
-
- Оглавление
-
-
-
-
- $chapter): ?>
-
-
- Глава = $index + 1 ?>: = e($chapter['title']) ?>
-
-
-
- = $chapter['content'] ?>
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/config/config.php b/config/config.php
deleted file mode 100755
index 35e767f..0000000
--- a/config/config.php
+++ /dev/null
@@ -1,51 +0,0 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-} catch(PDOException $e) {
- error_log("DB Error: " . $e->getMessage());
- die("Ошибка подключения к базе данных");
-}
-
-// Добавляем константы для новых путей
-define('CONTROLLERS_PATH', __DIR__ . '/../controllers/');
-define('VIEWS_PATH', __DIR__ . '/../views/');
-define('LAYOUTS_PATH', VIEWS_PATH . 'layouts/');
-
-// Автозагрузка контроллеров
-spl_autoload_register(function ($class_name) {
- $controller_file = CONTROLLERS_PATH . $class_name . '.php';
- if (file_exists($controller_file)) {
- require_once $controller_file;
- }
-});
-?>
\ No newline at end of file