Joywork/admin/article-edit.php
2026-05-22 21:21:54 +03:00

337 lines
17 KiB
PHP
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($_SERVER['DOCUMENT_ROOT'] . "/config.php");
if (!$_SESSION['superadmin']) { header("location:/index.php"); die("Доступ запрещён!"); }
$get = clearInputData($_GET);
$id = isset($get['id']) ? (int)$get['id'] : 0;
$article = null;
$err = '';
$success = '';
$errorMessages = array(
'empty' => 'Заполните заголовок и слаг',
'empty_slug' => 'Слаг не может быть пустым',
'slug_exists' => 'Статья с таким слагом уже существует',
'slug_file_conflict' => 'Слаг конфликтует с существующим файлом в корне сайта. Выберите другой.',
'image_too_large' => 'Изображение слишком большое (макс. 5 MB)',
);
if (!empty($_GET['err']) && isset($errorMessages[$_GET['err']])) {
$err = $errorMessages[$_GET['err']];
}
if (!empty($_GET['saved'])) {
$success = 'Статья сохранена';
}
if ($id > 0) {
$article = Article::getById($id);
if (!$article) {
header("location:/admin/articles.php");
exit();
}
}
// Если есть черновик в сессии (после ошибки сохранения) — используем его как источник
$draft = null;
if (!empty($err) && !empty($_SESSION['article_form_draft'])) {
$draft = $_SESSION['article_form_draft'];
unset($_SESSION['article_form_draft']); // одноразово
}
// Значения для формы: приоритет — черновик из сессии, потом БД, потом дефолты
$src = $draft ? $draft : $article;
$f_title = Article::val($src, 'title', '');
$f_page_title = Article::val($src, 'page_title', '');
$f_description = Article::val($src, 'description', '');
$f_slug = Article::val($src, 'slug', '');
$f_date_c = Article::val($src, 'date_created', date('Y-m-d'));
$f_date_u = Article::val($src, 'date_updated', date('Y-m-d'));
$f_time = Article::val($src, 'reading_time', 5);
$f_sort = Article::val($src, 'sort_order', 0);
$f_author_n = Article::val($src, 'author_name', 'Станислав Дмитриев');
$f_author_j = Article::val($src, 'author_job', 'руководитель JoyWork');
$f_author_i = Article::val($src, 'author_img', '/images/articles/rukofodJW.png');
$f_image = Article::val($src, 'image', '');
$f_content = Article::val($src, 'content', '');
if ($draft) {
$f_published = !empty($draft['published']);
} else {
$f_published = (!$article || !empty($article['published']));
}
require_once($_SERVER['DOCUMENT_ROOT'] . "/templates/header-admin.php");
?>
<div class="main-title" style="display:flex; align-items:center; justify-content:space-between;">
<div style="display:flex; align-items:center; gap:15px;">
<a href="/admin/articles.php" style="color:#666; text-decoration:none; font-size:14px;">← Назад к списку</a>
<h1 style="margin:0;"><?= $id ? 'Редактирование статьи' : 'Новая статья' ?></h1>
</div>
<div style="display:flex; gap:10px;">
<?php if ($id): ?>
<a href="/article.php?id=<?= $id ?>" target="_blank" class="medium-button" style="background:#fff; border:1px solid #ccc; color:#333;">Предпросмотр ↗</a>
<?php endif; ?>
<button type="submit" form="article-form" class="medium-button">Сохранить</button>
</div>
</div>
<?php if ($err): ?>
<div style="margin:10px 20px; padding:12px; background:#ffebee; color:#c62828; border-radius:4px;"><?= htmlspecialchars($err) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div style="margin:10px 20px; padding:12px; background:#e8f5e9; color:#2e7d32; border-radius:4px;"><?= htmlspecialchars($success) ?></div>
<?php endif; ?>
<div class="users fr__users" style="margin-right:20px;">
<form method="post" enctype="multipart/form-data" id="article-form" action="/admin/ajax/saveArticle.php">
<?php if ($id): ?>
<input type="hidden" name="id" value="<?= $id ?>">
<?php endif; ?>
<div class="new-object">
<div class="new-object__form">
<div class="block">
<div class="line">
<div class="label"><span>Заголовок H1:</span></div>
<div class="inputs">
<input type="text" class="text" name="title" id="field-title" style="width:100%;"
value="<?= htmlspecialchars($f_title) ?>"
placeholder="Заголовок статьи (H1)" required>
</div>
</div>
<div class="line">
<div class="label"><span>Мета-заголовок:</span></div>
<div class="inputs">
<input type="text" class="text" name="page_title" style="width:100%;"
value="<?= htmlspecialchars($f_page_title) ?>"
placeholder="Заголовок для SEO (title тег)">
</div>
</div>
<div class="line" style="align-items:flex-start;">
<div class="label"><span>Описание:</span></div>
<div class="inputs">
<textarea name="description" class="text" style="width:100%; min-height:70px;"
placeholder="Краткое описание для SEO (meta description) и для карточек на /blog.php"><?= htmlspecialchars($f_description) ?></textarea>
</div>
</div>
<div class="line">
<div class="label"><span>Слаг (URL):</span></div>
<div class="inputs" style="display:flex; gap:8px; align-items:center;">
<input type="text" class="text" name="slug" id="field-slug" style="flex:1;"
value="<?= htmlspecialchars($f_slug) ?>"
placeholder="url-stranicy-bez-rasshireniya" required>
<span style="color:#999; font-size:12px;">joywork.ru/<span id="slug-preview"><?= htmlspecialchars($f_slug) ?></span>.php</span>
</div>
</div>
</div>
<div class="block">
<div class="line">
<div class="label"><span>Дата создания:</span></div>
<div class="inputs">
<input type="date" class="text" name="date_created" style="width:160px;"
value="<?= htmlspecialchars($f_date_c) ?>">
</div>
</div>
<div class="line">
<div class="label"><span>Дата обновления:</span></div>
<div class="inputs">
<input type="date" class="text" name="date_updated" style="width:160px;"
value="<?= htmlspecialchars($f_date_u) ?>">
</div>
</div>
<div class="line">
<div class="label"><span>Время чтения (мин.):</span></div>
<div class="inputs">
<input type="number" class="text" name="reading_time" style="width:80px;" min="1" max="60"
value="<?= htmlspecialchars($f_time) ?>">
</div>
</div>
<div class="line">
<div class="label"><span>Опубликована:</span></div>
<div class="inputs">
<label style="display:flex; align-items:center; gap:8px; cursor:pointer;">
<input type="checkbox" name="published" value="1" <?= $f_published ? 'checked' : '' ?>>
<span>Показывать на сайте</span>
</label>
</div>
</div>
<div class="line">
<div class="label"><span>Порядок сортировки:</span></div>
<div class="inputs">
<input type="number" class="text" name="sort_order" style="width:80px;"
value="<?= htmlspecialchars($f_sort) ?>"
title="Больше = выше в списке">
</div>
</div>
</div>
<div class="block">
<div class="line">
<div class="label"><span>Имя автора:</span></div>
<div class="inputs">
<input type="text" class="text" name="author_name" style="width:280px;"
value="<?= htmlspecialchars($f_author_n) ?>">
</div>
</div>
<div class="line">
<div class="label"><span>Должность автора:</span></div>
<div class="inputs">
<input type="text" class="text" name="author_job" style="width:280px;"
value="<?= htmlspecialchars($f_author_j) ?>">
</div>
</div>
<div class="line">
<div class="label"><span>Фото автора (путь):</span></div>
<div class="inputs" style="display:flex; gap:10px; align-items:center;">
<input type="text" class="text" name="author_img" id="field-author-img" style="width:350px;"
value="<?= htmlspecialchars($f_author_i) ?>">
<?php if (!empty($f_author_i)): ?>
<img src="<?= htmlspecialchars($f_author_i) ?>" style="width:36px;height:36px;border-radius:50%;object-fit:cover;" alt="">
<?php endif; ?>
</div>
</div>
</div>
<div class="block">
<div class="line">
<div class="label"><span>Обложка статьи:</span></div>
<div class="inputs" style="display:flex; gap:15px; align-items:flex-start;">
<div>
<input type="file" name="image_file" id="image-file" accept="image/jpeg,image/png,image/webp" style="margin-bottom:6px;">
<br>
<span style="color:#999; font-size:12px;">или укажите путь вручную:</span><br>
<input type="text" class="text" name="image" id="field-image" style="width:350px; margin-top:4px;"
value="<?= htmlspecialchars($f_image) ?>"
placeholder="/images/articles/art-69.jpg">
</div>
<?php if (!empty($f_image)): ?>
<img id="image-preview" src="<?= htmlspecialchars($f_image) ?>"
style="max-width:160px; max-height:100px; border-radius:4px; object-fit:cover;" alt="">
<?php else: ?>
<img id="image-preview" src="" style="max-width:160px; max-height:100px; border-radius:4px; object-fit:cover; display:none;" alt="">
<?php endif; ?>
</div>
</div>
</div>
<div class="block" style="border-bottom:none; padding:15px 20px;">
<div style="font-weight:500; margin-bottom:10px;">Контент статьи:</div>
<textarea name="content" id="article-content" style="width:100%; min-height:400px;"><?= htmlspecialchars($f_content) ?></textarea>
</div>
</div>
</div>
<div style="padding:15px 20px; text-align:right; border-top:1px solid #eee;">
<button type="submit" class="medium-button">Сохранить статью</button>
</div>
</form>
</div>
<script src="/js/ckeditor/ckeditor.js"></script>
<script>
CKEDITOR.stylesSet.add('article_styles', [
{ name: 'Чек-лист (зелёные галочки)', element: 'ul', attributes: { 'class': 'check-list' } },
{ name: 'Подзаголовок H2', element: 'h2' },
{ name: 'Подзаголовок H3', element: 'h3' },
{ name: 'Выделенный блок', element: 'div', attributes: { 'class': 'highlight-block' }, styles: { 'background': '#f5f5f5', 'padding': '15px', 'border-left': '4px solid #29b13a' } }
]);
CKEDITOR.replace('article-content', {
height: 600,
language: 'ru',
allowedContent: true,
extraAllowedContent: 'script[*]; iframe[*]',
stylesSet: 'article_styles',
contentsCss: ['/css/article-editor.css?v=<?= filemtime($_SERVER["DOCUMENT_ROOT"] . "/css/article-editor.css") ?>'],
bodyClass: 'c',
protectedSource: [
/<script[\s\S]*?<\/script>/gi
],
toolbarGroups: [
{ name: 'clipboard', groups: ['clipboard', 'undo'] },
{ name: 'editing', groups: ['find', 'selection'] },
{ name: 'basicstyles', groups: ['basicstyles', 'cleanup'] },
{ name: 'paragraph', groups: ['list', 'indent', 'blocks', 'align'] },
{ name: 'links' },
{ name: 'insert' },
'/',
{ name: 'styles' },
{ name: 'colors' },
{ name: 'tools' },
{ name: 'document', groups: ['mode'] }
],
removeButtons: 'Save,NewPage,Form,Checkbox,Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,Language,Flash,Smiley,About,ShowBlocks',
filebrowserUploadUrl: '/admin/ajax/uploadArticleImage.php?ckeditor=1',
filebrowserUploadMethod: 'form',
// При вставке из Word/Google Docs — автоматически снимать inline-стили
// чтобы заголовки были чистыми <h2>текст</h2> без вложенных span/b
forcePasteAsPlainText: true
});
// CKEditor хранит данные в своём поле — синхронизируем textarea перед submit
document.getElementById('article-form').addEventListener('submit', function() {
for (var i in CKEDITOR.instances) {
CKEDITOR.instances[i].updateElement();
}
});
document.getElementById('field-title').addEventListener('input', function() {
var slug = document.getElementById('field-slug').value;
if (!slug) {
var generated = makeSlug(this.value);
document.getElementById('field-slug').value = generated;
document.getElementById('slug-preview').textContent = generated;
}
});
document.getElementById('field-slug').addEventListener('input', function() {
document.getElementById('slug-preview').textContent = this.value;
});
document.getElementById('field-image').addEventListener('input', function() {
var preview = document.getElementById('image-preview');
if (this.value) {
preview.src = this.value;
preview.style.display = 'block';
} else {
preview.style.display = 'none';
}
});
document.getElementById('image-file').addEventListener('change', function() {
if (!this.files[0]) return;
var formData = new FormData();
formData.append('file', this.files[0]);
fetch('/admin/ajax/uploadArticleImage.php', { method: 'POST', body: formData })
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.location) {
document.getElementById('field-image').value = data.location;
var preview = document.getElementById('image-preview');
preview.src = data.location;
preview.style.display = 'block';
}
});
});
function makeSlug(str) {
var cyr = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
var lat = ['a','b','v','g','d','e','yo','zh','z','i','j','k','l','m','n','o','p','r','s','t','u','f','h','cz','ch','sh','shh','','y','','e','yu','ya'];
str = str.toLowerCase();
var result = '';
for (var i = 0; i < str.length; i++) {
var idx = cyr.indexOf(str[i]);
if (idx >= 0) result += lat[idx];
else result += str[i];
}
return result.replace(/[^a-z0-9\s-]/g, '').replace(/[\s-]+/g, '-').replace(/^-+|-+$/g, '');
}
</script>
<?php require_once($_SERVER['DOCUMENT_ROOT'] . "/templates/footer-admin.php"); ?>