208 lines
7.2 KiB
PHP
208 lines
7.2 KiB
PHP
<?php
|
|
|
|
// Find all content files in a directory (supporting language variants)
|
|
function findAllContentFiles(string $dir, string $lang, string $defaultLang, array $availableLangs): array {
|
|
if (!is_dir($dir)) return [];
|
|
|
|
$files = scandir($dir) ?: [];
|
|
$contentFiles = [];
|
|
|
|
foreach ($files as $file) {
|
|
if ($file === '.' || $file === '..' || $file === 'index.php') continue;
|
|
|
|
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
|
if (!in_array($ext, CONTENT_EXTENSIONS)) continue;
|
|
|
|
$filePath = "$dir/$file";
|
|
if (!is_file($filePath)) continue;
|
|
|
|
// Parse filename to check for language variant
|
|
$parts = explode('.', $file);
|
|
|
|
// Check if this is a language-specific file
|
|
if (count($parts) >= 3) {
|
|
// Pattern: name.lang.ext
|
|
$fileLang = $parts[count($parts) - 2];
|
|
if (in_array($fileLang, $availableLangs)) {
|
|
// Only include if it matches current language
|
|
if ($fileLang === $lang) {
|
|
$contentFiles[] = [
|
|
'path' => $filePath,
|
|
'name' => $file,
|
|
'sort_key' => $parts[0]
|
|
];
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Default files (no language suffix) - include if no language-specific version exists
|
|
$baseName = $parts[0];
|
|
$hasLangVersion = false;
|
|
|
|
if ($lang !== $defaultLang) {
|
|
// Check if language-specific version exists
|
|
foreach (CONTENT_EXTENSIONS as $checkExt) {
|
|
if (file_exists("$dir/$baseName.$lang.$checkExt")) {
|
|
$hasLangVersion = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$hasLangVersion) {
|
|
$contentFiles[] = [
|
|
'path' => $filePath,
|
|
'name' => $file,
|
|
'sort_key' => $baseName
|
|
];
|
|
}
|
|
}
|
|
|
|
// Sort by filename (alphanumerical)
|
|
usort($contentFiles, fn($a, $b) => strnatcmp($a['sort_key'], $b['sort_key']));
|
|
|
|
return array_column($contentFiles, 'path');
|
|
}
|
|
|
|
function resolveTranslatedPath(Context $ctx, string $requestPath): string {
|
|
// If default language, no translation needed
|
|
if ($ctx->currentLang === $ctx->defaultLang) {
|
|
return $requestPath;
|
|
}
|
|
|
|
$parts = explode('/', trim($requestPath, '/'));
|
|
$resolvedParts = [];
|
|
$currentPath = $ctx->contentDir;
|
|
|
|
foreach ($parts as $segment) {
|
|
if (empty($segment)) continue;
|
|
|
|
// Check all subdirectories for slug matches
|
|
$found = false;
|
|
if (is_dir($currentPath)) {
|
|
$subdirs = getSubdirectories($currentPath);
|
|
|
|
foreach ($subdirs as $dir) {
|
|
$metadata = loadMetadata("$currentPath/$dir", $ctx->currentLang, $ctx->defaultLang);
|
|
if ($metadata && isset($metadata['slug']) && $metadata['slug'] === $segment) {
|
|
$resolvedParts[] = $dir;
|
|
$currentPath .= "/$dir";
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no slug match, use segment as-is
|
|
if (!$found) {
|
|
$resolvedParts[] = $segment;
|
|
$currentPath .= "/$segment";
|
|
}
|
|
}
|
|
|
|
return implode('/', $resolvedParts);
|
|
}
|
|
|
|
function parseRequestPath(Context $ctx): array {
|
|
// Resolve translated slugs to actual directory names
|
|
$resolvedPath = resolveTranslatedPath($ctx, $ctx->requestPath);
|
|
$contentPath = rtrim($ctx->contentDir, '/') . '/' . ltrim($resolvedPath, '/');
|
|
|
|
if (is_file($contentPath)) {
|
|
return ['type' => 'file', 'path' => realpath($contentPath)];
|
|
}
|
|
|
|
if (is_dir($contentPath)) {
|
|
// Check if directory has subdirectories (PHP 8.4: cleaner with array_any later)
|
|
$hasSubdirs = !empty(getSubdirectories($contentPath));
|
|
|
|
// If directory has subdirectories, it's an article-type folder (list view)
|
|
if ($hasSubdirs) {
|
|
return ['type' => 'directory', 'path' => realpath($contentPath)];
|
|
}
|
|
|
|
// No subdirectories - it's a page-type folder
|
|
// Find all content files in this directory
|
|
$contentFiles = findAllContentFiles($contentPath, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
|
|
|
if (!empty($contentFiles)) {
|
|
return ['type' => 'page', 'path' => realpath($contentPath), 'files' => $contentFiles, 'needsSlash' => !$ctx->hasTrailingSlash];
|
|
}
|
|
|
|
// No content files found
|
|
return ['type' => 'directory', 'path' => realpath($contentPath)];
|
|
}
|
|
|
|
return ['type' => 'not_found', 'path' => $contentPath];
|
|
}
|
|
|
|
function loadMetadata(string $dirPath, string $lang, string $defaultLang): ?array {
|
|
$metadataFile = "$dirPath/metadata.ini";
|
|
if (!file_exists($metadataFile)) return null;
|
|
|
|
$metadata = parse_ini_file($metadataFile, true);
|
|
if (!$metadata) return null;
|
|
|
|
// Extract base metadata (non-section values)
|
|
$baseMetadata = array_filter($metadata, fn($key) => !is_array($metadata[$key]), ARRAY_FILTER_USE_KEY);
|
|
|
|
// If current language is not default, merge language-specific overrides
|
|
if ($lang !== $defaultLang && isset($metadata[$lang]) && is_array($metadata[$lang])) {
|
|
$baseMetadata = array_merge($baseMetadata, $metadata[$lang]);
|
|
}
|
|
|
|
return $baseMetadata ?: null;
|
|
}
|
|
|
|
function loadTranslations(string $lang): array {
|
|
$translationFile = dirname(__DIR__) . "/custom/languages/$lang.ini";
|
|
if (file_exists($translationFile)) {
|
|
return parse_ini_file($translationFile) ?: [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function buildNavigation(Context $ctx): array {
|
|
$navItems = [];
|
|
$items = getSubdirectories($ctx->contentDir);
|
|
|
|
foreach ($items as $item) {
|
|
$itemPath = "{$ctx->contentDir}/$item";
|
|
$metadata = loadMetadata($itemPath, $ctx->currentLang, $ctx->defaultLang);
|
|
|
|
// Check if this item should be in menu
|
|
if (!$metadata || empty($metadata['menu'])) {
|
|
continue;
|
|
}
|
|
|
|
// Check if content exists for current language
|
|
if ($ctx->currentLang !== $ctx->defaultLang) {
|
|
$contentFiles = findAllContentFiles($itemPath, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
|
|
|
// If no content files, check if metadata has title for this language
|
|
$hasContent = !empty($contentFiles) || ($metadata && isset($metadata['title']));
|
|
|
|
if (!$hasContent) continue;
|
|
}
|
|
|
|
// Extract title and build URL
|
|
$title = $metadata['title'] ?? extractTitle($itemPath, $ctx->currentLang, $ctx->defaultLang) ?? ucfirst($item);
|
|
|
|
// Use translated slug if available
|
|
$urlSlug = ($ctx->currentLang !== $ctx->defaultLang && $metadata && isset($metadata['slug']))
|
|
? $metadata['slug']
|
|
: $item;
|
|
|
|
$navItems[] = [
|
|
'title' => $title,
|
|
'url' => $ctx->langPrefix . '/' . urlencode($urlSlug) . '/',
|
|
'order' => (int)($metadata['menu_order'] ?? 999)
|
|
];
|
|
}
|
|
|
|
// Sort by menu_order
|
|
usort($navItems, fn($a, $b) => $a['order'] <=> $b['order']);
|
|
|
|
return $navItems;
|
|
}
|