144 lines
5.2 KiB
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";
|