get('langPrefix', '') : ''; $cached = getCachedMarkdown($filePath, $langPrefix); if ($cached !== null) { echo $cached; } else { // Update to newer versions before PHP 9.0 release // Current versions have been patched for PHP 8.4+ compatibility if (!class_exists('Parsedown')) { require_once __DIR__ . '/vendor/Parsedown.php'; } if (!class_exists('ParsedownExtra')) { require_once __DIR__ . '/vendor/ParsedownExtra.php'; } $html = '
' . (new ParsedownExtra())->text(file_get_contents($filePath)) . '
'; // Add language prefix to internal links if ($langPrefix) { $html = preg_replace( '/href="(\/[^"]*)"/', 'href="' . $langPrefix . '$1"', $html ); } setCachedMarkdown($filePath, $html, $langPrefix); echo $html; } } elseif (in_array($ext, ['html', 'php'])) { // Make template variables available to PHP content files if ($ctx !== null && $ext === 'php') { $templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [], $ctx); extract($templateVars); } include $filePath; } return ob_get_clean(); } function renderTemplate(Context $ctx, string $content, int $statusCode = 200): void { global $GLOBALS; // Get basic template vars $navigation = $ctx->navigation; $homeLabel = $ctx->homeLabel; $pageTitle = null; // Let plugins add template variables $templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [ 'content' => $content, 'navigation' => $navigation, 'homeLabel' => $homeLabel, 'pageTitle' => $pageTitle ], $ctx); extract($templateVars); http_response_code($statusCode); include $ctx->templates->base; exit; } function renderFile(Context $ctx, string $filePath): void { $realPath = realpath($filePath); if (!$realPath || !str_starts_with($realPath, $ctx->contentDir) || !is_readable($realPath)) { renderTemplate($ctx, "

403 Forbidden

Access denied.

", 403); } $ext = pathinfo($realPath, PATHINFO_EXTENSION); if (in_array($ext, CONTENT_EXTENSIONS)) { $content = renderContentFile($realPath, $ctx); $pageDir = dirname($realPath); $pageMetadata = loadMetadata($pageDir); // Load page-level plugins getPluginManager()->loadPagePlugins($pageMetadata); $navigation = $ctx->navigation; $homeLabel = $ctx->homeLabel; $pageTitle = $pageMetadata['title'] ?? null; $metaDescription = extractMetaDescription($pageDir, $pageMetadata); // Check for page-specific CSS $pageCss = findPageCss($pageDir, $ctx->contentDir); $pageCssUrl = $pageCss['url'] ?? null; $pageCssHash = $pageCss['hash'] ?? null; // 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; } // 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); extract($templateVars); // Wrap content with page template ob_start(); require $ctx->templates->page; $wrappedContent = ob_get_clean(); include $ctx->templates->base; exit; } // Unknown type - 404 renderTemplate($ctx, "

404 - Not Found

The requested file could not be found.

", 404); } function renderMultipleFiles(Context $ctx, array $files, string $pageDir): void { $content = ''; foreach ($files as $file) { $content .= renderContentFile($file, $ctx); } $pageMetadata = loadMetadata($pageDir); // Load page-level plugins getPluginManager()->loadPagePlugins($pageMetadata); $navigation = $ctx->navigation; $homeLabel = $ctx->homeLabel; $pageTitle = $pageMetadata['title'] ?? null; $metaDescription = extractMetaDescription($pageDir, $pageMetadata); // Check for page-specific CSS $pageCss = findPageCss($pageDir, $ctx->contentDir); $pageCssUrl = $pageCss['url'] ?? null; $pageCssHash = $pageCss['hash'] ?? null; // Check for cover image $coverImage = findCoverImage($pageDir); $socialImageUrl = null; if ($coverImage) { $relativePath = str_replace($ctx->contentDir, '', $pageDir); $relativePath = trim($relativePath, '/'); $socialImageUrl = '/' . ($relativePath ? $relativePath . '/' : '') . $coverImage; } // 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); extract($templateVars); // Wrap content with page template ob_start(); require $ctx->templates->page; $wrappedContent = ob_get_clean(); include $ctx->templates->base; exit; }