SEO: canonical, sitemap.xml, robots.txt, JSON-LD, noindex admin
This commit is contained in:
@@ -16,6 +16,9 @@ RewriteRule ^feed/?$ /feed.php [L,QSA]
|
|||||||
RewriteRule ^rss/?$ /feed.php [L,QSA]
|
RewriteRule ^rss/?$ /feed.php [L,QSA]
|
||||||
RewriteRule ^rss\.xml$ /feed.php [L,QSA]
|
RewriteRule ^rss\.xml$ /feed.php [L,QSA]
|
||||||
|
|
||||||
|
# Sitemap
|
||||||
|
RewriteRule ^sitemap\.xml$ /sitemap.php [L]
|
||||||
|
|
||||||
# Ajoute .php si le fichier correspondant existe
|
# Ajoute .php si le fichier correspondant existe
|
||||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f
|
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f
|
||||||
RewriteRule ^(.+?)/?$ /$1.php [L,QSA]
|
RewriteRule ^(.+?)/?$ /$1.php [L,QSA]
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ $action = $_GET['action'] ?? 'list';
|
|||||||
$uuid = $_GET['uuid'] ?? '';
|
$uuid = $_GET['uuid'] ?? '';
|
||||||
$slug = $_GET['slug'] ?? '';
|
$slug = $_GET['slug'] ?? '';
|
||||||
|
|
||||||
|
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category'];
|
||||||
|
$metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : null;
|
||||||
|
unset($_noindexActions);
|
||||||
|
|
||||||
// ─── Extraction de métadonnées depuis une URL ────────────────────────────────
|
// ─── Extraction de métadonnées depuis une URL ────────────────────────────────
|
||||||
function fetchUrlMeta(string $url): array
|
function fetchUrlMeta(string $url): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /?action=edit
|
||||||
|
Disallow: /?action=create
|
||||||
|
Disallow: /?action=admin
|
||||||
|
Disallow: /?action=delete
|
||||||
|
Disallow: /?action=diff
|
||||||
|
Disallow: /?action=categories
|
||||||
|
Disallow: /?action=add_files
|
||||||
|
Disallow: /?action=import_image
|
||||||
|
Disallow: /?action=sources
|
||||||
|
Disallow: /?action=profile
|
||||||
|
Disallow: /login
|
||||||
|
Disallow: /logout.php
|
||||||
|
Disallow: /oidc/
|
||||||
|
|
||||||
|
Sitemap: https://varlog.a5l.fr/sitemap.xml
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
define('BASE_PATH', realpath(__DIR__ . '/../'));
|
||||||
|
|
||||||
|
require_once BASE_PATH . '/src/helpers.php';
|
||||||
|
require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/ArticleManager.php';
|
||||||
|
|
||||||
|
$articles = new ArticleManager(BASE_PATH . '/data');
|
||||||
|
$privateCats = $articles->getPrivateCategories();
|
||||||
|
|
||||||
|
$published = array_filter($articles->getAll(true), static function (array $a) use ($privateCats): bool {
|
||||||
|
$cat = trim($a['category'] ?? '');
|
||||||
|
if ($cat !== '' && in_array($cat, $privateCats, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (strtotime((string)($a['published_at'] ?? '')) > time()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
header('Content-Type: application/xml; charset=UTF-8');
|
||||||
|
header('X-Robots-Tag: noindex');
|
||||||
|
|
||||||
|
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||||
|
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||||
|
|
||||||
|
// Homepage
|
||||||
|
echo ' <url>' . "\n";
|
||||||
|
echo ' <loc>' . htmlspecialchars(rtrim(APP_URL, '/') . '/') . '</loc>' . "\n";
|
||||||
|
echo ' <changefreq>daily</changefreq>' . "\n";
|
||||||
|
echo ' <priority>1.0</priority>' . "\n";
|
||||||
|
echo ' </url>' . "\n";
|
||||||
|
|
||||||
|
foreach ($published as $article) {
|
||||||
|
$loc = htmlspecialchars(rtrim(APP_URL, '/') . '/post/' . rawurlencode($article['slug'] ?? ''));
|
||||||
|
$lastmod = date('Y-m-d', strtotime((string)($article['updated_at'] ?? $article['published_at'] ?? 'now')));
|
||||||
|
echo ' <url>' . "\n";
|
||||||
|
echo ' <loc>' . $loc . '</loc>' . "\n";
|
||||||
|
echo ' <lastmod>' . $lastmod . '</lastmod>' . "\n";
|
||||||
|
echo ' <changefreq>monthly</changefreq>' . "\n";
|
||||||
|
echo ' <priority>0.8</priority>' . "\n";
|
||||||
|
echo ' </url>' . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</urlset>' . "\n";
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
<!-- SEO -->
|
<!-- SEO -->
|
||||||
<meta name="description" content="<?= htmlspecialchars(($seoDescription ?? '') ?: 'Varlog est un journal personnel en ligne de Cédrix. Informatique, hack et loisirs techniques.') ?>">
|
<meta name="description" content="<?= htmlspecialchars(($seoDescription ?? '') ?: 'Varlog est un journal personnel en ligne de Cédrix. Informatique, hack et loisirs techniques.') ?>">
|
||||||
<meta name="robots" content="index, follow">
|
<meta name="robots" content="<?= htmlspecialchars($metaRobots ?? 'index, follow') ?>">
|
||||||
|
<link rel="canonical" href="<?= htmlspecialchars($canonical ?? $ogUrl ?? rtrim(APP_URL, '/') . '/') ?>">
|
||||||
|
|
||||||
<!-- Open Graph -->
|
<!-- Open Graph -->
|
||||||
<meta property="og:title" content="<?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? 'varlog')) ?>">
|
<meta property="og:title" content="<?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? 'varlog')) ?>">
|
||||||
@@ -25,6 +26,10 @@
|
|||||||
<meta property="article:published_time" content="<?= htmlspecialchars(date('c', strtotime((string)$articlePublishedAt))) ?>">
|
<meta property="article:published_time" content="<?= htmlspecialchars(date('c', strtotime((string)$articlePublishedAt))) ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($jsonLd ?? '')): ?>
|
||||||
|
<script type="application/ld+json"><?= $jsonLd ?></script>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- RSS autodiscovery -->
|
<!-- RSS autodiscovery -->
|
||||||
<link rel="alternate" type="application/rss+xml" title="varlog" href="/feed">
|
<link rel="alternate" type="application/rss+xml" title="varlog" href="/feed">
|
||||||
|
|
||||||
|
|||||||
@@ -90,4 +90,28 @@ ob_start();
|
|||||||
<?php
|
<?php
|
||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
$title = 'varlog';
|
$title = 'varlog';
|
||||||
|
|
||||||
|
// Pages avec curseur = contenu non-canonique → noindex
|
||||||
|
if (!empty($cursor)) {
|
||||||
|
$metaRobots = 'noindex, follow';
|
||||||
|
$canonical = rtrim(APP_URL, '/') . '/';
|
||||||
|
} elseif ($filterCat !== '') {
|
||||||
|
$canonical = rtrim(APP_URL, '/') . '/?' . http_build_query(['cat' => $filterCat]);
|
||||||
|
} else {
|
||||||
|
$canonical = rtrim(APP_URL, '/') . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON-LD WebSite sur la page d'accueil sans filtre
|
||||||
|
if (empty($cursor) && $filterCat === '') {
|
||||||
|
$jsonLd = json_encode([
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'WebSite',
|
||||||
|
'name' => 'varlog',
|
||||||
|
'url' => rtrim(APP_URL, '/') . '/',
|
||||||
|
'description' => 'Journal personnel de Cédrix. Informatique, hack et loisirs techniques.',
|
||||||
|
'inLanguage' => 'fr-FR',
|
||||||
|
'author' => ['@type' => 'Person', 'name' => 'Cédrix'],
|
||||||
|
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
include __DIR__ . '/layout.php';
|
include __DIR__ . '/layout.php';
|
||||||
|
|||||||
+37
-2
@@ -255,10 +255,45 @@ $hasSources = (!empty($externalLinks) || !empty($files))
|
|||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
$title = htmlspecialchars($article['title']);
|
$title = htmlspecialchars($article['title']);
|
||||||
$seoTitle = ($article['seo_title'] ?? '') ?: $article['title'];
|
$seoTitle = ($article['seo_title'] ?? '') ?: $article['title'];
|
||||||
$seoDescription = $article['seo_description'] ?? '';
|
|
||||||
$ogImage = $article['og_image'] ?? '';
|
|
||||||
$ogType = 'article';
|
$ogType = 'article';
|
||||||
$ogUrl = url('post/' . rawurlencode($article['slug'] ?? ''));
|
$ogUrl = url('post/' . rawurlencode($article['slug'] ?? ''));
|
||||||
|
$canonical = $ogUrl;
|
||||||
$articlePublishedAt = $article['published_at'] ?? '';
|
$articlePublishedAt = $article['published_at'] ?? '';
|
||||||
$mainClass = 'container-fluid';
|
$mainClass = 'container-fluid';
|
||||||
|
|
||||||
|
// Auto-description depuis le contenu si le champ SEO est vide
|
||||||
|
$seoDescription = $article['seo_description'] ?? '';
|
||||||
|
if ($seoDescription === '') {
|
||||||
|
$plain = strip_tags((new Parsedown())->text($article['content']));
|
||||||
|
$plain = preg_replace('/\s+/', ' ', $plain);
|
||||||
|
$seoDescription = mb_strimwidth(trim((string)$plain), 0, 155, '…');
|
||||||
|
}
|
||||||
|
|
||||||
|
// og:image : cover puis fallback og_image du meta
|
||||||
|
if ($ogImage === null || $ogImage === '') {
|
||||||
|
$ogImage = $article['og_image'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON-LD Article
|
||||||
|
$jsonLdData = [
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'BlogPosting',
|
||||||
|
'headline' => $seoTitle,
|
||||||
|
'description' => $seoDescription,
|
||||||
|
'url' => $canonical,
|
||||||
|
'datePublished' => date('c', strtotime((string)$articlePublishedAt)),
|
||||||
|
'dateModified' => date('c', strtotime((string)($article['updated_at'] ?? $articlePublishedAt))),
|
||||||
|
'author' => ['@type' => 'Person', 'name' => 'Cédrix'],
|
||||||
|
'publisher' => [
|
||||||
|
'@type' => 'Person',
|
||||||
|
'name' => 'Cédrix',
|
||||||
|
'url' => rtrim(APP_URL, '/'),
|
||||||
|
],
|
||||||
|
'inLanguage' => 'fr-FR',
|
||||||
|
];
|
||||||
|
if (!empty($ogImage)) {
|
||||||
|
$jsonLdData['image'] = $ogImage;
|
||||||
|
}
|
||||||
|
$jsonLd = json_encode($jsonLdData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
include __DIR__ . '/layout.php';
|
include __DIR__ . '/layout.php';
|
||||||
|
|||||||
Reference in New Issue
Block a user