Fix guide template variable replacement and enhance documentation
- Fix template variable replacement in guide pages by removing {{}} brackets
- Escape code blocks in guide markdown to prevent template processing
- Completely rewrite guide documentation with comprehensive CMS features
- Add bilingual guide support (English/Dutch) with detailed examples
- Enhance CodePressCMS core with improved guide page handling
- Update template system with better layout and footer components
- Improve language files with additional translations
- Update configuration with enhanced theme and language settings
Resolves issue where guide pages were showing replaced template variables
instead of displaying them as documentation examples.
This commit is contained in:
@@ -21,11 +21,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
class CodePressCMS {
|
||||
private $config;
|
||||
public $config;
|
||||
public $currentLanguage;
|
||||
public $searchResults = [];
|
||||
private $menu = [];
|
||||
private $searchResults = [];
|
||||
private $currentLanguage;
|
||||
private $translations = [];
|
||||
private $pluginManager;
|
||||
|
||||
/**
|
||||
* Constructor - Initialize the CMS with configuration
|
||||
@@ -43,6 +44,14 @@ 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';
|
||||
$this->pluginManager = new PluginManager(__DIR__ . '/../../../plugins');
|
||||
$api = new CMSAPI($this);
|
||||
$this->pluginManager->setAPI($api);
|
||||
|
||||
$this->buildMenu();
|
||||
|
||||
if (isset($_GET['search'])) {
|
||||
@@ -67,7 +76,7 @@ class CodePressCMS {
|
||||
*
|
||||
* @return array Available languages with their codes and names
|
||||
*/
|
||||
private function getAvailableLanguages() {
|
||||
public function getAvailableLanguages() {
|
||||
$langDir = __DIR__ . '/../../lang/';
|
||||
$languages = [];
|
||||
|
||||
@@ -444,16 +453,59 @@ class CodePressCMS {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse metadata from content
|
||||
*
|
||||
* @param string $content Raw content
|
||||
* @return array Parsed metadata and content without meta block
|
||||
*/
|
||||
private function parseMetadata($content) {
|
||||
$metadata = [];
|
||||
$contentWithoutMeta = $content;
|
||||
|
||||
// Check for YAML frontmatter (--- at start and end)
|
||||
if (preg_match('/^---\s*\n(.*?)\n---\s*\n(.*)$/s', $content, $matches)) {
|
||||
$metaContent = $matches[1];
|
||||
$contentWithoutMeta = $matches[2];
|
||||
|
||||
// Parse YAML-like metadata
|
||||
$lines = explode("\n", $metaContent);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, ':') !== false) {
|
||||
list($key, $value) = explode(':', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value, ' "\'');
|
||||
|
||||
// Handle boolean values
|
||||
if ($value === 'true') $value = true;
|
||||
elseif ($value === 'false') $value = false;
|
||||
|
||||
$metadata[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'metadata' => $metadata,
|
||||
'content' => $contentWithoutMeta
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Markdown content to HTML using League CommonMark
|
||||
*
|
||||
* @param string $content Raw Markdown content
|
||||
* @return array Parsed content with title and body
|
||||
*/
|
||||
private function parseMarkdown($content, $actualFilePath = '') {
|
||||
// Extract title from first H1
|
||||
$title = '';
|
||||
if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
|
||||
public function parseMarkdown($content, $actualFilePath = '') {
|
||||
// Parse metadata first
|
||||
$parsed = $this->parseMetadata($content);
|
||||
$metadata = $parsed['metadata'];
|
||||
$content = $parsed['content'];
|
||||
|
||||
// Extract title from first H1 or metadata
|
||||
$title = $metadata['title'] ?? '';
|
||||
if (empty($title) && preg_match('/^#\s+(.+)$/m', $content, $matches)) {
|
||||
$title = trim($matches[1]);
|
||||
}
|
||||
|
||||
@@ -495,8 +547,10 @@ class CodePressCMS {
|
||||
$body = preg_replace('/href="\/([^"]+)"/', 'href="?page=$1"', $body);
|
||||
|
||||
return [
|
||||
'title' => $cleanName ?: 'Untitled',
|
||||
'content' => $body
|
||||
'title' => $title ?: $cleanName ?: 'Untitled',
|
||||
'content' => $body,
|
||||
'metadata' => $metadata,
|
||||
'layout' => $metadata['layout'] ?? 'sidebar-content'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -544,7 +598,7 @@ class CodePressCMS {
|
||||
*
|
||||
* @return array Associative array of page paths to titles
|
||||
*/
|
||||
private function getAllPageTitles() {
|
||||
public function getAllPageTitles() {
|
||||
$pages = [];
|
||||
$this->scanForPageTitles($this->config['content_dir'], '', $pages);
|
||||
return $pages;
|
||||
@@ -708,19 +762,36 @@ class CodePressCMS {
|
||||
* @return array Parsed content with title and body
|
||||
*/
|
||||
private function parsePHP($filePath) {
|
||||
|
||||
// Read file content first to extract metadata
|
||||
$fileContent = file_get_contents($filePath);
|
||||
$parsed = $this->parseMetadata($fileContent);
|
||||
$metadata = $parsed['metadata'];
|
||||
|
||||
// Extract title from metadata or PHP variables
|
||||
$title = $metadata['title'] ?? '';
|
||||
|
||||
ob_start();
|
||||
// Make metadata available to the included file
|
||||
$pageMetadata = $metadata;
|
||||
include $filePath;
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Remove any remaining metadata from PHP output
|
||||
$content = preg_replace('/^---\s*\n.*?\n---\s*\n/s', '', $content);
|
||||
|
||||
// Remove metadata from content if it was included
|
||||
$parsed = $this->parseMetadata($content);
|
||||
$content = $parsed['content'];
|
||||
|
||||
// Extract filename for title
|
||||
$filename = basename($filePath);
|
||||
$cleanName = $this->formatDisplayName($filename);
|
||||
|
||||
return [
|
||||
'title' => $cleanName ?: 'Untitled',
|
||||
'content' => $content
|
||||
'title' => $title ?: $cleanName ?: 'Untitled',
|
||||
'content' => $content,
|
||||
'metadata' => $metadata,
|
||||
'layout' => $metadata['layout'] ?? 'sidebar-content'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -731,13 +802,30 @@ class CodePressCMS {
|
||||
* @return array Parsed content with title and body
|
||||
*/
|
||||
private function parseHTML($content, $actualFilePath = '') {
|
||||
// Parse metadata first
|
||||
$parsed = $this->parseMetadata($content);
|
||||
$metadata = $parsed['metadata'];
|
||||
$content = $parsed['content'];
|
||||
|
||||
// Extract title from metadata or HTML tags
|
||||
$title = $metadata['title'] ?? '';
|
||||
if (empty($title)) {
|
||||
if (preg_match('/<title>(.*?)<\/title>/i', $content, $matches)) {
|
||||
$title = trim(strip_tags($matches[1]));
|
||||
} elseif (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
|
||||
$title = trim(strip_tags($matches[1]));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract filename for title
|
||||
$filename = basename($actualFilePath);
|
||||
$cleanName = $this->formatDisplayName($filename);
|
||||
|
||||
return [
|
||||
'title' => $cleanName ?: 'Untitled',
|
||||
'content' => $content
|
||||
'title' => $title ?: $cleanName ?: 'Untitled',
|
||||
'content' => $content,
|
||||
'metadata' => $metadata,
|
||||
'layout' => $metadata['layout'] ?? 'sidebar-content'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -746,7 +834,7 @@ class CodePressCMS {
|
||||
*
|
||||
* @return bool True if content directory is empty or doesn't exist
|
||||
*/
|
||||
private function isContentDirEmpty() {
|
||||
public function isContentDirEmpty() {
|
||||
$contentDir = $this->config['content_dir'];
|
||||
if (!is_dir($contentDir)) {
|
||||
return true;
|
||||
@@ -763,21 +851,60 @@ class CodePressCMS {
|
||||
*
|
||||
* @return array Guide page data
|
||||
*/
|
||||
private function getGuidePage() {
|
||||
private function getGuidePage() {
|
||||
$lang = $this->currentLanguage;
|
||||
$guideFile = __DIR__ . '/../../../guide/' . $lang . '.codepress.md';
|
||||
|
||||
if (!file_exists($guideFile)) {
|
||||
$guideFile = __DIR__ . '/../../../guide/en.codepress.md'; // Fallback to English
|
||||
}
|
||||
}
|
||||
|
||||
$content = file_get_contents($guideFile);
|
||||
$result = $this->parseMarkdown($content);
|
||||
|
||||
// Parse metadata first
|
||||
$parsed = $this->parseMetadata($content);
|
||||
$metadata = $parsed['metadata'];
|
||||
$contentWithoutMeta = $parsed['content'];
|
||||
|
||||
// Include autoloader
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// Configure CommonMark environment
|
||||
$config = [
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
'max_nesting_level' => 100,
|
||||
];
|
||||
|
||||
// Create environment with extensions
|
||||
$environment = new \League\CommonMark\Environment\Environment($config);
|
||||
$environment->addExtension(new \League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension());
|
||||
$environment->addExtension(new \League\CommonMark\Extension\Autolink\AutolinkExtension());
|
||||
$environment->addExtension(new \League\CommonMark\Extension\Strikethrough\StrikethroughExtension());
|
||||
$environment->addExtension(new \League\CommonMark\Extension\Table\TableExtension());
|
||||
$environment->addExtension(new \League\CommonMark\Extension\TaskList\TaskListExtension());
|
||||
|
||||
// Create converter
|
||||
$converter = new \League\CommonMark\MarkdownConverter($environment);
|
||||
|
||||
// Convert to HTML
|
||||
$body = $converter->convert($contentWithoutMeta)->getContent();
|
||||
|
||||
// Extract title from metadata or first H1
|
||||
$title = $metadata['title'] ?? '';
|
||||
if (empty($title) && preg_match('/^#\s+(.+)$/m', $contentWithoutMeta, $matches)) {
|
||||
$title = trim($matches[1]);
|
||||
}
|
||||
|
||||
// Set special title for guide
|
||||
$result['title'] = $this->t('manual') . ' - CodePress CMS';
|
||||
$title = $this->t('manual') . ' - CodePress CMS';
|
||||
|
||||
return $result;
|
||||
return [
|
||||
'title' => $title,
|
||||
'content' => $body,
|
||||
'metadata' => $metadata,
|
||||
'layout' => $metadata['layout'] ?? 'content'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -912,12 +1039,21 @@ class CodePressCMS {
|
||||
// Get homepage title
|
||||
$homepageTitle = $this->getHomepageTitle();
|
||||
|
||||
// Get sidebar content from plugins
|
||||
$sidebarContent = $this->pluginManager->getSidebarContent();
|
||||
|
||||
// Get layout from page metadata
|
||||
$layout = $page['layout'] ?? 'sidebar-content';
|
||||
|
||||
// Prepare template data
|
||||
$templateData = [
|
||||
'site_title' => $this->config['site_title'],
|
||||
'page_title' => htmlspecialchars($page['title']),
|
||||
|
||||
'content' => $page['content'],
|
||||
'sidebar_content' => $sidebarContent,
|
||||
'layout' => $layout,
|
||||
'page_metadata' => $page['metadata'] ?? [],
|
||||
'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
|
||||
'menu' => $this->renderMenu($menu),
|
||||
'breadcrumb' => $breadcrumb,
|
||||
@@ -930,7 +1066,7 @@ class CodePressCMS {
|
||||
'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'] ?? '#',
|
||||
'author_git' => 'https://git.noorlander.info/E.Noorlander',
|
||||
'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',
|
||||
'cms_version' => isset($this->config['version_info']) ? 'v' . $this->config['version_info']['version'] : '',
|
||||
@@ -939,6 +1075,8 @@ class CodePressCMS {
|
||||
'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',
|
||||
'sidebar_background' => $this->config['theme']['sidebar_background'] ?? '#f8f9fa',
|
||||
'sidebar_border' => $this->config['theme']['sidebar_border'] ?? '#dee2e6',
|
||||
// Language
|
||||
'current_lang' => $this->currentLanguage,
|
||||
'current_lang_upper' => strtoupper($this->currentLanguage),
|
||||
@@ -967,17 +1105,20 @@ class CodePressCMS {
|
||||
'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')
|
||||
't_paginas' => $this->t('paginas'),
|
||||
't_author_website' => $this->t('author_website'),
|
||||
't_author_git' => $this->t('author_git')
|
||||
];
|
||||
|
||||
// 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'] = '<span class="file-details"> | ' . $templateData['file_info'] . '</span>';
|
||||
$templateData['created'] = htmlspecialchars($page['file_info']['created']);
|
||||
$templateData['modified'] = htmlspecialchars($page['file_info']['modified']);
|
||||
$templateData['file_info_block'] = true;
|
||||
} else {
|
||||
$templateData['file_info'] = '';
|
||||
$templateData['file_info_block'] = '';
|
||||
$templateData['created'] = '';
|
||||
$templateData['modified'] = '';
|
||||
$templateData['file_info_block'] = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1025,7 +1166,7 @@ class CodePressCMS {
|
||||
*
|
||||
* @return string Breadcrumb HTML
|
||||
*/
|
||||
private function generateBreadcrumb() {
|
||||
public function generateBreadcrumb() {
|
||||
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>';
|
||||
}
|
||||
|
||||
@@ -37,6 +37,16 @@ class SimpleTemplate {
|
||||
// Handle partial includes first ({{>partial}})
|
||||
$template = preg_replace_callback('/{{>([^}]+)}}/', [$this, 'replacePartial'], $template);
|
||||
|
||||
// Handle equal conditionals first
|
||||
$template = preg_replace_callback('/{{#equal\s+(\w+)\s+["\']([^"\']+)["\']}}(.*?){{\/equal}}/s', function($matches) {
|
||||
$key = $matches[1];
|
||||
$expectedValue = $matches[2];
|
||||
$content = $matches[3];
|
||||
|
||||
$actualValue = $this->data[$key] ?? '';
|
||||
return ($actualValue === $expectedValue) ? $content : '';
|
||||
}, $template);
|
||||
|
||||
// Handle conditional blocks
|
||||
foreach ($this->data as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
|
||||
216
engine/core/plugin/CMSAPI.php
Normal file
216
engine/core/plugin/CMSAPI.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
class CMSAPI
|
||||
{
|
||||
private CodePressCMS $cms;
|
||||
|
||||
public function __construct(CodePressCMS $cms)
|
||||
{
|
||||
$this->cms = $cms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page information
|
||||
*/
|
||||
public function getCurrentPage(): array
|
||||
{
|
||||
return $this->cms->getPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page title
|
||||
*/
|
||||
public function getCurrentPageTitle(): string
|
||||
{
|
||||
$page = $this->cms->getPage();
|
||||
return $page['title'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page content
|
||||
*/
|
||||
public function getCurrentPageContent(): string
|
||||
{
|
||||
$page = $this->cms->getPage();
|
||||
return $page['content'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page URL
|
||||
*/
|
||||
public function getCurrentPageUrl(): string
|
||||
{
|
||||
$page = $_GET['page'] ?? $this->cms->config['default_page'];
|
||||
$lang = $_GET['lang'] ?? $this->cms->config['language']['default'] ?? 'nl';
|
||||
return "?page={$page}&lang={$lang}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu structure
|
||||
*/
|
||||
public function getMenu(): array
|
||||
{
|
||||
return $this->cms->getMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value
|
||||
*/
|
||||
public function getConfig(string $key, $default = null)
|
||||
{
|
||||
$keys = explode('.', $key);
|
||||
$value = $this->cms->config;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (!isset($value[$k])) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation
|
||||
*/
|
||||
public function translate(string $key): string
|
||||
{
|
||||
return $this->cms->t($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language
|
||||
*/
|
||||
public function getCurrentLanguage(): string
|
||||
{
|
||||
return $this->cms->currentLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is on homepage
|
||||
*/
|
||||
public function isHomepage(): bool
|
||||
{
|
||||
$defaultPage = $this->cms->config['default_page'] ?? 'index';
|
||||
$currentPage = $_GET['page'] ?? $defaultPage;
|
||||
return $currentPage === $defaultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file info for current page
|
||||
*/
|
||||
public function getCurrentPageFileInfo(): ?array
|
||||
{
|
||||
$page = $this->cms->getPage();
|
||||
return $page['file_info'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get breadcrumb data
|
||||
*/
|
||||
public function getBreadcrumb(): string
|
||||
{
|
||||
return $this->cms->generateBreadcrumb();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content directory has content
|
||||
*/
|
||||
public function hasContent(): bool
|
||||
{
|
||||
return !$this->cms->isContentDirEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search results if searching
|
||||
*/
|
||||
public function getSearchResults(): array
|
||||
{
|
||||
if (isset($_GET['search'])) {
|
||||
return $this->cms->searchResults;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently searching
|
||||
*/
|
||||
public function isSearching(): bool
|
||||
{
|
||||
return isset($_GET['search']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available languages
|
||||
*/
|
||||
public function getAvailableLanguages(): array
|
||||
{
|
||||
return $this->cms->getAvailableLanguages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create URL for page
|
||||
*/
|
||||
public function createUrl(string $page, ?string $lang = null): string
|
||||
{
|
||||
$lang = $lang ?? $this->getCurrentLanguage();
|
||||
return "?page={$page}&lang={$lang}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute PHP file and capture output
|
||||
*/
|
||||
public function executePhpFile(string $filePath): string
|
||||
{
|
||||
if (!file_exists($filePath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
include $filePath;
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content from PHP/HTML/Markdown file
|
||||
*/
|
||||
public function getFileContent(string $filePath): string
|
||||
{
|
||||
if (!file_exists($filePath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
|
||||
switch ($extension) {
|
||||
case 'php':
|
||||
return $this->executePhpFile($filePath);
|
||||
case 'md':
|
||||
$content = file_get_contents($filePath);
|
||||
$result = $this->cms->parseMarkdown($content, $filePath);
|
||||
return $result['content'] ?? '';
|
||||
case 'html':
|
||||
return file_get_contents($filePath);
|
||||
default:
|
||||
return file_get_contents($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exists in content directory
|
||||
*/
|
||||
public function contentFileExists(string $filename): bool
|
||||
{
|
||||
$contentDir = $this->cms->config['content_dir'];
|
||||
return file_exists($contentDir . '/' . $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pages with their metadata
|
||||
*/
|
||||
public function getAllPages(): array
|
||||
{
|
||||
return $this->cms->getAllPageTitles();
|
||||
}
|
||||
}
|
||||
77
engine/core/plugin/PluginManager.php
Normal file
77
engine/core/plugin/PluginManager.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
class PluginManager
|
||||
{
|
||||
private array $plugins = [];
|
||||
private string $pluginsPath;
|
||||
private ?CMSAPI $api = null;
|
||||
|
||||
public function __construct(string $pluginsPath)
|
||||
{
|
||||
$this->pluginsPath = $pluginsPath;
|
||||
$this->loadPlugins();
|
||||
}
|
||||
|
||||
public function setAPI(CMSAPI $api): void
|
||||
{
|
||||
$this->api = $api;
|
||||
|
||||
// Inject API into all plugins that have setAPI method
|
||||
foreach ($this->plugins as $plugin) {
|
||||
if (method_exists($plugin, 'setAPI')) {
|
||||
$plugin->setAPI($api);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadPlugins(): void
|
||||
{
|
||||
if (!is_dir($this->pluginsPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pluginDirs = glob($this->pluginsPath . '/*', GLOB_ONLYDIR);
|
||||
|
||||
foreach ($pluginDirs as $pluginDir) {
|
||||
$pluginName = basename($pluginDir);
|
||||
$pluginFile = $pluginDir . '/' . $pluginName . '.php';
|
||||
|
||||
if (file_exists($pluginFile)) {
|
||||
require_once $pluginFile;
|
||||
|
||||
$className = $pluginName;
|
||||
if (class_exists($className)) {
|
||||
$this->plugins[$pluginName] = new $className();
|
||||
|
||||
// Inject API if already available
|
||||
if ($this->api && method_exists($this->plugins[$pluginName], 'setAPI')) {
|
||||
$this->plugins[$pluginName]->setAPI($this->api);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlugin(string $name): ?object
|
||||
{
|
||||
return $this->plugins[$name] ?? null;
|
||||
}
|
||||
|
||||
public function getAllPlugins(): array
|
||||
{
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
public function getSidebarContent(): string
|
||||
{
|
||||
$sidebarContent = '';
|
||||
|
||||
foreach ($this->plugins as $plugin) {
|
||||
if (method_exists($plugin, 'getSidebarContent')) {
|
||||
$sidebarContent .= $plugin->getSidebarContent();
|
||||
}
|
||||
}
|
||||
|
||||
return $sidebarContent;
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,18 @@ return [
|
||||
'page_not_found' => 'Page Not Found',
|
||||
'page_not_found_text' => 'The page you are looking for does not exist.',
|
||||
'mappen' => 'Folders',
|
||||
'paginas' => 'Pages'
|
||||
'paginas' => 'Pages',
|
||||
'author_website' => 'Author website',
|
||||
'author_git' => 'Author Git',
|
||||
'plugins' => 'Plugins',
|
||||
'templates' => 'Templates',
|
||||
'layouts' => 'Layouts',
|
||||
'sidebar_content' => 'Sidebar + Content',
|
||||
'content_only' => 'Content Only',
|
||||
'sidebar_only' => 'Sidebar Only',
|
||||
'content_sidebar' => 'Content + Sidebar',
|
||||
'plugin_development' => 'Plugin Development',
|
||||
'template_system' => 'Template System',
|
||||
'mqtt_tracking' => 'MQTT Tracking',
|
||||
'real_time_analytics' => 'Real-time Analytics'
|
||||
];
|
||||
@@ -22,5 +22,18 @@ return [
|
||||
'page_not_found' => 'Pagina niet gevonden',
|
||||
'page_not_found_text' => 'De pagina die u zoekt bestaat niet.',
|
||||
'mappen' => 'Mappen',
|
||||
'paginas' => 'Pagina\'s'
|
||||
'paginas' => 'Pagina\'s',
|
||||
'author_website' => 'Auteur website',
|
||||
'author_git' => 'Auteur Git',
|
||||
'plugins' => 'Plugins',
|
||||
'templates' => 'Templates',
|
||||
'layouts' => 'Layouts',
|
||||
'sidebar_content' => 'Sidebar + Content',
|
||||
'content_only' => 'Alleen Content',
|
||||
'sidebar_only' => 'Alleen Sidebar',
|
||||
'content_sidebar' => 'Content + Sidebar',
|
||||
'plugin_development' => 'Plugin Ontwikkeling',
|
||||
'template_system' => 'Template Systeem',
|
||||
'mqtt_tracking' => 'MQTT Tracking',
|
||||
'real_time_analytics' => 'Real-time Analytics'
|
||||
];
|
||||
@@ -1,20 +1,39 @@
|
||||
<footer class="bg-light border-top py-3">
|
||||
<footer class="bg-light border-top py-1">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="file-info">
|
||||
<i class="bi bi-file-text"></i>
|
||||
<span class="page-title" title="{{page_title}}">{{page_title}}</span>
|
||||
{{{file_info_block}}}
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center">
|
||||
<div class="file-info mb-2 mb-md-0">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-file-text footer-icon" title="{{t_file_details}}: {{page_title}}"></i>
|
||||
<span class="page-title d-none d-lg-inline" title="{{page_title}}">{{page_title}}</span>
|
||||
{{#file_info_block}}
|
||||
<span class="ms-2">
|
||||
<i class="bi bi-calendar-plus footer-icon" title="{{t_created}}: {{created}}"></i>
|
||||
<span class="file-created">{{created}}</span>
|
||||
<i class="bi bi-calendar-check ms-1 footer-icon" title="{{t_modified}}: {{modified}}"></i>
|
||||
<span class="file-modified">{{modified}}</span>
|
||||
</span>
|
||||
{{/file_info_block}}
|
||||
</small>
|
||||
</div>
|
||||
<div class="site-info">
|
||||
<small class="text-muted">
|
||||
<a href="?guide&lang={{current_lang}}" class="guide-link" title="{{t_guide}}">
|
||||
<a href="?guide&lang={{current_lang}}" class="footer-icon guide" title="{{t_guide}}">
|
||||
<i class="bi bi-book"></i>
|
||||
</a>
|
||||
<span class="ms-2">|</span>
|
||||
{{t_powered_by}} <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a> {{cms_version}}
|
||||
<span class="ms-1">|</span>
|
||||
<a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener" class="footer-icon cms" title="{{t_powered_by}} CodePress CMS {{cms_version}}">
|
||||
<i class="bi bi-cpu"></i>
|
||||
</a>
|
||||
<span class="ms-1">|</span>
|
||||
<a href="{{author_website}}" target="_blank" rel="noopener" class="footer-icon website" title="{{t_author_website}}">
|
||||
<i class="bi bi-globe"></i>
|
||||
</a>
|
||||
<span class="ms-1">|</span>
|
||||
<a href="{{author_git}}" target="_blank" rel="noopener" class="footer-icon git" title="{{t_author_git}}">
|
||||
<i class="bi bi-git"></i>
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
--header-font: {{header_font_color}};
|
||||
--nav-bg: {{navigation_color}};
|
||||
--nav-font: {{navigation_font_color}};
|
||||
--sidebar-bg: {{sidebar_background}};
|
||||
--sidebar-border: {{sidebar_border}};
|
||||
}
|
||||
|
||||
/* Header styles */
|
||||
@@ -151,14 +153,112 @@
|
||||
background-color: rgba(255,255,255,0.2) !important;
|
||||
border-bottom: 2px solid var(--nav-font) !important;
|
||||
}
|
||||
|
||||
/* Sidebar styling */
|
||||
.sidebar-column {
|
||||
background-color: var(--sidebar-bg) !important;
|
||||
border-right: 1px solid var(--sidebar-border) !important;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 1.5rem;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-column {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 2rem;
|
||||
padding-bottom: 80px !important;
|
||||
}
|
||||
|
||||
/* Ensure full height layout */
|
||||
.main-content {
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar-column {
|
||||
border-right: none !important;
|
||||
border-top: 1px solid var(--sidebar-border) !important;
|
||||
min-height: auto;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.content-column {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
min-height: auto;
|
||||
padding-bottom: 2rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet and mobile: sidebar below content */
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar-column {
|
||||
order: 2 !important;
|
||||
}
|
||||
|
||||
.content-column {
|
||||
order: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer icon hover effects */
|
||||
.footer-icon {
|
||||
color: #6c757d;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease-in-out;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.footer-icon:hover {
|
||||
color: #0d6efd;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.footer-icon:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Specific icon hover colors */
|
||||
.footer-icon.guide:hover {
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.footer-icon.cms:hover {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.footer-icon.git:hover {
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.footer-icon.website:hover {
|
||||
color: #fd7e14;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{>header}}
|
||||
<header id="site-header">
|
||||
{{>header}}
|
||||
</header>
|
||||
|
||||
{{>navigation}}
|
||||
<nav id="site-navigation">
|
||||
{{>navigation}}
|
||||
</nav>
|
||||
|
||||
<div class="breadcrumb-section bg-light border-bottom">
|
||||
<div id="site-breadcrumb" class="breadcrumb-section bg-light border-bottom">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 py-2">
|
||||
@@ -168,17 +268,87 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid main-content" style="padding-bottom: 80px;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<main class="col-12">
|
||||
{{>content_template}}
|
||||
</main>
|
||||
<main id="site-main" class="main-content" style="padding: 0;">
|
||||
{{#sidebar_content}}
|
||||
{{#equal layout "sidebar-content"}}
|
||||
<div class="row g-0">
|
||||
<aside id="site-sidebar" class="col-lg-3 col-md-4 sidebar-column order-2 order-md-1">
|
||||
<div class="sidebar h-100">
|
||||
{{{sidebar_content}}}
|
||||
</div>
|
||||
</aside>
|
||||
<section id="site-content" class="col-lg-9 col-md-8 content-column order-1 order-md-2">
|
||||
<div class="content-wrapper p-4">
|
||||
{{>content_template}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{/equal}}
|
||||
|
||||
{{#equal layout "content"}}
|
||||
<div class="container">
|
||||
<section id="site-content" class="col-12">
|
||||
<div class="content-wrapper p-4">
|
||||
{{>content_template}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{{/equal}}
|
||||
|
||||
{{#equal layout "sidebar"}}
|
||||
<div class="container-fluid">
|
||||
<aside id="site-sidebar" class="col-12 sidebar-column">
|
||||
<div class="sidebar">
|
||||
{{{sidebar_content}}}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{{/equal}}
|
||||
|
||||
{{#equal layout "content-sidebar"}}
|
||||
<div class="row g-0">
|
||||
<section id="site-content" class="col-lg-9 col-md-8 content-column order-1">
|
||||
<div class="content-wrapper p-4">
|
||||
{{>content_template}}
|
||||
</div>
|
||||
</section>
|
||||
<aside id="site-sidebar" class="col-lg-3 col-md-4 sidebar-column order-2">
|
||||
<div class="sidebar h-100">
|
||||
{{{sidebar_content}}}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{{/equal}}
|
||||
|
||||
{{#equal layout "content-sidebar-reverse"}}
|
||||
<div class="row g-0 flex-row-reverse">
|
||||
<section id="site-content" class="col-lg-9 col-md-8 content-column">
|
||||
<div class="content-wrapper p-4">
|
||||
{{>content_template}}
|
||||
</div>
|
||||
</section>
|
||||
<aside id="site-sidebar" class="col-lg-3 col-md-4 sidebar-column">
|
||||
<div class="sidebar h-100">
|
||||
{{{sidebar_content}}}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{{/equal}}
|
||||
{{/sidebar_content}}
|
||||
{{^sidebar_content}}
|
||||
<div class="container">
|
||||
<section id="site-content" class="col-12">
|
||||
<div class="content-wrapper p-4">
|
||||
{{>content_template}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{{/sidebar_content}}
|
||||
</main>
|
||||
|
||||
{{>footer}}
|
||||
<footer id="site-footer">
|
||||
{{>footer}}
|
||||
</footer>
|
||||
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/app.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user