config = $config; $this->buildMenu(); if (isset($_GET['search'])) { $this->performSearch($_GET['search']); } } private function buildMenu() { $this->menu = $this->scanDirectory($this->config['content_dir'], ''); } private function scanDirectory($dir, $prefix) { if (!is_dir($dir)) return []; $items = scandir($dir); sort($items); $result = []; foreach ($items as $item) { if ($item[0] === '.') continue; $path = $dir . '/' . $item; $relativePath = $prefix ? $prefix . '/' . $item : $item; if (is_dir($path)) { $result[] = [ 'type' => 'folder', 'title' => ucfirst($item), 'path' => $relativePath, 'children' => $this->scanDirectory($path, $relativePath) ]; } elseif (preg_match('/\.(md|php|html)$/', $item)) { $title = ucfirst(pathinfo($item, PATHINFO_FILENAME)); $result[] = [ 'type' => 'file', 'title' => $title, 'path' => $relativePath, 'url' => '?page=' . $relativePath ]; } } return $result; } private function performSearch($query) { $this->searchResults = []; $this->searchInDirectory($this->config['content_dir'], '', $query); } 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) ]; } } } } 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 . '...'; } public function getPage() { if (isset($_GET['search'])) { return $this->getSearchResults(); } $page = $_GET['page'] ?? $this->config['default_page']; $page = preg_replace('/\.[^.]+$/', '', $page); $filePath = $this->config['content_dir'] . '/' . $page; $actualFilePath = null; 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 (isset($result) && $actualFilePath) { $result['file_info'] = $this->getFileInfo($actualFilePath); return $result; } return $this->getError404(); } 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']) ]; } 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]; } private function getSearchResults() { $query = $_GET['search']; $content = '

Search Results for: "' . htmlspecialchars($query) . '"

'; if (empty($this->searchResults)) { $content .= '

No results found.

'; } else { $content .= '

Found ' . count($this->searchResults) . ' results:

'; foreach ($this->searchResults as $result) { $content .= '
'; $content .= '
'; $content .= '
' . htmlspecialchars($result['title']) . '
'; $content .= '

' . htmlspecialchars($result['path']) . '

'; $content .= '

' . htmlspecialchars($result['snippet']) . '

'; $content .= '
'; } } return [ 'title' => 'Search Results', 'content' => $content ]; } private function parseMarkdown($content) { $lines = explode("\n", $content); $title = ''; $body = ''; $inBody = false; foreach ($lines as $line) { if (!$inBody && preg_match('/^#\s+(.+)$/', $line, $matches)) { $title = $matches[1]; $inBody = true; } elseif ($inBody || trim($line) !== '') { $body .= $line . "\n"; $inBody = true; } } $body = preg_replace('/### (.+)/', '

$1

', $body); $body = preg_replace('/## (.+)/', '

$1

', $body); $body = preg_replace('/# (.+)/', '

$1

