factorisation: site_lang, posts_per_page, site_license, contact dynamique

This commit is contained in:
Cedric Abonnel
2026-05-13 00:23:09 +02:00
parent ff47161721
commit 26c0a03e71
11 changed files with 133 additions and 27 deletions
+22 -2
View File
@@ -1278,14 +1278,34 @@ footer.mt-5 { margin-top: 0 !important; }
.author-profile-link:hover { color: var(--vl-accent); }
.author-profile-bio {
.author-bio-wrap {
flex: 1;
margin: 0;
}
.author-profile-bio {
margin: 0 0 .25rem;
color: var(--vl-muted);
line-height: 1.7;
font-size: .9375rem;
}
.author-profile-bio.bio-clamped {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.bio-toggle {
background: none;
border: none;
padding: 0;
color: var(--vl-accent);
font-size: .875rem;
cursor: pointer;
text-decoration: underline;
}
/* ─── Page "Mes liens" ───────────────────── */
.liens-page {
+1 -1
View File
@@ -74,7 +74,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
<title><?= htmlspecialchars(siteTitle()) ?></title>
<link><?= htmlspecialchars($base) ?></link>
<description><?= htmlspecialchars(siteClaim()) ?></description>
<language>fr</language>
<language><?= htmlspecialchars(siteLang()) ?></language>
<lastBuildDate><?= htmlspecialchars($lastBuild) ?></lastBuildDate>
<atom:link href="<?= htmlspecialchars($feedUrl) ?>" rel="self" type="application/rss+xml"/>
+8 -3
View File
@@ -1770,8 +1770,13 @@ switch ($action) {
exit;
}
saveSiteSettings([
'site_title' => $_POST['site_title'] ?? '',
'site_claim' => $_POST['site_claim'] ?? '',
'site_title' => $_POST['site_title'] ?? '',
'site_claim' => $_POST['site_claim'] ?? '',
'site_author' => $_POST['site_author'] ?? '',
'site_lang' => $_POST['site_lang'] ?? '',
'posts_per_page' => $_POST['posts_per_page'] ?? '',
'site_license_label' => $_POST['site_license_label'] ?? '',
'site_license_url' => $_POST['site_license_url'] ?? '',
]);
header('Location: /admin/site?saved=1');
exit;
@@ -2152,7 +2157,7 @@ switch ($action) {
}
return true;
}));
$perPage = 12;
$perPage = postsPerPage();
$cursor = trim($_GET['cursor'] ?? '');
// Trouve la position du curseur dans la liste triée
+33 -2
View File
@@ -39,11 +39,36 @@ function siteAuthor(): string
return siteSettings()['site_author'] ?? '';
}
function siteLang(): string
{
return siteSettings()['site_lang'] ?? 'fr-FR';
}
function siteLangOgLocale(): string
{
return str_replace('-', '_', siteLang());
}
function postsPerPage(): int
{
return max(1, (int)(siteSettings()['posts_per_page'] ?? 12));
}
function siteLicenseLabel(): string
{
return siteSettings()['site_license_label'] ?? 'CC BY 4.0';
}
function siteLicenseUrl(): string
{
return siteSettings()['site_license_url'] ?? 'https://creativecommons.org/licenses/by/4.0/';
}
function saveSiteSettings(array $data): void
{
$current = siteSettings();
$allowed = ['site_title', 'site_claim', 'site_author'];
foreach ($allowed as $key) {
$stringKeys = ['site_title', 'site_claim', 'site_author', 'site_lang', 'site_license_label', 'site_license_url'];
foreach ($stringKeys as $key) {
if (array_key_exists($key, $data)) {
$val = trim((string)$data[$key]);
if ($val !== '') {
@@ -51,6 +76,12 @@ function saveSiteSettings(array $data): void
}
}
}
if (array_key_exists('posts_per_page', $data)) {
$val = (int)$data['posts_per_page'];
if ($val > 0) {
$current['posts_per_page'] = $val;
}
}
file_put_contents(
siteSettingsPath(),
json_encode($current, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+30
View File
@@ -360,6 +360,36 @@ function adminStatusBadge(array $a, int $now): string
maxlength="100" placeholder="ex : Cédrix">
<div class="form-text">Utilisé dans les métadonnées des articles (meta author, JSON-LD).</div>
</div>
<div class="mb-3">
<label for="site-lang" class="form-label small fw-semibold">Langue du site</label>
<input type="text" id="site-lang" name="site_lang"
class="form-control form-control-sm"
value="<?= htmlspecialchars(siteLang()) ?>"
maxlength="20" placeholder="ex : fr-FR">
<div class="form-text">Format BCP 47 (ex&nbsp;: <code>fr</code>, <code>fr-FR</code>). Utilisé dans <code>&lt;html lang&gt;</code>, og:locale, RSS et JSON-LD.</div>
</div>
<div class="mb-3">
<label for="posts-per-page" class="form-label small fw-semibold">Articles par page</label>
<input type="number" id="posts-per-page" name="posts_per_page"
class="form-control form-control-sm"
value="<?= postsPerPage() ?>"
min="1" max="100">
</div>
<div class="mb-3">
<label for="site-license-label" class="form-label small fw-semibold">Licence (libellé)</label>
<input type="text" id="site-license-label" name="site_license_label"
class="form-control form-control-sm"
value="<?= htmlspecialchars(siteLicenseLabel()) ?>"
maxlength="80" placeholder="ex : CC BY 4.0">
</div>
<div class="mb-3">
<label for="site-license-url" class="form-label small fw-semibold">Licence (URL)</label>
<input type="url" id="site-license-url" name="site_license_url"
class="form-control form-control-sm"
value="<?= htmlspecialchars(siteLicenseUrl()) ?>"
maxlength="200">
<div class="form-text">Affiché dans le footer.</div>
</div>
<button type="submit" class="btn btn-primary btn-sm">Enregistrer</button>
</form>
</div>
+20 -1
View File
@@ -20,9 +20,28 @@ $_initials = mb_strtoupper(mb_substr($_apName, 0, 1, 'UTF-8'), 'UTF-8');
<?= htmlspecialchars(parse_url($_apUrl, PHP_URL_HOST) ?: $_apUrl) ?> ↗
</a>
<?php endif; ?>
<a href="/liens/<?= rawurlencode($_apSlug) ?>" class="author-profile-link">Mes liens →</a>
</div>
<?php if ($_apBio !== ''): ?>
<p class="author-profile-bio"><?= nl2br(htmlspecialchars($_apBio)) ?></p>
<div class="author-bio-wrap">
<p class="author-profile-bio bio-clamped" id="author-bio"><?= nl2br(htmlspecialchars($_apBio)) ?></p>
<button class="bio-toggle" id="bio-toggle" hidden>plus</button>
</div>
<script>
(function(){
var bio = document.getElementById('author-bio');
var btn = document.getElementById('bio-toggle');
requestAnimationFrame(function() {
if (bio.scrollHeight > bio.clientHeight + 2) { btn.hidden = false; }
});
btn.addEventListener('click', function() {
var exp = btn.getAttribute('aria-expanded') === 'true';
bio.classList.toggle('bio-clamped', exp);
btn.textContent = exp ? 'plus' : 'moins';
btn.setAttribute('aria-expanded', exp ? 'false' : 'true');
});
})();
</script>
<?php endif; ?>
</div>
+3 -2
View File
@@ -46,13 +46,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$error && $contactEmail !== '') {
$subjectClean = mb_encode_mimeheader(
'[varlog contact] ' . mb_strimwidth($subject, 0, 100, '…'),
'[' . siteTitle() . ' contact] ' . mb_strimwidth($subject, 0, 100, '…'),
'UTF-8',
'B'
);
$nameClean = mb_encode_mimeheader($name, 'UTF-8', 'B');
$headers = 'From: =?UTF-8?B?' . base64_encode('varlog contact') . "?= <noreply@varlog>\r\n";
$fromEmail = $_ENV['CONTACT_FROM_EMAIL'] ?? ('noreply@' . (parse_url(APP_URL, PHP_URL_HOST) ?? 'localhost'));
$headers = 'From: =?UTF-8?B?' . base64_encode(siteTitle() . ' contact') . "?= <{$fromEmail}>\r\n";
$headers .= "Reply-To: {$nameClean} <{$from}>\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "Content-Transfer-Encoding: 8bit\r\n";
+13 -13
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="fr">
<html lang="<?= htmlspecialchars(siteLang()) ?>">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? siteTitle())) ?></title>
@@ -14,7 +14,7 @@
<meta property="og:title" content="<?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? siteTitle())) ?>">
<meta property="og:description" content="<?= htmlspecialchars(($seoDescription ?? '') ?: siteClaim()) ?>">
<meta property="og:type" content="<?= htmlspecialchars($ogType ?? 'website') ?>">
<meta property="og:locale" content="fr_FR">
<meta property="og:locale" content="<?= htmlspecialchars(siteLangOgLocale()) ?>">
<meta property="og:url" content="<?= htmlspecialchars($ogUrl ?? APP_URL) ?>">
<meta property="og:site_name" content="<?= htmlspecialchars(siteTitle()) ?>">
<?php if (!empty($ogImage ?? '')): ?>
@@ -56,16 +56,16 @@
<div class="container-fluid">
<?php
$_layoutAction = $_GET['action'] ?? 'list';
$_layoutPrivateCats = isset($articles) ? $articles->getPrivateCategories() : [];
$_layoutCats = isset($articles) ? array_filter(
$articles->getCategories(),
function ($cat) use ($_layoutPrivateCats) {
return isLoggedIn() || !in_array($cat, $_layoutPrivateCats, true);
},
ARRAY_FILTER_USE_KEY
) : [];
$_layoutCurrentCat = trim($_GET['cat'] ?? '');
?>
$_layoutPrivateCats = isset($articles) ? $articles->getPrivateCategories() : [];
$_layoutCats = isset($articles) ? array_filter(
$articles->getCategories(),
function ($cat) use ($_layoutPrivateCats) {
return isLoggedIn() || !in_array($cat, $_layoutPrivateCats, true);
},
ARRAY_FILTER_USE_KEY
) : [];
$_layoutCurrentCat = trim($_GET['cat'] ?? '');
?>
<a class="navbar-brand d-flex flex-column lh-1" href="/">
<span><?= htmlspecialchars(siteTitle()) ?></span>
<small class="navbar-tagline"><?= htmlspecialchars(siteClaim()) ?></small>
@@ -116,7 +116,7 @@
<div class="footer-about">
<strong><?= htmlspecialchars(siteTitle()) ?></strong>
<p><?= htmlspecialchars(siteClaim()) ?></p>
<small>&copy; <?= date('Y') ?> &mdash; <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">CC BY 4.0</a></small>
<small>&copy; <?= date('Y') ?> &mdash; <a href="<?= htmlspecialchars(siteLicenseUrl()) ?>" target="_blank" rel="noopener"><?= htmlspecialchars(siteLicenseLabel()) ?></a></small>
</div>
<nav class="footer-nav" aria-label="Liens du site">
<a href="/about">À propos</a>
+1 -1
View File
@@ -207,7 +207,7 @@ $catVal = trim($category ?? '');
<tbody>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap" style="width:1%">Auteur</th>
<td>Cédrix</td>
<td><?= htmlspecialchars(siteAuthor() ?: '—') ?></td>
</tr>
<tr>
<th class="text-muted fw-normal ps-0 pe-2 text-nowrap">Publication</th>
+1 -1
View File
@@ -147,7 +147,7 @@ if (empty($cursor) && $filterCat === '') {
'name' => siteTitle(),
'url' => rtrim(APP_URL, '/') . '/',
'description' => siteClaim(),
'inLanguage' => 'fr-FR',
'inLanguage' => siteLang(),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
+1 -1
View File
@@ -310,7 +310,7 @@ $jsonLdData = [
'name' => siteAuthor() !== '' ? siteAuthor() : siteTitle(),
'url' => rtrim(APP_URL, '/'),
],
'inLanguage' => 'fr-FR',
'inLanguage' => siteLang(),
];
if (!empty($ogImage)) {
$jsonLdData['image'] = $ogImage;