Update PHP version to 8.4 and add property hooks
This commit is contained in:
parent
32449d2edd
commit
673c02d237
14 changed files with 939 additions and 606 deletions
|
|
@ -13,7 +13,7 @@
|
|||
- Sparse commenting—only mark main sections
|
||||
|
||||
### Technology Stack
|
||||
- **Allowed:** HTML, PHP (8.3+), CSS
|
||||
- **Allowed:** HTML, PHP (8.4+), CSS
|
||||
- **Not allowed:** JavaScript
|
||||
- Use modern PHP features when they improve readability or performance
|
||||
- Leverage modern CSS features for smart, efficient styling
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# FolderWeb
|
||||
|
||||
A minimal, file-based PHP framework for publishing content that will work for decades. **Just enough, nothing more.** FolderWeb applies minimal PHP to enable modern conveniences while remaining maintainable for years or decades. No frameworks, no build tools, no JavaScript—just HTML, PHP 8.3+, and CSS. This is not a CMS with an admin panel, not a single-page application.
|
||||
A minimal, file-based PHP framework for publishing content that will work for decades. **Just enough, nothing more.** FolderWeb applies minimal PHP to enable modern conveniences while remaining maintainable for years or decades. No frameworks, no build tools, no JavaScript—just HTML, PHP 8.4+, and CSS. This is not a CMS with an admin panel, not a single-page application.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP 8.3 or higher
|
||||
- **PHP 8.4 or higher** (uses property hooks, readonly classes, and modern array functions)
|
||||
- A web server (Apache, Nginx, or PHP's built-in server)
|
||||
|
||||
### Quick Start
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
function createContext(): Context {
|
||||
// Load configuration
|
||||
$configFile = file_exists(__DIR__ . '/../custom/config.ini')
|
||||
? __DIR__ . '/../custom/config.ini'
|
||||
|
|
@ -33,6 +34,19 @@ if (!empty($pathParts[0]) && in_array($pathParts[0], $availableLangs) && $pathPa
|
|||
}
|
||||
|
||||
// Resolve templates with custom fallback to defaults
|
||||
$baseTemplate = resolveTemplate('base');
|
||||
$pageTemplate = resolveTemplate('page');
|
||||
$listTemplate = resolveTemplate('list');
|
||||
$templates = new Templates(
|
||||
base: resolveTemplate('base'),
|
||||
page: resolveTemplate('page'),
|
||||
list: resolveTemplate('list')
|
||||
);
|
||||
|
||||
return new Context(
|
||||
contentDir: $contentDir,
|
||||
currentLang: $currentLang,
|
||||
defaultLang: $defaultLang,
|
||||
availableLangs: $availableLangs,
|
||||
templates: $templates,
|
||||
requestPath: $requestPath,
|
||||
hasTrailingSlash: $hasTrailingSlash
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
// Find all content files in a directory (supporting language variants)
|
||||
function findAllContentFiles(string $dir, string $lang, string $defaultLang): array {
|
||||
global $availableLangs;
|
||||
|
||||
function findAllContentFiles(string $dir, string $lang, string $defaultLang, array $availableLangs): array {
|
||||
if (!is_dir($dir)) return [];
|
||||
|
||||
$files = scandir($dir) ?: [];
|
||||
|
|
@ -67,15 +65,15 @@ function findAllContentFiles(string $dir, string $lang, string $defaultLang): ar
|
|||
return array_column($contentFiles, 'path');
|
||||
}
|
||||
|
||||
function resolveTranslatedPath(string $requestPath, string $contentDir, string $lang, string $defaultLang): string {
|
||||
function resolveTranslatedPath(Context $ctx, string $requestPath): string {
|
||||
// If default language, no translation needed
|
||||
if ($lang === $defaultLang) {
|
||||
if ($ctx->currentLang === $ctx->defaultLang) {
|
||||
return $requestPath;
|
||||
}
|
||||
|
||||
$parts = explode('/', trim($requestPath, '/'));
|
||||
$resolvedParts = [];
|
||||
$currentPath = $contentDir;
|
||||
$currentPath = $ctx->contentDir;
|
||||
|
||||
foreach ($parts as $segment) {
|
||||
if (empty($segment)) continue;
|
||||
|
|
@ -86,7 +84,7 @@ function resolveTranslatedPath(string $requestPath, string $contentDir, string $
|
|||
$subdirs = getSubdirectories($currentPath);
|
||||
|
||||
foreach ($subdirs as $dir) {
|
||||
$metadata = loadMetadata("$currentPath/$dir", $lang, $defaultLang);
|
||||
$metadata = loadMetadata("$currentPath/$dir", $ctx->currentLang, $ctx->defaultLang);
|
||||
if ($metadata && isset($metadata['slug']) && $metadata['slug'] === $segment) {
|
||||
$resolvedParts[] = $dir;
|
||||
$currentPath .= "/$dir";
|
||||
|
|
@ -106,17 +104,17 @@ function resolveTranslatedPath(string $requestPath, string $contentDir, string $
|
|||
return implode('/', $resolvedParts);
|
||||
}
|
||||
|
||||
function parseRequestPath(string $requestPath, string $contentDir, bool $hasTrailingSlash, string $lang, string $defaultLang): array {
|
||||
function parseRequestPath(Context $ctx): array {
|
||||
// Resolve translated slugs to actual directory names
|
||||
$resolvedPath = resolveTranslatedPath($requestPath, $contentDir, $lang, $defaultLang);
|
||||
$contentPath = rtrim($contentDir, '/') . '/' . ltrim($resolvedPath, '/');
|
||||
$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
|
||||
// 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)
|
||||
|
|
@ -126,10 +124,10 @@ function parseRequestPath(string $requestPath, string $contentDir, bool $hasTrai
|
|||
|
||||
// No subdirectories - it's a page-type folder
|
||||
// Find all content files in this directory
|
||||
$contentFiles = findAllContentFiles($contentPath, $lang, $defaultLang);
|
||||
$contentFiles = findAllContentFiles($contentPath, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
||||
|
||||
if (!empty($contentFiles)) {
|
||||
return ['type' => 'page', 'path' => realpath($contentPath), 'files' => $contentFiles, 'needsSlash' => !$hasTrailingSlash];
|
||||
return ['type' => 'page', 'path' => realpath($contentPath), 'files' => $contentFiles, 'needsSlash' => !$ctx->hasTrailingSlash];
|
||||
}
|
||||
|
||||
// No content files found
|
||||
|
|
@ -165,13 +163,13 @@ function loadTranslations(string $lang): array {
|
|||
return [];
|
||||
}
|
||||
|
||||
function buildNavigation(string $contentDir, string $currentLang, string $defaultLang): array {
|
||||
function buildNavigation(Context $ctx): array {
|
||||
$navItems = [];
|
||||
$items = getSubdirectories($contentDir);
|
||||
$items = getSubdirectories($ctx->contentDir);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$itemPath = "$contentDir/$item";
|
||||
$metadata = loadMetadata($itemPath, $currentLang, $defaultLang);
|
||||
$itemPath = "{$ctx->contentDir}/$item";
|
||||
$metadata = loadMetadata($itemPath, $ctx->currentLang, $ctx->defaultLang);
|
||||
|
||||
// Check if this item should be in menu
|
||||
if (!$metadata || empty($metadata['menu'])) {
|
||||
|
|
@ -179,8 +177,8 @@ function buildNavigation(string $contentDir, string $currentLang, string $defaul
|
|||
}
|
||||
|
||||
// Check if content exists for current language
|
||||
if ($currentLang !== $defaultLang) {
|
||||
$contentFiles = findAllContentFiles($itemPath, $currentLang, $defaultLang);
|
||||
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']));
|
||||
|
|
@ -189,17 +187,16 @@ function buildNavigation(string $contentDir, string $currentLang, string $defaul
|
|||
}
|
||||
|
||||
// Extract title and build URL
|
||||
$title = $metadata['title'] ?? extractTitle($itemPath, $currentLang, $defaultLang) ?? ucfirst($item);
|
||||
$langPrefix = getLangPrefix($currentLang, $defaultLang);
|
||||
$title = $metadata['title'] ?? extractTitle($itemPath, $ctx->currentLang, $ctx->defaultLang) ?? ucfirst($item);
|
||||
|
||||
// Use translated slug if available
|
||||
$urlSlug = ($currentLang !== $defaultLang && $metadata && isset($metadata['slug']))
|
||||
$urlSlug = ($ctx->currentLang !== $ctx->defaultLang && $metadata && isset($metadata['slug']))
|
||||
? $metadata['slug']
|
||||
: $item;
|
||||
|
||||
$navItems[] = [
|
||||
'title' => $title,
|
||||
'url' => $langPrefix . '/' . urlencode($urlSlug) . '/',
|
||||
'url' => $ctx->langPrefix . '/' . urlencode($urlSlug) . '/',
|
||||
'order' => (int)($metadata['menu_order'] ?? 999)
|
||||
];
|
||||
}
|
||||
|
|
|
|||
41
app/context.php
Normal file
41
app/context.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
readonly class Templates {
|
||||
public function __construct(
|
||||
public string $base,
|
||||
public string $page,
|
||||
public string $list
|
||||
) {}
|
||||
}
|
||||
|
||||
class Context {
|
||||
// Use asymmetric visibility for immutability (PHP 8.4)
|
||||
public function __construct(
|
||||
public private(set) string $contentDir,
|
||||
public private(set) string $currentLang,
|
||||
public private(set) string $defaultLang,
|
||||
public private(set) array $availableLangs,
|
||||
public private(set) Templates $templates,
|
||||
public private(set) string $requestPath,
|
||||
public private(set) bool $hasTrailingSlash
|
||||
) {}
|
||||
|
||||
// Property hooks - computed properties (PHP 8.4)
|
||||
public string $langPrefix {
|
||||
get => $this->currentLang !== $this->defaultLang
|
||||
? "/{$this->currentLang}"
|
||||
: '';
|
||||
}
|
||||
|
||||
public array $navigation {
|
||||
get => buildNavigation($this);
|
||||
}
|
||||
|
||||
public string $homeLabel {
|
||||
get => loadMetadata($this->contentDir, $this->currentLang, $this->defaultLang)['slug'] ?? 'Home';
|
||||
}
|
||||
|
||||
public array $translations {
|
||||
get => loadTranslations($this->currentLang);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,5 +11,5 @@
|
|||
<p>Custom templates and styles go in <code>/custom/</code> and automatically override defaults. The core files in <code>/app/default/</code> remain untouched and updateable.</p>
|
||||
|
||||
<h3>Modern Standards</h3>
|
||||
<p>Use modern PHP 8.3+ features and modern CSS capabilities. Avoid JavaScript entirely—it's not needed for content-focused sites.</p>
|
||||
<p>Use modern PHP 8.4+ features (property hooks, readonly classes, modern array functions) and modern CSS capabilities. Avoid JavaScript entirely—it's not needed for content-focused sites.</p>
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<h3>Backend</h3>
|
||||
<ul>
|
||||
<li><strong>PHP 8.3+</strong> - Modern PHP with type hints, arrow functions, match expressions</li>
|
||||
<li><strong>PHP 8.4+</strong> - Modern PHP with property hooks, readonly classes, array_find(), and type safety</li>
|
||||
<li><strong>Apache</strong> - With mod_rewrite for clean URLs</li>
|
||||
<li><strong>Parsedown</strong> - Simple, reliable Markdown parser</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ FolderWeb is designed to be the simplest way to publish content on the web. This
|
|||
|
||||
## Installation
|
||||
|
||||
FolderWeb requires PHP 8.3+ and Apache with `mod_rewrite` enabled.
|
||||
FolderWeb requires PHP 8.4+ and Apache with `mod_rewrite` enabled.
|
||||
|
||||
### Using Docker (Recommended for Development)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
title = "Articles"
|
||||
menu = true
|
||||
menu_order = 1
|
||||
page_template = "list-card-grid"
|
||||
page_template = "list"
|
||||
|
|
|
|||
|
|
@ -14,12 +14,8 @@ function getSubdirectories(string $dir): array {
|
|||
);
|
||||
}
|
||||
|
||||
function getLangPrefix(string $currentLang, string $defaultLang): string {
|
||||
return $currentLang !== $defaultLang ? "/$currentLang" : '';
|
||||
}
|
||||
|
||||
function extractTitle(string $filePath, string $lang, string $defaultLang): ?string {
|
||||
$files = findAllContentFiles($filePath, $lang, $defaultLang);
|
||||
$files = findAllContentFiles($filePath, $lang, $defaultLang, []);
|
||||
if (empty($files)) return null;
|
||||
|
||||
// Check the first content file for a title
|
||||
|
|
@ -55,15 +51,16 @@ function extractDateFromFolder(string $folderName): ?string {
|
|||
}
|
||||
|
||||
function findCoverImage(string $dirPath): ?string {
|
||||
foreach (COVER_IMAGE_EXTENSIONS as $ext) {
|
||||
if (file_exists("$dirPath/cover.$ext")) {
|
||||
return "cover.$ext";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
// PHP 8.4: array_find() - cleaner than foreach
|
||||
$found = array_find(
|
||||
COVER_IMAGE_EXTENSIONS,
|
||||
fn($ext) => file_exists("$dirPath/cover.$ext")
|
||||
);
|
||||
return $found ? "cover.$found" : null;
|
||||
}
|
||||
|
||||
function findPdfFile(string $dirPath): ?string {
|
||||
$pdfs = glob("$dirPath/*.pdf");
|
||||
// PHP 8.4: array_find() with glob
|
||||
$pdfs = glob("$dirPath/*.pdf") ?: [];
|
||||
return $pdfs ? basename($pdfs[0]) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
<?php
|
||||
|
||||
function prepareTemplateContext(): array {
|
||||
global $contentDir, $currentLang, $defaultLang;
|
||||
|
||||
return [
|
||||
'navigation' => buildNavigation($contentDir, $currentLang, $defaultLang),
|
||||
'homeLabel' => loadMetadata($contentDir, $currentLang, $defaultLang)['slug'] ?? 'Home',
|
||||
'translations' => loadTranslations($currentLang)
|
||||
];
|
||||
}
|
||||
|
||||
function renderContentFile(string $filePath): string {
|
||||
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
|
||||
|
|
@ -25,22 +15,23 @@ function renderContentFile(string $filePath): string {
|
|||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function renderTemplate(string $content, int $statusCode = 200): void {
|
||||
global $baseTemplate;
|
||||
|
||||
extract(prepareTemplateContext());
|
||||
function renderTemplate(Context $ctx, string $content, int $statusCode = 200): void {
|
||||
// Extract all necessary variables for base template
|
||||
$currentLang = $ctx->currentLang;
|
||||
$navigation = $ctx->navigation;
|
||||
$homeLabel = $ctx->homeLabel;
|
||||
$translations = $ctx->translations;
|
||||
$pageTitle = null; // No specific page title for error pages
|
||||
|
||||
http_response_code($statusCode);
|
||||
include $baseTemplate;
|
||||
include $ctx->templates->base;
|
||||
exit;
|
||||
}
|
||||
|
||||
function renderFile(string $filePath): void {
|
||||
global $baseTemplate, $pageTemplate, $contentDir, $currentLang, $defaultLang;
|
||||
|
||||
function renderFile(Context $ctx, string $filePath): void {
|
||||
$realPath = realpath($filePath);
|
||||
if (!$realPath || !str_starts_with($realPath, $contentDir) || !is_readable($realPath)) {
|
||||
renderTemplate("<article><h1>403 Forbidden</h1><p>Access denied.</p></article>", 403);
|
||||
if (!$realPath || !str_starts_with($realPath, $ctx->contentDir) || !is_readable($realPath)) {
|
||||
renderTemplate($ctx, "<article><h1>403 Forbidden</h1><p>Access denied.</p></article>", 403);
|
||||
}
|
||||
|
||||
$ext = pathinfo($realPath, PATHINFO_EXTENSION);
|
||||
|
|
@ -48,20 +39,23 @@ function renderFile(string $filePath): void {
|
|||
if (in_array($ext, CONTENT_EXTENSIONS)) {
|
||||
$content = renderContentFile($realPath);
|
||||
|
||||
// Prepare template variables
|
||||
extract(prepareTemplateContext());
|
||||
// Prepare template variables using property hooks
|
||||
$currentLang = $ctx->currentLang;
|
||||
$navigation = $ctx->navigation;
|
||||
$homeLabel = $ctx->homeLabel;
|
||||
$translations = $ctx->translations;
|
||||
|
||||
$pageDir = dirname($realPath);
|
||||
$pageMetadata = loadMetadata($pageDir, $currentLang, $defaultLang);
|
||||
$pageMetadata = loadMetadata($pageDir, $ctx->currentLang, $ctx->defaultLang);
|
||||
$pageTitle = $pageMetadata['title'] ?? null;
|
||||
|
||||
// Wrap content with page template
|
||||
ob_start();
|
||||
include $pageTemplate;
|
||||
include $ctx->templates->page;
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Wrap with base template
|
||||
include $baseTemplate;
|
||||
include $ctx->templates->base;
|
||||
exit;
|
||||
}
|
||||
|
||||
|
|
@ -71,32 +65,33 @@ function renderFile(string $filePath): void {
|
|||
exit;
|
||||
}
|
||||
|
||||
function renderMultipleFiles(array $filePaths, string $pageDir): void {
|
||||
global $baseTemplate, $pageTemplate, $contentDir, $currentLang, $defaultLang;
|
||||
|
||||
function renderMultipleFiles(Context $ctx, array $filePaths, string $pageDir): void {
|
||||
// Validate all files are safe
|
||||
foreach ($filePaths as $filePath) {
|
||||
$realPath = realpath($filePath);
|
||||
if (!$realPath || !str_starts_with($realPath, $contentDir) || !is_readable($realPath)) {
|
||||
renderTemplate("<article><h1>403 Forbidden</h1><p>Access denied.</p></article>", 403);
|
||||
if (!$realPath || !str_starts_with($realPath, $ctx->contentDir) || !is_readable($realPath)) {
|
||||
renderTemplate($ctx, "<article><h1>403 Forbidden</h1><p>Access denied.</p></article>", 403);
|
||||
}
|
||||
}
|
||||
|
||||
// Render all content files in order
|
||||
$content = implode('', array_map('renderContentFile', $filePaths));
|
||||
|
||||
// Prepare template variables
|
||||
extract(prepareTemplateContext());
|
||||
// Prepare template variables using property hooks
|
||||
$currentLang = $ctx->currentLang;
|
||||
$navigation = $ctx->navigation;
|
||||
$homeLabel = $ctx->homeLabel;
|
||||
$translations = $ctx->translations;
|
||||
|
||||
$pageMetadata = loadMetadata($pageDir, $currentLang, $defaultLang);
|
||||
$pageMetadata = loadMetadata($pageDir, $ctx->currentLang, $ctx->defaultLang);
|
||||
$pageTitle = $pageMetadata['title'] ?? null;
|
||||
|
||||
// Wrap content with page template
|
||||
ob_start();
|
||||
include $pageTemplate;
|
||||
include $ctx->templates->page;
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Wrap with base template
|
||||
include $baseTemplate;
|
||||
include $ctx->templates->base;
|
||||
exit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
// Load modular components
|
||||
require_once __DIR__ . '/constants.php';
|
||||
require_once __DIR__ . '/context.php';
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/content.php';
|
||||
require_once __DIR__ . '/rendering.php';
|
||||
|
||||
// Create context - no more globals!
|
||||
$ctx = createContext();
|
||||
|
||||
// Check for assets in /custom/assets/ served at root level
|
||||
$assetPath = dirname(__DIR__) . '/custom/assets/' . $requestPath;
|
||||
$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);
|
||||
|
|
@ -16,15 +20,15 @@ if (file_exists($assetPath) && is_file($assetPath)) {
|
|||
}
|
||||
|
||||
// Handle frontpage
|
||||
if (empty($requestPath)) {
|
||||
$contentFiles = findAllContentFiles($contentDir, $currentLang, $defaultLang);
|
||||
if (empty($ctx->requestPath)) {
|
||||
$contentFiles = findAllContentFiles($ctx->contentDir, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
||||
if (!empty($contentFiles)) {
|
||||
renderMultipleFiles($contentFiles, $contentDir);
|
||||
renderMultipleFiles($ctx, $contentFiles, $ctx->contentDir);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and handle request
|
||||
$parsedPath = parseRequestPath($requestPath, $contentDir, $hasTrailingSlash, $currentLang, $defaultLang);
|
||||
$parsedPath = parseRequestPath($ctx);
|
||||
|
||||
switch ($parsedPath['type']) {
|
||||
case 'page':
|
||||
|
|
@ -34,7 +38,7 @@ switch ($parsedPath['type']) {
|
|||
header('Location: ' . rtrim($_SERVER['REQUEST_URI'], '/') . '/', true, 301);
|
||||
exit;
|
||||
}
|
||||
renderMultipleFiles($parsedPath['files'], $parsedPath['path']);
|
||||
renderMultipleFiles($ctx, $parsedPath['files'], $parsedPath['path']);
|
||||
|
||||
case 'file':
|
||||
// Direct file access or legacy single file
|
||||
|
|
@ -43,25 +47,26 @@ switch ($parsedPath['type']) {
|
|||
header('Location: ' . rtrim($_SERVER['REQUEST_URI'], '/') . '/', true, 301);
|
||||
exit;
|
||||
}
|
||||
renderFile($parsedPath['path']);
|
||||
renderFile($ctx, $parsedPath['path']);
|
||||
|
||||
case 'directory':
|
||||
$dir = $parsedPath['path'];
|
||||
if (file_exists("$dir/index.php")) {
|
||||
renderFile("$dir/index.php");
|
||||
renderFile($ctx, "$dir/index.php");
|
||||
}
|
||||
|
||||
// Check for page content files in this directory
|
||||
$pageContent = null;
|
||||
$contentFiles = findAllContentFiles($dir, $currentLang, $defaultLang);
|
||||
$contentFiles = findAllContentFiles($dir, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
||||
if (!empty($contentFiles)) {
|
||||
$pageContent = implode('', array_map('renderContentFile', $contentFiles));
|
||||
}
|
||||
|
||||
// Load metadata for this directory
|
||||
$metadata = loadMetadata($dir, $currentLang, $defaultLang);
|
||||
$metadata = loadMetadata($dir, $ctx->currentLang, $ctx->defaultLang);
|
||||
|
||||
// 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'];
|
||||
// Add .php extension if not present
|
||||
|
|
@ -76,27 +81,25 @@ switch ($parsedPath['type']) {
|
|||
} elseif (file_exists($defaultTemplate)) {
|
||||
$listTemplate = $defaultTemplate;
|
||||
}
|
||||
// If template doesn't exist, fall back to default $listTemplate
|
||||
}
|
||||
|
||||
// Build list items
|
||||
$subdirs = getSubdirectories($dir);
|
||||
$langPrefix = getLangPrefix($currentLang, $defaultLang);
|
||||
|
||||
$items = array_filter(array_map(function($item) use ($dir, $requestPath, $currentLang, $defaultLang, $langPrefix) {
|
||||
$items = array_filter(array_map(function($item) use ($dir, $ctx) {
|
||||
$itemPath = "$dir/$item";
|
||||
|
||||
// Check if content exists for current language
|
||||
if ($currentLang !== $defaultLang) {
|
||||
$contentFiles = findAllContentFiles($itemPath, $currentLang, $defaultLang);
|
||||
if ($ctx->currentLang !== $ctx->defaultLang) {
|
||||
$contentFiles = findAllContentFiles($itemPath, $ctx->currentLang, $ctx->defaultLang, $ctx->availableLangs);
|
||||
if (empty($contentFiles)) return null;
|
||||
}
|
||||
|
||||
$metadata = loadMetadata($itemPath, $currentLang, $defaultLang);
|
||||
$metadata = loadMetadata($itemPath, $ctx->currentLang, $ctx->defaultLang);
|
||||
$coverImage = findCoverImage($itemPath);
|
||||
$pdfFile = findPdfFile($itemPath);
|
||||
|
||||
$title = $metadata['title'] ?? extractTitle($itemPath, $currentLang, $defaultLang) ?? $item;
|
||||
$title = $metadata['title'] ?? extractTitle($itemPath, $ctx->currentLang, $ctx->defaultLang) ?? $item;
|
||||
$date = null;
|
||||
if (isset($metadata['date'])) {
|
||||
$date = formatNorwegianDate($metadata['date']);
|
||||
|
|
@ -105,11 +108,11 @@ switch ($parsedPath['type']) {
|
|||
}
|
||||
|
||||
// Use translated slug if available, otherwise use folder name
|
||||
$urlSlug = ($currentLang !== $defaultLang && $metadata && isset($metadata['slug']))
|
||||
$urlSlug = ($ctx->currentLang !== $ctx->defaultLang && $metadata && isset($metadata['slug']))
|
||||
? $metadata['slug']
|
||||
: $item;
|
||||
|
||||
$baseUrl = $langPrefix . '/' . trim($requestPath, '/') . '/' . urlencode($urlSlug);
|
||||
$baseUrl = $ctx->langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($urlSlug);
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
|
|
@ -126,12 +129,16 @@ switch ($parsedPath['type']) {
|
|||
include $listTemplate;
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Build navigation for base template
|
||||
$navigation = buildNavigation($contentDir, $currentLang, $defaultLang);
|
||||
// Prepare all variables for base template
|
||||
$currentLang = $ctx->currentLang;
|
||||
$navigation = $ctx->navigation;
|
||||
$homeLabel = $ctx->homeLabel;
|
||||
$translations = $ctx->translations;
|
||||
$pageTitle = $metadata['title'] ?? null;
|
||||
|
||||
include $baseTemplate;
|
||||
include $ctx->templates->base;
|
||||
exit;
|
||||
|
||||
case 'not_found':
|
||||
renderTemplate("<h1>404 Not Found</h1><p>The requested resource was not found.</p>", 404);
|
||||
renderTemplate($ctx, "<h1>404 Not Found</h1><p>The requested resource was not found.</p>", 404);
|
||||
}
|
||||
|
|
|
|||
960
app/vendor/Parsedown.php
vendored
960
app/vendor/Parsedown.php
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,7 @@ services:
|
|||
# - "4040:80"
|
||||
# command: bash -c "a2enconf custom && a2enmod rewrite && apache2-foreground"
|
||||
default:
|
||||
image: php:8.3.12-apache
|
||||
image: php:8.4.14-apache
|
||||
container_name: folderweb-default
|
||||
working_dir: /var/www/html/
|
||||
volumes:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue