2025-11-01 22:50:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Find all content files in a directory
|
|
|
|
|
function findAllContentFiles(string $dir): array {
|
2025-11-01 22:50:02 +01:00
|
|
|
if (!is_dir($dir)) return [];
|
|
|
|
|
|
|
|
|
|
$files = scandir($dir) ?: [];
|
|
|
|
|
$contentFiles = [];
|
|
|
|
|
|
|
|
|
|
foreach ($files as $file) {
|
2025-11-01 22:54:42 +01:00
|
|
|
if ($file === '.' || $file === '..' || $file === 'index.php') continue;
|
2025-11-01 22:50:02 +01:00
|
|
|
|
|
|
|
|
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
2025-11-01 22:54:42 +01:00
|
|
|
if (!in_array($ext, CONTENT_EXTENSIONS)) continue;
|
2025-11-01 22:50:02 +01:00
|
|
|
|
|
|
|
|
$filePath = "$dir/$file";
|
|
|
|
|
if (!is_file($filePath)) continue;
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
$contentFiles[] = [
|
|
|
|
|
'path' => $filePath,
|
|
|
|
|
'name' => $file,
|
|
|
|
|
'ext' => $ext
|
|
|
|
|
];
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
// Let plugins filter content files (e.g., by language)
|
|
|
|
|
$contentFiles = Hooks::apply(Hook::PROCESS_CONTENT, $contentFiles, $dir);
|
|
|
|
|
|
|
|
|
|
// Sort by filename
|
|
|
|
|
usort($contentFiles, fn($a, $b) => strnatcmp($a['name'], $b['name']));
|
2025-11-01 22:50:02 +01:00
|
|
|
|
|
|
|
|
return array_column($contentFiles, 'path');
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 23:16:34 +01:00
|
|
|
function resolveSlugToFolder(string $parentDir, string $slug): ?string {
|
|
|
|
|
if (!is_dir($parentDir)) return null;
|
|
|
|
|
|
|
|
|
|
$items = scandir($parentDir) ?: [];
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
if ($item === '.' || $item === '..' || !is_dir("$parentDir/$item")) continue;
|
|
|
|
|
|
|
|
|
|
// Check if folder name matches slug
|
|
|
|
|
if ($item === $slug) {
|
|
|
|
|
return $item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check metadata for custom slug
|
|
|
|
|
$metadata = loadMetadata("$parentDir/$item");
|
|
|
|
|
if ($metadata && isset($metadata['slug']) && $metadata['slug'] === $slug) {
|
|
|
|
|
return $item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
function parseRequestPath(Context $ctx): array {
|
2025-11-25 20:19:12 +01:00
|
|
|
$requestPath = $ctx->requestPath;
|
|
|
|
|
|
|
|
|
|
if (empty($requestPath)) {
|
|
|
|
|
return ['type' => 'frontpage', 'path' => $ctx->contentDir];
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-25 23:16:34 +01:00
|
|
|
// Try resolving slug to actual folder path
|
|
|
|
|
$pathParts = explode('/', trim($requestPath, '/'));
|
|
|
|
|
$resolvedPath = $ctx->contentDir;
|
|
|
|
|
|
|
|
|
|
foreach ($pathParts as $part) {
|
|
|
|
|
$resolved = resolveSlugToFolder($resolvedPath, $part);
|
|
|
|
|
if ($resolved === null) {
|
|
|
|
|
// Slug not found, return not_found
|
|
|
|
|
return ['type' => 'not_found', 'path' => $ctx->contentDir . '/' . $requestPath];
|
|
|
|
|
}
|
|
|
|
|
$resolvedPath .= '/' . $resolved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$contentPath = $resolvedPath;
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
// Check if it's a directory
|
2025-11-01 22:50:02 +01:00
|
|
|
if (is_dir($contentPath)) {
|
2025-11-25 20:19:12 +01:00
|
|
|
$items = scandir($contentPath) ?: [];
|
|
|
|
|
$subdirs = array_filter($items, fn($item) =>
|
|
|
|
|
$item !== '.' && $item !== '..' && is_dir("$contentPath/$item")
|
|
|
|
|
);
|
2025-11-01 22:50:02 +01:00
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
if (!empty($subdirs)) {
|
|
|
|
|
return ['type' => 'list', 'path' => $contentPath];
|
|
|
|
|
} else {
|
|
|
|
|
return ['type' => 'page', 'path' => $contentPath];
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
return ['type' => 'not_found', 'path' => $contentPath];
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-25 20:19:12 +01:00
|
|
|
function loadMetadata(string $dirPath): ?array {
|
2025-11-01 22:50:02 +01:00
|
|
|
$metadataFile = "$dirPath/metadata.ini";
|
|
|
|
|
if (!file_exists($metadataFile)) return null;
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
$metadata = parse_ini_file($metadataFile, true);
|
|
|
|
|
if (!$metadata) return null;
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
// Get base metadata (non-array values)
|
2025-11-01 22:50:02 +01:00
|
|
|
$baseMetadata = array_filter($metadata, fn($key) => !is_array($metadata[$key]), ARRAY_FILTER_USE_KEY);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
// Store full metadata for plugins to access
|
|
|
|
|
$baseMetadata['_raw'] = $metadata;
|
|
|
|
|
|
|
|
|
|
// Let plugins modify metadata (e.g., merge language sections)
|
|
|
|
|
return Hooks::apply(Hook::PROCESS_CONTENT, $baseMetadata, $dirPath, 'metadata');
|
2025-11-01 22:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
function buildNavigation(Context $ctx): array {
|
2025-11-25 20:19:12 +01:00
|
|
|
$items = scandir($ctx->contentDir) ?: [];
|
2025-11-01 22:50:02 +01:00
|
|
|
$navItems = [];
|
2025-11-25 23:16:34 +01:00
|
|
|
$langPrefix = $ctx->get('langPrefix', '');
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
foreach ($items as $item) {
|
2025-11-25 20:19:12 +01:00
|
|
|
if ($item === '.' || $item === '..' || !is_dir($ctx->contentDir . "/$item")) continue;
|
|
|
|
|
|
2025-11-01 23:33:09 +01:00
|
|
|
$itemPath = "{$ctx->contentDir}/$item";
|
2025-11-25 20:19:12 +01:00
|
|
|
$metadata = loadMetadata($itemPath);
|
|
|
|
|
|
|
|
|
|
// Only include if explicitly marked as menu item
|
|
|
|
|
// parse_ini_file returns boolean true as 1, false as empty string, and "true"/"false" as strings
|
|
|
|
|
if (!$metadata || !isset($metadata['menu']) || !$metadata['menu']) {
|
2025-11-01 22:50:02 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
2025-11-25 20:19:12 +01:00
|
|
|
|
|
|
|
|
// Extract title
|
|
|
|
|
$title = $metadata['title'] ?? extractTitle($itemPath) ?? ucfirst($item);
|
|
|
|
|
|
|
|
|
|
// Use slug if available, otherwise use folder name
|
|
|
|
|
$urlSlug = ($metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item;
|
|
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
$navItems[] = [
|
|
|
|
|
'title' => $title,
|
2025-11-25 23:16:34 +01:00
|
|
|
'url' => $langPrefix . '/' . urlencode($urlSlug) . '/',
|
2025-11-01 22:50:02 +01:00
|
|
|
'order' => (int)($metadata['menu_order'] ?? 999)
|
|
|
|
|
];
|
|
|
|
|
}
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
// Sort by menu_order
|
|
|
|
|
usort($navItems, fn($a, $b) => $a['order'] <=> $b['order']);
|
2025-11-25 20:19:12 +01:00
|
|
|
|
2025-11-01 22:50:02 +01:00
|
|
|
return $navItems;
|
|
|
|
|
}
|