web_writer/book_edit.php

413 lines
20 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once 'config/config.php';
require_login();
$user_id = $_SESSION['user_id'];
$bookModel = new Book($pdo);
// Проверяем, редактируем ли существующую книгу
$book_id = $_GET['id'] ?? null;
$book = null;
$is_edit = false;
if ($book_id) {
$book = $bookModel->findById($book_id);
if (!$book || $book['user_id'] != $user_id) {
$_SESSION['error'] = "Книга не найдена или у вас нет доступа";
redirect('books.php');
}
$is_edit = true;
}
// Обработка формы
$cover_error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
$_SESSION['error'] = "Ошибка безопасности";
redirect($is_edit ? "book_edit.php?id=$book_id" : 'book_edit.php');
}
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$genre = trim($_POST['genre'] ?? '');
$editor_type = $_POST['editor_type'] ?? 'markdown';
if (empty($title)) {
$_SESSION['error'] = "Название книги обязательно";
} else {
$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;
if ($series_id && !$sort_order_in_series) {
$seriesModel = new Series($pdo);
$sort_order_in_series = $seriesModel->getNextSortOrder($series_id);
}
$data = [
'title' => $title,
'description' => $description,
'genre' => $genre,
'user_id' => $user_id,
'series_id' => $series_id,
'sort_order_in_series' => $sort_order_in_series,
'editor_type' => $editor_type
];
$data['published'] = isset($_POST['published']) ? 1 : 0;
// Проверяем, изменился ли тип редактора
$editor_changed = false;
$old_editor_type = null;
if ($is_edit && $book['editor_type'] !== $editor_type) {
$editor_changed = true;
$old_editor_type = $book['editor_type'];
}
// Обработка загрузки обложки
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
$cover_result = handleCoverUpload($_FILES['cover_image'], $book_id);
if ($cover_result['success']) {
$bookModel->updateCover($book_id, $cover_result['filename']);
// Обновляем данные книги
$book = $bookModel->findById($book_id);
} else {
$cover_error = $cover_result['error'];
}
}
// Обработка удаления обложки
if (isset($_POST['delete_cover']) && $_POST['delete_cover'] == '1') {
$bookModel->deleteCover($book_id);
$book = $bookModel->findById($book_id);
}
if ($is_edit) {
$success = $bookModel->update($book_id, $data);
// Конвертируем контент глав, если изменился редактор
if ($success && $editor_changed) {
$conversion_success = $bookModel->convertChaptersContent($book_id, $old_editor_type, $editor_type);
if (!$conversion_success) {
$_SESSION['warning'] = "Книга обновлена, но возникли ошибки при конвертации содержания глав";
} else {
$_SESSION['info'] = "Книга обновлена. Содержание глав сконвертировано в новый формат редактора.";
}
}
$message = $success ? "Книга успешно обновлена" : "Ошибка при обновлении книги";
} else {
$success = $bookModel->create($data);
$message = $success ? "Книга успешно создана" : "Ошибка при создании книги";
if ($success) {
$new_book_id = $pdo->lastInsertId();
redirect("book_edit.php?id=$new_book_id");
}
}
if ($success) {
$_SESSION['success'] = $message;
redirect('books.php');
} else {
$_SESSION['error'] = $message;
}
}
}
$page_title = $is_edit ? "Редактирование книги" : "Создание новой книги";
include 'views/header.php';
?>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<div style="max-width: 100%; margin-bottom: 0.5rem;">
<label for="title" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Название книги *
</label>
<input type="text" id="title" name="title"
value="<?= e($book['title'] ?? $_POST['title'] ?? '') ?>"
placeholder="Введите название книги"
style="width: 100%; margin-bottom: 1.5rem;"
required>
<label for="genre" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Жанр
</label>
<input type="text" id="genre" name="genre"
value="<?= e($book['genre'] ?? $_POST['genre'] ?? '') ?>"
placeholder="Например: Фантастика, Роман, Детектив..."
style="width: 100%; margin-bottom: 1.5rem;">
<label for="editor_type" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Режим редактора
</label>
<select id="editor_type" name="editor_type" style="width: 100%; margin-bottom: 1.5rem;" onchange="showEditorWarning(this)">
<option value="markdown" <?= ($book['editor_type'] ?? 'markdown') == 'markdown' ? 'selected' : '' ?>>Markdown редактор</option>
<option value="html" <?= ($book['editor_type'] ?? '') == 'html' ? 'selected' : '' ?>>HTML редактор (TinyMCE)</option>
</select>
<div id="editor_warning" style="display: none; background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 4px; margin-bottom: 1rem;">
<strong>Внимание:</strong> При смене редактора содержимое всех глав будет автоматически сконвертировано в новый формат.
</div>
<script>
function showEditorWarning(select) {
const warning = document.getElementById('editor_warning');
const currentEditor = '<?= $book['editor_type'] ?? 'markdown' ?>';
if (select.value !== currentEditor) {
warning.style.display = 'block';
} else {
warning.style.display = 'none';
}
}
// Показать предупреждение при загрузке, если редактор уже отличается
document.addEventListener('DOMContentLoaded', function() {
const currentEditor = '<?= $book['editor_type'] ?? 'markdown' ?>';
const selectedEditor = document.getElementById('editor_type').value;
if (currentEditor !== selectedEditor) {
document.getElementById('editor_warning').style.display = 'block';
}
});
</script>
<label for="series_id" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Серия
</label>
<select id="series_id" name="series_id" style="width: 100%; margin-bottom: 1rem;">
<option value="">-- Без серии --</option>
<?php
$seriesModel = new Series($pdo);
$user_series = $seriesModel->findByUser($user_id, false);
foreach ($user_series as $ser):
$selected = ($ser['id'] == ($book['series_id'] ?? 0)) ? 'selected' : '';
?>
<option value="<?= $ser['id'] ?>" <?= $selected ?>>
<?= e($ser['title']) ?>
</option>
<?php endforeach; ?>
</select>
<label for="sort_order_in_series" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Порядок в серии
</label>
<input type="number" id="sort_order_in_series" name="sort_order_in_series"
value="<?= e($book['sort_order_in_series'] ?? '') ?>"
placeholder="Номер по порядку в серии"
min="1"
style="width: 100%; margin-bottom: 1.5rem;">
<!-- Обложка -->
<div style="margin-bottom: 1.5rem;">
<label for="cover_image" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Обложка книги
</label>
<?php if (!empty($book['cover_image'])): ?>
<div style="margin-bottom: 1rem;">
<p><strong>Текущая обложка:</strong></p>
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
alt="Обложка"
style="max-width: 200px; height: auto; border-radius: 4px; border: 1px solid #ddd;">
<div style="margin-top: 0.5rem;">
<label style="display: inline-flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" name="delete_cover" value="1">
Удалить обложку
</label>
</div>
</div>
<?php endif; ?>
<input type="file" id="cover_image" name="cover_image"
accept="image/jpeg, image/png, image/gif, image/webp"
style="height: 2.6rem;">
<small style="color: #666;">
Разрешены: JPG, PNG, GIF, WebP. Максимальный размер: 5MB.
Рекомендуемый размер: 300×450 пикселей.
</small>
<?php if (!empty($cover_error)): ?>
<div style="color: #d32f2f; margin-top: 0.5rem;">
❌ <?= e($cover_error) ?>
</div>
<?php endif; ?>
</div>
<label for="description" style="display: block; margin-bottom: 0.5rem; font-weight: bold;">
Описание книги
</label>
<textarea id="description" name="description"
placeholder="Краткое описание сюжета или аннотация..."
rows="6"
style="width: 100%;"><?= e($book['description'] ?? $_POST['description'] ?? '') ?></textarea>
<div>
<label for="published">
<input type="checkbox" id="published" name="published" value="1"
<?= !empty($book['published']) || (!empty($_POST['published']) && $_POST['published']) ? 'checked' : '' ?>>
Опубликовать книгу (показывать на публичной странице автора)
</label>
</div>
</div>
<div style="display: flex; gap: 5px; flex-wrap: wrap; align-items: center;">
<button type="submit" class="contrast button">
<?= $is_edit ? '💾 Сохранить изменения' : '📖 Создать книгу' ?>
</button>
</div>
</form>
<?php if ($is_edit): ?>
<form method="post" action="book_normalize_content.php" onsubmit="return confirm('Нормализовать контент всех глав книги? Это действие нельзя отменить.')">
<input type="hidden" name="book_id" value="<?= $book_id ?>">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="button secondary">🔄 Нормализовать контент глав</button>
<p style="margin-top: 0.5rem; font-size: 0.8em; color: #666;">
Если контент глав отображается неправильно после смены редактора, можно нормализовать его структуру.
</p>
</form>
<?php endif; ?>
<?php if ($is_edit): ?>
<form method="post" action="book_delete.php" style="display: inline;" onsubmit="return confirm('Вы уверены, что хотите удалить книгу «<?= e($book['title']) ?>»? Все главы также будут удалены.');">
<input type="hidden" name="book_id" value="<?= $book['id'] ?>">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact secondary" style="background: #ff4444; border-color: #ff4444; color: white;" title="Удалить книгу">
🗑️ Удалить главу
</button>
</form>
<?php endif ?>
<?php if ($is_edit): ?>
<div style="margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 5px;">
<h3>Публичная ссылка для чтения</h3>
<div style="display: flex; gap: 5px; align-items: center; flex-wrap: wrap;">
<input type="text"
id="share-link"
value="<?= e(SITE_URL . '/view_book.php?share_token=' . $book['share_token']) ?>"
readonly
style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; background: white; width:80%;">
<br>
<button type="button" onclick="copyShareLink()" class="compact-button secondary" style="width: 15%;">
📋 Копировать
</button>
<form method="post" action="book_regenerate_token.php" style="display: inline; margin-top: 1.5em;">
<input type="hidden" name="book_id" value="<?= $book_id ?>">
<input type="hidden" name="csrf_token" value="<?= generate_csrf_token() ?>">
<button type="submit" class="compact-button secondary" onclick="return confirm('Создать новую ссылку? Старая ссылка перестанет работать.')" >
🔄 Обновить
</button>
</form>
<p style="margin-top: -1rem; font-size: 0.8em; color: #666; width: 100%;">
<strong>Примечание:</strong> В публичном просмотре отображаются только главы со статусом "Опубликована"
</p>
</div>
</div>
<script>
function copyShareLink() {
const shareLink = document.getElementById('share-link');
shareLink.select();
shareLink.setSelectionRange(0, 99999);
document.execCommand('copy');
// Показать уведомление
const button = event.target;
const originalText = button.innerHTML;
button.innerHTML = '✅ Скопировано';
setTimeout(() => {
button.innerHTML = originalText;
}, 2000);
}
</script>
<?php endif; ?>
<?php if ($is_edit): ?>
<div style="margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 5px;">
<h3>Экспорт книги</h3>
<p style="margin-bottom: 0.5rem;">Экспортируйте книгу в различные форматы:</p>
<div style="display: flex; gap: 5px; flex-wrap: wrap;">
<a href="export_book.php?book_id=<?= $book_id ?>&format=pdf" class="adaptive-button secondary" target="_blank">
📄 PDF
</a>
<a href="export_book.php?book_id=<?= $book_id ?>&format=docx" class="adaptive-button secondary" target="_blank">
📝 DOCX
</a>
<a href="export_book.php?book_id=<?= $book_id ?>&format=html" class="adaptive-button secondary" target="_blank">
🌐 HTML
</a>
<a href="export_book.php?book_id=<?= $book_id ?>&format=txt" class="adaptive-button secondary" target="_blank">
📄 TXT
</a>
</div>
<p style="margin-top: 0.5rem; font-size: 0.9em; color: #666;">
<strong>Примечание:</strong> Экспортируются все главы книги (включая черновики)
</p>
</div>
<?php endif; ?>
<?php if ($is_edit): ?>
<div style="margin-top: 3rem;">
<h2>Главы этой книги</h2>
<a href="chapters.php?book_id=<?= $book_id ?>" class="compact-button secondary">
📑 Все главы
</a>
&nbsp;
<a href="chapter_edit.php?book_id=<?= $book_id ?>" role="button" class="compact-button secondary">
✏️ Добавить главу
</a>
<?php
// Получаем главы книги
$stmt = $pdo->prepare("SELECT * FROM chapters WHERE book_id = ? ORDER BY sort_order, created_at");
$stmt->execute([$book_id]);
$chapters = $stmt->fetchAll();
if ($chapters): ?>
<div style="overflow-x: auto;">
<table style="width: 100%;">
<thead>
<tr>
<th style="text-align: left; padding: 12px 8px;">Название</th>
<th style="text-align: left; padding: 12px 8px;">Статус</th>
<th style="text-align: left; padding: 12px 8px;">Слов</th>
<th style="text-align: left; padding: 12px 8px;">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($chapters as $chapter): ?>
<tr style="border-bottom: 1px solid #eee;">
<td style="padding: 12px 8px;"><?= e($chapter['title']) ?></td>
<td style="padding: 12px 8px;">
<span style="color: <?= $chapter['status'] == 'published' ? 'green' : 'orange' ?>">
<?= $chapter['status'] == 'published' ? 'Опубликована' : 'Черновик' ?>
</span>
</td>
<td style="padding: 12px 8px;"><?= $chapter['word_count'] ?></td>
<td style="padding: 12px 8px;">
<a href="chapter_edit.php?id=<?= $chapter['id'] ?>" role="button" class="compact-button secondary" style="text-decoration: none;">
Редактировать
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div style="text-align: center; padding: 2rem; background: #f9f9f9; border-radius: 5px;">
<p style="margin-bottom: 1rem;">В этой книге пока нет глав.</p>
<a href="chapter_edit.php?book_id=<?= $book_id ?>" role="button" class="compact-button secondary" >
✏️ Добавить первую главу
</a>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php include 'views/footer.php'; ?>