337 lines
17 KiB
PHP
337 lines
17 KiB
PHP
<?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"); ?>
|