Files
2026-05-17 05:22:06 +03:00

257 lines
13 KiB
PHP
Raw Permalink 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 __DIR__ . '/../../inc/auth.php';
require_once __DIR__ . '/../../inc/functions.php';
$session = auth_require('owner');
$pdo = getDB();
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
$title = trim($_POST['title'] ?? '');
$slug = trim($_POST['slug'] ?? '');
$content = $_POST['content'] ?? '';
$excerpt = trim($_POST['excerpt'] ?? '');
$category = $_POST['category'] ?? 'risk-engineering';
$status = $_POST['status'] ?? 'draft';
$csrf = $_POST['csrf_token'] ?? '';
if (!auth_csrf_verify($csrf)) {
$message = 'Ошибка безопасности.';
} elseif (!$title || !$content) {
$message = 'Заголовок и содержание обязательны.';
} else {
if (!$slug) {
$slug = preg_replace('/[^a-z0-9-]/', '', mb_strtolower(str_replace(' ', '-', $title)));
}
try {
if ($id > 0) {
$stmt = $pdo->prepare("UPDATE blog_posts SET title=?, slug=?, content=?, excerpt=?, category=?, status=? WHERE id=?");
$stmt->execute([$title, $slug, $content, $excerpt, $category, $status, $id]);
$message = 'Пост обновлён.';
} else {
$stmt = $pdo->prepare("INSERT INTO blog_posts (title, slug, content, excerpt, category, author_id, status, published_at) VALUES (?,?,?,?,?,?,?,?)");
$published = $status === 'published' ? date('Y-m-d H:i:s') : null;
$stmt->execute([$title, $slug, $content, $excerpt, $category, $session['user_id'], $status, $published]);
$message = 'Пост создан.';
}
auth_log($session['user_id'], 'blog_save', "Title: $title");
} catch (\PDOException $e) {
$message = 'Ошибка: ' . ($e->getCode() === '23000' ? 'Такой slug уже существует.' : $e->getMessage());
}
}
}
if (isset($_GET['delete'])) {
$id = (int)$_GET['delete'];
if (auth_csrf_verify($_GET['csrf'] ?? '')) {
$pdo->prepare("DELETE FROM blog_posts WHERE id=?")->execute([$id]);
$message = 'Пост удалён.';
}
}
if (isset($_GET['publish'])) {
$id = (int)$_GET['publish'];
if (auth_csrf_verify($_GET['csrf'] ?? '')) {
$pdo->prepare("UPDATE blog_posts SET status='published', published_at=NOW() WHERE id=?")->execute([$id]);
$message = 'Пост опубликован.';
}
}
$editPost = null;
if (isset($_GET['edit'])) {
$stmt = $pdo->prepare("SELECT * FROM blog_posts WHERE id=?");
$stmt->execute([(int)$_GET['edit']]);
$editPost = $stmt->fetch();
}
$posts = $pdo->query("SELECT bp.*, u.full_name as author_name FROM blog_posts bp LEFT JOIN users u ON u.id=bp.author_id ORDER BY bp.created_at DESC")->fetchAll();
$csrf = auth_csrf_token();
$categories = [
'audit' => 'Аудит',
'sla' => 'SLA',
'incident' => 'Инциденты',
'supervision' => 'Технадзор',
'documentation' => 'Документация',
'risk' => 'Риск-инжиниринг',
'cases' => 'Кейсы',
];
require __DIR__ . '/../../inc/header.php';
?>
<div class="main-header">
<div>
<h1>Управление блогом</h1>
<div class="breadcrumb"><a href="/service/dashboard.php">Главная</a> / Блог</div>
</div>
<button class="btn btn-primary btn-sm" onclick="openModal()" data-tooltip="Создать новую статью">+ Новая статья</button>
</div>
<?php if ($message): ?>
<div class="alert alert-info"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Заголовок</th>
<th>Категория</th>
<th>Автор</th>
<th>Статус</th>
<th>Дата</th>
<th class="actions">Действия</th>
</tr>
</thead>
<tbody>
<?php if (empty($posts)): ?>
<tr><td colspan="6" style="text-align:center;color:var(--text-muted);">Нет статей</td></tr>
<?php endif; ?>
<?php foreach ($posts as $p): ?>
<tr>
<td><strong><?= htmlspecialchars($p['title']) ?></strong></td>
<td><span class="badge badge-medium"><?= $categories[$p['category']] ?? $p['category'] ?></span></td>
<td><?= htmlspecialchars($p['author_name'] ?: '—') ?></td>
<td><span class="badge badge-<?= $p['status'] === 'published' ? 'done' : ($p['status'] === 'draft' ? 'draft' : 'cancelled') ?>"><?= $p['status'] ?></span></td>
<td><?= $p['published_at'] ? date('d.m.Y', strtotime($p['published_at'])) : date('d.m.Y', strtotime($p['created_at'])) ?></td>
<td class="actions">
<a href="/blog/<?= htmlspecialchars($p['slug']) ?>" class="btn btn-secondary btn-sm" target="_blank" data-tooltip="Просмотр">👁️</a>
<a href="?edit=<?= $p['id'] ?>" class="btn btn-secondary btn-sm" data-tooltip="Редактировать">✏️</a>
<?php if ($p['status'] !== 'published'): ?>
<a href="?publish=<?= $p['id'] ?>&csrf=<?= $csrf ?>" class="btn btn-success btn-sm" data-tooltip="Опубликовать">📢</a>
<?php endif; ?>
<a href="?delete=<?= $p['id'] ?>&csrf=<?= $csrf ?>" class="btn btn-danger btn-sm" onclick="return confirm('Удалить статью?')" data-tooltip="Удалить">🗑️</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="modal-overlay <?= $editPost ? 'open' : '' ?>" id="blogModal">
<div class="modal" style="max-width:720px;">
<button class="modal-close" onclick="closeModal()">&times;</button>
<h2><?= $editPost ? 'Редактировать' : 'Новая' ?> статья</h2>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
<input type="hidden" name="id" value="<?= $editPost['id'] ?? 0 ?>">
<div class="form-group">
<label for="title">Заголовок *</label>
<input type="text" id="title" name="title" value="<?= htmlspecialchars($editPost['title'] ?? '') ?>" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="slug">Slug (оставьте пустым для автогенерации)</label>
<input type="text" id="slug" name="slug" value="<?= htmlspecialchars($editPost['slug'] ?? '') ?>">
</div>
<div class="form-group">
<label for="category">Категория</label>
<select id="category" name="category">
<?php foreach ($categories as $key => $label): ?>
<option value="<?= $key ?>" <?= ($editPost['category'] ?? '') === $key ? 'selected' : '' ?>><?= $label ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="status">Статус</label>
<select id="status" name="status">
<option value="draft" <?= ($editPost['status'] ?? '') === 'draft' ? 'selected' : '' ?>>Черновик</option>
<option value="published" <?= ($editPost['status'] ?? '') === 'published' ? 'selected' : '' ?>>Опубликовать</option>
<option value="archived" <?= ($editPost['status'] ?? '') === 'archived' ? 'selected' : '' ?>>Архив</option>
</select>
</div>
<div class="form-group">
<label for="excerpt">Краткое описание</label>
<input type="text" id="excerpt" name="excerpt" value="<?= htmlspecialchars($editPost['excerpt'] ?? '') ?>">
</div>
</div>
<div class="form-group">
<label for="content">Содержание * (HTML)</label>
<div class="editor-toolbar">
<button type="button" data-insert="bold" title="Жирный"><b>Ж</b></button>
<button type="button" data-insert="italic" title="Курсив"><i>К</i></button>
<button type="button" data-insert="h2" title="Заголовок 2">H2</button>
<button type="button" data-insert="h3" title="Заголовок 3">H3</button>
<button type="button" data-insert="ul" title="Маркированный список">• Список</button>
<button type="button" data-insert="ol" title="Нумерованный список">1. Список</button>
<button type="button" data-insert="link" title="Ссылка">🔗</button>
<button type="button" data-insert="image" title="Изображение">🖼️</button>
</div>
<textarea id="content" name="content" rows="15" style="font-family:monospace;"><?= htmlspecialchars($editPost['content'] ?? '') ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Сохранить</button>
<button type="button" class="btn btn-secondary" onclick="closeModal()">Отмена</button>
</form>
</div>
</div>
<script>
function openModal() { document.getElementById('blogModal').classList.add('open'); }
function closeModal() { document.getElementById('blogModal').classList.remove('open'); window.location.href = 'blog.php'; }
// Slug auto-generation from title
(function(){
var titleInput = document.getElementById('title');
var slugInput = document.getElementById('slug');
if (!titleInput || !slugInput) return;
var slugTouched = slugInput.value !== '';
slugInput.addEventListener('input', function(){ slugTouched = true; });
titleInput.addEventListener('input', function(){
if (slugTouched) return;
var val = titleInput.value.toLowerCase().replace(/[^a-zа-яё0-9\s-]/gi, '').replace(/\s+/g, '-');
// Transliterate Cyrillic
var map = {'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'yo','ж':'zh','з':'z','и':'i','й':'y','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r','с':'s','т':'t','у':'u','ф':'f','х':'kh','ц':'ts','ч':'ch','ш':'sh','щ':'shch','ъ':'','ы':'y','ь':'','э':'e','ю':'yu','я':'ya'};
var result = '';
for (var i = 0; i < val.length; i++) {
var ch = val[i];
result += map[ch] !== undefined ? map[ch] : ch;
}
slugInput.value = result.replace(/-+/g, '-').replace(/^-|-$/g, '');
});
})();
// Editor toolbar
(function(){
var textarea = document.getElementById('content');
if (!textarea) return;
document.querySelectorAll('.editor-toolbar button').forEach(function(btn){
btn.addEventListener('click', function(e){
e.preventDefault();
var action = btn.getAttribute('data-insert');
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var selected = textarea.value.substring(start, end);
var insert = '';
switch(action) {
case 'bold': insert = '<strong>' + (selected || 'текст') + '</strong>'; break;
case 'italic': insert = '<em>' + (selected || 'текст') + '</em>'; break;
case 'h2': insert = '<h2>' + (selected || 'Заголовок') + '</h2>'; break;
case 'h3': insert = '<h3>' + (selected || 'Заголовок') + '</h3>'; break;
case 'ul': insert = '<ul>\n <li>' + (selected || 'Пункт') + '</li>\n</ul>'; break;
case 'ol': insert = '<ol>\n <li>' + (selected || 'Пункт') + '</li>\n</ol>'; break;
case 'link': insert = '<a href="">' + (selected || 'текст ссылки') + '</a>'; break;
case 'image': insert = '<img src="" alt="' + (selected || 'описание') + '">'; break;
}
textarea.value = textarea.value.substring(0, start) + insert + textarea.value.substring(end);
textarea.focus();
textarea.selectionStart = textarea.selectionEnd = start + insert.length;
});
});
})();
</script>
<?php require __DIR__ . '/../../inc/footer.php'; ?>
<style>
.editor-toolbar { display:flex; gap:4px; margin-bottom:8px; flex-wrap:wrap; }
.editor-toolbar button { padding:4px 10px; background:var(--bg-input); border:1px solid var(--border); border-radius:4px; color:var(--text-primary); cursor:pointer; font-size:12px; transition:all 0.2s; }
.editor-toolbar button:hover { border-color:var(--accent); background:var(--accent-dim); }
</style>