config = $config; $this->currentLanguage = $this->getCurrentLanguage(); $this->translations = $this->loadTranslations($this->currentLanguage); $this->buildMenu(); if (isset($_GET['search'])) { $this->performSearch($_GET['search']); } } /** * Get current language from URL or use default * * @return string Current language code */ private function getCurrentLanguage() { return $_GET['lang'] ?? $this->config['language']['default'] ?? 'nl'; } /** * Load translations for specified language * * @param string $lang Language code * @return array Translations array */ private function loadTranslations($lang) { $langFile = __DIR__ . '/../../lang/' . $lang . '.php'; error_log("Loading language file: " . $langFile); if (file_exists($langFile)) { $translations = include $langFile; error_log("Loaded translations for " . $lang . ": " . print_r($translations, true)); return $translations; } // Fallback to default language $defaultLang = $this->config['language']['default'] ?? 'nl'; $defaultLangFile = __DIR__ . '/../../lang/' . $defaultLang . '.php'; error_log("Fallback to default language: " . $defaultLangFile); return file_exists($defaultLangFile) ? include $defaultLangFile : []; } /** * Get translated text * * @param string $key Translation key * @return string Translated text */ public function t($key) { return $this->translations[$key] ?? $key; } /** * Build menu structure from content directory * * @return void */ private function buildMenu() { $this->menu = $this->scanDirectory($this->config['content_dir'], ''); } /** * Recursively scan directory for content files and folders * * @param string $dir Directory path to scan * @param string $prefix Relative path prefix * @return array Array of menu items */ private function scanDirectory($dir, $prefix) { if (!is_dir($dir)) return []; $items = scandir($dir); sort($items); $result = []; foreach ($items as $item) { if ($item[0] === '.') continue; // Skip language-specific content that doesn't match current language if (preg_match('/^(nl|uk)\./', $item)) { $langPrefix = substr($item, 0, 2); if (($langPrefix === 'nl' && $this->currentLanguage !== 'nl') || ($langPrefix === 'uk' && $this->currentLanguage !== 'en')) { continue; } } $path = $dir . '/' . $item; $relativePath = $prefix ? $prefix . '/' . $item : $item; if (is_dir($path)) { $result[] = [ 'type' => 'folder', 'title' => $this->formatDisplayName($item), 'path' => $relativePath, 'children' => $this->scanDirectory($path, $relativePath) ]; } elseif (preg_match('/\.(md|php|html)$/', $item)) { // Always use filename for navigation (not H1 titles from content) $filename = pathinfo($item, PATHINFO_FILENAME); $title = $this->formatDisplayName($filename); $pathWithoutExt = preg_replace('/\.[^.]+$/', '', $relativePath); $result[] = [ 'type' => 'file', 'title' => $title, 'path' => $pathWithoutExt, 'url' => '?page=' . $pathWithoutExt . '&lang=' . $this->currentLanguage ]; } } return $result; } /** * Perform search across all content files * * @param string $query Search query string * @return void */ private function performSearch($query) { $this->searchResults = []; $this->searchInDirectory($this->config['content_dir'], '', $query); } /** * Recursively search for query in directory files * * @param string $dir Directory to search in * @param string $prefix Relative path prefix * @param string $query Search query * @return void */ private function searchInDirectory($dir, $prefix, $query) { if (!is_dir($dir)) return; $items = scandir($dir); foreach ($items as $item) { if ($item[0] === '.') continue; $path = $dir . '/' . $item; $relativePath = $prefix ? $prefix . '/' . $item : $item; if (is_dir($path)) { $this->searchInDirectory($path, $relativePath, $query); } elseif (preg_match('/\.(md|php|html)$/', $item)) { $content = file_get_contents($path); if (stripos($content, $query) !== false || stripos($item, $query) !== false) { $title = ucfirst(pathinfo($item, PATHINFO_FILENAME)); $this->searchResults[] = [ 'title' => $title, 'path' => $relativePath, 'url' => '?page=' . $relativePath, 'snippet' => $this->createSnippet($content, $query) ]; } } } } /** * Create search snippet with highlighted query * * @param string $content Full content to create snippet from * @param string $query Search query to highlight * @return string Formatted snippet */ private function createSnippet($content, $query) { $content = strip_tags($content); $pos = stripos($content, $query); if ($pos === false) return substr($content, 0, 100) . '...'; $start = max(0, $pos - 50); $snippet = substr($content, $start, 150); return '...' . $snippet . '...'; } /** * Get current page content based on request * * @return array Page data with title and content */ public function getPage() { if (isset($_GET['search'])) { return $this->getSearchResults(); } // Check if guide is requested if (isset($_GET['guide'])) { return $this->getGuidePage(); } // Check if content directory is empty if ($this->isContentDirEmpty()) { return $this->getGuidePage(); } $page = $_GET['page'] ?? $this->config['default_page']; $page = preg_replace('/\.[^.]+$/', '', $page); $filePath = $this->config['content_dir'] . '/' . $page; $actualFilePath = null; // Check for exact file matches first if (file_exists($filePath . '.md')) { $actualFilePath = $filePath . '.md'; $result = $this->parseMarkdown(file_get_contents($actualFilePath)); } elseif (file_exists($filePath . '.php')) { $actualFilePath = $filePath . '.php'; $result = $this->parsePHP($actualFilePath); } elseif (file_exists($filePath . '.html')) { $actualFilePath = $filePath . '.html'; $result = $this->parseHTML(file_get_contents($actualFilePath)); } elseif (file_exists($filePath)) { $actualFilePath = $filePath; $extension = pathinfo($filePath, PATHINFO_EXTENSION); if ($extension === 'md') { $result = $this->parseMarkdown(file_get_contents($actualFilePath)); } elseif ($extension === 'php') { $result = $this->parsePHP($actualFilePath); } elseif ($extension === 'html') { $result = $this->parseHTML(file_get_contents($actualFilePath)); } } // If no file found, check if it's a directory if (!isset($result) && is_dir($filePath)) { return $this->getDirectoryListing($page, $filePath); } if (isset($result) && $actualFilePath) { $result['file_info'] = $this->getFileInfo($actualFilePath); return $result; } return $this->getError404(); } /** * Get file information including creation and modification dates * * @param string $filePath Path to the file * @return array|null File information or null if file doesn't exist */ private function getFileInfo($filePath) { if (!file_exists($filePath)) { return null; } $stats = stat($filePath); $created = date('d-m-Y H:i', $stats['ctime']); $modified = date('d-m-Y H:i', $stats['mtime']); return [ 'created' => $created, 'modified' => $modified, 'size' => $this->formatFileSize($stats['size']) ]; } /** * Format file size in human readable format * * @param int $bytes File size in bytes * @return string Formatted file size */ private function formatFileSize($bytes) { $units = ['B', 'KB', 'MB', 'GB']; $bytes = max($bytes, 0); $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); $pow = min($pow, count($units) - 1); $bytes /= pow(1024, $pow); return round($bytes, 2) . ' ' . $units[$pow]; } /** * Get search results page content * * @return array Search results page data */ private function getSearchResults() { $query = $_GET['search']; $content = '
' . $this->t('no_results') . '.
'; } else { $content .= '' . count($this->searchResults) . ' ' . $this->t('results_found') . ':
'; foreach ($this->searchResults as $result) { $content .= '' . htmlspecialchars($result['path']) . '
'; $content .= '' . htmlspecialchars($result['snippet']) . '
'; $content .= 'Directory not found.
' ]; } $items = scandir($dirPath); sort($items); $hasContent = false; // Collect all items $allItems = []; foreach ($items as $item) { if ($item[0] === '.') continue; $itemPath = $dirPath . '/' . $item; $relativePath = $pagePath ? $pagePath . '/' . $item : $item; if (is_dir($itemPath)) { $allItems[] = [ 'name' => ucfirst($item), 'path' => $relativePath, 'url' => '?page=' . $relativePath, 'icon' => 'bi-folder', 'type' => 'directory' ]; } elseif (preg_match('/\.(md|php|html)$/', $item)) { $extractedTitle = $this->extractPageTitle($itemPath); $fileTitle = $extractedTitle ?: ucfirst(pathinfo($item, PATHINFO_FILENAME)); $pathWithoutExt = preg_replace('/\.[^.]+$/', '', $relativePath); $icon = pathinfo($item, PATHINFO_EXTENSION) === 'md' ? 'bi-file-text' : (pathinfo($item, PATHINFO_EXTENSION) === 'php' ? 'bi-file-code' : 'bi-file-earmark'); $allItems[] = [ 'name' => $fileTitle, 'path' => $pathWithoutExt, 'url' => '?page=' . $pathWithoutExt, 'icon' => $icon, 'type' => 'file' ]; } } // Display all items in a single column if (!empty($allItems)) { $content .= '' . $this->t('directory_empty') . '.
'; } return [ 'title' => $title, 'content' => $content ]; } /** * Get 404 error page content * * @return array 404 page data */ private function getError404() { return [ 'title' => $this->t('page_not_found'), 'content' => '' . $this->t('page_not_found_text') . '
' ]; } /** * Get menu structure * * @return array Menu structure for navigation */ public function getMenu() { return $this->menu; } /** * Render the complete page with template * * @return void */ public function render() { $page = $this->getPage(); $menu = $this->getMenu(); $breadcrumb = $this->generateBreadcrumb(); // Get homepage title $homepageTitle = $this->getHomepageTitle(); // Prepare template data $templateData = [ 'site_title' => $this->config['site_title'], 'page_title' => htmlspecialchars($page['title']), 'content' => $page['content'], 'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', 'menu' => $this->renderMenu($menu), 'breadcrumb' => $breadcrumb, 'default_page' => $this->config['default_page'], 'homepage' => $this->config['default_page'], 'homepage_title' => $homepageTitle, 'is_homepage' => (!isset($_GET['page']) || $_GET['page'] === $this->config['default_page']), 'home_active_class' => (!isset($_GET['page']) || $_GET['page'] === $this->config['default_page']) ? 'active' : '', 'is_guide_page' => isset($_GET['guide']), 'lang_switch_url' => isset($_GET['guide']) ? '&guide' : '&page=' . $this->config['default_page'], 'author_name' => $this->config['author']['name'] ?? 'CodePress Developer', 'author_website' => $this->config['author']['website'] ?? '#', 'author_git' => $this->config['author']['git'] ?? '#', 'seo_description' => $this->config['seo']['description'] ?? 'CodePress CMS - Lightweight file-based content management system', 'seo_keywords' => $this->config['seo']['keywords'] ?? 'cms, php, content management, file-based', // Theme colors 'header_color' => $this->config['theme']['header_color'] ?? '#0d6efd', 'header_font_color' => $this->config['theme']['header_font_color'] ?? '#ffffff', 'navigation_color' => $this->config['theme']['navigation_color'] ?? '#f8f9fa', 'navigation_font_color' => $this->config['theme']['navigation_font_color'] ?? '#000000', // Language 'current_lang' => $this->currentLanguage, 'current_lang_upper' => strtoupper($this->currentLanguage), 'available_langs' => $this->config['language']['available'] ?? ['nl', 'en'], // Translations 't_home' => $this->t('home'), 't_search' => $this->t('search'), 't_search_placeholder' => $this->t('search_placeholder'), 't_search_button' => $this->t('search_button'), 't_welcome' => $this->t('welcome'), 't_created' => $this->t('created'), 't_modified' => $this->t('modified'), 't_author' => $this->t('author'), 't_manual' => $this->t('manual'), 't_no_content' => $this->t('no_content'), 't_no_results' => $this->t('no_results'), 't_results_found' => $this->t('results_found'), 't_breadcrumb_home' => $this->t('breadcrumb_home'), 't_file_details' => $this->t('file_details'), 't_guide' => $this->t('guide'), 't_powered_by' => $this->t('powered_by'), 't_directory_empty' => $this->t('directory_empty'), 't_page_not_found' => $this->t('page_not_found'), 't_page_not_found_text' => $this->t('page_not_found_text'), 't_mappen' => $this->t('mappen'), 't_paginas' => $this->t('paginas') ]; // File info for footer if (isset($page['file_info'])) { $templateData['file_info'] = $this->t('created') . ': ' . htmlspecialchars($page['file_info']['created']) . ' | ' . $this->t('modified') . ': ' . htmlspecialchars($page['file_info']['modified']); $templateData['file_info_block'] = ' | ' . $templateData['file_info'] . ''; } else { $templateData['file_info'] = ''; $templateData['file_info_block'] = ''; } // Check if content exists for guide link $hasContent = !$this->isContentDirEmpty(); $templateData['has_content'] = $hasContent; // Don't show site title link on guide page $templateData['show_site_link'] = !$this->isContentDirEmpty() && !isset($_GET['guide']); // Load and render all templates with data $layoutTemplate = file_get_contents($this->config['templates_dir'] . '/layout.mustache'); $headerTemplate = file_get_contents($this->config['templates_dir'] . '/assets/header.mustache'); $navigationTemplate = file_get_contents($this->config['templates_dir'] . '/assets/navigation.mustache'); $footerTemplate = file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache'); // Determine content type and load appropriate template $contentType = $this->getContentType($page); $contentTemplateFile = $this->config['templates_dir'] . '/' . $contentType . '_content.mustache'; $contentTemplate = file_exists($contentTemplateFile) ? file_get_contents($contentTemplateFile) : '