Add support for page-specific JavaScript files with cache busting via MD5 hash. The script is loaded at the end of the body with defer attribute. The JavaScript file must be named script.js and located in the same directory as the page content.
261 lines
9.6 KiB
PHP
261 lines
9.6 KiB
PHP
<?php
|
|
|
|
// Load modular components
|
|
require_once __DIR__ . '/constants.php';
|
|
require_once __DIR__ . '/hooks.php';
|
|
require_once __DIR__ . '/context.php';
|
|
require_once __DIR__ . '/helpers.php';
|
|
require_once __DIR__ . '/plugins.php';
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/content.php';
|
|
require_once __DIR__ . '/rendering.php';
|
|
|
|
// Create context
|
|
$ctx = createContext();
|
|
|
|
// Store globally for easy access
|
|
$GLOBALS['ctx'] = $ctx;
|
|
|
|
// Check for assets in /custom/assets/ served at root level
|
|
$assetPath = dirname(__DIR__) . '/custom/assets/' . $ctx->requestPath;
|
|
if (file_exists($assetPath) && is_file($assetPath)) {
|
|
header('Content-Type: ' . (mime_content_type($assetPath) ?: 'application/octet-stream'));
|
|
readfile($assetPath);
|
|
exit;
|
|
}
|
|
|
|
// Check for assets in content directory (CSS, images, etc.)
|
|
$contentAssetPath = $ctx->contentDir . '/' . $ctx->requestPath;
|
|
if (file_exists($contentAssetPath) && is_file($contentAssetPath)) {
|
|
$ext = pathinfo($contentAssetPath, PATHINFO_EXTENSION);
|
|
// Define MIME types for asset files
|
|
$mimeTypes = [
|
|
'css' => 'text/css',
|
|
'jpg' => 'image/jpeg',
|
|
'jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
'svg' => 'image/svg+xml',
|
|
'pdf' => 'application/pdf',
|
|
'woff' => 'font/woff',
|
|
'woff2' => 'font/woff2',
|
|
'ttf' => 'font/ttf',
|
|
'otf' => 'font/otf',
|
|
'js' => 'application/javascript',
|
|
];
|
|
|
|
$extLower = strtolower($ext);
|
|
if (isset($mimeTypes[$extLower])) {
|
|
header('Content-Type: ' . $mimeTypes[$extLower]);
|
|
readfile($contentAssetPath);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// 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 '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
|
echo '<feed xmlns="http://www.w3.org/2005/Atom">' . "\n";
|
|
echo ' <title>' . htmlspecialchars($feedTitle) . '</title>' . "\n";
|
|
echo ' <link href="' . htmlspecialchars($listUrl) . '" rel="alternate"/>' . "\n";
|
|
echo ' <link href="' . htmlspecialchars($feedUrl) . '" rel="self"/>' . "\n";
|
|
echo ' <id>' . htmlspecialchars($listUrl) . '</id>' . "\n";
|
|
echo ' <updated>' . $updated . 'T00:00:00Z</updated>' . "\n";
|
|
|
|
foreach ($items as $item) {
|
|
$absoluteUrl = $baseUrl . $item['url'];
|
|
$itemDate = ($item['rawDate'] ?? date('Y-m-d')) . 'T00:00:00Z';
|
|
|
|
echo ' <entry>' . "\n";
|
|
echo ' <title>' . htmlspecialchars($item['title']) . '</title>' . "\n";
|
|
echo ' <link href="' . htmlspecialchars($absoluteUrl) . '" rel="alternate"/>' . "\n";
|
|
echo ' <id>' . htmlspecialchars($absoluteUrl) . '</id>' . "\n";
|
|
echo ' <updated>' . $itemDate . '</updated>' . "\n";
|
|
if ($item['summary']) {
|
|
echo ' <summary>' . htmlspecialchars($item['summary']) . '</summary>' . "\n";
|
|
}
|
|
if ($item['content']) {
|
|
$safeContent = str_replace(']]>', ']]]]><![CDATA[>', $item['content']);
|
|
echo ' <content type="html"><![CDATA[' . $safeContent . ']]></content>' . "\n";
|
|
}
|
|
echo ' </entry>' . "\n";
|
|
}
|
|
|
|
echo '</feed>' . "\n";
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle frontpage
|
|
if (empty($ctx->requestPath)) {
|
|
$contentFiles = findAllContentFiles($ctx->contentDir);
|
|
if (!empty($contentFiles)) {
|
|
renderMultipleFiles($ctx, $contentFiles, $ctx->contentDir);
|
|
}
|
|
}
|
|
|
|
// Parse and handle request
|
|
$parsedPath = parseRequestPath($ctx);
|
|
|
|
switch ($parsedPath['type']) {
|
|
case 'page':
|
|
$dir = $parsedPath['path'];
|
|
|
|
// Redirect to add trailing slash if needed
|
|
if (!$ctx->hasTrailingSlash) {
|
|
header('Location: ' . rtrim($_SERVER['REQUEST_URI'], '/') . '/', true, 301);
|
|
exit;
|
|
}
|
|
|
|
$contentFiles = findAllContentFiles($dir);
|
|
if (!empty($contentFiles)) {
|
|
renderMultipleFiles($ctx, $contentFiles, $dir);
|
|
}
|
|
break;
|
|
|
|
case 'list':
|
|
$dir = $parsedPath['path'];
|
|
|
|
// Redirect to add trailing slash if needed
|
|
if (!$ctx->hasTrailingSlash) {
|
|
header('Location: ' . rtrim($_SERVER['REQUEST_URI'], '/') . '/', true, 301);
|
|
exit;
|
|
}
|
|
|
|
// Check for page content files in this directory
|
|
$pageContent = null;
|
|
$contentFiles = findAllContentFiles($dir);
|
|
if (!empty($contentFiles)) {
|
|
$pageContent = implode('', array_map('renderContentFile', $contentFiles));
|
|
}
|
|
|
|
// Load metadata for this directory
|
|
$metadata = loadMetadata($dir);
|
|
|
|
// Check if hide_list is enabled - if so, treat as page
|
|
if (isset($metadata['hide_list']) && $metadata['hide_list']) {
|
|
if (!empty($contentFiles)) {
|
|
renderMultipleFiles($ctx, $contentFiles, $dir);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Select list template based on metadata page_template
|
|
$listTemplate = $ctx->templates->list;
|
|
if (isset($metadata['page_template']) && !empty($metadata['page_template'])) {
|
|
$templateName = $metadata['page_template'];
|
|
$customTemplate = dirname(__DIR__) . "/custom/templates/$templateName.php";
|
|
$defaultTemplate = __DIR__ . "/default/templates/$templateName.php";
|
|
|
|
if (file_exists($customTemplate)) {
|
|
$listTemplate = $customTemplate;
|
|
} elseif (file_exists($defaultTemplate)) {
|
|
$listTemplate = $defaultTemplate;
|
|
}
|
|
}
|
|
|
|
// Build list items
|
|
$items = buildListItems($dir, $ctx, $metadata);
|
|
|
|
// Prepare all variables for base template
|
|
$navigation = $ctx->navigation;
|
|
$homeLabel = $ctx->homeLabel;
|
|
$pageTitle = $metadata['title'] ?? null;
|
|
$metaDescription = extractMetaDescription($dir, $metadata);
|
|
|
|
// Check for page-specific CSS and JS
|
|
$pageCss = findPageCss($dir, $ctx->contentDir);
|
|
$pageCssUrl = $pageCss['url'] ?? null;
|
|
$pageCssHash = $pageCss['hash'] ?? null;
|
|
|
|
$pageJs = findPageJs($dir, $ctx->contentDir);
|
|
$pageJsUrl = $pageJs['url'] ?? null;
|
|
$pageJsHash = $pageJs['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('pageJsUrl', $pageJsUrl);
|
|
$ctx->set('pageJsHash', $pageJsHash);
|
|
$ctx->set('feedUrl', $feedUrl);
|
|
|
|
// Let plugins add template variables
|
|
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
|
|
'navigation' => $navigation,
|
|
'homeLabel' => $homeLabel,
|
|
'pageTitle' => $pageTitle,
|
|
'metaDescription' => $metaDescription,
|
|
'pageCssUrl' => $pageCssUrl,
|
|
'pageCssHash' => $pageCssHash,
|
|
'pageJsUrl' => $pageJsUrl,
|
|
'pageJsHash' => $pageJsHash,
|
|
'items' => $items,
|
|
'pageContent' => $pageContent,
|
|
'feedUrl' => $feedUrl
|
|
], $ctx);
|
|
|
|
extract($templateVars);
|
|
|
|
ob_start();
|
|
require $listTemplate;
|
|
$content = ob_get_clean();
|
|
|
|
renderTemplate($ctx, $content);
|
|
break;
|
|
|
|
case 'not_found':
|
|
http_response_code(404);
|
|
renderTemplate($ctx, "<h1>404 - Page Not Found</h1><p>The requested page could not be found.</p>", 404);
|
|
break;
|
|
}
|