<?php
/**
* CMS: LaiCMS (v1.0 Edition 2026)
* File: admin/edit_post.php
* Description: Редактирование с поддержкой категорий, загрузкой медиа и адаптивным интерфейсом.
*/
require_once '../system/db.php';
require_once '../system/functions.php';
if (!isset($_SESSION['user_id'])) {
header("Location: /users/login.php");
exit;
}
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
// Загружаем данные поста
$stmt = $mysqli->prepare("SELECT * FROM posts WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $id);
$stmt->execute();
$post = $stmt->get_result()->fetch_assoc();
// Проверка прав
if (!$post || ($post['author_id'] != $_SESSION['user_id'] && !isAdmin())) {
die("У вас нет прав для редактирования этой статьи.");
}
/**
* ОБРАБОТЧИК ЗАГРУЗКИ ИЗОБРАЖЕНИЙ (AJAX для TinyMCE)
*/
if (isset($_FILES['file'])) {
$img_dir = '../uploads/blog/';
if (!is_dir($img_dir)) mkdir($img_dir, 0777, true);
$file = $_FILES['file'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (in_array($ext, $allowed)) {
$name = md5(time() . $file['name']) . '.' . $ext;
$destination = $img_dir . $name;
if (move_uploaded_file($file['tmp_name'], $destination)) {
header('Content-Type: application/json');
echo json_encode(['location' => '/uploads/blog/' . $name]);
exit;
}
}
header("HTTP/1.1 500 Server Error");
exit;
}
// Получаем категории для выпадающего списка
$categories_res = $mysqli->query("SELECT * FROM categories ORDER BY name ASC");
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_FILES['file'])) {
if (!isset($_POST['csrf_token']) || !check_csrf($_POST['csrf_token'])) {
die("Ошибка безопасности (CSRF)");
}
$title = trim($_POST['title']);
$cat_id = (int)$_POST['category_id'];
$allowed_tags = '<p><br><b><i><strong><em><ul><ol><li><a><img><iframe><table><thead><tbody><tr><td><th><h1><h2><h3><h4><h5><h6><div><span><blockquote><code><hr>';
$content = strip_tags($_POST['content'], $allowed_tags);
if (mb_strlen($title) < 5) {
set_flash("Заголовок слишком короткий", "error");
} elseif (mb_strlen($content) < 20) {
set_flash("Текст слишком мал", "error");
} else {
// Обновляем заголовок, контент и КАТЕГОРИЮ
$update = $mysqli->prepare("UPDATE posts SET title = ?, content = ?, category_id = ? WHERE id = ?");
$update->bind_param("ssii", $title, $content, $cat_id, $id);
if ($update->execute()) {
if (file_exists('../seo_gen.php')) {
ob_start(); include '../seo_gen.php'; ob_end_clean();
}
set_flash("Публикация «" . _e($title) . "» обновлена!", "success");
header("Location: /blog/view.php?slug=" . $post['slug']);
exit;
} else {
set_flash("Ошибка базы данных.", "error");
}
}
}
include '../system/header.php';
?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.2/tinymce.min.js"></script>
<style>
.editor-wrapper { max-width: 1100px; margin: 0 auto; }
.tox-tinymce { border-radius: 12px !important; border: 1px solid var(--pico-muted-border-color) !important; box-shadow: 0 10px 30px rgba(0,0,0,0.05); }
.edit-header { background: var(--pico-card-sectioning-background-color); border-radius: 15px 15px 0 0; border-bottom: 1px solid var(--pico-muted-border-color); padding: 1.5rem; }
</style>
<div class="editor-wrapper">
<article style="padding: 0; border: none; background: transparent;">
<div class="edit-header">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px;">
<hgroup style="margin: 0;">
<h2 style="margin:0; font-size: 1.5rem;"><i class="fa-solid fa-pen-to-square" style="color: var(--pico-primary);"></i> Редактор контента</h2>
<p class="secondary" style="margin:0;">ID записи: #<?= $id ?></p>
</hgroup>
<a href="/blog/view.php?slug=<?= $post['slug'] ?>" target="_blank" class="outline secondary" style="font-size: 0.8rem;">
<i class="fa-solid fa-eye"></i> Предпросмотр
</a>
</div>
</div>
<div style="padding: 1.5rem; background: var(--pico-card-background-color); border-radius: 0 0 15px 15px; border: 1px solid var(--pico-muted-border-color); border-top: none;">
<?php display_flash(); ?>
<form method="POST" id="postForm">
<input type="hidden" name="csrf_token" value="<?= _e($_SESSION['csrf_token']) ?>">
<div class="grid">
<div style="flex: 3;">
<label for="title">Заголовок</label>
<input type="text" id="title" name="title" value="<?= _e($post['title']) ?>" required>
</div>
<div>
<label for="category_id">Категория</label>
<select id="category_id" name="category_id">
<option value="0">Без категории</option>
<?php while($cat = $categories_res->fetch_assoc()): ?>
<option value="<?= $cat['id'] ?>" <?= ($post['category_id'] == $cat['id']) ? 'selected' : '' ?>>
<?= _e($cat['name']) ?>
</option>
<?php endwhile; ?>
</select>
</div>
</div>
<label for="word-editor">Текст публикации</label>
<textarea id="word-editor" name="content"><?= $post['content'] ?></textarea>
<footer style="margin-top: 2rem; background: transparent; padding: 0;">
<div class="grid">
<button type="submit" class="primary"><i class="fa-solid fa-floppy-disk"></i> Сохранить изменения</button>
<a href="/admin/blog_manage.php" role="button" class="outline secondary">К списку постов</a>
</div>
</footer>
</form>
</div>
</article>
</div>
<script>
tinymce.init({
selector: '#word-editor',
language: 'ru',
promotion: false,
branding: false,
height: 600,
images_upload_url: 'edit_post.php?id=<?= $id ?>',
automatic_uploads: true,
paste_data_images: true,
plugins: ['advlist', 'autolink', 'lists', 'link', 'image', 'media', 'table', 'code', 'fullscreen', 'emoticons', 'wordcount', 'autosave'],
toolbar: 'undo redo | blocks | bold italic underline | forecolor backcolor | alignleft aligncenter alignright | bullist numlist | link image media emoticons | code fullscreen preview',
// Автоматическая адаптация под темную/светлую тему Pico CSS
skin: (document.documentElement.getAttribute('data-theme') === 'dark' ? 'oxide-dark' : 'oxide'),
content_css: (document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default'),
content_style: `
body { font-family: "Inter", sans-serif; font-size: 16px; max-width: 800px; margin: 20px auto; padding: 20px !important; }
img { max-width: 100%; height: auto; border-radius: 10px; }
iframe { width: 100% !important; border-radius: 10px; aspect-ratio: 16/9; }
`,
setup: function (editor) {
editor.on('change', function () {
editor.save();
window.isChanged = true;
});
}
});
const form = document.getElementById('postForm');
window.isChanged = false;
window.onbeforeunload = () => { if (window.isChanged) return "Вы не сохранили изменения!"; };
form.onsubmit = () => { window.onbeforeunload = null; };
</script>
<?php include '../system/footer.php'; ?>