', $body); $body = preg_replace('/\*\*(.+?)\*\*/', '$1', $body); $body = preg_replace('/\*(.+?)\*/', '$1', $body); // Convert Markdown links to HTML links $body = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $body); // Convert relative internal links to CMS format $body = preg_replace('/href="\/blog\/([^"]+)"/', 'href="?page=blog/$1"', $body); $body = preg_replace('/href="\/([^"]+)"/', 'href="?page=$1"', $body); // Auto-link page titles to existing content pages $body = $this->autoLinkPageTitles($body); $body = preg_replace('/\n\n/', '

', $body); $body = '

' . $body . '

'; $body = preg_replace('/

<\/p>/', '', $body); $body = preg_replace('/

()/', '$1', $body); $body = preg_replace('/(<\/h[1-6]>)<\/p>/', '$1', $body); return [ 'title' => $title ?: 'Untitled', 'content' => $body ]; } private function autoLinkPageTitles($content) { // Get all available pages with their titles $pages = $this->getAllPageTitles(); foreach ($pages as $pagePath => $pageTitle) { // Create a pattern that matches the exact page title (case-insensitive) $pattern = '/\b' . preg_quote($pageTitle, '/') . '\b/i'; // Replace with link, but avoid linking inside existing links or headings $replacement = function($matches) use ($pageTitle, $pagePath) { // Check if we're inside an HTML tag or link $before = substr($matches[0], 0, 50); if (preg_match('/<[^>]*$/', $before) || preg_match('/href=/', $before)) { return $matches[0]; // Don't link inside HTML tags } return '' . $matches[0] . ''; }; $content = preg_replace_callback($pattern, $replacement, $content); } return $content; } private function getAllPageTitles() { $pages = []; $this->scanForPageTitles($this->config['content_dir'], '', $pages); return $pages; } private function scanForPageTitles($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->scanForPageTitles($path, $relativePath, $pages); } elseif (preg_match('/\.(md|php|html)$/', $item)) { $title = $this->extractPageTitle($path); if ($title && !empty(trim($title))) { $pagePath = preg_replace('/\.[^.]+$/', '', $relativePath); $pages[$pagePath] = $title; } } } } private function extractPageTitle($filePath) { $content = file_get_contents($filePath); $extension = pathinfo($filePath, PATHINFO_EXTENSION); if ($extension === 'md') { // Extract first H1 from Markdown if (preg_match('/^#\s+(.+)$/m', $content, $matches)) { return trim($matches[1]); } } elseif ($extension === 'php') { // Extract title from PHP file if (preg_match('/\$title\s*=\s*["\']([^"\']+)["\']/', $content, $matches)) { return trim($matches[1]); } } elseif ($extension === 'html') { // Extract title from HTML file if (preg_match('/(.*?)<\/title>/i', $content, $matches)) { return trim(strip_tags($matches[1])); } if (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) { return trim(strip_tags($matches[1])); } } return null; } private function parsePHP($filePath) { ob_start(); $title = 'Untitled'; include $filePath; $content = ob_get_clean(); return [ 'title' => $title, 'content' => $content ]; } private function parseHTML($content) { $title = 'Untitled'; if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) { $title = strip_tags($matches[1]); } elseif (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) { $title = strip_tags($matches[1]); } return [ 'title' => $title, 'content' => $content ]; } private function getError404() { return [ 'title' => 'Page Not Found', 'content' => '<h1>404 - Page Not Found</h1><p>The page you are looking for does not exist.</p>' ]; } public function getMenu() { return $this->menu; } public function render() { $page = $this->getPage(); $menu = $this->getMenu(); $breadcrumb = $this->getBreadcrumb(); $template = file_get_contents($this->config['templates_dir'] . '/layout.html'); $template = str_replace('{{site_title}}', $this->config['site_title'], $template); $template = str_replace('{{page_title}}', $page['title'], $template); $template = str_replace('{{content}}', $page['content'], $template); $template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template); $template = str_replace('{{breadcrumb}}', $breadcrumb, $template); // File info for footer $fileInfo = ''; if (isset($page['file_info'])) { $fileInfo = '<i class="bi bi-file-text"></i> Created: ' . htmlspecialchars($page['file_info']['created']) . ' | Modified: ' . htmlspecialchars($page['file_info']['modified']); } $template = str_replace('{{file_info}}', $fileInfo, $template); $menuHtml = $this->renderMenu($menu); $template = str_replace('{{menu}}', $menuHtml, $template); echo $template; } private function getBreadcrumb() { if (isset($_GET['search'])) { return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li><li class="breadcrumb-item active">Search</li></ol></nav>'; } $page = $_GET['page'] ?? $this->config['default_page']; $page = preg_replace('/\.[^.]+$/', '', $page); if ($page === $this->config['default_page']) { return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item active">Home</li></ol></nav>'; } $parts = explode('/', $page); $breadcrumb = '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li>'; $path = ''; foreach ($parts as $i => $part) { $path .= ($path ? '/' : '') . $part; $title = ucfirst($part); if ($i === count($parts) - 1) { $breadcrumb .= '<li class="breadcrumb-item active">' . $title . '</li>'; } else { $breadcrumb .= '<li class="breadcrumb-item"><a href="?page=' . $path . '">' . $title . '</a></li>'; } } $breadcrumb .= '</ol></nav>'; return $breadcrumb; } private function renderMenu($items, $level = 0) { $html = ''; foreach ($items as $item) { if ($item['type'] === 'folder') { $hasChildren = !empty($item['children']); $html .= '<li class="nav-item">'; if ($hasChildren) { $folderId = 'folder-' . str_replace('/', '-', $item['path']); $html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="false">'; $html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']); $html .= '</span>'; $html .= '<ul class="nav flex-column ms-2 collapse" id="' . $folderId . '">'; $html .= $this->renderMenu($item['children'], $level + 1); $html .= '</ul>'; } else { $html .= '<span class="nav-link folder-disabled" disabled>'; $html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']); $html .= '</span>'; } $html .= '</li>'; } else { $active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : ''; $html .= '<li class="nav-item">'; $html .= '<a class="nav-link page-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>'; $html .= '</li>'; } } return $html; } } $cms = new CodePressCMS($config); $cms->render();