fix #29 : envoyer le lien magique par email (envoyer_mail_smtp)

This commit is contained in:
Cedric Abonnel
2026-05-13 23:41:58 +02:00
commit 8a85c15372
129 changed files with 22818 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
<?php
ob_start();
$CONTEXT = 3;
$base = rtrim(APP_URL, '/');
$effectiveTitle = ($seoTitle !== '') ? $seoTitle : $title;
$effectiveDesc = ($seoDescription !== '') ? $seoDescription : $autoSeoDesc;
$effectiveSlug = $postSlug;
$coverFilename = ($newCover ?? '') !== '' ? $newCover : ($article['cover'] ?? '');
$suggestedOgImage = $coverFilename !== ''
? $base . '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($coverFilename)
: '';
?>
<div class="d-flex align-items-center gap-3 mb-4 flex-wrap">
<a href="/edit/<?= htmlspecialchars($uuid) ?>" class="btn btn-outline-secondary">← Retour à l'édition</a>
<h1 class="h4 mb-0">Confirmer les modifications</h1>
</div>
<form method="POST" action="/edit/<?= htmlspecialchars($uuid) ?>">
<input type="hidden" name="_confirm" value="1">
<input type="hidden" name="title" value="<?= htmlspecialchars($title) ?>">
<input type="hidden" name="content" value="<?= htmlspecialchars($content) ?>">
<?php if ($published): ?>
<input type="hidden" name="published" value="1">
<?php endif; ?>
<input type="hidden" name="published_at" value="<?= htmlspecialchars($published_at) ?>">
<!-- seo_title et seo_description sont des champs visibles dans la colonne droite -->
<!-- og_image est calculé depuis la couverture côté serveur -->
<input type="hidden" name="category" value="<?= htmlspecialchars($category) ?>">
<?php foreach ($confirmTags ?? [] as $_ctk => $_ctv): ?>
<input type="hidden" name="tags[<?= htmlspecialchars($_ctk) ?>]" value="<?= htmlspecialchars($_ctv) ?>">
<?php endforeach; ?>
<?php if (($newCover ?? '') !== ''): ?>
<input type="hidden" name="cover_file" value="<?= htmlspecialchars($newCover) ?>">
<?php endif; ?>
<?php foreach ($fmetaNames as $fi => $fname): ?>
<input type="hidden" name="fmeta_name[]" value="<?= htmlspecialchars($fname) ?>">
<input type="hidden" name="fmeta_author[]" value="<?= htmlspecialchars($fmetaAuthors[$fi] ?? '') ?>">
<input type="hidden" name="fmeta_source[]" value="<?= htmlspecialchars($fmetaSources[$fi] ?? '') ?>">
<?php endforeach; ?>
<div class="row g-4">
<div class="col-lg-8">
<?php if (!empty($changes)): ?>
<div class="alert alert-info py-2 mb-3">
<?= htmlspecialchars(ucfirst(implode(' · ', $changes))) ?>
</div>
<?php else: ?>
<div class="alert alert-secondary py-2 mb-3">Aucune modification détectée.</div>
<?php endif; ?>
<!-- ─── Diff ─────────────────────────────────────────────────────────────── -->
<div class="mb-4">
<h2 class="h6 fw-semibold mb-2">Diff du contenu</h2>
<?php if ($diffLines === []): ?>
<div class="text-muted small">Contenu identique.</div>
<?php else:
$total = count($diffLines);
$show = [];
for ($i = 0; $i < $total; $i++) {
if ($diffLines[$i][0] !== '=') {
for ($c = max(0, $i - $CONTEXT); $c <= min($total - 1, $i + $CONTEXT); $c++) {
$show[$c] = true;
}
}
}
$hasChanges = false;
foreach ($diffLines as [$op]) {
if ($op !== '=') {
$hasChanges = true;
break;
}
}
?>
<div class="d-flex gap-3 mb-1 small">
<span class="diff-del px-2 py-1 rounded"> Supprimé</span>
<span class="diff-ins px-2 py-1 rounded">+ Ajouté</span>
</div>
<div class="diff-view font-monospace small">
<?php $inEllipsis = false;
for ($i = 0; $i < $total; $i++): ?>
<?php [$op, $line] = $diffLines[$i]; ?>
<?php if (!isset($show[$i])): ?>
<?php if (!$inEllipsis): $inEllipsis = true; ?>
<div class="diff-ellipsis text-muted px-2">⋯</div>
<?php endif;
continue; ?>
<?php else: $inEllipsis = false; endif; ?>
<?php if ($op === '!'): ?>
<div class="diff-warning text-warning px-2"><?= htmlspecialchars($line) ?></div>
<?php elseif ($op === '-'): ?>
<div class="diff-del px-2">&nbsp;<?= htmlspecialchars($line) ?></div>
<?php elseif ($op === '+'): ?>
<div class="diff-ins px-2">+&nbsp;<?= htmlspecialchars($line) ?></div>
<?php else: ?>
<div class="diff-eq px-2 text-muted">&nbsp;&nbsp;<?= htmlspecialchars($line) ?></div>
<?php endif; ?>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<!-- ─── Slug ─────────────────────────────────────────────────────────────── -->
<?php
$slugDefault = ($titleChanged && $autoSlug !== $postSlug) ? $autoSlug : $postSlug;
$slugOriginal = $postSlug;
?>
<div class="mb-3">
<label for="confirm-slug" class="form-label fw-semibold">
Slug (URL permanente)
<small class="text-muted fw-normal">— /post/<span id="slug-display"><?= htmlspecialchars($slugDefault) ?></span></small>
</label>
<input type="text" class="form-control form-control-sm font-monospace" id="confirm-slug" name="slug"
value="<?= htmlspecialchars($slugDefault) ?>"
pattern="[a-z0-9][a-z0-9\-]*"
oninput="document.getElementById('slug-display').textContent=this.value">
<?php if ($titleChanged && $autoSlug !== $slugOriginal): ?>
<div class="mt-2 d-flex align-items-center gap-2 flex-wrap">
<small class="text-muted">Slug recalculé depuis le nouveau titre. Slug initial :</small>
<code class="small"><?= htmlspecialchars($slugOriginal) ?></code>
<button type="button" id="slug-btn-keep" class="btn btn-sm btn-outline-secondary py-0"
data-slug-keep="<?= htmlspecialchars($slugOriginal) ?>">
Conserver
</button>
</div>
<?php endif; ?>
</div>
<!-- ─── Commentaire de révision ──────────────────────────────────────────── -->
<div class="mb-4">
<label for="revision_comment" class="form-label fw-semibold">
Commentaire de révision <small class="text-muted fw-normal">(optionnel)</small>
</label>
<input type="text" class="form-control" id="revision_comment" name="revision_comment"
value="<?= htmlspecialchars($autoRevisionComment) ?>"
placeholder="ex. Correction typos, ajout section X…">
</div>
<div class="d-flex gap-3 flex-wrap">
<button type="submit" class="btn btn-success">Confirmer et enregistrer</button>
<a href="/edit/<?= htmlspecialchars($uuid) ?>" class="btn btn-outline-secondary">← Retour</a>
</div>
</div><!-- /col-lg-8 -->
<!-- ─── SEO ───────────────────────────────────────────────────────────────── -->
<div class="col-lg-4">
<div class="card border-secondary mb-3">
<div class="card-header bg-transparent py-2">
<span class="fw-semibold small">SEO — titre, description, image</span>
</div>
<div class="card-body">
<div class="mb-3">
<label for="seo_title" class="form-label small">
Titre SEO <small class="text-muted">(og:title, &lt;title&gt;)</small>
</label>
<input type="text" class="form-control form-control-sm" id="seo_title" name="seo_title"
maxlength="70"
value="<?= htmlspecialchars($seoTitle) ?>"
placeholder="<?= htmlspecialchars($title) ?>">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 3060 car.</small>
<small id="seo_title_counter" class="text-muted">0 / 60</small>
</div>
</div>
<div class="mb-3">
<label for="seo_description" class="form-label small">
Description SEO <small class="text-muted">(meta description)</small>
</label>
<textarea class="form-control form-control-sm" id="seo_description" name="seo_description"
rows="3" maxlength="200"
placeholder="<?= htmlspecialchars(mb_strimwidth($autoSeoDesc, 0, 80, '…')) ?>"><?= htmlspecialchars($seoDescription) ?></textarea>
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 120155 car.</small>
<small id="seo_desc_counter" class="text-muted">0 / 155</small>
</div>
</div>
</div>
</div>
<div class="card border-secondary">
<div class="card-header bg-transparent py-2">
<span class="fw-semibold small">Aperçu dans les moteurs de recherche</span>
</div>
<div class="card-body p-3">
<?php
$pubTs = strtotime((string)($published_at ?? $article['published_at'] ?? ''));
$modTs = time(); // sera mis à jour à la sauvegarde
$pubFmt = $pubTs ? date('d/m/Y H:i', $pubTs) : '—';
$modFmt = date('d/m/Y H:i', $modTs);
$catVal = trim($category ?? '');
?>
<div class="seo-preview mb-3">
<div class="seo-preview-url small text-truncate mb-1" id="preview-url">
<?= htmlspecialchars(rtrim($base, '/') . '/post/' . $effectiveSlug) ?>
</div>
<div class="seo-preview-title mb-1" id="preview-title">
<?= htmlspecialchars($effectiveTitle) ?>
</div>
<div class="seo-preview-desc small" id="preview-desc">
<?= htmlspecialchars($effectiveDesc) ?>
</div>
</div>
<table class="table table-sm table-borderless mb-0 small">
<tbody>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap" style="width:1%">Auteur</th>
<td><?= htmlspecialchars(currentUserName() ?: (currentUserEmail() ?? '—')) ?></td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Publication</th>
<td><?= htmlspecialchars($pubFmt) ?></td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Modification</th>
<td><?= htmlspecialchars($modFmt) ?> <small class="text-muted">(après enreg.)</small></td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Langue</th>
<td>fr-FR</td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Catégorie</th>
<td><?= $catVal !== '' ? htmlspecialchars($catVal) : '<span class="text-muted">—</span>' ?></td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">og:image</th>
<td class="text-truncate" style="max-width:0">
<?= $suggestedOgImage !== '' ? '<span title="' . htmlspecialchars($suggestedOgImage) . '">' . htmlspecialchars($coverFilename) . '</span>' : '<span class="text-muted">— (pas de couverture)</span>' ?>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div><!-- /row -->
</form>
<style>
.diff-view { border: 1px solid var(--bs-border-color, #dee2e6); border-radius: 6px; overflow-x: auto; }
.diff-view > div { padding: 1px 8px; white-space: pre; line-height: 1.5; }
.diff-del { background: #ffeef0; color: #b91c1c; }
.diff-ins { background: #e6ffec; color: #15803d; }
.diff-eq { }
.diff-ellipsis { background: #f8f9fa; padding: 2px 8px; user-select: none; }
.seo-preview { border: 1px solid #dee2e6; border-radius: 6px; padding: 10px 12px; background: #fff; }
.seo-preview-url { color: #006621; font-size: .78rem; }
.seo-preview-title { color: #1a0dab; font-size: 1.05rem; font-weight: 500; line-height: 1.3; word-break: break-word; }
.seo-preview-desc { color: #545454; line-height: 1.5; }
</style>
<div id="pc-data" hidden
data-default-title="<?= htmlspecialchars($effectiveTitle) ?>"
data-default-desc="<?= htmlspecialchars($effectiveDesc) ?>"
data-base-url="<?= htmlspecialchars(rtrim($base, '/') . '/post/') ?>"></div>
<script src="/assets/js/post_confirm.js"></script>
<?php
$content = ob_get_clean();
$title = 'Confirmer — ' . $title;
include __DIR__ . '/layout.php';