2025-11-01 22:50:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
2025-11-30 22:26:37 +01:00
|
|
|
function renderContentFile(string $filePath, ?Context $ctx = null): string {
|
2025-11-01 22:54:42 +01:00
|
|
|
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:54:42 +01:00
|
|
|
ob_start();
|
|
|
|
|
if ($ext === 'md') {
|
2025-11-03 21:50:43 +01:00
|
|
|
require_once __DIR__ . '/cache.php';
|
2025-11-30 22:26:37 +01:00
|
|
|
$langPrefix = $ctx ? $ctx->get('langPrefix', '') : '';
|
|
|
|
|
$cached = getCachedMarkdown($filePath, $langPrefix);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-03 21:50:43 +01:00
|
|
|
if ($cached !== null) {
|
|
|
|
|
echo $cached;
|
|
|
|
|
} else {
|
2025-11-30 22:26:37 +01:00
|
|
|
// Update to newer versions before PHP 9.0 release
|
|
|
|
|
// Current versions have been patched for PHP 8.4+ compatibility
|
2025-11-03 21:50:43 +01:00
|
|
|
if (!class_exists('Parsedown')) {
|
|
|
|
|
require_once __DIR__ . '/vendor/Parsedown.php';
|
|
|
|
|
}
|
2025-11-29 23:39:32 +01:00
|
|
|
if (!class_exists('ParsedownExtra')) {
|
|
|
|
|
require_once __DIR__ . '/vendor/ParsedownExtra.php';
|
|
|
|
|
}
|
|
|
|
|
$html = '<article>' . (new ParsedownExtra())->text(file_get_contents($filePath)) . '</article>';
|
2025-11-30 22:26:37 +01:00
|
|
|
|
|
|
|
|
// Add language prefix to internal links
|
|
|
|
|
if ($langPrefix) {
|
|
|
|
|
$html = preg_replace(
|
|
|
|
|
'/href="(\/[^"]*)"/',
|
|
|
|
|
'href="' . $langPrefix . '$1"',
|
|
|
|
|
$html
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setCachedMarkdown($filePath, $html, $langPrefix);
|
2025-11-03 21:50:43 +01:00
|
|
|
echo $html;
|
2025-11-01 22:54:42 +01:00
|
|
|
}
|
|
|
|
|
} elseif (in_array($ext, ['html', 'php'])) {
|
2025-11-30 22:26:37 +01:00
|
|
|
// Make template variables available to PHP content files
|
|
|
|
|
if ($ctx !== null && $ext === 'php') {
|
|
|
|
|
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [], $ctx);
|
|
|
|
|
extract($templateVars);
|
|
|
|
|
}
|
2025-11-01 22:54:42 +01:00
|
|
|
include $filePath;
|
|
|
|
|
}
|
|
|
|
|
return ob_get_clean();
|
|
|
|
|
}
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
function renderTemplate(Context $ctx, string $content, int $statusCode = 200): void {
|
2025-11-25 20:19:12 +01:00
|
|
|
global $GLOBALS;
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Get basic template vars
|
2025-11-01 23:33:09 +01:00
|
|
|
$navigation = $ctx->navigation;
|
|
|
|
|
$homeLabel = $ctx->homeLabel;
|
2025-11-25 20:19:12 +01:00
|
|
|
$pageTitle = null;
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Let plugins add template variables
|
|
|
|
|
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
|
|
|
|
|
'content' => $content,
|
|
|
|
|
'navigation' => $navigation,
|
|
|
|
|
'homeLabel' => $homeLabel,
|
|
|
|
|
'pageTitle' => $pageTitle
|
|
|
|
|
], $ctx);
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
extract($templateVars);
|
|
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
http_response_code($statusCode);
|
2025-11-01 23:33:09 +01:00
|
|
|
include $ctx->templates->base;
|
2025-11-01 22:50:02 +01:00
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
function renderFile(Context $ctx, string $filePath): void {
|
2025-11-01 22:50:02 +01:00
|
|
|
$realPath = realpath($filePath);
|
2025-11-01 23:33:09 +01:00
|
|
|
if (!$realPath || !str_starts_with($realPath, $ctx->contentDir) || !is_readable($realPath)) {
|
|
|
|
|
renderTemplate($ctx, "<article><h1>403 Forbidden</h1><p>Access denied.</p></article>", 403);
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$ext = pathinfo($realPath, PATHINFO_EXTENSION);
|
|
|
|
|
|
2025-11-01 22:54:42 +01:00
|
|
|
if (in_array($ext, CONTENT_EXTENSIONS)) {
|
2025-11-30 22:26:37 +01:00
|
|
|
$content = renderContentFile($realPath, $ctx);
|
2025-11-01 22:50:02 +01:00
|
|
|
|
|
|
|
|
$pageDir = dirname($realPath);
|
2025-11-25 20:19:12 +01:00
|
|
|
$pageMetadata = loadMetadata($pageDir);
|
|
|
|
|
|
2025-11-11 23:36:53 +01:00
|
|
|
// Load page-level plugins
|
|
|
|
|
getPluginManager()->loadPagePlugins($pageMetadata);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
$navigation = $ctx->navigation;
|
|
|
|
|
$homeLabel = $ctx->homeLabel;
|
2025-11-01 22:50:02 +01:00
|
|
|
$pageTitle = $pageMetadata['title'] ?? null;
|
2025-11-25 20:19:12 +01:00
|
|
|
$metaDescription = extractMetaDescription($pageDir, $pageMetadata);
|
|
|
|
|
|
2025-11-03 23:04:16 +01:00
|
|
|
// Check for page-specific CSS
|
|
|
|
|
$pageCss = findPageCss($pageDir, $ctx->contentDir);
|
|
|
|
|
$pageCssUrl = $pageCss['url'] ?? null;
|
|
|
|
|
$pageCssHash = $pageCss['hash'] ?? null;
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-04 23:00:53 +01:00
|
|
|
// Check for cover image for social media
|
|
|
|
|
$coverImage = findCoverImage($pageDir);
|
|
|
|
|
$socialImageUrl = null;
|
|
|
|
|
if ($coverImage) {
|
|
|
|
|
$relativePath = str_replace($ctx->contentDir, '', $pageDir);
|
|
|
|
|
$relativePath = trim($relativePath, '/');
|
|
|
|
|
$socialImageUrl = '/' . ($relativePath ? $relativePath . '/' : '') . $coverImage;
|
|
|
|
|
}
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Let plugins add template variables
|
|
|
|
|
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
|
|
|
|
|
'content' => $content,
|
|
|
|
|
'navigation' => $navigation,
|
|
|
|
|
'homeLabel' => $homeLabel,
|
|
|
|
|
'pageTitle' => $pageTitle,
|
|
|
|
|
'metaDescription' => $metaDescription,
|
|
|
|
|
'pageCssUrl' => $pageCssUrl,
|
|
|
|
|
'pageCssHash' => $pageCssHash,
|
|
|
|
|
'socialImageUrl' => $socialImageUrl
|
|
|
|
|
], $ctx);
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
extract($templateVars);
|
|
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
// Wrap content with page template
|
|
|
|
|
ob_start();
|
2025-11-25 20:19:12 +01:00
|
|
|
require $ctx->templates->page;
|
|
|
|
|
$wrappedContent = ob_get_clean();
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
include $ctx->templates->base;
|
2025-11-01 22:50:02 +01:00
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Unknown type - 404
|
|
|
|
|
renderTemplate($ctx, "<article><h1>404 - Not Found</h1><p>The requested file could not be found.</p></article>", 404);
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
function renderMultipleFiles(Context $ctx, array $files, string $pageDir): void {
|
|
|
|
|
$content = '';
|
|
|
|
|
foreach ($files as $file) {
|
2025-11-30 22:26:37 +01:00
|
|
|
$content .= renderContentFile($file, $ctx);
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
$pageMetadata = loadMetadata($pageDir);
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-11 23:36:53 +01:00
|
|
|
// Load page-level plugins
|
|
|
|
|
getPluginManager()->loadPagePlugins($pageMetadata);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
$navigation = $ctx->navigation;
|
|
|
|
|
$homeLabel = $ctx->homeLabel;
|
2025-11-01 22:50:02 +01:00
|
|
|
$pageTitle = $pageMetadata['title'] ?? null;
|
2025-11-25 20:19:12 +01:00
|
|
|
$metaDescription = extractMetaDescription($pageDir, $pageMetadata);
|
|
|
|
|
|
2025-11-03 23:04:16 +01:00
|
|
|
// Check for page-specific CSS
|
|
|
|
|
$pageCss = findPageCss($pageDir, $ctx->contentDir);
|
|
|
|
|
$pageCssUrl = $pageCss['url'] ?? null;
|
|
|
|
|
$pageCssHash = $pageCss['hash'] ?? null;
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
// Check for cover image
|
2025-11-04 23:00:53 +01:00
|
|
|
$coverImage = findCoverImage($pageDir);
|
|
|
|
|
$socialImageUrl = null;
|
|
|
|
|
if ($coverImage) {
|
|
|
|
|
$relativePath = str_replace($ctx->contentDir, '', $pageDir);
|
|
|
|
|
$relativePath = trim($relativePath, '/');
|
|
|
|
|
$socialImageUrl = '/' . ($relativePath ? $relativePath . '/' : '') . $coverImage;
|
|
|
|
|
}
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Let plugins add template variables
|
|
|
|
|
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
|
|
|
|
|
'content' => $content,
|
|
|
|
|
'navigation' => $navigation,
|
|
|
|
|
'homeLabel' => $homeLabel,
|
|
|
|
|
'pageTitle' => $pageTitle,
|
|
|
|
|
'metaDescription' => $metaDescription,
|
|
|
|
|
'pageCssUrl' => $pageCssUrl,
|
|
|
|
|
'pageCssHash' => $pageCssHash,
|
|
|
|
|
'socialImageUrl' => $socialImageUrl
|
|
|
|
|
], $ctx);
|
2025-11-30 22:26:37 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
extract($templateVars);
|
|
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
// Wrap content with page template
|
|
|
|
|
ob_start();
|
2025-11-25 20:19:12 +01:00
|
|
|
require $ctx->templates->page;
|
|
|
|
|
$wrappedContent = ob_get_clean();
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
include $ctx->templates->base;
|
2025-11-01 22:50:02 +01:00
|
|
|
exit;
|
|
|
|
|
}
|