diff --git a/app/default/templates/base.php b/app/default/templates/base.php index ce25bfd..ccf18d6 100644 --- a/app/default/templates/base.php +++ b/app/default/templates/base.php @@ -14,9 +14,6 @@ - - -
diff --git a/app/helpers.php b/app/helpers.php index 489202e..a84c786 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -32,73 +32,11 @@ function extractTitle(string $filePath): ?string { return null; } -// Build sorted list items for a directory -function buildListItems(string $dir, Context $ctx, ?array $parentMetadata): array { - $subdirs = getSubdirectories($dir); - - $items = array_filter(array_map(function($item) use ($dir, $ctx) { - $itemPath = "$dir/$item"; - $metadata = loadMetadata($itemPath); - $coverImage = findCoverImage($itemPath); - $pdfFile = findPdfFile($itemPath); - - $title = $metadata['title'] ?? extractTitle($itemPath) ?? $item; - - $rawDate = null; - $date = null; - if (isset($metadata['date'])) { - $rawDate = $metadata['date']; - $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format'); - } else { - $rawDate = extractRawDateFromFolder($item); - if ($rawDate) { - $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format'); - } else { - $rawDate = date("Y-m-d", filemtime($itemPath)); - $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format'); - } - } - - $urlSlug = ($metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item; - - $langPrefix = $ctx->get('langPrefix', ''); - $baseUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($urlSlug); - $assetUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($item); - - return [ - 'title' => $title, - 'url' => $baseUrl . '/', - 'date' => $date, - 'rawDate' => $rawDate, - 'summary' => $metadata['summary'] ?? null, - 'cover' => $coverImage ? "$assetUrl/$coverImage" : null, - 'pdf' => $pdfFile ? "$assetUrl/$pdfFile" : null, - 'redirect' => $metadata['redirect'] ?? null, - 'dirPath' => $itemPath - ]; - }, $subdirs)); - - $sortOrder = strtolower($parentMetadata['order'] ?? 'descending'); - if ($sortOrder === 'ascending') { - usort($items, fn($a, $b) => strcmp($a['date'] ?? '', $b['date'] ?? '')); - } else { - usort($items, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? '')); - } - - return $items; -} - -function extractRawDateFromFolder(string $folderName): ?string { - if (preg_match('/^(\d{4})-(\d{2})-(\d{2})-/', $folderName, $matches)) { - return $matches[1] . '-' . $matches[2] . '-' . $matches[3]; - } - return null; -} - function extractDateFromFolder(string $folderName): ?string { - $raw = extractRawDateFromFolder($folderName); - if ($raw) { - return Hooks::apply(Hook::PROCESS_CONTENT, $raw, 'date_format'); + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})-/', $folderName, $matches)) { + $dateString = $matches[1] . '-' . $matches[2] . '-' . $matches[3]; + // Let plugins format the date + return Hooks::apply(Hook::PROCESS_CONTENT, $dateString, 'date_format'); } return null; } diff --git a/app/rendering.php b/app/rendering.php index ef4e57a..7d61f89 100644 --- a/app/rendering.php +++ b/app/rendering.php @@ -48,16 +48,13 @@ function renderTemplate(Context $ctx, string $content, int $statusCode = 200): v $navigation = $ctx->navigation; $homeLabel = $ctx->homeLabel; + $pageTitle = null; $templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [ 'content' => $content, 'navigation' => $navigation, 'homeLabel' => $homeLabel, - 'pageTitle' => $ctx->get('pageTitle'), - 'metaDescription' => $ctx->get('metaDescription'), - 'pageCssUrl' => $ctx->get('pageCssUrl'), - 'pageCssHash' => $ctx->get('pageCssHash'), - 'feedUrl' => $ctx->get('feedUrl') + 'pageTitle' => $pageTitle ], $ctx); extract($templateVars); diff --git a/app/router.php b/app/router.php index d9710e0..64e706a 100644 --- a/app/router.php +++ b/app/router.php @@ -52,82 +52,6 @@ if (file_exists($contentAssetPath) && is_file($contentAssetPath)) { } } -// Handle Atom feed requests -if (str_ends_with($ctx->requestPath, 'feed.xml')) { - $feedPath = preg_replace('#/?feed\.xml$#', '', $ctx->requestPath); - - // Temporarily set requestPath to the parent directory for resolution - $reflection = new ReflectionProperty($ctx, 'requestPath'); - $originalPath = $ctx->requestPath; - $reflection->setValue($ctx, $feedPath); - - $parsedFeed = parseRequestPath($ctx); - - if ($parsedFeed['type'] !== 'list') { - $reflection->setValue($ctx, $originalPath); - } else { - $dir = $parsedFeed['path']; - $metadata = loadMetadata($dir); - - if (!isset($metadata['feed']) || !$metadata['feed']) { - $reflection->setValue($ctx, $originalPath); - } else { - $items = buildListItems($dir, $ctx, $metadata); - - // Render full content for each item - foreach ($items as &$item) { - $item['content'] = ''; - $contentFiles = findAllContentFiles($item['dirPath']); - foreach ($contentFiles as $file) { - $item['content'] .= renderContentFile($file, $ctx); - } - } - unset($item); - - // Build Atom XML - $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; - $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; - $baseUrl = "$scheme://$host"; - $langPrefix = $ctx->get('langPrefix', ''); - $listUrl = $baseUrl . $langPrefix . '/' . trim($feedPath, '/') . '/'; - $feedUrl = $baseUrl . $langPrefix . '/' . trim($feedPath, '/') . '/feed.xml'; - $feedTitle = $metadata['title'] ?? 'Feed'; - $updated = !empty($items) ? ($items[0]['rawDate'] ?? date('Y-m-d')) : date('Y-m-d'); - - header('Content-Type: application/atom+xml; charset=utf-8'); - echo '' . "\n"; - echo '' . "\n"; - echo ' ' . htmlspecialchars($feedTitle) . '' . "\n"; - echo ' ' . "\n"; - echo ' ' . "\n"; - echo ' ' . htmlspecialchars($listUrl) . '' . "\n"; - echo ' ' . $updated . 'T00:00:00Z' . "\n"; - - foreach ($items as $item) { - $absoluteUrl = $baseUrl . $item['url']; - $itemDate = ($item['rawDate'] ?? date('Y-m-d')) . 'T00:00:00Z'; - - echo ' ' . "\n"; - echo ' ' . htmlspecialchars($item['title']) . '' . "\n"; - echo ' ' . "\n"; - echo ' ' . htmlspecialchars($absoluteUrl) . '' . "\n"; - echo ' ' . $itemDate . '' . "\n"; - if ($item['summary']) { - echo ' ' . htmlspecialchars($item['summary']) . '' . "\n"; - } - if ($item['content']) { - $safeContent = str_replace(']]>', ']]]]>', $item['content']); - echo ' ' . "\n"; - } - echo ' ' . "\n"; - } - - echo '' . "\n"; - exit; - } - } -} - // Handle frontpage if (empty($ctx->requestPath)) { $contentFiles = findAllContentFiles($ctx->contentDir); @@ -197,7 +121,59 @@ switch ($parsedPath['type']) { } // Build list items - $items = buildListItems($dir, $ctx, $metadata); + $subdirs = getSubdirectories($dir); + + $items = array_filter(array_map(function($item) use ($dir, $ctx) { + $itemPath = "$dir/$item"; + $metadata = loadMetadata($itemPath); + $coverImage = findCoverImage($itemPath); + $pdfFile = findPdfFile($itemPath); + + $title = $metadata['title'] ?? extractTitle($itemPath) ?? $item; + $date = null; + if (isset($metadata['date'])) { + $date = $metadata['date']; + // Let plugins format date + $date = Hooks::apply(Hook::PROCESS_CONTENT, $date, 'date_format'); + } else { + $extractedDate = extractDateFromFolder($item); + if ($extractedDate) { + $date = $extractedDate; + } else { + // Convert timestamp to ISO format and let plugins format it + $isoDate = date("Y-m-d", filemtime($itemPath)); + $date = Hooks::apply(Hook::PROCESS_CONTENT, $isoDate, 'date_format'); + } + } + + // Use slug if available + $urlSlug = ($metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item; + + $langPrefix = $ctx->get('langPrefix', ''); + $baseUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($urlSlug); + + // Assets (cover, PDF) must use actual folder name, not translated slug + $assetUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($item); + + return [ + 'title' => $title, + 'url' => $baseUrl . '/', + 'date' => $date, + 'summary' => $metadata['summary'] ?? null, + 'cover' => $coverImage ? "$assetUrl/$coverImage" : null, + 'pdf' => $pdfFile ? "$assetUrl/$pdfFile" : null, + 'redirect' => $metadata['redirect'] ?? null + ]; + }, $subdirs)); + + // Sort by date - check metadata for order preference + $sortOrder = strtolower($metadata['order'] ?? 'descending'); + if ($sortOrder === 'ascending') { + usort($items, fn($a, $b) => strcmp($a['date'] ?? '', $b['date'] ?? '')); + } else { + // Default: descending (newest first) + usort($items, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? '')); + } // Prepare all variables for base template $navigation = $ctx->navigation; @@ -210,19 +186,6 @@ switch ($parsedPath['type']) { $pageCssUrl = $pageCss['url'] ?? null; $pageCssHash = $pageCss['hash'] ?? null; - // Build feed URL if feed is enabled - $langPrefix = $ctx->get('langPrefix', ''); - $feedUrl = (isset($metadata['feed']) && $metadata['feed']) - ? $langPrefix . '/' . trim($ctx->requestPath, '/') . '/feed.xml' - : null; - - // Store for base template (renderTemplate reads these from context) - $ctx->set('pageTitle', $pageTitle); - $ctx->set('metaDescription', $metaDescription); - $ctx->set('pageCssUrl', $pageCssUrl); - $ctx->set('pageCssHash', $pageCssHash); - $ctx->set('feedUrl', $feedUrl); - // Let plugins add template variables $templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [ 'navigation' => $navigation, @@ -232,8 +195,7 @@ switch ($parsedPath['type']) { 'pageCssUrl' => $pageCssUrl, 'pageCssHash' => $pageCssHash, 'items' => $items, - 'pageContent' => $pageContent, - 'feedUrl' => $feedUrl + 'pageContent' => $pageContent ], $ctx); extract($templateVars); diff --git a/docs/03-reference/02-metadata.md b/docs/03-reference/02-metadata.md index 4dd742c..7f8d806 100644 --- a/docs/03-reference/02-metadata.md +++ b/docs/03-reference/02-metadata.md @@ -194,20 +194,6 @@ hide_list = true **Type:** Boolean **Use case:** Section landing pages that should show content instead of list -### `feed` - -Enable an Atom feed for this list page, served at `/{list-path}/feed.xml`. - -```ini -feed = true -``` - -**Default:** `false` (no feed generated) -**Values:** `true` or `false` -**Type:** Boolean -**Applies to:** List pages only (directories with subdirectories) -**Effect:** Generates an Atom XML feed containing the full rendered content of each list item. Also adds an autodiscovery `` tag in the HTML ``. - ## Language-Specific Overrides Add language-specific sections to override fields: @@ -341,19 +327,20 @@ Folder: content/blog/2024-12-15-my-post/ ## Metadata in List Items -When rendering list views, each item in the `$items` array has these keys: +When rendering list views, each item receives these metadata fields: ```php $item = [ - 'title' => 'My Post', // From metadata, heading, or folder name - 'url' => '/blog/my-post/', // Full URL with trailing slash + lang prefix - 'date' => '15. desember 2024', // Formatted for display (plugin-processed) - 'rawDate' => '2024-12-15', // ISO YYYY-MM-DD (for feeds,