finish line!

This commit is contained in:
2025-11-26 17:25:27 +08:00
parent 833d125f64
commit 14434bc2ac
261 changed files with 4530 additions and 1014 deletions
+25 -1
View File
@@ -245,13 +245,14 @@ class BookController extends BaseController {
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 = $bookModel->getPublishedChapters($book['id']);
$chapters = $chapterModel->getPublishedChapters($book['id']);
// Получаем информацию об авторе
$stmt = $this->pdo->prepare("SELECT id, username, display_name FROM users WHERE id = ?");
@@ -266,6 +267,29 @@ class BookController extends BaseController {
]);
}
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();
+79 -56
View File
@@ -53,7 +53,8 @@ class ChapterController extends BaseController {
$error = "Ошибка безопасности";
} else {
$title = trim($_POST['title'] ?? '');
$content = $_POST['content'] ?? '';
$content = trim($_POST['content']) ?? '';
$content = $this->cleanChapterContent($content);
$status = $_POST['status'] ?? 'draft';
if (empty($title)) {
@@ -85,29 +86,16 @@ class ChapterController extends BaseController {
public function edit($id) {
$this->requireLogin();
$user_id = $_SESSION['user_id'];
$chapterModel = new Chapter($this->pdo);
$bookModel = new Book($this->pdo);
// Проверяем права доступа к главе
if (!$chapterModel->userOwnsChapter($id, $user_id)) {
if (isset($_POST['autosave']) && $_POST['autosave'] === 'true') {
// Для AJAX запросов возвращаем JSON
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Доступ запрещен']);
exit;
}
$_SESSION['error'] = "У вас нет доступа к этой главе";
$this->redirect('/books');
}
// Получаем главу и книгу
$chapter = $chapterModel->findById($id);
// Дополнительная проверка - глава должна существовать
if (!$chapter) {
if (isset($_POST['autosave']) && $_POST['autosave'] === 'true') {
if (!empty($_POST['autosave']) && $_POST['autosave'] === 'true') {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Глава не найдена']);
exit;
@@ -117,53 +105,69 @@ class ChapterController extends BaseController {
}
$book = $bookModel->findById($chapter['book_id']);
$error = '';
// Проверяем права доступа
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 = "Ошибка безопасности";
} else {
$title = trim($_POST['title'] ?? '');
$content = $_POST['content'] ?? '';
$status = $_POST['status'] ?? 'draft';
}
if (empty($title)) {
$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 {
$data = [
'title' => $title,
'content' => $content,
'status' => $status
];
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => $error]);
}
exit;
}
// Если это запрос автосейва, возвращаем JSON ответ
if (isset($_POST['autosave']) && $_POST['autosave'] === 'true') {
if ($chapterModel->update($id, $data)) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
} else {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Ошибка при сохранении']);
exit;
}
}
// Обычный POST запрос (сохранение формы)
if ($chapterModel->update($id, $data)) {
$_SESSION['success'] = "Глава успешно обновлена";
$this->redirect("/books/{$chapter['book_id']}/chapters");
} else {
$error = "Ошибка при обновлении главы";
}
// Обычное сохранение формы
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,
'error' => $error ?? '',
'page_title' => "Редактирование главы: " . e($chapter['title'])
]);
}
@@ -202,22 +206,41 @@ class ChapterController extends BaseController {
$this->redirect("/books/{$book_id}/chapters");
}
public function preview() {
$this->requireLogin();
$content = $_POST['content'] ?? '';
$content = $this->cleanChapterContent($content);
$title = $_POST['title'] ?? 'Предпросмотр';
// Просто используем HTML как есть
$html_content = $content;
$this->render('chapters/preview', [
'content' => $html_content,
'content' => $content,
'title' => $title,
'page_title' => "Предпросмотр: " . e($title)
]);
}
// Добавьте эту функцию в начало файла
function cleanChapterContent($content) {
// Удаляем лишние пробелы в начале и конце
$content = trim($content);
// Удаляем пустые абзацы и параграфы, содержащие только пробелы
$content = preg_replace('/<p[^>]*>\s*(?:<br\s*\/?>|&nbsp;)?\s*<\/p>/i', '', $content);
$content = preg_replace('/<p[^>]*>\s*<\/p>/i', '', $content);
// Удаляем последовательные пустые абзацы
$content = preg_replace('/(<\/p>\s*<p[^>]*>)+/', '</p><p>', $content);
// Удаляем лишние пробелы в начале и конце каждого параграфа
$content = preg_replace('/(<p[^>]*>)\s+/', '$1', $content);
$content = preg_replace('/\s+<\/p>/', '</p>', $content);
// Удаляем лишние переносы строк
$content = preg_replace('/\n{3,}/', "\n\n", $content);
return $content;
}
}
?>
+2 -1
View File
@@ -12,6 +12,7 @@ use TCPDF;
class ExportController extends BaseController {
public function export($book_id, $format = 'pdf') {
$this->requireLogin();
$user_id = $_SESSION['user_id'];
@@ -45,7 +46,7 @@ class ExportController extends BaseController {
}
// Для публичного доступа - только опубликованные главы
$chapters = $bookModel->getPublishedChapters($book['id']);
$chapters = $chapterModel->getPublishedChapters($book['id']);
// Получаем информацию об авторе
$author_name = $this->getAuthorName($book['user_id']);
+2
View File
@@ -109,10 +109,12 @@ class SeriesController extends BaseController {
// Получаем книги в серии
$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'])
]);