<?php
/**
* CMS: LaiCMS (v1.0 Edition 2026)
* File: admin/blog_manage.php
* Updated: Исправлена ошибка generate_slug, добавлен HTMX, Ultra-Compact UI.
*/
require_once '../system/db.php';
require_once '../system/functions.php';
if (!function_exists('isAdmin') || !isAdmin()) { die("Доступ запрещен."); }
/**
* Вспомогательная функция генерации Slug (Транслитерация)
*/
function generate_slug($text) {
$cyr = [
'а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я',
'А','Б','В','Г','Д','Е','Ё','Ж','З','И','Й','К','Л','М','Н','О','П','Р','С','Т','У','Ф','Х','Ц','Ч','Ш','Щ','Ъ','Ы','Ь','Э','Ю','Я'
];
$lat = [
'a','b','v','g','d','e','io','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','shb','','y','','e','yu','ya',
'a','b','v','g','d','e','io','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','shb','','y','','e','yu','ya'
];
$text = str_replace($cyr, $lat, $text);
$text = preg_replace('~[^\pL\d]+~u', '-', $text);
$text = trim($text, '-');
$text = strtolower($text);
return empty($text) ? 'n-a' : $text;
}
// 1. ЛОГИКА ДОБАВЛЕНИЯ КАТЕГОРИИ
if (isset($_POST['add_category'])) {
$cat_name = trim($_POST['cat_name']);
if (!empty($cat_name)) {
$stmt = $mysqli->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)");
$slug = generate_slug($cat_name);
$stmt->bind_param("ss", $cat_name, $slug);
$stmt->execute();
header("Location: blog_manage.php?success=cat"); exit;
}
}
// 2. УДАЛЕНИЕ (Поддержка HTMX и обычного GET)
if (isset($_GET['delete'])) {
$p_id = (int)$_GET['delete'];
$stmt = $mysqli->prepare("DELETE FROM posts WHERE id = ?");
$stmt->bind_param("i", $p_id);
$stmt->execute();
// Если запрос от HTMX, возвращаем пустоту (строка скроется на фронте)
if (isset($_SERVER['HTTP_HX_REQUEST'])) {
exit();
}
header("Location: blog_manage.php?success=del"); exit;
}
// Получаем данные
$categories = $mysqli->query("SELECT * FROM categories ORDER BY name ASC")->fetch_all(MYSQLI_ASSOC);
$posts = $mysqli->query("
SELECT p.*, u.username, u.role, c.name as cat_name
FROM posts p
JOIN users u ON p.author_id = u.id
LEFT JOIN categories c ON p.category_id = c.id
ORDER BY p.id DESC
");
$page_title = "Контент-центр";
include '../system/header.php';
?>
<style>
.compact-container { padding: 10px; max-width: 100%; }
.stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 1rem; }
.stat-mini { background: var(--pico-card-background-color); padding: 10px; border-radius: 12px; border: 1px solid var(--pico-muted-border-color); text-align: center; }
.blog-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.blog-table tr { transition: all 0.3s ease; }
.blog-table td { padding: 10px 5px; border-bottom: 1px solid var(--pico-muted-border-color); vertical-align: middle; }
.role-badge { font-size: 0.6rem; padding: 2px 5px; border-radius: 4px; font-weight: 800; }
.role-admin { background: #e74c3c22; color: #e74c3c; }
.role-user { background: #3498db22; color: #3498db; }
/* Плавное скрытие при удалении через HTMX */
.htmx-swapping { opacity: 0; transform: translateX(20px); }
@media screen and (max-width: 600px) {
.blog-table thead { display: none; }
.blog-table tr { display: block; padding: 10px; border: 1px solid var(--pico-muted-border-color); border-radius: 12px; margin-bottom: 10px; }
.blog-table td { display: flex; justify-content: space-between; border: none; padding: 4px 0; }
.blog-table td::before { content: attr(data-label); font-weight: bold; opacity: 0.5; font-size: 0.7rem; }
.hide-mobile { display: none; }
}
</style>
<div class="compact-container animate__animated animate__fadeIn">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin:0;"><i class="fa-solid fa-folder-tree"></i> Управление</h3>
<div style="display: flex; gap: 8px;">
<button class="outline secondary" style="padding: 5px 15px;" onclick="document.getElementById('cat-modal').showModal()"><i class="fa-solid fa-tags"></i></button>
<a href="/blog/add_post.php" role="button" style="padding: 5px 15px;"><i class="fa-solid fa-plus"></i></a>
</div>
</div>
<div class="stat-grid">
<div class="stat-mini"><small>Посты</small><div><strong><?= $posts->num_rows ?></strong></div></div>
<div class="stat-mini"><small>Рубрики</small><div><strong><?= count($categories) ?></strong></div></div>
</div>
<div class="search-box" style="margin-bottom: 1rem;">
<fieldset role="group">
<input type="text" id="pSearch" placeholder="Поиск по названию..." onkeyup="fastFilter()">
<select id="pCat" onchange="fastFilter()" style="width: auto;">
<option value="">Все рубрики</option>
<?php foreach($categories as $c): ?>
<option value="<?= htmlspecialchars($c['name']) ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</fieldset>
</div>
<table class="blog-table" id="blogTable">
<thead>
<tr style="opacity: 0.5; font-size: 0.75rem; text-transform: uppercase;">
<th style="width: 50px;">ID</th>
<th>Контент</th>
<th class="hide-mobile">Рубрика</th>
<th style="text-align: right;">Действия</th>
</tr>
</thead>
<tbody>
<?php while($p = $posts->fetch_assoc()): ?>
<tr class="post-item" id="post-<?= $p['id'] ?>">
<td data-label="ID">#<?= $p['id'] ?></td>
<td data-label="Заголовок">
<div style="font-weight: 600;"><?= mb_strimwidth(htmlspecialchars($p['title']), 0, 50, "...") ?></div>
<div style="font-size: 0.75rem; opacity: 0.7;">
<span class="role-badge role-<?= $p['role'] ?>"><?= strtoupper($p['role']) ?></span> <?= htmlspecialchars($p['username']) ?>
</div>
</td>
<td data-label="Рубрика" class="hide-mobile">
<mark style="font-size: 0.7rem; padding: 2px 6px;"><?= $p['cat_name'] ?? 'Без рубрики' ?></mark>
</td>
<td data-label="Действия">
<div style="display: flex; gap: 5px; justify-content: flex-end;">
<a href="/blog/edit_post.php?id=<?= $p['id'] ?>" class="button outline contrast" style="padding: 4px 8px;"><i class="fa-solid fa-pen-to-square"></i></a>
<button class="outline secondary"
style="padding: 4px 8px;"
hx-get="?delete=<?= $p['id'] ?>"
hx-target="#post-<?= $p['id'] ?>"
hx-swap="outerHTML swap:0.3s"
hx-confirm="Удалить этот пост?">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
<dialog id="cat-modal">
<article>
<header>
<a href="#close" class="close" onclick="document.getElementById('cat-modal').close()"></a>
<strong>Управление рубриками</strong>
</header>
<form method="POST">
<fieldset role="group">
<input type="text" name="cat_name" placeholder="Название рубрики..." required>
<button type="submit" name="add_category">Создать</button>
</fieldset>
</form>
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
<?php foreach($categories as $c): ?>
<span class="badge" style="background: var(--pico-muted-border-color); padding: 4px 10px; border-radius: 20px; font-size: 0.8rem;">
<?= htmlspecialchars($c['name']) ?>
</span>
<?php endforeach; ?>
</div>
</article>
</dialog>
<script>
function fastFilter() {
const s = document.getElementById("pSearch").value.toLowerCase();
const c = document.getElementById("pCat").value.toLowerCase();
document.querySelectorAll(".post-item").forEach(row => {
const text = row.innerText.toLowerCase();
row.style.display = (text.includes(s) && text.includes(c)) ? "" : "none";
});
}
</script>
<?php include '../system/footer.php'; ?>