|
|
|
|
@@ -45,9 +45,7 @@ class CodePressCMS {
|
|
|
|
|
$this->currentLanguage = $this->getCurrentLanguage();
|
|
|
|
|
$this->translations = $this->loadTranslations($this->currentLanguage);
|
|
|
|
|
|
|
|
|
|
// Initialize plugin manager
|
|
|
|
|
require_once __DIR__ . '/../plugin/PluginManager.php';
|
|
|
|
|
require_once __DIR__ . '/../plugin/CMSAPI.php';
|
|
|
|
|
// Initialize plugin manager (files already loaded in engine/core/index.php)
|
|
|
|
|
$this->pluginManager = new PluginManager(__DIR__ . '/../../../plugins');
|
|
|
|
|
$api = new CMSAPI($this);
|
|
|
|
|
$this->pluginManager->setAPI($api);
|
|
|
|
|
@@ -187,10 +185,10 @@ class CodePressCMS {
|
|
|
|
|
if ($item[0] === '.') continue;
|
|
|
|
|
|
|
|
|
|
// Skip language-specific content that doesn't match current language
|
|
|
|
|
if (preg_match('/^(nl|en)\./', $item)) {
|
|
|
|
|
$langPrefix = substr($item, 0, 2);
|
|
|
|
|
if (($langPrefix === 'nl' && $this->currentLanguage !== 'nl') ||
|
|
|
|
|
($langPrefix === 'en' && $this->currentLanguage !== 'en')) {
|
|
|
|
|
$availableLangs = array_keys($this->getAvailableLanguages());
|
|
|
|
|
$langPattern = '/^(' . implode('|', $availableLangs) . ')\./';
|
|
|
|
|
if (preg_match($langPattern, $item, $langMatch)) {
|
|
|
|
|
if ($langMatch[1] !== $this->currentLanguage) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -261,7 +259,7 @@ class CodePressCMS {
|
|
|
|
|
$this->searchResults[] = [
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'path' => $relativePath,
|
|
|
|
|
'url' => '?page=' . $relativePath,
|
|
|
|
|
'url' => '?page=' . $relativePath . '&lang=' . $this->currentLanguage,
|
|
|
|
|
'snippet' => $this->createSnippet($content, $query)
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
@@ -307,10 +305,6 @@ class CodePressCMS {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$page = $_GET['page'] ?? $this->config['default_page'];
|
|
|
|
|
// Sanitize page parameter to prevent XSS
|
|
|
|
|
$page = htmlspecialchars($page, ENT_QUOTES, 'UTF-8');
|
|
|
|
|
// Prevent path traversal
|
|
|
|
|
$page = str_replace(['../', '..\\', '..'], '', $page);
|
|
|
|
|
// Limit length
|
|
|
|
|
$page = substr($page, 0, 255);
|
|
|
|
|
// Only remove file extension at the end, not all dots
|
|
|
|
|
@@ -318,6 +312,13 @@ class CodePressCMS {
|
|
|
|
|
|
|
|
|
|
$filePath = $this->config['content_dir'] . '/' . $pageWithoutExt;
|
|
|
|
|
|
|
|
|
|
// Prevent path traversal using realpath validation
|
|
|
|
|
$realContentDir = realpath($this->config['content_dir']);
|
|
|
|
|
$realFilePath = realpath($filePath);
|
|
|
|
|
if ($realFilePath && $realContentDir && strpos($realFilePath, $realContentDir) !== 0) {
|
|
|
|
|
return $this->getError404();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if directory exists FIRST (directories take precedence over files)
|
|
|
|
|
if (is_dir($filePath)) {
|
|
|
|
|
return $this->getDirectoryListing($pageWithoutExt, $filePath);
|
|
|
|
|
@@ -325,13 +326,6 @@ class CodePressCMS {
|
|
|
|
|
|
|
|
|
|
$actualFilePath = null;
|
|
|
|
|
|
|
|
|
|
// Check if directory exists first (directories take precedence over files)
|
|
|
|
|
if (is_dir($filePath)) {
|
|
|
|
|
$directoryResult = $this->getDirectoryListing($pageWithoutExt, $filePath);
|
|
|
|
|
|
|
|
|
|
return $directoryResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for exact file matches if no directory found
|
|
|
|
|
if (file_exists($filePath . '.md')) {
|
|
|
|
|
$actualFilePath = $filePath . '.md';
|
|
|
|
|
@@ -509,10 +503,7 @@ class CodePressCMS {
|
|
|
|
|
$title = trim($matches[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Include autoloader
|
|
|
|
|
require_once __DIR__ . '/../../../vendor/autoload.php';
|
|
|
|
|
|
|
|
|
|
// Configure CommonMark environment
|
|
|
|
|
// Configure CommonMark environment (autoloader already loaded in bootstrap)
|
|
|
|
|
$config = [
|
|
|
|
|
'html_input' => 'strip',
|
|
|
|
|
'allow_unsafe_links' => false,
|
|
|
|
|
@@ -584,7 +575,7 @@ class CodePressCMS {
|
|
|
|
|
return $text; // Don't link existing links, current page title, or H1 headings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return '<a href="?page=' . $pagePath . '&lang=' . $this->currentLanguage . '" class="auto-link" title="Ga naar ' . htmlspecialchars($pageTitle) . '">' . $text . '</a>';
|
|
|
|
|
return '<a href="?page=' . $pagePath . '&lang=' . $this->currentLanguage . '" class="auto-link" title="' . $this->t('go_to') . ' ' . htmlspecialchars($pageTitle) . '">' . $text . '</a>';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$content = preg_replace_callback($pattern, $replacement, $content);
|
|
|
|
|
@@ -604,11 +595,6 @@ class CodePressCMS {
|
|
|
|
|
return $pages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all page names from content directory (for navigation)
|
|
|
|
|
*
|
|
|
|
|
* @return array Associative array of page paths to display names
|
|
|
|
|
*/
|
|
|
|
|
/**
|
|
|
|
|
* Recursively scan for page titles in directory
|
|
|
|
|
*
|
|
|
|
|
@@ -647,37 +633,6 @@ class CodePressCMS {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recursively scan for page names in directory (for navigation)
|
|
|
|
|
*
|
|
|
|
|
* @param string $dir Directory to scan
|
|
|
|
|
* @param string $prefix Relative path prefix
|
|
|
|
|
* @param array &$pages Reference to pages array to populate
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
private function scanForPageNames($dir, $prefix, &$pages) {
|
|
|
|
|
if (!is_dir($dir)) return;
|
|
|
|
|
|
|
|
|
|
$items = scandir($dir);
|
|
|
|
|
sort($items);
|
|
|
|
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
if ($item[0] === '.') continue;
|
|
|
|
|
|
|
|
|
|
$path = $dir . '/' . $item;
|
|
|
|
|
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
|
|
|
|
|
|
|
|
|
if (is_dir($path)) {
|
|
|
|
|
$this->scanForPageNames($path, $relativePath, $pages);
|
|
|
|
|
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
|
|
|
|
// Use filename without extension as display name
|
|
|
|
|
$displayName = preg_replace('/\.[^.]+$/', '', $item);
|
|
|
|
|
$pagePath = preg_replace('/\.[^.]+$/', '', $relativePath);
|
|
|
|
|
$pages[$pagePath] = $this->formatDisplayName($displayName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format display name from filename
|
|
|
|
|
*
|
|
|
|
|
@@ -685,39 +640,33 @@ class CodePressCMS {
|
|
|
|
|
* @return string Formatted display name
|
|
|
|
|
*/
|
|
|
|
|
private function formatDisplayName($filename) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remove language prefixes (nl. or en.) from display names
|
|
|
|
|
if (preg_match('/^(nl|en)\.(.+)$/', $filename, $matches)) {
|
|
|
|
|
// Remove language prefixes dynamically based on available languages
|
|
|
|
|
$availableLangs = array_keys($this->getAvailableLanguages());
|
|
|
|
|
$langPattern = '/^(' . implode('|', $availableLangs) . ')\.(.+)$/';
|
|
|
|
|
if (preg_match($langPattern, $filename, $matches)) {
|
|
|
|
|
$filename = $matches[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove language prefixes from directory names (nl.php-testen -> php-testen)
|
|
|
|
|
if (preg_match('/^(nl|en)\.php-(.+)$/', $filename, $matches)) {
|
|
|
|
|
$filename = 'php-' . $matches[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove file extensions (.md, .php, .html) from display names
|
|
|
|
|
$filename = preg_replace('/\.(md|php|html)$/', '', $filename);
|
|
|
|
|
|
|
|
|
|
// Handle special cases first (only for exact filenames, not directories)
|
|
|
|
|
// These should only apply to actual files, not directory names
|
|
|
|
|
if (strtolower($filename) === 'phpinfo' && !preg_match('/\//', $filename)) {
|
|
|
|
|
return 'phpinfo';
|
|
|
|
|
}
|
|
|
|
|
if (strtolower($filename) === 'ict' && !preg_match('/\//', $filename)) {
|
|
|
|
|
return 'ICT';
|
|
|
|
|
// Handle special cases (case-sensitive display names)
|
|
|
|
|
$specialCases = [
|
|
|
|
|
'phpinfo' => 'phpinfo',
|
|
|
|
|
'ict' => 'ICT',
|
|
|
|
|
];
|
|
|
|
|
if (isset($specialCases[strtolower($filename)])) {
|
|
|
|
|
return $specialCases[strtolower($filename)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace hyphens and underscores with spaces
|
|
|
|
|
// Replace hyphens and underscores with spaces, then title case
|
|
|
|
|
$name = str_replace(['-', '_'], ' ', $filename);
|
|
|
|
|
|
|
|
|
|
// Convert to title case (first letter uppercase, rest lowercase)
|
|
|
|
|
$name = ucwords(strtolower($name));
|
|
|
|
|
|
|
|
|
|
// Handle other special cases
|
|
|
|
|
$name = str_replace('Phpinfo', 'phpinfo', $name);
|
|
|
|
|
$name = str_replace('Ict', 'ICT', $name);
|
|
|
|
|
// Post-process special cases in compound names
|
|
|
|
|
foreach ($specialCases as $lower => $correct) {
|
|
|
|
|
$name = str_ireplace(ucfirst($lower), $correct, $name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $name;
|
|
|
|
|
}
|
|
|
|
|
@@ -866,10 +815,7 @@ private function getGuidePage() {
|
|
|
|
|
$metadata = $parsed['metadata'];
|
|
|
|
|
$contentWithoutMeta = $parsed['content'];
|
|
|
|
|
|
|
|
|
|
// Include autoloader
|
|
|
|
|
require_once __DIR__ . '/../../../vendor/autoload.php';
|
|
|
|
|
|
|
|
|
|
// Configure CommonMark environment
|
|
|
|
|
// Configure CommonMark environment (autoloader already loaded in bootstrap)
|
|
|
|
|
$config = [
|
|
|
|
|
'html_input' => 'strip',
|
|
|
|
|
'allow_unsafe_links' => false,
|
|
|
|
|
@@ -993,8 +939,6 @@ private function getGuidePage() {
|
|
|
|
|
$hasContent = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$content .= '</div>';
|
|
|
|
|
|
|
|
|
|
if (!$hasContent) {
|
|
|
|
|
$content .= '<p>' . $this->t('directory_empty') . '.</p>';
|
|
|
|
|
}
|
|
|
|
|
@@ -1167,8 +1111,11 @@ private function getGuidePage() {
|
|
|
|
|
* @return string Breadcrumb HTML
|
|
|
|
|
*/
|
|
|
|
|
public function generateBreadcrumb() {
|
|
|
|
|
// Sidebar toggle button (shown before home icon in breadcrumb)
|
|
|
|
|
$sidebarToggle = '<li class="breadcrumb-item sidebar-toggle-item"><button type="button" class="sidebar-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar" aria-label="Toggle Sidebar" aria-expanded="true"><i class="bi bi-layout-sidebar-inset"></i></button></li>';
|
|
|
|
|
|
|
|
|
|
if (isset($_GET['search'])) {
|
|
|
|
|
return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '&lang=' . $this->currentLanguage . '"></a></li><li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $this->t('search') . '</li></ol></nav>';
|
|
|
|
|
return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '&lang=' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $this->t('search') . '</li></ol></nav>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$page = $_GET['page'] ?? $this->config['default_page'];
|
|
|
|
|
@@ -1176,12 +1123,13 @@ private function getGuidePage() {
|
|
|
|
|
$page = preg_replace('/\.[^.]+$/', '', $page);
|
|
|
|
|
|
|
|
|
|
if ($page === $this->config['default_page']) {
|
|
|
|
|
return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item active"><i class="bi bi-house"></i></li></ol></nav>';
|
|
|
|
|
return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item active"><i class="bi bi-house"></i></li></ol></nav>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$breadcrumb = '<nav aria-label="breadcrumb"><ol class="breadcrumb">';
|
|
|
|
|
|
|
|
|
|
// Start with home icon linking to default page (root)
|
|
|
|
|
// Start with sidebar toggle, then home icon linking to default page (root)
|
|
|
|
|
$breadcrumb .= $sidebarToggle;
|
|
|
|
|
$breadcrumb .= '<li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '&lang=' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li>';
|
|
|
|
|
|
|
|
|
|
// Split page path and build breadcrumb items
|
|
|
|
|
@@ -1190,14 +1138,15 @@ private function getGuidePage() {
|
|
|
|
|
|
|
|
|
|
foreach ($parts as $i => $part) {
|
|
|
|
|
$currentPath .= ($currentPath ? '/' : '') . $part;
|
|
|
|
|
$title = ucfirst($part);
|
|
|
|
|
$title = htmlspecialchars(ucfirst($part), ENT_QUOTES, 'UTF-8');
|
|
|
|
|
$safePath = htmlspecialchars($currentPath, ENT_QUOTES, 'UTF-8');
|
|
|
|
|
|
|
|
|
|
if ($i === count($parts) - 1) {
|
|
|
|
|
// Last part - active page
|
|
|
|
|
$breadcrumb .= '<li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $title . '</li>';
|
|
|
|
|
} else {
|
|
|
|
|
// Parent directory - clickable link with separator
|
|
|
|
|
$breadcrumb .= '<li class="breadcrumb-item"> > </li><li class="breadcrumb-item"><a href="?page=' . $currentPath . '&lang=' . $this->currentLanguage . '">' . $title . '</a></li>';
|
|
|
|
|
$breadcrumb .= '<li class="breadcrumb-item"> > </li><li class="breadcrumb-item"><a href="?page=' . $safePath . '&lang=' . $this->currentLanguage . '">' . $title . '</a></li>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1287,7 +1236,6 @@ private function getGuidePage() {
|
|
|
|
|
private function getContentType($page) {
|
|
|
|
|
// Try to determine content type from page request
|
|
|
|
|
$pagePath = $_GET['page'] ?? $this->config['default_page'];
|
|
|
|
|
$pagePath = htmlspecialchars($pagePath, ENT_QUOTES, 'UTF-8');
|
|
|
|
|
$pagePath = preg_replace('/\.[^.]+$/', '', $pagePath);
|
|
|
|
|
|
|
|
|
|
$filePath = $this->config['content_dir'] . '/' . $pagePath;
|
|
|
|
|
|