v2.6.0: Content backup/git versioning, plugin type system, docs update

New features:
- ContentBackup class with ZIP backup/restore and git versioning
- Admin backup & restore page (content-backup.twig) with git init/commit/log/restore
- Plugin type system: system (blue) vs content (green) with visual badges
- PluginAPIInterface + AdminPluginAPI for plugin architecture
- Essential plugin flag (cannot edit/deactivate/delete)

Improvements:
- Consolidated enabled_plugins config (removed plugins.enabled)
- Removed Analytics/Logging toggles from admin config page
- Fixed Dashboard plugin Twig comments rendered as text
- Updated 20 guide files (NL+EN): configuratie, plugins, plugin-development,
  core-classes, theme-json, layouts, scss-styling, admin-beheerder, nieuw-thema, architectuur
- Improved accessibility test script (grep -E, min/max checks)

Cleanup:
- Removed unused classes: ARIAComponents, AccessibilityManager, ContentSecurityPolicy, etc.
- Removed vendor packages: mustache/mustache, php-mqtt/client
- Removed old templates: logs.twig, statistics.twig (now plugins)
- Moved language files to language/ directory

Tests:
- Pentest: 30/30 passed, 0 vulnerabilities
- WCAG 2.1 AA: 25/25 passed, 100% compliance
This commit is contained in:
2026-08-15 19:21:04 +02:00
parent cd498c8c3a
commit 1492dcf71f
207 changed files with 3742 additions and 22688 deletions
+94 -61
View File
@@ -146,26 +146,31 @@ class CodePressCMS {
}
/**
* Get all available languages from lang directory
* Get all available languages from the language directory
*
* Each language is a subdirectory under language/ containing a site.php file.
*
* @return array Available languages with their codes and names
*/
public function getAvailableLanguages() {
$langDir = __DIR__ . '/../../lang/';
$langDir = __DIR__ . '/../../../language/';
$languages = [];
if (!is_dir($langDir)) {
return $languages;
}
$files = scandir($langDir);
foreach ($files as $file) {
if (preg_match('/^([a-z]{2})\.php$/', $file, $matches)) {
$langCode = $matches[1];
$langFile = $langDir . $file;
$entries = scandir($langDir);
foreach ($entries as $entry) {
if (preg_match('/^[a-z]{2}$/', $entry) && is_dir($langDir . $entry)) {
$langCode = $entry;
$siteFile = $langDir . $entry . '/site.php';
if (file_exists($langFile)) {
$translations = include $langFile;
if (file_exists($siteFile)) {
$translations = include $siteFile;
if (!is_array($translations)) {
continue;
}
$languages[$langCode] = [
'code' => $langCode,
'name' => $translations['site_title'] ?? strtoupper($langCode),
@@ -179,22 +184,28 @@ class CodePressCMS {
}
/**
* Load translations for specified language
* Load site translations for specified language
*
* @param string $lang Language code
* @return array Translations array
*/
private function loadTranslations($lang) {
$langFile = __DIR__ . '/../../lang/' . $lang . '.php';
$langDir = __DIR__ . '/../../../language/';
$langFile = $langDir . $lang . '/site.php';
if (file_exists($langFile)) {
$translations = include $langFile;
return $translations;
if (is_array($translations)) {
return $translations;
}
}
// Fallback to default language
$defaultLang = $this->config['language']['default'] ?? 'nl';
$defaultLangFile = __DIR__ . '/../../lang/' . $defaultLang . '.php';
$defaultLangFile = $langDir . $defaultLang . '/site.php';
if (file_exists($defaultLangFile)) {
return include $defaultLangFile;
$translations = include $defaultLangFile;
if (is_array($translations)) {
return $translations;
}
}
// Return empty array if no translation found
return [];
@@ -208,19 +219,77 @@ class CodePressCMS {
*/
private function getNativeLanguageName($langCode) {
$names = [
'nl' => 'Nederlands',
'en' => 'English',
'fr' => 'Français',
'af' => 'Afrikaans',
'am' => 'አማርኛ',
'ar' => 'العربية',
'az' => 'Azərbaycan',
'bg' => 'Български',
'bn' => 'বাংলা',
'bs' => 'Bosanski',
'ca' => 'Català',
'cs' => 'Čeština',
'da' => 'Dansk',
'de' => 'Deutsch',
'el' => 'Ελληνικά',
'en' => 'English',
'es' => 'Español',
'et' => 'Eesti',
'eu' => 'Euskara',
'fa' => 'فارسی',
'fi' => 'Suomi',
'fil' => 'Filipino',
'fr' => 'Français',
'ga' => 'Gaeilge',
'gl' => 'Galego',
'gu' => 'ગુજરાતી',
'he' => 'עברית',
'hi' => 'हिन्दी',
'hr' => 'Hrvatski',
'hu' => 'Magyar',
'hy' => 'Հայերեն',
'id' => 'Bahasa Indonesia',
'is' => 'Íslenska',
'it' => 'Italiano',
'pt' => 'Português',
'ru' => 'Русский',
'zh' => '中文',
'ja' => '日本語',
'ar' => 'العربية'
'ka' => 'ქართული',
'kk' => 'Қазақша',
'km' => 'ខ្មែរ',
'ko' => '한국어',
'lo' => 'ລາວ',
'lt' => 'Lietuvių',
'lv' => 'Latviešu',
'mk' => 'Македонски',
'mn' => 'Монгол',
'mr' => 'मराठी',
'ms' => 'Bahasa Melayu',
'mt' => 'Malti',
'my' => 'မြန်မာ',
'ne' => 'नेपाली',
'nl' => 'Nederlands',
'no' => 'Norsk',
'pa' => 'ਪੰਜਾਬੀ',
'pl' => 'Polski',
'pt' => 'Português',
'ro' => 'Română',
'ru' => 'Русский',
'si' => 'සිංහල',
'sk' => 'Slovenčina',
'sl' => 'Slovenščina',
'sq' => 'Shqip',
'sr' => 'Српски',
'sv' => 'Svenska',
'sw' => 'Kiswahili',
'ta' => 'தமிழ்',
'te' => 'తెలుగు',
'th' => 'ไทย',
'tl' => 'Tagalog',
'tr' => 'Türkçe',
'uk' => 'Українська',
'ur' => 'اردو',
'uz' => 'Oʻzbek',
'vi' => 'Tiếng Việt',
'zh' => '中文',
];
return $names[$langCode] ?? strtoupper($langCode);
}
@@ -1051,11 +1120,6 @@ class CodePressCMS {
return $result;
}
/**
* Detect user language from browser headers
*
* @return string Language code ('nl' or 'en')
*/
/**
* Generate directory listing page
*
@@ -1077,9 +1141,7 @@ class CodePressCMS {
'title' => $title,
'content' => $content
];
// Debug: ensure we're returning the right title
if (!is_dir($dirPath)) {
return [
'title' => $title,
@@ -1219,7 +1281,7 @@ class CodePressCMS {
'lang_switch_url' => '',
'author_name' => $this->config['author']['name'] ?? 'CodePress Developer',
'author_website' => $this->normalizeUrl($this->config['author']['website'] ?? '') ?: '#',
'author_git' => 'https://git.noorlander.info/E.Noorlander',
'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',
'block_ai_bots' => !empty($this->config['security']['block_ai_bots']),
@@ -1496,35 +1558,6 @@ class CodePressCMS {
};
}
/**
* Determine content type for current page
*
* @param array $page Page data
* @return string Content type (markdown, php, html)
*/
private function getContentType($page) {
// Try to determine content type from page request
$pagePath = $_GET['page'] ?? $this->getEffectiveDefaultPage();
$pagePath = preg_replace('/\.[^.]+$/', '', $pagePath);
$filePath = $this->config['content_dir'] . '/' . $pagePath;
// Check for different file extensions
if (file_exists($filePath . '.md')) {
return 'markdown';
} elseif (file_exists($filePath . '.php')) {
return 'php';
} elseif (file_exists($filePath . '.html')) {
return 'html';
} elseif (file_exists($filePath)) {
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
return in_array($extension, ['md', 'php', 'html']) ? $extension : 'markdown';
}
// Default to markdown
return 'markdown';
}
/**
* Auto-detect the first available content page
*
@@ -1669,4 +1702,4 @@ class CodePressCMS {
{
return str_replace('-/assets/', '/-assets/', $content);
}
}// Guide fix: 1786349431
}