movmaker-webui/sitemap.php

144 lines
5.2 KiB
PHP

<?php
// Dynamic XML sitemap for public MVLog pages and enabled article permalinks.
declare(strict_types=1);
date_default_timezone_set('Europe/Copenhagen');
$root = __DIR__;
$config = is_file($root . '/config.php') ? require $root . '/config.php' : [];
$uploadsDir = (string)($config['uploads_dir'] ?? ($root . '/in-dir'));
$videosDir = (string)($config['videos_dir'] ?? ($root . '/out-dir'));
function sitemap_xml_escape(string $value): string {
return htmlspecialchars($value, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
function sitemap_lastmod_from_ts(int|float $ts): string {
return gmdate('Y-m-d', (int)$ts);
}
function sitemap_article_id_is_valid(string $id): bool {
return (bool)preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id);
}
function sitemap_slug_from_title(string $title, string $fallback = 'post'): string {
$slug = strtr($title, [
'æ'=>'ae','Æ'=>'ae','ø'=>'o','Ø'=>'o','å'=>'aa','Å'=>'aa',
'ä'=>'ae','Ä'=>'ae','ö'=>'oe','Ö'=>'oe','ü'=>'ue','Ü'=>'ue','ß'=>'ss','ẞ'=>'ss',
'č'=>'c','Č'=>'c','ć'=>'c','Ć'=>'c','ž'=>'z','Ž'=>'z','š'=>'s','Š'=>'s','đ'=>'d','Đ'=>'d',
]);
if (function_exists('iconv')) {
$ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
if ($ascii !== false) $slug = $ascii;
}
$slug = strtolower($slug);
$slug = preg_replace('/[^a-z0-9]+/', '-', $slug) ?? '';
$slug = trim($slug, '-');
return $slug !== '' ? $slug : $fallback;
}
function sitemap_title_from_data_file(string $dir): string {
$file = rtrim($dir, '/') . '/data.txt';
if (is_file($file)) {
foreach (file($file, FILE_IGNORE_NEW_LINES) ?: [] as $line) {
if (!str_contains($line, ':')) continue;
[$key, $value] = array_map('trim', explode(':', $line, 2));
if (in_array(strtolower($key), ['title','name'], true) && $value !== '') return $value;
}
}
return basename($dir);
}
function sitemap_add_url(array &$urls, string $loc, string $lastmod, string $changefreq, string $priority): void {
$urls[$loc] = [
'loc' => $loc,
'lastmod' => $lastmod,
'changefreq' => $changefreq,
'priority' => $priority,
];
}
$urls = [];
$staticPages = [
['https://bubulescu.org/', $root . '/index.php', 'daily', '1.0'],
['https://bubulescu.org/contact.php', $root . '/contact.php', 'monthly', '0.5'],
['https://bubulescu.org/feed.php', $root . '/feed.php', 'monthly', '0.3'],
];
foreach ($staticPages as [$loc, $path, $changefreq, $priority]) {
if (is_file($path)) {
sitemap_add_url($urls, $loc, sitemap_lastmod_from_ts((int)filemtime($path)), $changefreq, $priority);
}
}
$articleEntries = [];
$articleDirs = glob(rtrim($uploadsDir, '/') . '/*', GLOB_ONLYDIR) ?: [];
sort($articleDirs, SORT_STRING);
foreach ($articleDirs as $dir) {
if (!is_file($dir . '/.mvlog-permalink')) continue;
$idFile = $dir . '/.mvlog-id';
$articleId = is_file($idFile) ? strtolower(trim((string)@file_get_contents($idFile))) : '';
if (!sitemap_article_id_is_valid($articleId)) continue;
$state = [];
$stateFile = $dir . '/.movmaker-state.json';
if (is_file($stateFile)) {
$decoded = json_decode((string)@file_get_contents($stateFile), true);
if (is_array($decoded)) $state = $decoded;
}
$lastmodCandidates = [];
foreach ([$dir . '/data.txt', $stateFile, $idFile, $dir . '/.mvlog-permalink'] as $candidate) {
if (is_file($candidate)) $lastmodCandidates[] = (int)filemtime($candidate);
}
$output = isset($state['output']) && is_string($state['output']) ? basename($state['output']) : '';
if ($output !== '') {
$videoPath = rtrim($videosDir, '/') . '/' . $output;
if (is_file($videoPath)) $lastmodCandidates[] = (int)filemtime($videoPath);
}
$articleEntries[] = [
'id' => $articleId,
'base_slug' => sitemap_slug_from_title(sitemap_title_from_data_file($dir), $articleId),
'lastmod' => sitemap_lastmod_from_ts($lastmodCandidates ? max($lastmodCandidates) : (int)filemtime($dir)),
];
}
$usedSlugs = [];
foreach ($articleEntries as $entry) {
$slug = $entry['base_slug'];
if (isset($usedSlugs[$slug])) $slug = $entry['base_slug'] . '-' . substr($entry['id'], -6);
$n = 2;
while (isset($usedSlugs[$slug])) {
$slug = $entry['base_slug'] . '-' . substr($entry['id'], -6) . '-' . $n;
$n++;
}
$usedSlugs[$slug] = true;
sitemap_add_url(
$urls,
'https://bubulescu.org/post/' . rawurlencode($slug),
$entry['lastmod'],
'monthly',
'0.8'
);
}
ksort($urls, SORT_STRING);
header('Content-Type: application/xml; charset=UTF-8');
header('X-MVLog-Sitemap: dynamic');
header('Cache-Control: public, max-age=300');
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
foreach ($urls as $url) {
echo " <url>\n";
echo ' <loc>' . sitemap_xml_escape($url['loc']) . "</loc>\n";
echo ' <lastmod>' . sitemap_xml_escape($url['lastmod']) . "</lastmod>\n";
echo ' <changefreq>' . sitemap_xml_escape($url['changefreq']) . "</changefreq>\n";
echo ' <priority>' . sitemap_xml_escape($url['priority']) . "</priority>\n";
echo " </url>\n";
}
echo "</urlset>\n";