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 .= '
';
$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);
// Auto-link page titles to existing content pages (before markdown link processing)
$body = $this->autoLinkPageTitles($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);
$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)
// Use word boundaries to avoid partial matches
$pattern = '/\b' . preg_quote($pageTitle, '/') . '\b/i';
// Replace with link, but avoid linking inside existing links, headings, or markdown
$replacement = function($matches) use ($pageTitle, $pagePath) {
$text = $matches[0];
// Check if we're inside an existing link or markdown syntax
if (preg_match('/\[.*?\]\(.*?\)/', $text) ||
preg_match('/\[.*?\]:/', $text) ||
preg_match('/]*>/', $text) ||
preg_match('/href=/', $text)) {
return $text; // Don't link existing links
}
return '' . $text . '';
};
$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>/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>/i', $content, $matches)) {
$title = strip_tags($matches[1]);
} elseif (preg_match('/]*>(.*?)<\/h1>/i', $content, $matches)) {
$title = strip_tags($matches[1]);
}
return [
'title' => $title,
'content' => $content
];
}
private function getError404() {
return [
'title' => 'Page Not Found',
'content' => '404 - Page Not Found
The page you are looking for does not exist.
'
];
}
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 = ' 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 '';
}
$page = $_GET['page'] ?? $this->config['default_page'];
$page = preg_replace('/\.[^.]+$/', '', $page);
if ($page === $this->config['default_page']) {
return '';
}
$parts = explode('/', $page);
$breadcrumb = '';
return $breadcrumb;
}
private function renderMenu($items, $level = 0) {
$html = '';
foreach ($items as $item) {
if ($item['type'] === 'folder') {
$hasChildren = !empty($item['children']);
$html .= '';
if ($hasChildren) {
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
// Check if this folder contains the active page
$containsActive = $this->folderContainsActivePage($item['children']);
$ariaExpanded = $containsActive ? 'true' : 'false';
$collapseClass = $containsActive ? 'collapse show' : 'collapse';
$html .= '';
$html .= ' ' . htmlspecialchars($item['title']);
$html .= '';
$html .= '';
$html .= $this->renderMenu($item['children'], $level + 1);
$html .= '
';
} else {
$html .= '';
$html .= ' ' . htmlspecialchars($item['title']);
$html .= '';
}
$html .= '';
} else {
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
$html .= '';
$html .= '' . htmlspecialchars($item['title']) . '';
$html .= '';
}
}
return $html;
}
private function folderContainsActivePage($children) {
foreach ($children as $child) {
if ($child['type'] === 'folder') {
if (!empty($child['children']) && $this->folderContainsActivePage($child['children'])) {
return true;
}
} else {
if (isset($_GET['page']) && $_GET['page'] === $child['path']) {
return true;
}
}
}
return false;
}
}
$cms = new CodePressCMS($config);
$cms->render();