Permalinks changed from id to title slug...
This commit is contained in:
parent
07aa53689e
commit
db4a48d08d
7 changed files with 267 additions and 35 deletions
131
index.php
131
index.php
|
|
@ -4,16 +4,11 @@ $config = require __DIR__ . "/config.php";
|
|||
foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[$d])) mkdir($config[$d], 0775, true);
|
||||
if (!is_dir(__DIR__ . "/cache")) mkdir(__DIR__ . "/cache", 0775, true);
|
||||
|
||||
if (preg_match('~/index\.php(?:\?|$)~', (string)($_SERVER['REQUEST_URI'] ?? '')) && preg_match('/^[0-9]{14}[a-f0-9]{16}$/', (string)($_GET['id'] ?? ''))) {
|
||||
header('Location: /?id=' . rawurlencode((string)$_GET['id']), true, 301);
|
||||
exit;
|
||||
}
|
||||
|
||||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); }
|
||||
function render_description_html($s){
|
||||
$html = h($s);
|
||||
$linked = preg_replace_callback('/(?<![A-Za-z0-9_])(subscribe|contact|journal|postcard)(?![A-Za-z0-9_])/i', static function($m){
|
||||
return '<a class="description-contact-link" href="contact.php" target="_blank" rel="noopener noreferrer">' . $m[0] . '<span class="external-link-icon" aria-hidden="true">↗</span><span class="sr-only"> (opens in new tab)</span></a>';
|
||||
return '<a class="description-contact-link" href="/contact.php" target="_blank" rel="noopener noreferrer">' . $m[0] . '<span class="external-link-icon" aria-hidden="true">↗</span><span class="sr-only"> (opens in new tab)</span></a>';
|
||||
}, $html);
|
||||
return nl2br($linked ?? $html, false);
|
||||
}
|
||||
|
|
@ -46,7 +41,7 @@ function ci_contains($haystack, $needle){
|
|||
function keep_non_empty(array $params): array { return array_filter($params, fn($v)=>$v !== '' && $v !== null); }
|
||||
function query_url(array $params): string {
|
||||
$params = keep_non_empty($params);
|
||||
return $params ? ('?' . http_build_query($params)) : 'index.php';
|
||||
return $params ? ('/?' . http_build_query($params)) : '/';
|
||||
}
|
||||
|
||||
function hidden_video_outputs($config){
|
||||
|
|
@ -111,7 +106,7 @@ function public_url_path($path){
|
|||
$path = str_replace('\\', '/', trim((string)$path));
|
||||
if ($path === '') return '';
|
||||
$parts = array_values(array_filter(explode('/', $path), static fn($p) => $p !== ''));
|
||||
return implode('/', array_map('rawurlencode', $parts));
|
||||
return '/' . implode('/', array_map('rawurlencode', $parts));
|
||||
}
|
||||
function video_thumb_public_url($config, $videoName){
|
||||
$videoName = basename((string)$videoName);
|
||||
|
|
@ -175,6 +170,82 @@ function article_permalink_enabled($config, $articleId){
|
|||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) return false;
|
||||
return !empty($cache[$articleId]);
|
||||
}
|
||||
function permalink_slug_from_title($title, $fallback = 'post'){
|
||||
$slug = strtr((string)$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 permalink_title_for_dir($config, $dir, $videoCache = []){
|
||||
$dataFile = rtrim((string)$dir, '/') . '/data.txt';
|
||||
if (is_file($dataFile)) {
|
||||
foreach (file($dataFile, 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;
|
||||
}
|
||||
}
|
||||
$stateFile = rtrim((string)$dir, '/') . '/.movmaker-state.json';
|
||||
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
|
||||
$output = is_array($state) ? basename((string)($state['output'] ?? '')) : '';
|
||||
if ($output !== '') {
|
||||
$path = $config['videos_dir'] . '/' . $output;
|
||||
if (is_file($path)) {
|
||||
$meta = $videoCache[$output]['metadata'] ?? video_metadata($path);
|
||||
$title = trim((string)($meta['title'] ?? ''));
|
||||
if ($title !== '') return $title;
|
||||
}
|
||||
}
|
||||
return nice_title(basename((string)$dir));
|
||||
}
|
||||
function permalink_slug_map($config, $videoCache = []){
|
||||
$map = [];
|
||||
$used = [];
|
||||
$dirs = input_dirs($config['uploads_dir']);
|
||||
sort($dirs, SORT_STRING);
|
||||
foreach ($dirs as $dir) {
|
||||
if (!input_dir_permalink($dir)) continue;
|
||||
$id = read_article_id($dir);
|
||||
if ($id === '') continue;
|
||||
$base = permalink_slug_from_title(permalink_title_for_dir($config, $dir, $videoCache), $id);
|
||||
$slug = $base;
|
||||
if (isset($used[$slug])) $slug = $base . '-' . substr($id, -6);
|
||||
$n = 2;
|
||||
while (isset($used[$slug])) {
|
||||
$slug = $base . '-' . substr($id, -6) . '-' . $n;
|
||||
$n++;
|
||||
}
|
||||
$used[$slug] = true;
|
||||
$map[$slug] = $id;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
function article_permalink_url($config, $articleId, $videoCache = [], $absolute = false){
|
||||
$articleId = strtolower(trim((string)$articleId));
|
||||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) return '';
|
||||
foreach (permalink_slug_map($config, $videoCache) as $slug => $id) {
|
||||
if ($id === $articleId) {
|
||||
$path = '/post/' . rawurlencode($slug);
|
||||
return $absolute ? ('https://bubulescu.org' . $path) : $path;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
function article_id_for_permalink_slug($config, $slug, $videoCache = []){
|
||||
$slug = strtolower(trim((string)$slug, "/ \t\n\r\0\x0B"));
|
||||
if (!preg_match('/^[a-z0-9][a-z0-9-]*$/', $slug)) return '';
|
||||
$map = permalink_slug_map($config, $videoCache);
|
||||
return $map[$slug] ?? '';
|
||||
}
|
||||
function article_og_image_url($articleId){
|
||||
$articleId = strtolower(trim((string)$articleId));
|
||||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) return '';
|
||||
|
|
@ -203,8 +274,8 @@ function render_blank_page(){
|
|||
<title>MVLog</title>
|
||||
<?php include __DIR__ . '/_pwa_head.php'; ?>
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/img/moto_travel.png">
|
||||
<link rel="stylesheet" href="style.css?v=20260531u">
|
||||
<link rel="icon" type="image/png" href="/assets/img/moto_travel.png">
|
||||
<link rel="stylesheet" href="/style.css?v=20260531u">
|
||||
<style>html,body{height:100%;overflow:hidden}body{height:100dvh}main{flex:1;min-height:0}</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -324,9 +395,23 @@ usort($allVideos, function($a, $b) use ($videoCache) {
|
|||
return $bd <=> $ad;
|
||||
});
|
||||
|
||||
$articleId = trim((string)($_GET['id'] ?? ''));
|
||||
if ($articleId !== '' && !preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) {
|
||||
render_404_page();
|
||||
$postSlug = trim((string)($_GET['post'] ?? ''));
|
||||
$articleId = '';
|
||||
if ($postSlug !== '') {
|
||||
$articleId = article_id_for_permalink_slug($config, $postSlug, $videoCache);
|
||||
if ($articleId === '') render_404_page();
|
||||
} else {
|
||||
$articleId = trim((string)($_GET['id'] ?? ''));
|
||||
if ($articleId !== '' && !preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) {
|
||||
render_404_page();
|
||||
}
|
||||
if ($articleId !== '' && article_permalink_enabled($config, $articleId)) {
|
||||
$canonicalPath = article_permalink_url($config, $articleId, $videoCache, false);
|
||||
if ($canonicalPath !== '') {
|
||||
header('Location: ' . $canonicalPath, true, 301);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($articleId !== '') {
|
||||
if (!article_permalink_enabled($config, $articleId)) {
|
||||
|
|
@ -409,7 +494,8 @@ $defaultOgImageUrl = 'https://bubulescu.org/assets/img/bubulescuorg.jpg';
|
|||
$ogImageUrl = $defaultOgImageUrl;
|
||||
$ogImageAlt = 'journal of an unreliable narrator.';
|
||||
$ogType = ($articleId !== '') ? 'article' : 'website';
|
||||
$ogUrl = ($articleId !== '') ? ('https://bubulescu.org/?id=' . rawurlencode($articleId)) : 'https://bubulescu.org/';
|
||||
$canonicalArticleUrl = ($articleId !== '') ? article_permalink_url($config, $articleId, $videoCache, true) : '';
|
||||
$ogUrl = ($canonicalArticleUrl !== '') ? $canonicalArticleUrl : 'https://bubulescu.org/';
|
||||
$pageTitle = 'MVLog';
|
||||
if ($articleId !== '') {
|
||||
$customOg = article_og_image_url($articleId);
|
||||
|
|
@ -435,6 +521,7 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title><?=h($pageTitle)?></title>
|
||||
<link rel="canonical" href="<?=h($ogUrl)?>">
|
||||
<?php include __DIR__ . '/_pwa_head.php'; ?>
|
||||
|
||||
<!-- Basic Open Graph -->
|
||||
|
|
@ -457,9 +544,9 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
|||
<meta name="twitter:title" content="<?=h($ogImageAlt)?>">
|
||||
<meta name="twitter:description" content="Motorcycle road stories, hockey passion, pizza nerdism and facts remembered by an unreliable narrator.">
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" title="MVLog RSS" href="feed.php">
|
||||
<link rel="icon" type="image/png" href="assets/img/moto_travel.png">
|
||||
<link rel="stylesheet" href="style.css?v=20260531u">
|
||||
<link rel="alternate" type="application/rss+xml" title="MVLog RSS" href="/feed.php">
|
||||
<link rel="icon" type="image/png" href="/assets/img/moto_travel.png">
|
||||
<link rel="stylesheet" href="/style.css?v=20260531u">
|
||||
<style>
|
||||
.filter-form{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.55rem;align-items:center;margin:0 0 .4rem}
|
||||
.filter-input-wrap{position:relative;min-width:0}
|
||||
|
|
@ -524,7 +611,7 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
|||
<form method="get" class="filter-form">
|
||||
<div class="filter-input-wrap">
|
||||
<input name="q" value="<?=h($rawKeyword)?>" placeholder="Search title/teaser/quote_da/description or use date=... / location=..." aria-label="Search articles">
|
||||
<a class="filter-clear-inside <?=$rawKeyword === '' ? 'is-empty' : ''?>" href="index.php" aria-label="Clear filter" title="Clear filter">×</a>
|
||||
<a class="filter-clear-inside <?=$rawKeyword === '' ? 'is-empty' : ''?>" href="/" aria-label="Clear filter" title="Clear filter">×</a>
|
||||
</div>
|
||||
<button type="submit" class="filter-submit" aria-label="Filter">
|
||||
<span class="icon" aria-hidden="true">🔍</span>
|
||||
|
|
@ -541,9 +628,9 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
|||
$base = pathinfo($name, PATHINFO_FILENAME);
|
||||
$articleIdForVideo = video_article_id($config, $name);
|
||||
$permalinkEnabled = $articleIdForVideo !== '' && article_permalink_enabled($config, $articleIdForVideo);
|
||||
$permalinkUrl = $permalinkEnabled ? ('/?id=' . rawurlencode($articleIdForVideo)) : '';
|
||||
$shareUrl = $permalinkEnabled ? ('https://bubulescu.org/?id=' . rawurlencode($articleIdForVideo)) : '';
|
||||
$url = $config['public_videos'].'/'.rawurlencode($name);
|
||||
$permalinkUrl = $permalinkEnabled ? article_permalink_url($config, $articleIdForVideo, $videoCache, false) : '';
|
||||
$shareUrl = $permalinkEnabled ? article_permalink_url($config, $articleIdForVideo, $videoCache, true) : '';
|
||||
$url = '/' . ltrim((string)$config['public_videos'], '/') . '/'.rawurlencode($name);
|
||||
$m = $videoCache[$name]['metadata'] ?? video_metadata($v);
|
||||
$posterUrl = video_thumb_public_url($config, $name);
|
||||
$dateFilterUrl = query_url(['q' => 'date=' . (string)($m['date'] ?? '')]);
|
||||
|
|
@ -565,7 +652,7 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
|
|||
$hideMap = !empty($hiddenMapOutputs[$name]);
|
||||
if(!$hideMap && is_file($config['videos_dir']."/".$map)):
|
||||
?>
|
||||
<a class="map-image-link" href="<?=h($config['public_videos']."/".rawurlencode($map))?>"><img class="map-image" src="<?=h($config['public_videos']."/".rawurlencode($map))?>" alt="Map"></a>
|
||||
<a class="map-image-link" href="<?=h("/" . ltrim((string)$config['public_videos'], "/") . "/" . rawurlencode($map))?>"><img class="map-image" src="<?=h("/" . ltrim((string)$config['public_videos'], "/") . "/" . rawurlencode($map))?>" alt="Map"></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue