feat #58 : wizard multi-étapes création/édition d'article
Remplace le formulaire unique par un wizard 5 étapes (création) et
6 étapes (édition) avec auto-sauvegarde en brouillon, détection de
tags depuis le texte (TagSuggester), aperçu SEO, diff avant validation
et plan Markdown dynamique dans l'éditeur.
Détail des changements :
- ArticleManager : +6 méthodes (updatePartialMeta, saveDraftOverlay,
getDraftOverlay, hasDraftOverlay, discardDraftOverlay, commitDraftOverlay)
- .htaccess : routes /new/{uuid}/{1-5} et /edit/{uuid}/{1-6}
- index.php : cases create et edit réécrits en switch($step),
nouveau case autosave_draft et edit_discard_draft
- assets/js/wizard.js : autosave debounce, auto-resize textarea,
scroll curseur, plan TOC dynamique, toggle pills tags
- templates/wizard/ : nav.php + step1..6.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+302
-166
@@ -4,7 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
define('BASE_PATH', realpath(__DIR__ . '/../'));
|
||||
|
||||
$_sessionName = getenv('SESSION_NAME') ?: 'PHPSESSID';
|
||||
// Charger .env avant de lire SESSION_NAME, sinon getenv() retourne '' et le mauvais cookie est chargé
|
||||
require_once BASE_PATH . '/vendor/autoload.php';
|
||||
require_once BASE_PATH . '/config/config.php';
|
||||
|
||||
$_sessionName = $_ENV['SESSION_NAME'] ?? (getenv('SESSION_NAME') ?: 'PHPSESSID');
|
||||
if (session_status() === PHP_SESSION_NONE
|
||||
&& (isset($_COOKIE[$_sessionName]) || $_SERVER['REQUEST_METHOD'] === 'POST')
|
||||
) {
|
||||
@@ -18,7 +22,6 @@ unset($_sessionName);
|
||||
require_once BASE_PATH . '/src/helpers.php';
|
||||
require_once BASE_PATH . '/src/auth.php';
|
||||
require_once BASE_PATH . '/src/SiteSettings.php';
|
||||
require_once BASE_PATH . '/config/config.php';
|
||||
require_once BASE_PATH . '/src/ArticleManager.php';
|
||||
|
||||
$articles = new ArticleManager(BASE_PATH . '/data');
|
||||
@@ -488,56 +491,145 @@ switch ($action) {
|
||||
case 'create':
|
||||
requireAuth();
|
||||
|
||||
$title = $_POST['title'] ?? '';
|
||||
$content = $_POST['content'] ?? '';
|
||||
$postSlug = $_POST['slug'] ?? '';
|
||||
$published = isset($_POST['published']);
|
||||
$published_at = str_replace('T', ' ', $_POST['published_at'] ?? date('Y-m-d H:i:s'));
|
||||
$seoTitle = $_POST['seo_title'] ?? '';
|
||||
$seoDescription = $_POST['seo_description'] ?? '';
|
||||
$ogImage = $_POST['og_image'] ?? '';
|
||||
$category = $_POST['category'] ?? '';
|
||||
$errors = [];
|
||||
$step = max(1, min(5, (int)($_GET['step'] ?? 1)));
|
||||
$totalSteps = 5;
|
||||
$mode = 'create';
|
||||
$errors = [];
|
||||
|
||||
$postTags = [];
|
||||
foreach (($_POST['tags'] ?? []) as $_tk => $_tv) {
|
||||
$_vals = array_values(array_filter(array_map('trim', explode(',', (string)$_tv)), fn ($v) => $v !== ''));
|
||||
if ($_vals !== []) {
|
||||
$postTags[trim((string)$_tk)] = $_vals;
|
||||
}
|
||||
// UUID depuis l'URL ou la session
|
||||
if ($uuid === '') {
|
||||
$uuid = $_SESSION['wizard_create'] ?? '';
|
||||
}
|
||||
$draft = $uuid !== '' ? $articles->getByUuid($uuid) : null;
|
||||
|
||||
// Si session pointe vers un UUID inexistant, on repart de zéro
|
||||
if ($draft === null && $uuid !== '') {
|
||||
unset($_SESSION['wizard_create']);
|
||||
$uuid = '';
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (trim($title) === '') {
|
||||
$errors[] = 'Le titre est obligatoire.';
|
||||
}
|
||||
if (empty($errors)) {
|
||||
$newUuid = $articles->create($title, $content, $published, $postSlug, $published_at, currentUserEmail() ?? '', $seoTitle, $seoDescription, $ogImage, $category, $postTags);
|
||||
switch ($step) {
|
||||
|
||||
foreach ($_FILES['files']['tmp_name'] ?? [] as $i => $tmpName) {
|
||||
if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) {
|
||||
$articles->addFile($newUuid, [
|
||||
'name' => $_FILES['files']['name'][$i],
|
||||
'tmp_name' => $tmpName,
|
||||
'error' => $_FILES['files']['error'][$i],
|
||||
]);
|
||||
case 1:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$content = $_POST['content'] ?? '';
|
||||
$postSlug = trim($_POST['slug'] ?? '');
|
||||
if ($title === '') {
|
||||
$errors[] = 'Le titre est obligatoire.';
|
||||
} else {
|
||||
if ($draft === null) {
|
||||
$uuid = $articles->create($title, $content, false, $postSlug, date('Y-m-d H:i:s'), currentUserEmail() ?? '', '', '', '', '', []);
|
||||
foreach ($_FILES['files']['tmp_name'] ?? [] as $_fi => $_tmpName) {
|
||||
if ($_FILES['files']['error'][$_fi] === UPLOAD_ERR_OK) {
|
||||
$articles->addFile($uuid, ['name' => $_FILES['files']['name'][$_fi], 'tmp_name' => $_tmpName, 'error' => UPLOAD_ERR_OK]);
|
||||
}
|
||||
}
|
||||
$_SESSION['wizard_create'] = $uuid;
|
||||
} else {
|
||||
$articles->autosave($uuid, $title, $content, $postSlug);
|
||||
}
|
||||
header('Location: /new/' . rawurlencode($uuid) . '/2');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$title = $draft['title'] ?? ($_POST['title'] ?? '');
|
||||
$content = $draft['content'] ?? '';
|
||||
$postSlug = $draft['slug'] ?? '';
|
||||
$existingFiles = $uuid !== '' ? $articles->getFiles($uuid) : [];
|
||||
$article = $draft;
|
||||
$insertUrl = '';
|
||||
$formAction = $uuid !== '' ? '/new/' . rawurlencode($uuid) . '/1' : '/new';
|
||||
include BASE_PATH . '/templates/wizard/step1.php';
|
||||
break;
|
||||
|
||||
header('Location: /');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if ($draft === null) { header('Location: /new'); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$articles->updatePartialMeta($uuid, [
|
||||
'published' => isset($_POST['published']) && $_POST['published'] !== '',
|
||||
'published_at' => str_replace('T', ' ', $_POST['published_at'] ?? date('Y-m-d H:i:s')),
|
||||
]);
|
||||
header('Location: /new/' . rawurlencode($uuid) . '/3');
|
||||
exit;
|
||||
}
|
||||
$published = (bool)($draft['published'] ?? false);
|
||||
$published_at = $draft['published_at'] ?? date('Y-m-d H:i:s');
|
||||
include BASE_PATH . '/templates/wizard/step2.php';
|
||||
break;
|
||||
|
||||
$formAction = '/new';
|
||||
$action = 'create';
|
||||
$tagTypes = $articles->getTagTypes();
|
||||
$articleTags = $postTags;
|
||||
$allTagValues = [];
|
||||
foreach ($tagTypes as $_tk => $_) {
|
||||
$allTagValues[$_tk] = $articles->getAllTagValues($_tk);
|
||||
case 3:
|
||||
if ($draft === null) { header('Location: /new'); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$articles->updatePartialMeta($uuid, ['category' => trim($_POST['category'] ?? '')]);
|
||||
header('Location: /new/' . rawurlencode($uuid) . '/4');
|
||||
exit;
|
||||
}
|
||||
$category = $draft['category'] ?? '';
|
||||
$allCategories = $articles->getCategories();
|
||||
$privateCats = $articles->getPrivateCategories();
|
||||
include BASE_PATH . '/templates/wizard/step3.php';
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if ($draft === null) { header('Location: /new'); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_vals = array_values(array_filter(array_map('trim', explode(',', (string)($_POST['tags_flat'] ?? ''))), fn ($_v) => $_v !== ''));
|
||||
$articles->updatePartialMeta($uuid, ['tags' => $_vals !== [] ? ['tags' => $_vals] : []]);
|
||||
header('Location: /new/' . rawurlencode($uuid) . '/5');
|
||||
exit;
|
||||
}
|
||||
$_tagTypes = $articles->getTagTypes();
|
||||
$flatTagValues = [];
|
||||
foreach ($_tagTypes as $_tk => $_) {
|
||||
foreach ($articles->getAllTagValues($_tk) as $_v) { $flatTagValues[$_v] = true; }
|
||||
}
|
||||
foreach ($articles->getAllTagValues('tags') as $_v) { $flatTagValues[$_v] = true; }
|
||||
ksort($flatTagValues);
|
||||
$flatTagValues = array_keys($flatTagValues);
|
||||
$flatArticleTags = [];
|
||||
foreach (($draft['tags'] ?? []) as $_tagVals) {
|
||||
foreach ((array)$_tagVals as $_v) { if (!in_array($_v, $flatArticleTags, true)) $flatArticleTags[] = $_v; }
|
||||
}
|
||||
$draftContent = (string)($draft['content'] ?? '');
|
||||
require_once BASE_PATH . '/src/TagSuggester.php';
|
||||
include BASE_PATH . '/templates/wizard/step4.php';
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if ($draft === null) { header('Location: /new'); exit; }
|
||||
require_once BASE_PATH . '/src/Parsedown.php';
|
||||
$_pd = new Parsedown();
|
||||
$autoSeoDesc = mb_strimwidth(trim((string)preg_replace('/\s+/', ' ', strip_tags($_pd->text((string)($draft['content'] ?? ''))))), 0, 155, '…');
|
||||
unset($_pd);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$seoTitle = trim($_POST['seo_title'] ?? '');
|
||||
$seoDesc = trim($_POST['seo_description'] ?? '') ?: $autoSeoDesc;
|
||||
$coverFile = trim($_POST['cover_file'] ?? '') ?: ($draft['cover'] ?? '');
|
||||
$ogImage = $coverFile !== ''
|
||||
? rtrim(APP_URL, '/') . '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($coverFile)
|
||||
: ($draft['og_image'] ?? '');
|
||||
$articles->update($uuid, $draft['title'], $draft['content'], $draft['published'], $draft['slug'] ?? '', $draft['published_at'] ?? '', 'Création', $seoTitle, $seoDesc, $ogImage, $draft['category'] ?? '', $draft['tags'] ?? []);
|
||||
if ($coverFile !== '' && $coverFile !== ($draft['cover'] ?? '')) {
|
||||
$articles->setCover($uuid, $coverFile);
|
||||
}
|
||||
unset($_SESSION['wizard_create']);
|
||||
$final = $articles->getByUuid($uuid);
|
||||
header('Location: /post/' . rawurlencode($final['slug'] ?? $uuid));
|
||||
exit;
|
||||
}
|
||||
$title = $draft['title'] ?? '';
|
||||
$seoTitle = $draft['seo_title'] ?? '';
|
||||
$seoDescription = $draft['seo_description'] ?? '';
|
||||
$existingFiles = $articles->getFiles($uuid);
|
||||
$category = $draft['category'] ?? '';
|
||||
$published = (bool)($draft['published'] ?? false);
|
||||
$published_at = $draft['published_at'] ?? '';
|
||||
$postSlug = $draft['slug'] ?? '';
|
||||
$article = $draft;
|
||||
include BASE_PATH . '/templates/wizard/step5.php';
|
||||
break;
|
||||
}
|
||||
include BASE_PATH . '/templates/post_form.php';
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
@@ -718,165 +810,175 @@ switch ($action) {
|
||||
echo 'Article introuvable.';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!canDoOnArticle('edit_articles', $article)) {
|
||||
http_response_code(403);
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Toggle featured (admin only) — conservé depuis l'ancienne version
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['_toggle_featured'])) {
|
||||
if (!isAdmin()) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
$articles->setFeatured($uuid, !((bool)($article['featured'] ?? false)));
|
||||
header('Location: /edit/' . rawurlencode($uuid));
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/1');
|
||||
exit;
|
||||
}
|
||||
|
||||
$title = $_POST['title'] ?? $article['title'];
|
||||
$content = $_POST['content'] ?? $article['content'];
|
||||
$postSlug = $_POST['slug'] ?? $article['slug'];
|
||||
$published = isset($_POST['published']) ? true : $article['published'];
|
||||
$published_at = $_POST['published_at']
|
||||
?? date('Y-m-d\TH:i', strtotime((string)($article['published_at'] ?? 'now')));
|
||||
$seoTitle = $_POST['seo_title'] ?? ($article['seo_title'] ?? '');
|
||||
$seoDescription = $_POST['seo_description'] ?? ($article['seo_description'] ?? '');
|
||||
$ogImage = $_POST['og_image'] ?? ($article['og_image'] ?? '');
|
||||
$category = $_POST['category'] ?? ($article['category'] ?? '');
|
||||
$errors = [];
|
||||
$step = (int)($_GET['step'] ?? 0);
|
||||
$totalSteps = 6;
|
||||
$mode = 'edit';
|
||||
$errors = [];
|
||||
|
||||
// Tags : lire depuis POST si soumis, sinon depuis l'article
|
||||
$pendingTags = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
foreach (($_POST['tags'] ?? []) as $_tk => $_tv) {
|
||||
$_vals = array_values(array_filter(array_map('trim', explode(',', (string)$_tv)), fn ($v) => $v !== ''));
|
||||
if ($_vals !== []) {
|
||||
$pendingTags[trim((string)$_tk)] = $_vals;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$pendingTags = $article['tags'] ?? [];
|
||||
// Sans step : rediriger vers l'étape 1
|
||||
if ($step === 0) {
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/1');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (trim($title) === '') {
|
||||
$errors[] = 'Le titre est obligatoire.';
|
||||
}
|
||||
if (empty($errors)) {
|
||||
if (!empty($_POST['_confirm'])) {
|
||||
$coverFile = trim($_POST['cover_file'] ?? '') ?: ($article['cover'] ?? '');
|
||||
$ogImageFromCover = $coverFile !== ''
|
||||
? rtrim(APP_URL, '/') . '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($coverFile)
|
||||
: '';
|
||||
// Base de travail : draft overlay s'il existe, sinon article original
|
||||
$draft = $articles->getDraftOverlay($uuid) ?? $article;
|
||||
|
||||
$articles->update(
|
||||
$uuid,
|
||||
$title,
|
||||
$content,
|
||||
$published,
|
||||
$_POST['slug'] ?? '',
|
||||
str_replace('T', ' ', $_POST['published_at'] ?? ''),
|
||||
$_POST['revision_comment'] ?? '',
|
||||
$_POST['seo_title'] ?? '',
|
||||
$_POST['seo_description'] ?? '',
|
||||
$ogImageFromCover,
|
||||
$_POST['category'] ?? '',
|
||||
$pendingTags
|
||||
);
|
||||
switch ($step) {
|
||||
|
||||
$fmetaNames = $_POST['fmeta_name'] ?? [];
|
||||
$fmetaAuthors = $_POST['fmeta_author'] ?? [];
|
||||
$fmetaSources = $_POST['fmeta_source'] ?? [];
|
||||
foreach ($fmetaNames as $fi => $fname) {
|
||||
$articles->addFileMeta($uuid, $fname, trim($fmetaAuthors[$fi] ?? ''), trim($fmetaSources[$fi] ?? ''));
|
||||
case 1:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
$content = $_POST['content'] ?? '';
|
||||
if ($title === '') {
|
||||
$errors[] = 'Le titre est obligatoire.';
|
||||
} else {
|
||||
$articles->saveDraftOverlay($uuid, ['title' => $title, 'slug' => trim($_POST['slug'] ?? $draft['slug'])], $content);
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/2');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$title = $draft['title'];
|
||||
$content = $draft['content'];
|
||||
$postSlug = $draft['slug'];
|
||||
$existingFiles = $articles->getFiles($uuid);
|
||||
$insertUrl = '';
|
||||
if (isset($_GET['insert_url']) && filter_var($_GET['insert_url'], FILTER_VALIDATE_URL)) {
|
||||
$insertUrl = $_GET['insert_url'];
|
||||
}
|
||||
$formAction = '/edit/' . rawurlencode($uuid) . '/1';
|
||||
include BASE_PATH . '/templates/wizard/step1.php';
|
||||
break;
|
||||
|
||||
$coverFile = trim($_POST['cover_file'] ?? '');
|
||||
if ($coverFile !== '') {
|
||||
$articles->setCover($uuid, $coverFile);
|
||||
}
|
||||
|
||||
$updated = $articles->getByUuid($uuid);
|
||||
header('Location: /post/' . rawurlencode($updated['slug'] ?? $uuid));
|
||||
case 2:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$articles->saveDraftOverlay($uuid, [
|
||||
'published' => isset($_POST['published']) && $_POST['published'] !== '',
|
||||
'published_at' => str_replace('T', ' ', $_POST['published_at'] ?? ($draft['published_at'] ?? '')),
|
||||
]);
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/3');
|
||||
exit;
|
||||
}
|
||||
$published = (bool)($draft['published'] ?? false);
|
||||
$published_at = $draft['published_at'] ?? '';
|
||||
include BASE_PATH . '/templates/wizard/step2.php';
|
||||
break;
|
||||
|
||||
// ─── Page de confirmation ────────────────────────────────────
|
||||
$diffLines = lineDiff((string)($article['content'] ?? ''), $content);
|
||||
$titleChanged = ($title !== ($article['title'] ?? ''));
|
||||
$autoSlug = slugify($title);
|
||||
case 3:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$articles->saveDraftOverlay($uuid, ['category' => trim($_POST['category'] ?? '')]);
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/4');
|
||||
exit;
|
||||
}
|
||||
$category = $draft['category'] ?? '';
|
||||
$allCategories = $articles->getCategories();
|
||||
$privateCats = $articles->getPrivateCategories();
|
||||
include BASE_PATH . '/templates/wizard/step3.php';
|
||||
break;
|
||||
|
||||
$changes = [];
|
||||
if ($titleChanged) {
|
||||
$changes[] = 'titre modifié';
|
||||
case 4:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_vals = array_values(array_filter(array_map('trim', explode(',', (string)($_POST['tags_flat'] ?? ''))), fn ($_v) => $_v !== ''));
|
||||
$articles->saveDraftOverlay($uuid, ['tags' => $_vals !== [] ? ['tags' => $_vals] : []]);
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/5');
|
||||
exit;
|
||||
}
|
||||
if (($category ?? '') !== ($article['category'] ?? '')) {
|
||||
$changes[] = 'catégorie modifiée';
|
||||
$_tagTypes = $articles->getTagTypes();
|
||||
$flatTagValues = [];
|
||||
foreach ($_tagTypes as $_tk => $_) {
|
||||
foreach ($articles->getAllTagValues($_tk) as $_v) { $flatTagValues[$_v] = true; }
|
||||
}
|
||||
if ($pendingTags !== ($article['tags'] ?? [])) {
|
||||
$changes[] = 'tags modifiés';
|
||||
foreach ($articles->getAllTagValues('tags') as $_v) { $flatTagValues[$_v] = true; }
|
||||
ksort($flatTagValues);
|
||||
$flatTagValues = array_keys($flatTagValues);
|
||||
$flatArticleTags = [];
|
||||
foreach (($draft['tags'] ?? []) as $_tagVals) {
|
||||
foreach ((array)$_tagVals as $_v) { if (!in_array($_v, $flatArticleTags, true)) $flatArticleTags[] = $_v; }
|
||||
}
|
||||
if ($content !== ($article['content'] ?? '')) {
|
||||
$changes[] = 'contenu modifié';
|
||||
}
|
||||
$oldPublished = (bool)($article['published'] ?? false);
|
||||
if ($published !== $oldPublished) {
|
||||
$changes[] = $published ? 'article publié' : 'article dépublié';
|
||||
}
|
||||
$newCover = trim($_POST['cover_file'] ?? '');
|
||||
if ($newCover !== '' && $newCover !== ($article['cover'] ?? '')) {
|
||||
$changes[] = 'couverture modifiée';
|
||||
}
|
||||
$fmetaNames = $_POST['fmeta_name'] ?? [];
|
||||
$fmetaAuthors = $_POST['fmeta_author'] ?? [];
|
||||
$fmetaSources = $_POST['fmeta_source'] ?? [];
|
||||
foreach ($fmetaNames as $fi => $fname) {
|
||||
$savedMeta = ($article['files_meta'][$fname] ?? []);
|
||||
if (trim($fmetaAuthors[$fi] ?? '') !== ($savedMeta['author'] ?? '')
|
||||
|| trim($fmetaSources[$fi] ?? '') !== ($savedMeta['source_url'] ?? '')) {
|
||||
$changes[] = 'métadonnées fichiers modifiées';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$autoRevisionComment = !empty($changes) ? ucfirst(implode(', ', $changes)) : '';
|
||||
$draftContent = (string)($draft['content'] ?? '');
|
||||
require_once BASE_PATH . '/src/TagSuggester.php';
|
||||
include BASE_PATH . '/templates/wizard/step4.php';
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$articles->saveDraftOverlay($uuid, [
|
||||
'seo_title' => trim($_POST['seo_title'] ?? ''),
|
||||
'seo_description' => trim($_POST['seo_description'] ?? ''),
|
||||
]);
|
||||
header('Location: /edit/' . rawurlencode($uuid) . '/6');
|
||||
exit;
|
||||
}
|
||||
require_once BASE_PATH . '/src/Parsedown.php';
|
||||
$_pd = new Parsedown();
|
||||
$autoSeoDesc = mb_strimwidth(
|
||||
trim((string)preg_replace('/\s+/', ' ', strip_tags($_pd->text($content)))),
|
||||
0,
|
||||
155,
|
||||
'…'
|
||||
);
|
||||
$autoSeoDesc = mb_strimwidth(trim((string)preg_replace('/\s+/', ' ', strip_tags($_pd->text((string)($draft['content'] ?? ''))))), 0, 155, '…');
|
||||
unset($_pd);
|
||||
$title = $draft['title'];
|
||||
$seoTitle = $draft['seo_title'] ?? '';
|
||||
$seoDescription = $draft['seo_description'] ?? '';
|
||||
$postSlug = $draft['slug'];
|
||||
$published = (bool)($draft['published'] ?? false);
|
||||
$published_at = $draft['published_at'] ?? '';
|
||||
$category = $draft['category'] ?? '';
|
||||
include BASE_PATH . '/templates/wizard/step5.php';
|
||||
break;
|
||||
|
||||
// Tags sous forme de chaînes CSV pour les champs hidden du formulaire de confirmation
|
||||
$confirmTags = [];
|
||||
foreach ($pendingTags as $_tk => $_vals) {
|
||||
$confirmTags[$_tk] = implode(', ', $_vals);
|
||||
case 6:
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['_confirm'])) {
|
||||
$revisionComment = trim($_POST['revision_comment'] ?? '');
|
||||
// Si le slug a été modifié dans le formulaire de confirmation, le propager
|
||||
if (!empty($_POST['slug'])) {
|
||||
$articles->saveDraftOverlay($uuid, ['slug' => trim($_POST['slug'])]);
|
||||
}
|
||||
$articles->commitDraftOverlay($uuid, $revisionComment);
|
||||
$final = $articles->getByUuid($uuid);
|
||||
header('Location: /post/' . rawurlencode($final['slug'] ?? $uuid));
|
||||
exit;
|
||||
}
|
||||
|
||||
include BASE_PATH . '/templates/post_confirm.php';
|
||||
exit;
|
||||
}
|
||||
$draftData = $articles->getDraftOverlay($uuid) ?? $article;
|
||||
require_once BASE_PATH . '/src/Parsedown.php';
|
||||
$_pd = new Parsedown();
|
||||
$autoSeoDesc = mb_strimwidth(trim((string)preg_replace('/\s+/', ' ', strip_tags($_pd->text((string)($draftData['content'] ?? ''))))), 0, 155, '…');
|
||||
unset($_pd);
|
||||
$diffLines = lineDiff((string)($article['content'] ?? ''), (string)($draftData['content'] ?? ''));
|
||||
$titleChanged = ($draftData['title'] ?? '') !== ($article['title'] ?? '');
|
||||
$autoSlug = slugify($draftData['title'] ?? '');
|
||||
$postSlug = $draftData['slug'] ?? $article['slug'];
|
||||
$changes = [];
|
||||
if ($titleChanged) { $changes[] = 'titre modifié'; }
|
||||
if (($draftData['category'] ?? '') !== ($article['category'] ?? '')) { $changes[] = 'catégorie modifiée'; }
|
||||
if (($draftData['tags'] ?? []) !== ($article['tags'] ?? [])) { $changes[] = 'tags modifiés'; }
|
||||
if (($draftData['content'] ?? '') !== ($article['content'] ?? '')) { $changes[] = 'contenu modifié'; }
|
||||
if ((bool)($draftData['published'] ?? false) !== (bool)($article['published'] ?? false)) {
|
||||
$changes[] = ($draftData['published'] ?? false) ? 'article publié' : 'article dépublié';
|
||||
}
|
||||
$autoRevisionComment = !empty($changes) ? ucfirst(implode(', ', $changes)) : '';
|
||||
$title = $draftData['title'] ?? '';
|
||||
$seoTitle = $draftData['seo_title'] ?? '';
|
||||
$seoDescription = $draftData['seo_description'] ?? '';
|
||||
$published = (bool)($draftData['published'] ?? false);
|
||||
$published_at = $draftData['published_at'] ?? '';
|
||||
$category = $draftData['category'] ?? '';
|
||||
include BASE_PATH . '/templates/wizard/step6.php';
|
||||
break;
|
||||
}
|
||||
|
||||
$formAction = '/edit/' . rawurlencode($uuid);
|
||||
$action = 'edit';
|
||||
$existingFiles = $articles->getFiles($uuid);
|
||||
$insertUrl = '';
|
||||
if (isset($_GET['insert_url']) && filter_var($_GET['insert_url'], FILTER_VALIDATE_URL)) {
|
||||
$insertUrl = $_GET['insert_url'];
|
||||
}
|
||||
$tagTypes = $articles->getTagTypes();
|
||||
$articleTags = $pendingTags;
|
||||
$allTagValues = [];
|
||||
foreach ($tagTypes as $_tk => $_) {
|
||||
$allTagValues[$_tk] = $articles->getAllTagValues($_tk);
|
||||
}
|
||||
include BASE_PATH . '/templates/post_form.php';
|
||||
break;
|
||||
|
||||
case 'edit_tags':
|
||||
@@ -1338,6 +1440,40 @@ switch ($action) {
|
||||
echo json_encode(['ok' => $ok, 'time' => date('H:i:s')]);
|
||||
exit;
|
||||
|
||||
case 'autosave_draft':
|
||||
requireAuth();
|
||||
header('Content-Type: application/json');
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || $uuid === '') {
|
||||
echo json_encode(['ok' => false]);
|
||||
exit;
|
||||
}
|
||||
$_adArticle = $articles->getByUuid($uuid);
|
||||
if (!$_adArticle || !canDoOnArticle('edit_articles', $_adArticle)) {
|
||||
echo json_encode(['ok' => false]);
|
||||
exit;
|
||||
}
|
||||
$_adTitle = trim($_POST['title'] ?? '');
|
||||
$_adContent = $_POST['content'] ?? null;
|
||||
if ($_adTitle === '') {
|
||||
echo json_encode(['ok' => false]);
|
||||
exit;
|
||||
}
|
||||
$articles->saveDraftOverlay($uuid, ['title' => $_adTitle], $_adContent);
|
||||
echo json_encode(['ok' => true, 'time' => date('H:i:s')]);
|
||||
exit;
|
||||
|
||||
case 'edit_discard_draft':
|
||||
requireAuth();
|
||||
$_ddArticle = $articles->getByUuid($uuid);
|
||||
if (!$_ddArticle || !canDoOnArticle('edit_articles', $_ddArticle)) {
|
||||
http_response_code(403);
|
||||
echo 'Accès refusé.';
|
||||
exit;
|
||||
}
|
||||
$articles->discardDraftOverlay($uuid);
|
||||
header('Location: /post/' . rawurlencode($_ddArticle['slug'] ?? $uuid));
|
||||
exit;
|
||||
|
||||
case 'copy_file':
|
||||
requireAuth();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
Reference in New Issue
Block a user