diff --git a/public/.htaccess b/public/.htaccess index 97d66a6..f46705f 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -16,6 +16,9 @@ RewriteRule ^feed/?$ /feed.php [L,QSA] RewriteRule ^rss/?$ /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 RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f RewriteRule ^(.+?)/?$ /$1.php [L,QSA] diff --git a/public/index.php b/public/index.php index b9320a7..239ddf0 100644 --- a/public/index.php +++ b/public/index.php @@ -21,6 +21,10 @@ $action = $_GET['action'] ?? 'list'; $uuid = $_GET['uuid'] ?? ''; $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 ──────────────────────────────── function fetchUrlMeta(string $url): array { diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..f3362e1 --- /dev/null +++ b/public/robots.txt @@ -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 diff --git a/public/sitemap.php b/public/sitemap.php new file mode 100644 index 0000000..5ed94f9 --- /dev/null +++ b/public/sitemap.php @@ -0,0 +1,49 @@ +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 '' . "\n"; +echo '' . "\n"; + +// Homepage +echo ' ' . "\n"; +echo ' ' . htmlspecialchars(rtrim(APP_URL, '/') . '/') . '' . "\n"; +echo ' daily' . "\n"; +echo ' 1.0' . "\n"; +echo ' ' . "\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 ' ' . "\n"; + echo ' ' . $loc . '' . "\n"; + echo ' ' . $lastmod . '' . "\n"; + echo ' monthly' . "\n"; + echo ' 0.8' . "\n"; + echo ' ' . "\n"; +} + +echo '' . "\n"; diff --git a/templates/layout.php b/templates/layout.php index 58e179f..ded0912 100644 --- a/templates/layout.php +++ b/templates/layout.php @@ -7,7 +7,8 @@ - + + @@ -25,6 +26,10 @@ + + + + diff --git a/templates/post_list.php b/templates/post_list.php index d2cbb63..c37891f 100644 --- a/templates/post_list.php +++ b/templates/post_list.php @@ -90,4 +90,28 @@ ob_start(); $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'; diff --git a/templates/post_view.php b/templates/post_view.php index 0ec8ca4..af2eb86 100644 --- a/templates/post_view.php +++ b/templates/post_view.php @@ -255,10 +255,45 @@ $hasSources = (!empty($externalLinks) || !empty($files)) $content = ob_get_clean(); $title = htmlspecialchars($article['title']); $seoTitle = ($article['seo_title'] ?? '') ?: $article['title']; -$seoDescription = $article['seo_description'] ?? ''; -$ogImage = $article['og_image'] ?? ''; $ogType = 'article'; $ogUrl = url('post/' . rawurlencode($article['slug'] ?? '')); +$canonical = $ogUrl; $articlePublishedAt = $article['published_at'] ?? ''; $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';