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
+50 -191
View File
@@ -1,219 +1,78 @@
{% extends "layouts/admin.twig" %}
{% block title %}Dashboard - CodePress Admin{% endblock %}
{% block title %}{{ ta.dashboard|default('Dashboard') }} - {{ ta.admin_title|default('CodePress Admin') }}{% endblock %}
{% block content %}
<h2 class="mb-4"><i class="bi bi-speedometer2"></i> Dashboard</h2>
<h2 class="mb-4"><i class="bi bi-speedometer2"></i> {{ ta.dashboard|default('Dashboard') }}</h2>
<div class="alert alert-light border mb-4">
<strong>Welkom, {{ user.username }}</strong> — Ingelogd als <span class="badge bg-{{ user_role == 'admin' ? 'danger' : (user_role == 'content-manager' ? 'primary' : (user_role == 'bi-manager' ? 'success' : 'warning')) }}">{{ role_label(user_role) }}</span>
<strong>{{ ta.welcome|default('Welkom,') }} {{ user.username }}</strong> — {{ ta.logged_in_as|default('Ingelogd als') }} <span class="badge bg-{{ user_role == 'admin' ? 'danger' : (user_role == 'content-manager' ? 'primary' : (user_role == 'bi-manager' ? 'success' : 'warning')) }}">{{ role_label(user_role) }}</span>
</div>
{# Analytics stats - only for roles with statistics permission #}
{% if has_permission('statistics') %}
<div class="row g-4 mb-4">
<div class="col-md-4">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Weergaven (30 dagen)</h6>
<h3 class="mb-0">{{ (analytics_summary.totals.views ?? 0)|number_format(0, ',', '.') }}</h3>
</div>
<i class="bi bi-eye stat-icon text-primary"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Unieke bezoekers (30 dagen)</h6>
<h3 class="mb-0">{{ (analytics_summary.totals.uniques ?? 0)|number_format(0, ',', '.') }}</h3>
</div>
<i class="bi bi-people stat-icon text-success"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Grootste land</h6>
<h3 class="mb-0">
{{ get_country_flag(analytics_summary.countries|keys|first) }}
<span class="fs-5">{{ get_country_name(analytics_summary.countries|keys|first) }}</span>
</h3>
</div>
<a href="/admin/statistics" class="btn btn-sm btn-outline-secondary">Statistieken →</a>
</div>
</div>
</div>
</div>
{% endif %}
{# Content stats - only for roles with content permission #}
{% if has_permission('content') %}
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Pagina's</h6>
<h3 class="mb-0">{{ stats.pages }}</h3>
</div>
<i class="bi bi-file-earmark-text stat-icon text-primary"></i>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Mappen</h6>
<h3 class="mb-0">{{ stats.directories }}</h3>
</div>
<i class="bi bi-folder stat-icon text-warning"></i>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Content grootte</h6>
<h3 class="mb-0">{{ stats.content_size }}</h3>
</div>
<i class="bi bi-hdd stat-icon text-info"></i>
</div>
</div>
</div>
</div>
{% endif %}
{# System stats - only for admin/site-admin #}
{% if has_permission('plugins') or has_permission('config') %}
<div class="row g-4 mb-4">
{% if has_permission('plugins') %}
<div class="col-md-3">
<div class="card stat-card shadow-sm">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Plugins</h6>
<h3 class="mb-0">{{ stats.plugins }}</h3>
</div>
<i class="bi bi-plug stat-icon text-success"></i>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div class="row g-4">
{# Site information - only for admin #}
{% if has_permission('config') %}
{# Site information #}
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header"><i class="bi bi-info-circle"></i> Site informatie</div>
<div class="card-header"><i class="bi bi-info-circle"></i> {{ ta.site_info|default('Site informatie') }}</div>
<div class="card-body">
<table class="table table-sm mb-0">
<tr><td class="text-muted">Site titel</td><td>{{ site_config.site_title|default('CodePress') }}</td></tr>
<tr><td class="text-muted">Standaard taal</td><td>{{ site_config.language.default|default('nl') }}</td></tr>
<tr><td class="text-muted">Auteur</td><td>{{ site_config.author.name|default('-') }}</td></tr>
<tr><td class="text-muted">CodePress versie</td><td>{{ stats.cms_version }}</td></tr>
<tr><td class="text-muted">PHP versie</td><td>{{ stats.php_version }}</td></tr>
<tr><td class="text-muted">Besturingssysteem</td><td>{{ stats.os }}</td></tr>
<tr><td class="text-muted">Config geladen</td><td>{% if stats.config_exists %}<span class="badge bg-success">Ja</span>{% else %}<span class="badge bg-danger">Nee</span>{% endif %}</td></tr>
<tr><td class="text-muted">{{ ta.site_title|default('Site titel') }}</td><td>{{ site_config.site_title|default('CodePress') }}</td></tr>
<tr><td class="text-muted">{{ ta.default_lang|default('Standaard taal') }}</td><td>{{ site_config.language.default|default('nl') }}</td></tr>
<tr><td class="text-muted">{{ ta.cms_version|default('CodePress versie') }}</td><td>{{ stats.cms_version }}</td></tr>
<tr><td class="text-muted">{{ ta.php_version|default('PHP versie') }}</td><td>{{ stats.php_version }}</td></tr>
<tr><td class="text-muted">{{ ta.os|default('Besturingssysteem') }}</td><td>{{ stats.os }}</td></tr>
<tr><td class="text-muted">{{ ta.config_loaded|default('Config geladen') }}</td><td>{% if stats.config_exists %}<span class="badge bg-success">{{ ta.yes|default('Ja') }}</span>{% else %}<span class="badge bg-danger">{{ ta.no|default('Nee') }}</span>{% endif %}</td></tr>
</table>
</div>
</div>
</div>
{# Content information #}
{% if has_permission('content') %}
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header"><i class="bi bi-folder2-open"></i> {{ ta.content_info|default('Content informatie') }}</div>
<div class="card-body">
<table class="table table-sm mb-0">
<tr><td class="text-muted">{{ ta.pages|default('Pagina\'s') }}</td><td><span class="badge bg-primary">{{ stats.pages }}</span></td></tr>
<tr><td class="text-muted">{{ ta.folders|default('Mappen') }}</td><td><span class="badge bg-warning text-dark">{{ stats.directories }}</span></td></tr>
<tr><td class="text-muted">{{ ta.content_size|default('Content grootte') }}</td><td>{{ stats.content_size }}</td></tr>
</table>
</div>
</div>
</div>
{% endif %}
{# Recent activity - only for roles with logs permission #}
{% if has_permission('logs') %}
{# Plugins overview #}
{% if has_permission('plugins') %}
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-activity"></i> Recente activiteit</span>
<a href="/admin/logs?tab=admin" class="btn btn-sm btn-outline-secondary">Bekijk alle →</a>
<span><i class="bi bi-plug"></i> {{ ta.plugins|default('Plugins') }}</span>
<a href="/admin/plugins" class="btn btn-sm btn-outline-secondary">{{ ta.manage|default('Beheren') }}</a>
</div>
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
{% if recent_logs is empty %}
<p class="text-muted mb-0">Geen activiteit geregistreerd.</p>
{% else %}
<ul class="list-unstyled mb-0">
{% for log in recent_logs %}
<li class="mb-2 pb-2 border-bottom small">
<span class="text-muted">{{ log.time }}</span>
<span class="badge bg-{{ log.level == 'warning' ? 'warning text-dark' : (log.level == 'error' ? 'danger' : 'info') }} me-1">{{ log.level }}</span>
<code class="text-muted">{{ log.ip }}</code>
{{ log.message }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-globe"></i> Recente requests</span>
<a href="/admin/logs?tab=requests" class="btn btn-sm btn-outline-secondary">Bekijk alle →</a>
</div>
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
{% if recent_requests is empty %}
<p class="text-muted mb-0">Geen requests geregistreerd.</p>
{% else %}
<ul class="list-unstyled mb-0">
{% for log in recent_requests %}
<li class="mb-2 pb-2 border-bottom small d-flex justify-content-between align-items-center">
<div>
<span class="text-muted me-1">{{ log.time }}</span>
<code class="text-muted me-1">{{ log.ip }}</code>
<span class="fw-bold">{{ log.page }}</span>
</div>
{% if log.visitor_info is not empty %}
<span class="badge bg-{{ log.visitor_info.badge }}" title="{{ log.ua }}">
<i class="bi {{ log.visitor_info.icon }}"></i> {{ log.visitor_info.label }}
</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
<div class="card-body">
<table class="table table-sm mb-0">
{% for pluginName, pluginInfo in plugin_overview %}
<tr>
<td>
<i class="bi bi-plug-fill"></i> {{ pluginName }}
</td>
<td>
{% if pluginInfo.enabled %}
<span class="badge bg-success">{{ ta.active|default('Actief') }}</span>
{% else %}
<span class="badge bg-secondary">{{ ta.inactive|default('Inactief') }}</span>
{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="2" class="text-muted text-center">{{ ta.no_plugins|default('Geen plugins gevonden.') }}</td></tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endif %}
{# Quick actions - role-specific #}
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header"><i class="bi bi-lightning"></i> Snelle acties</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if has_permission('content') %}
<a href="/admin/content-new" class="btn btn-outline-primary"><i class="bi bi-plus-lg"></i> Nieuwe pagina</a>
<a href="/admin/content" class="btn btn-outline-info"><i class="bi bi-folder2-open"></i> Content beheren</a>
{% endif %}
{% if has_permission('config') %}
<a href="/admin/config" class="btn btn-outline-secondary"><i class="bi bi-sliders"></i> Configuratie bewerken</a>
{% endif %}
{% if has_permission('statistics') %}
<a href="/admin/statistics" class="btn btn-outline-secondary"><i class="bi bi-bar-chart"></i> Statistieken bekijken</a>
{% endif %}
{% if has_permission('theme') %}
<a href="/admin/theme" class="btn btn-outline-secondary"><i class="bi bi-palette"></i> Thema beheren</a>
{% endif %}
{% if has_permission('plugins') %}
<a href="/admin/plugins" class="btn btn-outline-secondary"><i class="bi bi-plug"></i> Plugins beheren</a>
{% endif %}
<a href="/" target="_blank" class="btn btn-outline-success"><i class="bi bi-box-arrow-up-right"></i> Website bekijken</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}