Security: verwijder hardcoded wachtwoord, voeg random-wachtwoord-generator toe bij eerste installatie
This commit is contained in:
@@ -1,13 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Analytics - Aggregated stats recorder & statistics manager for CodePress CMS
|
||||
* Analytics - Aggregated stats recorder & statistics manager for CodePress CMS.
|
||||
*
|
||||
* Registreert geaggregeerde bezoekersstatistieken (views, uniques, landen,
|
||||
* referrers, bots) in een JSON-bestand met file locking en retentie-cleanup.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class Analytics
|
||||
{
|
||||
/**
|
||||
* Pad naar het JSON-statistiekbestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string Pad naar stats.json.
|
||||
*/
|
||||
private string $statsFile;
|
||||
|
||||
/**
|
||||
* Analytics configuratie-array.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string,mixed> Analytics configuratie.
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* Initialiseer de Analytics-recorder.
|
||||
*
|
||||
* Stelt het pad naar het statistiekbestand in en maakt de opslagmap aan
|
||||
* indien deze nog niet bestaat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $analyticsConfig {
|
||||
* Optioneel. Analytics configuratie.
|
||||
*
|
||||
* @type bool $enabled Of analytics actief is. Default false.
|
||||
* @type int $retention_days Aantal dagen dat statistieken bewaard blijven. Default 400.
|
||||
* }
|
||||
*/
|
||||
public function __construct(array $analyticsConfig = [])
|
||||
{
|
||||
$this->config = $analyticsConfig;
|
||||
@@ -19,14 +52,21 @@ class Analytics
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a page visit in aggregated stats.json
|
||||
* Registreer een paginabezoek in de geaggregeerde statistieken.
|
||||
*
|
||||
* @param string $page Page key
|
||||
* @param string $ip Client IP
|
||||
* @param string $userAgent User Agent string
|
||||
* @param string $referrer Referrer string
|
||||
* @param string $country Resolved country code (e.g. NL, BE)
|
||||
* @param string $status Status string (ok, blocked:ai, blocked:ratelimit, etc.)
|
||||
* Identificeert bezoeker (bot/mens), anonimiseert het IP-adres per dag met
|
||||
* een zout, aggregeert totals/countries/pages/referrers/days/uniques, past
|
||||
* de retentie toe en schrijft atomair (met bestandslock) naar stats.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $page Page key van de bezochte pagina.
|
||||
* @param string $ip Client IP-adres.
|
||||
* @param string $userAgent User-Agent string van de bezoeker.
|
||||
* @param string $referrer Referrer URL string.
|
||||
* @param string|null $country Opgeloste landcode (bijv. NL, BE). Null indien onbekend.
|
||||
* @param string $status Status string (ok, blocked:ai, blocked:ratelimit, enz.).
|
||||
* @return void
|
||||
*/
|
||||
public function record(string $page, string $ip, string $userAgent, string $referrer, ?string $country, string $status): void
|
||||
{
|
||||
@@ -145,10 +185,16 @@ class Analytics
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregated statistics data for a specific period
|
||||
* Haal geaggregeerde statistieken op voor een specifieke periode.
|
||||
*
|
||||
* @param int $days Number of days (e.g. 7, 30, 90, 0 for all)
|
||||
* @return array Aggregated stats
|
||||
* Leest stats.json en aggregeert totals, landen, pagina's, referrers en
|
||||
* een daily chart. Bij $days = 0 wordt alles-tijd geretourneerd, anders
|
||||
* worden ontbrekende dagen in de range opgevuld voor een vloeiende grafiek.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $days Aantal dagen (bijv. 7, 30, 90), of 0 voor alles-tijd. Default 30.
|
||||
* @return array<string,mixed> Geaggregeerde statistieken met keys totals, countries, pages, referrers, daily_chart.
|
||||
*/
|
||||
public function getStats(int $days = 30): array
|
||||
{
|
||||
|
||||
+41
-12
@@ -1,12 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* BotGuard - Bot, AI Crawler, and Scraper detection & protection
|
||||
* BotGuard - Bot, AI Crawler, and Scraper detection & protection.
|
||||
*
|
||||
* Detecteert en blokkeert bots, AI-crawlers en scrapers op basis van de
|
||||
* User-Agent string, en genereert dynamische robots.txt-inhoud op basis
|
||||
* van de beveiligingsinstellingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class BotGuard
|
||||
{
|
||||
/**
|
||||
* Map of bot signatures by category and pattern
|
||||
* Geeft de map met bot-handtekeningen per categorie en patroon.
|
||||
*
|
||||
* Retourneert een multi-dimensionale array met de categorieën 'ai',
|
||||
* 'search' en 'scraper', elk met een mapping van patroon naar weergavelabel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,array<string,string>> Bot-handtekeningen per categorie.
|
||||
*/
|
||||
public static function getBotSignatures(): array
|
||||
{
|
||||
@@ -62,10 +75,16 @@ class BotGuard
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify a User-Agent string
|
||||
* Identificeer een User-Agent string.
|
||||
*
|
||||
* @param string $ua User-Agent string
|
||||
* @return array Array with category, pattern, and display label
|
||||
* Vergelijkt de User-Agent met de bekende bot-handtekeningen (ai, search,
|
||||
* scraper), daarna met een generieke bot-regex. Bij geen match wordt de
|
||||
* bezoeker als mens beschouwd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ua User-Agent string.
|
||||
* @return array{category:string,pattern:string,label:string} Array met category, pattern en display label.
|
||||
*/
|
||||
public static function identify(string $ua): array
|
||||
{
|
||||
@@ -105,11 +124,16 @@ class BotGuard
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a request should be blocked based on security settings
|
||||
* Bepaal of een request geblokkeerd moet worden op basis van beveiligingsinstellingen.
|
||||
*
|
||||
* @param string $ua User-Agent string
|
||||
* @param array $securitySettings Security configuration array
|
||||
* @return string|null Reason string if blocked, null if allowed
|
||||
* Controleert achtereenvolgens: lege User-Agent, custom blocklist, en
|
||||
* categorie-specifieke blokkades (ai, search, scraper, generic bot).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ua User-Agent string.
|
||||
* @param array $securitySettings Beveiligingsconfiguratie-array.
|
||||
* @return string|null Reden-string indien geblokkeerd (bijv. 'blocked:ai'), null indien toegestaan.
|
||||
*/
|
||||
public static function shouldBlock(string $ua, array $securitySettings): ?string
|
||||
{
|
||||
@@ -158,10 +182,15 @@ class BotGuard
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dynamic robots.txt content based on security settings
|
||||
* Genereer dynamische robots.txt-inhoud op basis van de beveiligingsinstellingen.
|
||||
*
|
||||
* @param array $securitySettings Security configuration array
|
||||
* @return string Robots.txt content
|
||||
* Stelt globale regels op voor zoekmachines en voegt, indien AI-bots
|
||||
* geblokkeerd worden, per AI-crawler een Disallow-blok toe.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $securitySettings Beveiligingsconfiguratie-array.
|
||||
* @return string Robots.txt-inhoud.
|
||||
*/
|
||||
public static function generateRobotsTxt(array $securitySettings): string
|
||||
{
|
||||
|
||||
@@ -1,16 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Cache interface voor key-value opslag met TTL-ondersteuning.
|
||||
*
|
||||
* Definieert een algemeen contract voor cache-implementaties binnen CodePress.
|
||||
* Implementaties hoeven alleen lezen, schrijven, verwijderen en controleren
|
||||
* van waarden op basis van een sleutel te ondersteunen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
interface CacheInterface {
|
||||
/**
|
||||
* Haal een waarde uit de cache op basis van de sleutel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel van de cache-waarde.
|
||||
* @return mixed De gecachte waarde, of null wanneer deze ontbreekt of verlopen is.
|
||||
*/
|
||||
public function get(string $key);
|
||||
|
||||
/**
|
||||
* Sla een waarde op in de cache met een time-to-live.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel waaronder de waarde wordt opgeslagen.
|
||||
* @param mixed $value Te cachen waarde.
|
||||
* @param int $ttl Levensduur in seconden. Default 3600.
|
||||
* @return bool True bij succes, false bij een schrijffout.
|
||||
*/
|
||||
public function set(string $key, $value, int $ttl = 3600): bool;
|
||||
|
||||
/**
|
||||
* Verwijder een waarde uit de cache.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel die verwijderd moet worden.
|
||||
* @return bool True bij succes of wanneer de sleutel niet bestond.
|
||||
*/
|
||||
public function delete(string $key): bool;
|
||||
|
||||
/**
|
||||
* Wis alle waarden uit de cache.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True bij succes.
|
||||
*/
|
||||
public function clear(): bool;
|
||||
|
||||
/**
|
||||
* Controleer of een sleutel bestaat en nog niet verlopen is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel die gecontroleerd moet worden.
|
||||
* @return bool True wanneer de sleutel bestaat en geldig is.
|
||||
*/
|
||||
public function has(string $key): bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestandssysteem-gebaseerde cache-implementatie.
|
||||
*
|
||||
* Slaat gecachte waarden op als geserialiseerde bestanden in een map. Elke
|
||||
* cache-sleutel wordt omgezet naar een md5-hash zodat ook lange of vreemde
|
||||
* sleutels veilig als bestandsnaam gebruikt kunnen worden. Verlopen bestanden
|
||||
* worden bij lezen of controleren automatisch verwijderd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class FileCache implements CacheInterface {
|
||||
/**
|
||||
* Map waar de cache-bestanden in worden opgeslagen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $cacheDir;
|
||||
|
||||
/**
|
||||
* Maak een nieuwe file-cache aan en zorg dat de cachemap bestaat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $cacheDir Pad naar de cachemap. Default '/tmp/codepress_cache'.
|
||||
*/
|
||||
public function __construct(string $cacheDir = '/tmp/codepress_cache') {
|
||||
$this->cacheDir = $cacheDir;
|
||||
if (!is_dir($this->cacheDir)) {
|
||||
@@ -18,6 +95,17 @@ class FileCache implements CacheInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal een waarde uit de cache.
|
||||
*
|
||||
* Wanneer het cache-bestand niet bestaat of verlopen is wordt null
|
||||
* teruggegeven; in het laatste geval wordt het bestand direct verwijderd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel van de op te halen waarde.
|
||||
* @return mixed De gecachte waarde of null.
|
||||
*/
|
||||
public function get(string $key) {
|
||||
$file = $this->getCacheFile($key);
|
||||
if (!file_exists($file)) {
|
||||
@@ -33,6 +121,16 @@ class FileCache implements CacheInterface {
|
||||
return $data['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sla een waarde op in de cache met de opgegeven TTL.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel waaronder wordt opgeslagen.
|
||||
* @param mixed $value Te cachen waarde.
|
||||
* @param int $ttl Levensduur in seconden. Default 3600.
|
||||
* @return bool True bij succes, false bij een schrijffout.
|
||||
*/
|
||||
public function set(string $key, $value, int $ttl = 3600): bool {
|
||||
$file = $this->getCacheFile($key);
|
||||
$data = [
|
||||
@@ -43,6 +141,17 @@ class FileCache implements CacheInterface {
|
||||
return file_put_contents($file, serialize($data)) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijder een waarde uit de cache.
|
||||
*
|
||||
* Geeft true terug wanneer het bestand niet (meer) bestaat, zodat de
|
||||
* aanroeper dit niet zelf hoeft te controleren.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel die verwijderd moet worden.
|
||||
* @return bool True bij succes of wanneer de sleutel niet bestond.
|
||||
*/
|
||||
public function delete(string $key): bool {
|
||||
$file = $this->getCacheFile($key);
|
||||
if (file_exists($file)) {
|
||||
@@ -51,6 +160,15 @@ class FileCache implements CacheInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wis alle cache-bestanden in de cachemap.
|
||||
*
|
||||
* Submappen worden niet verwijderd, alleen losse bestanden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True bij succes.
|
||||
*/
|
||||
public function clear(): bool {
|
||||
$files = glob($this->cacheDir . '/*');
|
||||
foreach ($files as $file) {
|
||||
@@ -61,6 +179,17 @@ class FileCache implements CacheInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controleer of een sleutel bestaat en nog geldig is.
|
||||
*
|
||||
* Let op: verlopen bestanden worden hier niet automatisch verwijderd;
|
||||
* controleer vooraf eventueel via get() indien opruiming gewenst is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Sleutel die gecontroleerd moet worden.
|
||||
* @return bool True wanneer de sleutel bestaat en de TTL nog niet verlopen is.
|
||||
*/
|
||||
public function has(string $key): bool {
|
||||
$file = $this->getCacheFile($key);
|
||||
if (!file_exists($file)) {
|
||||
@@ -71,6 +200,17 @@ class FileCache implements CacheInterface {
|
||||
return $data['expires'] > time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bepaal het pad naar het cache-bestand voor een sleutel.
|
||||
*
|
||||
* De sleutel wordt gehashed met md5 zodat deze veilig als bestandsnaam
|
||||
* gebruikt kan worden, ongeacht de lengte of tekenset van de sleutel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Cache-sleutel.
|
||||
* @return string Volledig pad naar het cache-bestand.
|
||||
*/
|
||||
private function getCacheFile(string $key): string {
|
||||
return $this->cacheDir . '/' . md5($key) . '.cache';
|
||||
}
|
||||
|
||||
+420
-198
@@ -3,36 +3,86 @@
|
||||
|
||||
|
||||
/**
|
||||
* CodePressCMS - Lightweight file-based content management system
|
||||
*
|
||||
* Features:
|
||||
* - Markdown, PHP, and HTML content support
|
||||
* - Dynamic navigation with dropdown menus
|
||||
* - Search functionality
|
||||
* - Breadcrumb navigation
|
||||
* - Auto-linking between pages
|
||||
* - Bootstrap 5 styling
|
||||
* - File-based organization
|
||||
* - SEO friendly URLs
|
||||
* - Responsive design
|
||||
*
|
||||
* @author Edwin Noorlander
|
||||
* @version 1.0.0
|
||||
* @license MIT
|
||||
* Hoofdclass van het file-based CodePress CMS.
|
||||
*
|
||||
* Lichtgewicht content management systeem zonder database. Ondersteunt
|
||||
* Markdown, PHP en HTML content, dynamische navigatie met dropdown-menus,
|
||||
* zoekfunctionaliteit, breadcrumb-navigatie, auto-linking tussen pagina's,
|
||||
* Bootstrap 5 styling, file-based organisatie, SEO-vriendelijke URLs en
|
||||
* responsive design.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class CodePressCMS {
|
||||
/**
|
||||
* Configuratie-array met alle site-instellingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Actieve taalcode van het huidige verzoek (bijv. 'nl' of 'en').
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
public $currentLanguage;
|
||||
|
||||
/**
|
||||
* Resultaten van de laatst uitgevoerde zoekopdracht.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array
|
||||
*/
|
||||
public $searchResults = [];
|
||||
|
||||
/**
|
||||
* Opgebouwde menu-structuur vanuit de content-directory.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array
|
||||
*/
|
||||
private $menu = [];
|
||||
|
||||
/**
|
||||
* Effectieve standaardpagina die op de taal-root URL wordt geserveerd.
|
||||
*
|
||||
* Wordt lazy geinitialiseerd in getEffectiveDefaultPage().
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $effectiveDefaultPage = null;
|
||||
|
||||
/**
|
||||
* Geladen vertalingen voor de actieve taal.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array
|
||||
*/
|
||||
private $translations = [];
|
||||
|
||||
/**
|
||||
* PluginManager-instance die plugins en hooks beheert.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var PluginManager
|
||||
*/
|
||||
private $pluginManager;
|
||||
|
||||
/**
|
||||
* Constructor - Initialize the CMS with configuration
|
||||
*
|
||||
* @param array $config Configuration array containing site settings
|
||||
* Initialiseer het CMS met de opgegeven configuratie.
|
||||
*
|
||||
* Laadt versie-informatie, stelt de actieve taal en vertalingen in,
|
||||
* initialiseert de PluginManager en de CMSAPI, bouwt het menu op en
|
||||
* voert een eventuele zoekopdracht uit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $config Configuration array containing site settings.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($config) {
|
||||
$this->config = $config;
|
||||
@@ -62,12 +112,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a clean URL for a page
|
||||
*
|
||||
* @param string $page Page path (e.g., 'blog/leren/artikel')
|
||||
* @param string|null $lang Language code
|
||||
* @param array $params Additional query parameters
|
||||
* @return string Clean URL
|
||||
* Bouw een clean URL voor een pagina.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string|null $page Page path (e.g., 'blog/leren/artikel').
|
||||
* @param string|null $lang Language code.
|
||||
* @param array $params Additional query parameters.
|
||||
* @return string Clean URL.
|
||||
*/
|
||||
public function buildUrl($page = null, $lang = null, $params = []) {
|
||||
$lang = $lang ?: $this->currentLanguage;
|
||||
@@ -86,8 +138,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize page parameter to prevent XSS attacks
|
||||
* Removes any characters that are not alphanumeric, dashes, underscores, or slashes
|
||||
* Sanitize de pagina-parameter om XSS-aanvallen te voorkomen.
|
||||
*
|
||||
* Verwijdert alle tekens die niet alfanumeriek, streepje, underscore of
|
||||
* slash zijn. De punt blijft behouden zodat content-bestandsextensies
|
||||
* (md/php/html) in de URL overleven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $page Page parameter.
|
||||
* @return string Gesanitizeerde pagina-parameter of 'invalid-page'.
|
||||
*/
|
||||
private function sanitizePageParam(string $page): string {
|
||||
// Remove any characters that could be used for XSS; keep the dot so
|
||||
@@ -97,9 +157,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective default page (handles 'auto' mode)
|
||||
* Bepaal de effectieve standaardpagina (behandelt 'auto' en 'newest' modus).
|
||||
*
|
||||
* @return string Page key that is served on the language root URL
|
||||
* Het resultaat wordt gecached in $effectiveDefaultPage.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Page key that is served on the language root URL.
|
||||
*/
|
||||
public function getEffectiveDefaultPage(): string
|
||||
{
|
||||
@@ -115,11 +179,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a clean admin URL
|
||||
*
|
||||
* @param string $route Admin route
|
||||
* @param array $params Additional query parameters
|
||||
* @return string Clean admin URL
|
||||
* Bouw een clean admin URL.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $route Admin route.
|
||||
* @param array $params Additional query parameters.
|
||||
* @return string Clean admin URL.
|
||||
*/
|
||||
public static function buildAdminUrl($route = '', $params = []) {
|
||||
$url = $route ? '/admin/' . $route : '/admin';
|
||||
@@ -130,16 +196,24 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build URL string for template use (non-static, with current lang context)
|
||||
* Bouw een URL-string voor gebruik in templates (niet-statisch, met huidige taal-context).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $page Pagina-pad.
|
||||
* @param array $extraParams Extra query parameters.
|
||||
* @return string Clean URL.
|
||||
*/
|
||||
public function url($page = 'index', $extraParams = []) {
|
||||
return $this->buildUrl($page, $this->currentLanguage, $extraParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language from request or config
|
||||
*
|
||||
* @return string Current language code
|
||||
* Bepaal de actieve taal vanuit het verzoek of de configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Current language code.
|
||||
*/
|
||||
private function getCurrentLanguage() {
|
||||
$lang = $_GET['lang'] ?? $this->config['language']['default'] ?? 'nl';
|
||||
@@ -149,11 +223,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Verzamel alle beschikbare talen vanuit de language-directory.
|
||||
*
|
||||
* Elke taal is een subdirectory onder language/ met een site.php-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array Available languages with their codes and names.
|
||||
*/
|
||||
public function getAvailableLanguages() {
|
||||
$langDir = __DIR__ . '/../../../language/';
|
||||
@@ -187,10 +263,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load site translations for specified language
|
||||
*
|
||||
* @param string $lang Language code
|
||||
* @return array Translations array
|
||||
* Laad site-vertalingen voor de opgegeven taal.
|
||||
*
|
||||
* Valt terug op de standaardtaal en daarna op een lege array.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $lang Language code.
|
||||
* @return array Translations array.
|
||||
*/
|
||||
private function loadTranslations($lang) {
|
||||
$langDir = __DIR__ . '/../../../language/';
|
||||
@@ -215,10 +295,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get native language name for language code
|
||||
*
|
||||
* @param string $langCode Language code
|
||||
* @return string Native language name
|
||||
* Geef de native taalnaam voor een taalcode terug.
|
||||
*
|
||||
* Bevat een statische mapping van ISO-codes naar native taalnamen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $langCode Language code.
|
||||
* @return string Native language name.
|
||||
*/
|
||||
private function getNativeLanguageName($langCode) {
|
||||
$names = [
|
||||
@@ -297,18 +381,23 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translated text
|
||||
*
|
||||
* @param string $key Translation key
|
||||
* @return string Translated text
|
||||
* Geef vertaalde tekst voor een sleutel terug.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Translation key.
|
||||
* @return string Translated text, of de sleutel zelf indien geen vertaling gevonden.
|
||||
*/
|
||||
public function t($key) {
|
||||
return $this->translations[$key] ?? $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build menu structure from content directory
|
||||
*
|
||||
* Bouw de menu-structuur op vanuit de content-directory.
|
||||
*
|
||||
* Roept de plugin-actie 'onMenuBuild' aan na het scannen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return void
|
||||
*/
|
||||
private function buildMenu() {
|
||||
@@ -317,11 +406,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan directory for content files and folders
|
||||
*
|
||||
* @param string $dir Directory path to scan
|
||||
* @param string $prefix Relative path prefix
|
||||
* @return array Array of menu items
|
||||
* Scan recursief een directory voor content-bestanden en mappen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $dir Directory path to scan.
|
||||
* @param string $prefix Relative path prefix.
|
||||
* @return array Array of menu items.
|
||||
*/
|
||||
private function scanDirectory($dir, $prefix) {
|
||||
if (!is_dir($dir)) return [];
|
||||
@@ -375,9 +466,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform search across all content files
|
||||
*
|
||||
* @param string $query Search query string
|
||||
* Voer een zoekopdracht uit over alle content-bestanden.
|
||||
*
|
||||
* Reset de zoekresultaten, doorzoekt de content-directory en vuurt de
|
||||
* plugin-actie 'onSearch' af.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $query Search query string.
|
||||
* @return void
|
||||
*/
|
||||
private function performSearch($query) {
|
||||
@@ -387,11 +483,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search for query in directory files
|
||||
*
|
||||
* @param string $dir Directory to search in
|
||||
* @param string $prefix Relative path prefix
|
||||
* @param string $query Search query
|
||||
* Doorzoek recursief de bestanden in een directory naar de query.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $dir Directory to search in.
|
||||
* @param string $prefix Relative path prefix.
|
||||
* @param string $query Search query.
|
||||
* @return void
|
||||
*/
|
||||
private function searchInDirectory($dir, $prefix, $query) {
|
||||
@@ -424,11 +522,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create search snippet with highlighted query
|
||||
*
|
||||
* @param string $content Full content to create snippet from
|
||||
* @param string $query Search query to highlight
|
||||
* @return string Formatted snippet
|
||||
* Maak een zoeksnippet met de gevonden query.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Full content to create snippet from.
|
||||
* @param string $query Search query to highlight.
|
||||
* @return string Formatted snippet.
|
||||
*/
|
||||
private function createSnippet($content, $query) {
|
||||
$content = strip_tags($content);
|
||||
@@ -441,9 +541,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page content based on request
|
||||
*
|
||||
* @return array Page data with title and content
|
||||
* Bepaal de content van de huidige pagina op basis van het verzoek.
|
||||
*
|
||||
* Behandelt zoek-, guide-, welcome- en 404-verzoeken, path-traversal-
|
||||
* validatie, directory-listings en taal-specifieke bestandsversies.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array Page data with title and content.
|
||||
*/
|
||||
public function getPage() {
|
||||
if (isset($_GET['search'])) {
|
||||
@@ -550,16 +654,19 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the first content file matching a base path by type priority.
|
||||
* Bepaal het eerste bestaande content-bestand voor een base-path op type-prioriteit.
|
||||
*
|
||||
* A request without explicit extension (e.g. "test") serves the first
|
||||
* existing file among test.md → test.php → test.html. When $preferredExt
|
||||
* is given (the URL carried an extension), that type is tried first so
|
||||
* /nl/test.php keeps serving test.php even when test.md also exists.
|
||||
* Een verzoek zonder expliciete extensie (bijv. "test") serveert het
|
||||
* eerste bestaande bestand uit test.md -> test.php -> test.html. Wanneer
|
||||
* $preferredExt is opgegeven (de URL bevatte een extensie) wordt dat
|
||||
* type eerst geprobeerd, zodat /nl/test.php test.php serveert ook als
|
||||
* test.md bestaat.
|
||||
*
|
||||
* @param string $basePath Absolute path without extension
|
||||
* @param string $preferredExt Optional extension to try first (md|php|html)
|
||||
* @return array|null ['path' => string, 'content' => array] or null
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $basePath Absolute path without extension.
|
||||
* @param string|null $preferredExt Optional extension to try first (md|php|html).
|
||||
* @return array|null ['path' => string, 'content' => array] or null.
|
||||
*/
|
||||
private function resolveContentByType(string $basePath, ?string $preferredExt = null): ?array
|
||||
{
|
||||
@@ -584,11 +691,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file information including creation and modification dates
|
||||
*
|
||||
* @param string $filePath Path to the file
|
||||
* @param array $metadata Optional frontmatter metadata
|
||||
* @return array|null File information or null if file doesn't exist
|
||||
* Verzamel bestandsinformatie inclusief aanmaak- en wijzigingsdatums.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filePath Path to the file.
|
||||
* @param array $metadata Optional frontmatter metadata.
|
||||
* @return array|null File information or null if file doesn't exist.
|
||||
*/
|
||||
private function getFileInfo($filePath, array $metadata = []) {
|
||||
if (!file_exists($filePath)) {
|
||||
@@ -624,10 +733,12 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size in human readable format
|
||||
*
|
||||
* @param int $bytes File size in bytes
|
||||
* @return string Formatted file size
|
||||
* Format een bestandsgrootte in leesbare vorm (B, KB, MB, GB).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $bytes File size in bytes.
|
||||
* @return string Formatted file size.
|
||||
*/
|
||||
private function formatFileSize($bytes) {
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
@@ -641,9 +752,10 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search results page content
|
||||
*
|
||||
* @return array Search results page data
|
||||
* Bouw de content voor de zoekresultatenpagina.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array Search results page data.
|
||||
*/
|
||||
private function getSearchResults() {
|
||||
$query = $_GET['search'];
|
||||
@@ -670,10 +782,15 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse metadata from content
|
||||
*
|
||||
* @param string $content Raw content
|
||||
* @return array Parsed metadata and content without meta block
|
||||
* Parse YAML-frontmatter metadata uit de content.
|
||||
*
|
||||
* Herkent een blok dat begint en eindigt met `---` en levert een array
|
||||
* met de metadata en de content zonder het meta-blok.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Raw content.
|
||||
* @return array Parsed metadata and content without meta block.
|
||||
*/
|
||||
private function parseMetadata($content) {
|
||||
$metadata = [];
|
||||
@@ -708,9 +825,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hosts that count as internal for external-link detection
|
||||
* Geef de hosts die gelden als intern voor externe-link-detectie.
|
||||
*
|
||||
* @return array List of host names
|
||||
* Bevat de huidige HTTP_HOST (inclusief www-variant) en eventueel de
|
||||
* author-website host.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array List of host names.
|
||||
*/
|
||||
private function getInternalHosts(): array
|
||||
{
|
||||
@@ -737,11 +858,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a URL: if no scheme is present, prepend https://.
|
||||
* Handles hostnames stored without protocol (e.g. "noorlander.info").
|
||||
* Normaliseer een URL: ontbreekt een scheme, dan wordt https:// voorgevoegd.
|
||||
*
|
||||
* @param string $url Raw URL or hostname
|
||||
* @return string Normalized absolute URL, or '' if empty
|
||||
* Behandelt hostnames die zonder protocol zijn opgeslagen (bijv. "noorlander.info").
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $url Raw URL or hostname.
|
||||
* @return string Normalized absolute URL, or '' if empty.
|
||||
*/
|
||||
private function normalizeUrl(string $url): string
|
||||
{
|
||||
@@ -756,11 +880,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Markdown content to HTML using League CommonMark
|
||||
*
|
||||
* @param string $content Raw Markdown content
|
||||
* @param string $actualFilePath Path to the source file (used for display name fallback)
|
||||
* @return array Parsed content with title and body
|
||||
* Parse Markdown-content naar HTML via League CommonMark.
|
||||
*
|
||||
* Trekt eerst frontmatter-metadata uit, bepaalt de titel en configureert
|
||||
* de CommonMark-omgeving met extensies en custom afbeeldingsgrootte-syntax.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Raw Markdown content.
|
||||
* @param string $actualFilePath Path to the source file (used for display name fallback).
|
||||
* @return array Parsed content with title and body.
|
||||
*/
|
||||
public function parseMarkdown($content, $actualFilePath = '') {
|
||||
// Parse metadata first
|
||||
@@ -857,11 +986,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-link page titles found in content
|
||||
*
|
||||
* @param string $content Content to process for auto-linking
|
||||
* @param string $excludeTitle Title to exclude from auto-linking (current page title)
|
||||
* @return string Content with auto-linked page titles
|
||||
* Auto-link pagina-titels die in de content worden gevonden.
|
||||
*
|
||||
* Bestaande `<a>`-tags, `<h1>`-inhoud en Markdown-links worden beschermd
|
||||
* tegen dubbele linking. De titel van de huidige pagina wordt overgeslagen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Content to process for auto-linking.
|
||||
* @param string $excludeTitle Title to exclude from auto-linking (current page title).
|
||||
* @return string Content with auto-linked page titles.
|
||||
*/
|
||||
private function autoLinkPageTitles($content, $excludeTitle = '') {
|
||||
$pages = $this->getAllPageTitles();
|
||||
@@ -897,9 +1031,11 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all content entries (mappen + bestanden) from the content directory.
|
||||
*
|
||||
* @return array List of entries: ['path' => ..., 'title' => ..., 'type' => ...]
|
||||
* Verzamel alle content-entries (mappen + bestanden) uit de content-directory.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array List of entries: ['path' => ..., 'title' => ..., 'type' => ...].
|
||||
* type is 'md'/'php'/'html' voor bestanden, 'folder' voor mappen.
|
||||
*/
|
||||
public function getAllPageTitles() {
|
||||
@@ -909,17 +1045,19 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan for content entries in a directory.
|
||||
*
|
||||
* Returns a flat list of ['path' => relativePath, 'title' => ..., 'type' => ...].
|
||||
* Scan recursief een directory voor content-entries.
|
||||
*
|
||||
* Levert een platte lijst van ['path' => relativePath, 'title' => ..., 'type' => ...].
|
||||
* Mappen krijgen type 'folder'; bestanden krijgen hun extensie als type.
|
||||
* Bestanden met dezelfde naam maar ander type blijven apart (de extensie
|
||||
* zit in het pad), en mappen krijgen hun eigen entry zodat de boom zichtbaar
|
||||
* blijft.
|
||||
*
|
||||
* @param string $dir Directory to scan
|
||||
* @param string $prefix Relative path prefix
|
||||
* @param array &$pages Reference to pages array to populate
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $dir Directory to scan.
|
||||
* @param string $prefix Relative path prefix.
|
||||
* @param array &$pages Reference to pages array to populate.
|
||||
* @return void
|
||||
*/
|
||||
private function scanForPageTitles($dir, $prefix, &$pages) {
|
||||
@@ -958,10 +1096,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format display name from filename
|
||||
*
|
||||
* @param string $filename Filename without extension
|
||||
* @return string Formatted display name
|
||||
* Format een bestands- of mapnaam tot een leesbare weergavenaam.
|
||||
*
|
||||
* Verwijdert taal-prefixen (op basis van getAvailableLanguages()) en
|
||||
* content-extensies, zet streepjes/underscores om in spaties en past
|
||||
* special cases toe (bijv. 'ict' -> 'ICT').
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filename Filename without extension.
|
||||
* @return string Formatted display name.
|
||||
*/
|
||||
private function formatDisplayName($filename) {
|
||||
$filename = (string)$filename;
|
||||
@@ -996,10 +1140,15 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract page title from file content
|
||||
*
|
||||
* @param string $filePath Path to the file
|
||||
* @return string|null Extracted title or null if not found
|
||||
* Extract de paginatitel uit de content van een bestand.
|
||||
*
|
||||
* Voor Markdown wordt het eerste H1 gebruikt, voor PHP een $title-
|
||||
* variabele en voor HTML de <title>- of <h1>-tag.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filePath Path to the file.
|
||||
* @return string|null Extracted title or null if not found.
|
||||
*/
|
||||
private function extractPageTitle($filePath) {
|
||||
$content = file_get_contents($filePath);
|
||||
@@ -1029,10 +1178,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PHP file and capture output
|
||||
*
|
||||
* @param string $filePath Path to PHP file
|
||||
* @return array Parsed content with title and body
|
||||
* Parse een PHP-bestand en vang de output op.
|
||||
*
|
||||
* Stelt ContentAPI en frontmatter-metadata beschikbaar aan het
|
||||
* geinclude bestand. Wanneer het bestand een string returnt wordt
|
||||
* die als content gebruikt in plaats van de gebufferde output.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filePath Path to PHP file.
|
||||
* @return array Parsed content with title and body.
|
||||
*/
|
||||
private function parsePHP($filePath) {
|
||||
// Read file content first to extract metadata
|
||||
@@ -1076,10 +1231,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HTML content and extract title
|
||||
*
|
||||
* @param string $content Raw HTML content
|
||||
* @return array Parsed content with title and body
|
||||
* Parse HTML-content en extraheer de titel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Raw HTML content.
|
||||
* @param string $actualFilePath Path to the source file (used for display name fallback).
|
||||
* @return array Parsed content with title and body.
|
||||
*/
|
||||
private function parseHTML($content, $actualFilePath = '') {
|
||||
// Parse metadata first
|
||||
@@ -1110,9 +1268,11 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content directory is empty
|
||||
*
|
||||
* @return bool True if content directory is empty or doesn't exist
|
||||
* Controleer of de content-directory leeg is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True if content directory is empty or doesn't exist.
|
||||
*/
|
||||
public function isContentDirEmpty() {
|
||||
$contentDir = $this->config['content_dir'];
|
||||
@@ -1132,12 +1292,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get welcome page content for new installations (empty content directory)
|
||||
* Geef de content voor de welkomstpagina bij nieuwe installaties.
|
||||
*
|
||||
* Shows a clear "new installation" message with next steps when the
|
||||
* content directory has no content files yet.
|
||||
* Toont een duidelijke "nieuwe installatie"-boodschap met vervolgstappen
|
||||
* wanneer de content-directory nog geen content-bestanden bevat.
|
||||
*
|
||||
* @return array Welcome page data
|
||||
* @since 2.6.4
|
||||
* @return array Welcome page data.
|
||||
*/
|
||||
private function getWelcomePage() {
|
||||
$adminUrl = $this->buildAdminUrl();
|
||||
@@ -1178,9 +1339,13 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get guide page content based on user language
|
||||
*
|
||||
* @return array Guide page data
|
||||
* Geef de content van een handleiding-pagina op basis van de gebruikerstaal.
|
||||
*
|
||||
* Valt terug op het Engelse equivalent indien de gevraagde taal niet
|
||||
* beschikbaar is. Bouwt tevens breadcrumbs op voor de handleiding.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array Guide page data.
|
||||
*/
|
||||
private function getGuidePage() {
|
||||
$lang = $this->currentLanguage;
|
||||
@@ -1247,11 +1412,17 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate directory listing page
|
||||
*
|
||||
* @param string $pagePath Relative path to directory
|
||||
* @param string $dirPath Absolute path to directory
|
||||
* @return array Directory listing page data
|
||||
* Genereer een directory-listingpagina.
|
||||
*
|
||||
* Toont de titel van de map en een lijst met alle mappen en bestanden
|
||||
* daarin. Leest eventuele layout/plugins-metadata uit een index.md in
|
||||
* dezelfde map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pagePath Relative path to directory.
|
||||
* @param string $dirPath Absolute path to directory.
|
||||
* @return array Directory listing page data.
|
||||
*/
|
||||
private function getDirectoryListing($pagePath, $dirPath) {
|
||||
// Get the directory name from the path, not from a potential file
|
||||
@@ -1351,9 +1522,10 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 404 error page content
|
||||
*
|
||||
* @return array 404 page data
|
||||
* Geef de content van de 404-foutpagina.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array 404 page data.
|
||||
*/
|
||||
private function getError404() {
|
||||
$staticFile = __DIR__ . '/../../../admin/static/404.html';
|
||||
@@ -1371,17 +1543,23 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu structure
|
||||
*
|
||||
* @return array Menu structure for navigation
|
||||
* Geef de opgebouwde menu-structuur terug.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return array Menu structure for navigation.
|
||||
*/
|
||||
public function getMenu() {
|
||||
return $this->menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the complete page with template
|
||||
*
|
||||
* Render de volledige pagina met het actieve theme.
|
||||
*
|
||||
* Bepaalt pagina-data, vuurt plugin-acties af, stelt template-data samen
|
||||
* (menu, sidebar, breadcrumbs, SEO, theme-assets) en echoot de
|
||||
* gerenderde layout via ThemeManager.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return void
|
||||
*/
|
||||
public function render() {
|
||||
@@ -1547,11 +1725,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate breadcrumb navigation HTML
|
||||
*
|
||||
* @param bool $hasSidebar Whether sidebar content exists and should show toggle
|
||||
* @param array|null $guidePage Guide page data (when on a guide page)
|
||||
* @return string Breadcrumb HTML
|
||||
* Genereer breadcrumb-navigatie-HTML.
|
||||
*
|
||||
* Ondersteunt guide-pagina's, zoekpagina's en reguliere content-pagina's
|
||||
* met subdirectory's. Toont optioneel een sidebar-toggle-knop.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param bool $hasSidebar Whether sidebar content exists and should show toggle.
|
||||
* @param array|null $guidePage Guide page data (when on a guide page).
|
||||
* @return string Breadcrumb HTML.
|
||||
*/
|
||||
public function generateBreadcrumb($hasSidebar = true, $guidePage = null) {
|
||||
// Sidebar toggle button (shown before home icon in breadcrumb)
|
||||
@@ -1630,11 +1813,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render menu HTML with dropdown support
|
||||
*
|
||||
* @param array $items Menu items to render
|
||||
* @param int $level Current nesting level
|
||||
* @return string Rendered menu HTML
|
||||
* Render menu-HTML met ondersteuning voor dropdowns.
|
||||
*
|
||||
* Bestanden op root-niveau worden als tabs getoond; bestanden in mappen
|
||||
* als dropdown-items. Mappen zonder kinderen worden als uitgevinkt getoond.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $items Menu items to render.
|
||||
* @param int $level Current nesting level.
|
||||
* @return string Rendered menu HTML.
|
||||
*/
|
||||
private function renderMenu($items, $level = 0) {
|
||||
$html = '';
|
||||
@@ -1698,13 +1886,16 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a frontmatter layout value to a theme template key.
|
||||
* Vertaal een frontmatter layout-waarde naar een theme template-sleutel.
|
||||
*
|
||||
* Legacy values are translated to the new theme keys. Unknown values
|
||||
* are passed through so ThemeManager can fall back to default_layout.
|
||||
* Legacy waarden worden omgezet naar de nieuwe theme-keys. Onbekende
|
||||
* waarden worden doorgegeven zodat ThemeManager kan terugvallen op
|
||||
* default_layout.
|
||||
*
|
||||
* @param string $layout Layout value from page metadata
|
||||
* @return string Theme template key
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $layout Layout value from page metadata.
|
||||
* @return string Theme template key.
|
||||
*/
|
||||
private function mapLayoutToThemeKey(string $layout): string {
|
||||
return match ($layout) {
|
||||
@@ -1717,12 +1908,14 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-detect the first available content page
|
||||
* Detecteer automatisch de eerste beschikbare content-pagina.
|
||||
*
|
||||
* Scans the content directory for the first .md/.php/.html file
|
||||
* (preferring non-language-prefixed files) and returns its page key.
|
||||
* Scant de content-directory naar het eerste .md/.php/.html-bestand
|
||||
* (met voorkeur voor bestanden zonder taal-prefix) en geeft de page-key terug.
|
||||
*
|
||||
* @return string Detected page key, or 'index' as fallback
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Detected page key, or 'index' as fallback.
|
||||
*/
|
||||
private function detectDefaultPage(): string
|
||||
{
|
||||
@@ -1748,9 +1941,11 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the newest modified page (last modified timestamp)
|
||||
* Detecteer de meest recent gewijzigde of aangemaakte pagina.
|
||||
*
|
||||
* @return string Page key that was most recently modified or created
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Page key that was most recently modified or created.
|
||||
*/
|
||||
private function detectNewestPage(): string
|
||||
{
|
||||
@@ -1825,9 +2020,10 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get homepage title
|
||||
*
|
||||
* @return string Homepage title
|
||||
* Geef de titel van de homepagina terug.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @return string Homepage title.
|
||||
*/
|
||||
private function getHomepageTitle() {
|
||||
// Use a generic "Home" label instead of the page name
|
||||
@@ -1836,10 +2032,12 @@ class CodePressCMS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if folder contains the currently active page
|
||||
*
|
||||
* @param array $children Array of child items
|
||||
* @return bool True if active page is found in children
|
||||
* Controleer of een map de actieve pagina bevat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $children Array of child items.
|
||||
* @return bool True if active page is found in children.
|
||||
*/
|
||||
private function folderContainsActivePage($children) {
|
||||
foreach ($children as $child) {
|
||||
@@ -1856,6 +2054,22 @@ class CodePressCMS {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwerk content-HTML vóór renderen.
|
||||
*
|
||||
* Herschrijft legacy `-/assets/`-shortcuts en `<img src>`/`<a href>`-
|
||||
* attributen die wijzen naar bestanden in de content-directory naar het
|
||||
* `/-media/`-endpoint, zodat deze bestanden (die buiten de webroot
|
||||
* staan) correct worden geserveerd. Absolute URLs, reeds herschreven
|
||||
* `/-media/`- en `/-assets/`-URLs, theme/plugin/admin-asset-URLs,
|
||||
* data:-/mailto:-URLs, fragment-URLs en clean taal-voorziene
|
||||
* pagina-routes blijven ongemoeid.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $content Content HTML to process.
|
||||
* @return string Verwerkte content met herschreven media-URLs.
|
||||
*/
|
||||
private function processContent(string $content): string
|
||||
{
|
||||
// Legacy rewrite for the old -/assets/ shortcut
|
||||
@@ -1872,12 +2086,20 @@ class CodePressCMS {
|
||||
// - theme/plugin asset URLs (/themes/..., /plugins/..., /admin/...)
|
||||
// - data: and mailto: URLs
|
||||
// - fragment-only URLs (#anchor)
|
||||
// - clean language-prefixed page routes (/<lang>/<page>) — these are
|
||||
// valid internal page URLs (used by autoLinkPageTitles) and must not
|
||||
// be rewritten to the /-media/ endpoint
|
||||
$langCodes = array_keys($this->getAvailableLanguages());
|
||||
$langPattern = empty($langCodes) ? '' : '|/(?:' . implode('|', array_map('preg_quote', $langCodes)) . ')/';
|
||||
$skipPattern = '~^(?:[a-z][a-z0-9+.\-]*:|//|/themes/|/plugins/|/admin/|/-media/|/-assets/|data:|mailto:' . $langPattern . ')~i';
|
||||
|
||||
$content = preg_replace_callback(
|
||||
'~(<(?:img|a)\b[^>]*\b(?:src|href)\s*=\s*")([^"]+)(")~i',
|
||||
function ($m) {
|
||||
function ($m) use ($skipPattern) {
|
||||
$url = $m[2];
|
||||
// Leave absolute, already-rewritten, asset, data: and anchor URLs alone
|
||||
if (preg_match('~^(?:[a-z][a-z0-9+.\-]*:|//|/themes/|/plugins/|/admin/|/-media/|/-assets/|data:|mailto:)~i', $url)) {
|
||||
// Leave absolute, already-rewritten, asset, data:, anchor and
|
||||
// language-prefixed page-route URLs alone
|
||||
if (preg_match($skipPattern, $url)) {
|
||||
return $m[0];
|
||||
}
|
||||
// Strip a leading /content/ prefix if present, then prefix with /-media/
|
||||
|
||||
@@ -7,13 +7,46 @@
|
||||
* - ZIP backup of the entire content directory
|
||||
* - Restore from an uploaded ZIP file
|
||||
* - Optional git-based versioning (init / commit / log / restore)
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class ContentBackup
|
||||
{
|
||||
/**
|
||||
* Pad naar de content-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string Pad naar de content-map.
|
||||
*/
|
||||
private string $contentDir;
|
||||
|
||||
/**
|
||||
* Pad naar de project-root.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string Pad naar de project-root.
|
||||
*/
|
||||
private string $projectRoot;
|
||||
|
||||
/**
|
||||
* Of git beschikbaar is in de content-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var bool Of git beschikbaar is.
|
||||
*/
|
||||
private bool $gitAvailable;
|
||||
|
||||
/**
|
||||
* Initialiseer de ContentBackup-instantie.
|
||||
*
|
||||
* Stelt de content-map en project-root in en detecteert of git
|
||||
* beschikbaar is op het systeem.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $contentDir Pad naar de content-map.
|
||||
* @param string $projectRoot Optioneel. Pad naar de project-root. Default de map drie niveaus boven deze class.
|
||||
*/
|
||||
public function __construct(string $contentDir, string $projectRoot = '')
|
||||
{
|
||||
$this->contentDir = rtrim($contentDir, '/');
|
||||
@@ -22,7 +55,11 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether git is available and the content dir is inside a git repo.
|
||||
* Controleer of git beschikbaar is en de content-map in een git-repo zit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien git beschikbaar is.
|
||||
*/
|
||||
public function isGitAvailable(): bool
|
||||
{
|
||||
@@ -30,10 +67,15 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ZIP backup of the content directory.
|
||||
* Maak een ZIP-backup van de content-map.
|
||||
*
|
||||
* @param string $outputPath Where to write the ZIP file
|
||||
* @return bool True on success
|
||||
* Controleert of ZipArchive beschikbaar is en de content-map bestaat,
|
||||
* en voegt recursief alle bestanden en mappen toe aan het ZIP-archief.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $outputPath Pad waar het ZIP-bestand geschreven moet worden.
|
||||
* @return bool True bij succes, false bij falen.
|
||||
*/
|
||||
public function createZipBackup(string $outputPath): bool
|
||||
{
|
||||
@@ -54,10 +96,15 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore content from a ZIP file.
|
||||
* Herstel content vanuit een ZIP-bestand.
|
||||
*
|
||||
* @param string $zipPath Path to the uploaded ZIP file
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
* Pakt het ZIP-bestand uit in een tijdelijke map, maakt een backup van de
|
||||
* huidige content-map, kopieert de nieuwe content en ruimt oude backups op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $zipPath Pad naar het geüploade ZIP-bestand.
|
||||
* @return array{success:bool,message:string} Resultaat-array met success en message.
|
||||
*/
|
||||
public function restoreFromZip(string $zipPath): array
|
||||
{
|
||||
@@ -112,9 +159,14 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize git in the content directory.
|
||||
* Initialiseer een git-repository in de content-map.
|
||||
*
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
* Voert `git init` uit en configureert een default identiteit (user.email
|
||||
* en user.name) zodat commits werken zonder globale git-config.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array{success:bool,message:string} Resultaat-array met success en message.
|
||||
*/
|
||||
public function gitInit(): array
|
||||
{
|
||||
@@ -135,10 +187,16 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit all changes in the content directory.
|
||||
* Commit alle wijzigingen in de content-map.
|
||||
*
|
||||
* @param string $message Commit message
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
* Voegt alle bestanden toe met `git add -A`, controleert of er wijzigingen
|
||||
* zijn via `git status --porcelain`, en commit met de opgegeven message.
|
||||
* Bij een lege message wordt een standaardmessage gegenereerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $message Commit message.
|
||||
* @return array{success:bool,message:string} Resultaat-array met success en message.
|
||||
*/
|
||||
public function gitCommit(string $message): array
|
||||
{
|
||||
@@ -173,10 +231,15 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the git log for the content directory.
|
||||
* Haal de git-log op voor de content-map.
|
||||
*
|
||||
* @param int $limit Maximum number of entries
|
||||
* @return array ['success' => bool, 'commits' => array, 'message' => string]
|
||||
* Leest commits uit met `git log --pretty=format:%H|%h|%ai|%s` en parseert
|
||||
* deze naar een array met hash, short, date en message per commit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $limit Maximum aantal entries. Default 20.
|
||||
* @return array{success:bool,commits:array<int,array{hash:string,short:string,date:string,message:string}>,message:string} Resultaat-array met success, commits en message.
|
||||
*/
|
||||
public function gitLog(int $limit = 20): array
|
||||
{
|
||||
@@ -221,10 +284,16 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore content to a specific git commit.
|
||||
* Herstel content naar een specifieke git-commit.
|
||||
*
|
||||
* @param string $commitHash Commit hash to restore to
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
* Controleert de commit-hash, saniteert deze en voert
|
||||
* `git checkout <hash> -- .` uit om de bestanden van die commit
|
||||
* in de working tree te herstellen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $commitHash Commit-hash om naar te herstellen.
|
||||
* @return array{success:bool,message:string} Resultaat-array met success en message.
|
||||
*/
|
||||
public function gitRestore(string $commitHash): array
|
||||
{
|
||||
@@ -251,7 +320,11 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the content directory has a git repository.
|
||||
* Controleer of de content-map een git-repository bevat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien een .git-map aanwezig is.
|
||||
*/
|
||||
public function hasGitRepo(): bool
|
||||
{
|
||||
@@ -262,6 +335,15 @@ class ContentBackup
|
||||
// Private helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Detecteer of git beschikbaar is op het systeem.
|
||||
*
|
||||
* Voert `git --version` uit en controleert de exit-code.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien git uitvoerbaar is.
|
||||
*/
|
||||
private function detectGit(): bool
|
||||
{
|
||||
$result = $this->execGitCapture(['--version']);
|
||||
@@ -269,9 +351,16 @@ class ContentBackup
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a git command and capture output, error, and exit code separately.
|
||||
* Voer een git-commando uit en capture output, error en exit-code apart.
|
||||
*
|
||||
* @return array ['output' => string, 'error' => string, 'exit' => int]
|
||||
* Gebruikt proc_open met aparte pipes voor stdout, stderr en stdin.
|
||||
* Alle argumenten worden via escapeshellarg ge-escaped.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array<int,string> $args Git-commando-argumenten.
|
||||
* @param string|null $cwd Optioneel. Werkdirectory voor het git-proces.
|
||||
* @return array{output:string,error:string,exit:int} Array met output, error en exit-code.
|
||||
*/
|
||||
private function execGitCapture(array $args, ?string $cwd = null): array
|
||||
{
|
||||
@@ -305,6 +394,19 @@ class ContentBackup
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Voeg een map recursief toe aan een ZipArchive.
|
||||
*
|
||||
* Itereert over alle entries in $dir en voegt mappen (als lege dir) en
|
||||
* bestanden toe onder de opgegeven $prefix in het ZIP-archief.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param ZipArchive $zip ZIP-archief om aan toe te voegen.
|
||||
* @param string $dir Pad naar de bronmap.
|
||||
* @param string $prefix Prefix (pad binnen het ZIP) voor de entries.
|
||||
* @return void
|
||||
*/
|
||||
private function addDirToZip(ZipArchive $zip, string $dir, string $prefix): void
|
||||
{
|
||||
$entries = scandir($dir);
|
||||
@@ -325,6 +427,18 @@ class ContentBackup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kopieer een map recursief naar een doelmap.
|
||||
*
|
||||
* Maakt de doelmap aan indien nodig en kopieert alle bestanden en
|
||||
* submappen vanuit de bronmap.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $src Pad naar de bronmap.
|
||||
* @param string $dst Pad naar de doelmap.
|
||||
* @return void
|
||||
*/
|
||||
private function copyDir(string $src, string $dst): void
|
||||
{
|
||||
if (!is_dir($src)) {
|
||||
@@ -352,6 +466,16 @@ class ContentBackup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijder een map en alle inhoud recursief.
|
||||
*
|
||||
* Verwijdert eerst alle bestanden en submappen, daarna de map zelf.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $dir Pad naar de te verwijderen map.
|
||||
* @return void
|
||||
*/
|
||||
private function removeDir(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
@@ -376,6 +500,17 @@ class ContentBackup
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruim oude content-backups op, waarbij alleen de laatste bewaard blijft.
|
||||
*
|
||||
* Zoekt backups via een glob-patroon op de content-mapnaam met .bak.*
|
||||
* suffix, sorteert op wijzigingstijd (oudste eerst) en verwijdert alle
|
||||
* backups behalve de meest recente.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanupOldBackups(): void
|
||||
{
|
||||
$parentDir = dirname($this->contentDir);
|
||||
|
||||
+180
-11
@@ -1,13 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* GeoIP - Country lookup provider chain (Local binary, MMDB, and API)
|
||||
* GeoIP country lookup provider chain.
|
||||
*
|
||||
* Ondersteunt drie providers voor het resolueren van een landcode op basis
|
||||
* van een IP-adres: een lokale binaire database (DB-IP), een MaxMind MMDB
|
||||
* bestand en een externe API. Providers vallen terug op de lokale database
|
||||
* wanneer een lookup geen resultaat oplevert.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class GeoIP
|
||||
{
|
||||
/**
|
||||
* De analytics-configuratie uit config.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array Bevat o.a. geoip_provider, geoip_mmdb_path en geoip_api_url.
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* FileCache instantie voor het cachen van API-lookups.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var FileCache|null
|
||||
*/
|
||||
private ?FileCache $cache = null;
|
||||
|
||||
/**
|
||||
* Initialiseert de GeoIP-provider met de analytics-configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $analyticsConfig De "analytics" sectie uit config.json. Default lege array.
|
||||
*/
|
||||
public function __construct(array $analyticsConfig = [])
|
||||
{
|
||||
$this->config = $analyticsConfig;
|
||||
@@ -16,10 +43,16 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve country code (2-letter ISO alpha-2, upper-case) from an IP address
|
||||
* Resolves een 2-letter ISO alpha-2 landcode uit een IP-adres.
|
||||
*
|
||||
* @param string $ip IPv4 or IPv6 address
|
||||
* @return string|null Country code or null if unresolved/private
|
||||
* De gekozen provider wordt gelezen uit de configuratie. Private en
|
||||
* gereserveerde ranges worden direct afgewezen. MMDB en API vallen
|
||||
* terug op de lokale database bij een mislukte lookup.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip IPv4- of IPv6-adres.
|
||||
* @return string|null Landcode in hoofdletters, of null bij onoplosbare/private adressen.
|
||||
*/
|
||||
public function lookupCountry(string $ip): ?string
|
||||
{
|
||||
@@ -53,7 +86,15 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a country code; placeholder codes (ZZ/XX) count as unknown
|
||||
* Normaliseert een landcode en verwerpt placeholder codes.
|
||||
*
|
||||
* Geldige codes zijn exact twee letters. De codes ZZ en XX worden
|
||||
* beschouwd als onbekend en resulteren in null.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string|null $code Ruwe landcode uit een provider.
|
||||
* @return string|null Genormaliseerde landcode of null indien ongeldig/onbekend.
|
||||
*/
|
||||
private static function normalizeCode(?string $code): ?string
|
||||
{
|
||||
@@ -65,7 +106,16 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search lookup in local DB-IP IPv4/IPv6 binary files
|
||||
* Binaire zoekopdracht in lokale DB-IP IPv4/IPv6 binaire bestanden.
|
||||
*
|
||||
* Leest de bestanden uit `admin/storage/geoip` en voert een binaire
|
||||
* zoekopdracht uit op basis van het IP-adres. Ondersteunt zowel IPv4
|
||||
* (10-byte records) als IPv6 (34-byte records).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip IPv4- of IPv6-adres.
|
||||
* @return string|null Landcode in hoofdletters of null indien niet gevonden.
|
||||
*/
|
||||
public function lookupLocal(string $ip): ?string
|
||||
{
|
||||
@@ -161,7 +211,16 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* External API lookup with caching
|
||||
* Externe API-lookup met caching.
|
||||
*
|
||||
* Stuurt een HTTP-verzoek naar de geconfigureerde GeoIP API en cachet
|
||||
* het resultaat zeven dagen in een FileCache. De API-URL mag een
|
||||
* optionele API-key bevatten.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip IPv4- of IPv6-adres.
|
||||
* @return string|null Landcode of null indien de lookup faalt.
|
||||
*/
|
||||
private function lookupApi(string $ip): ?string
|
||||
{
|
||||
@@ -192,7 +251,16 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure-PHP MaxMind MMDB reader
|
||||
* Pure-PHP MaxMind MMDB reader.
|
||||
*
|
||||
* Opent een MMDB-bestand en extraheert de landcode via de ingebouwde
|
||||
* MMDBReader. Fouten worden gesilenced en resulteren in null.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip IPv4- of IPv6-adres.
|
||||
* @param string $filePath Pad naar het MMDB-bestand.
|
||||
* @return string|null Landcode of null bij een fout.
|
||||
*/
|
||||
private function lookupMMDB(string $ip, string $filePath): ?string
|
||||
{
|
||||
@@ -206,7 +274,12 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert 2-letter ISO country code to regional indicator flag emoji
|
||||
* Zet een 2-letter ISO landcode om naar een regionale vlag-emoji.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string|null $code Landcode of null.
|
||||
* @return string Vlag-emoji of een wereldbol bij ongeldige invoer.
|
||||
*/
|
||||
public static function getCountryFlagEmoji(?string $code): string
|
||||
{
|
||||
@@ -222,7 +295,17 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Get country name in Dutch or English
|
||||
* Geeft de landnaam in Nederlands of Engels.
|
||||
*
|
||||
* Bevat een statische mapping van de meest voorkomende landcodes naar
|
||||
* hun naam in de gevraagde taal. Onbekende codes worden ongewijzigd
|
||||
* teruggegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string|null $code Landcode of null.
|
||||
* @param string $lang Gewenste taal, 'nl' of 'en'. Default 'nl'.
|
||||
* @return string Landnaam in de gevraagde taal of de code zelf.
|
||||
*/
|
||||
public static function getCountryName(?string $code, string $lang = 'nl'): string
|
||||
{
|
||||
@@ -275,14 +358,49 @@ class GeoIP
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in pure-PHP MaxMind DB Reader
|
||||
* Ingebouwde pure-PHP MaxMind DB reader.
|
||||
*
|
||||
* Minimale implementatie van de MaxMind DB binary reader, nodig omdat de
|
||||
* externe GeoIP2 dependency optioneel is. Ondersteunt 28-bit record sizes
|
||||
* en de veelvoorkomende datatypes (pointer, string, map, uint, bool).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class MMDBReader
|
||||
{
|
||||
/**
|
||||
* Pad naar het MMDB-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $file;
|
||||
|
||||
/**
|
||||
* Bestandshandle voor het MMDB-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var resource|null
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
/**
|
||||
* Metadata uit de MMDB-header.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array Bevat o.a. node_count, record_size en ip_version.
|
||||
*/
|
||||
private array $meta;
|
||||
|
||||
/**
|
||||
* Opent het MMDB-bestand en laadt de metadata.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $file Pad naar het MMDB-bestand.
|
||||
* @throws \InvalidArgumentException Als het bestand niet bestaat.
|
||||
* @throws \RuntimeException Als het bestandformaat ongeldig is.
|
||||
*/
|
||||
public function __construct(string $file)
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
@@ -293,6 +411,11 @@ class MMDBReader
|
||||
$this->loadMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sluit de bestandshandle bij het vernietigen van de instantie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->handle) {
|
||||
@@ -300,6 +423,16 @@ class MMDBReader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laadt de MMDB-metadata vanaf het einde van het bestand.
|
||||
*
|
||||
* Zoekt naar de MaxMind marker en decodeert de daaropvolgende data.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @throws \RuntimeException Als de marker niet gevonden wordt.
|
||||
* @return void
|
||||
*/
|
||||
private function loadMetadata(): void
|
||||
{
|
||||
$stat = fstat($this->handle);
|
||||
@@ -319,6 +452,18 @@ class MMDBReader
|
||||
$this->meta = $this->decodeData($metaOffset)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Voert een lookup uit voor een IP-adres in de MMDB-boom.
|
||||
*
|
||||
* Werkt door de binaire boom te doorlopen op basis van de bits van het
|
||||
* IP-adres. IPv4-adressen in een IPv6-boom worden correct afgehandeld
|
||||
* via de ipv4_instance_count metadata.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip IPv4- of IPv6-adres.
|
||||
* @return array|null Gedecodeerde datarecord of null indien niet gevonden.
|
||||
*/
|
||||
public function get(string $ip): ?array
|
||||
{
|
||||
$ipBin = inet_pton($ip);
|
||||
@@ -355,6 +500,18 @@ class MMDBReader
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leest een kind-node (left/right) uit de MMDB-boom.
|
||||
*
|
||||
* Ondersteunt uitsluitend 28-bit record sizes.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $node Huidig node-index.
|
||||
* @param int $bit Bitwaarde (0 voor left, 1 voor right).
|
||||
* @param int $recordSize Record size in bits.
|
||||
* @return int Doel-node-index of 0 bij niet-ondersteunde record sizes.
|
||||
*/
|
||||
private function readNode(int $node, int $bit, int $recordSize): int
|
||||
{
|
||||
$bytesPerRecord = $recordSize / 4; // 28-bit -> 3.5 bytes per record
|
||||
@@ -372,6 +529,18 @@ class MMDBReader
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodeert een MMDB-datarecord vanaf de gegeven offset.
|
||||
*
|
||||
* Ondersteunt pointer, string, double, uint, map en bool. De array
|
||||
* return value bevat de gedecodeerde waarde en de cursorpositie na
|
||||
* het record.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $offset Start-offset in het bestand.
|
||||
* @return array Pair [mixed $value, int $newOffset].
|
||||
*/
|
||||
private function decodeData(int $offset): array
|
||||
{
|
||||
fseek($this->handle, $offset);
|
||||
|
||||
+201
-35
@@ -1,34 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LogManager - Dynamic logging for CodePress CMS
|
||||
* Dynamische logging voor CodePress CMS.
|
||||
*
|
||||
* Supports three drivers:
|
||||
* - syslog: Send log entries to a remote syslog server (UDP) or local syslog.
|
||||
* - sqlite: Store log entries in a SQLite database (default when no syslog
|
||||
* server is configured and SQLite is available).
|
||||
* - file: Fallback to plain text files when SQLite is unavailable.
|
||||
* Ondersteunt drie drivers:
|
||||
* - syslog: Stuurt log entries naar een remote syslog-server (UDP) of de
|
||||
* lokale syslog.
|
||||
* - sqlite: Slaat log entries op in een SQLite-database (standaard wanneer
|
||||
* geen syslog-server geconfigureerd is en SQLite beschikbaar is).
|
||||
* - file: Fallback naar platte-tekstbestanden wanneer SQLite niet
|
||||
* beschikbaar is.
|
||||
*
|
||||
* Which event types are recorded is controlled dynamically via the
|
||||
* "logging.events" config section (admin, requests, errors, security,
|
||||
* Welke event-types geregistreerd worden, wordt dynamisch gestuurd via de
|
||||
* "logging.events" configuratiesectie (admin, requests, errors, security,
|
||||
* content, system).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class LogManager {
|
||||
/**
|
||||
* Event-type voor admin-acties.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_ADMIN = 'admin';
|
||||
|
||||
/**
|
||||
* Event-type voor request-logging.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_REQUESTS = 'requests';
|
||||
|
||||
/**
|
||||
* Event-type voor fouten en waarschuwingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_ERRORS = 'errors';
|
||||
|
||||
/**
|
||||
* Event-type voor beveiligingsgebeurtenissen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_SECURITY = 'security';
|
||||
|
||||
/**
|
||||
* Event-type voor content-wijzigingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_CONTENT = 'content';
|
||||
|
||||
/**
|
||||
* Event-type voor systeemgebeurtenissen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const EVENT_SYSTEM = 'system';
|
||||
|
||||
/**
|
||||
* De "logging" configuratiesectie, of null indien niet geïnitialiseerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array|null
|
||||
*/
|
||||
private static $config = null;
|
||||
|
||||
/**
|
||||
* Lazy-initialized PDO-verbinding met de SQLite-database, of null.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var \PDO|null
|
||||
*/
|
||||
private static $pdo = null;
|
||||
|
||||
/**
|
||||
* Pad naar het SQLite-databasebestand, of null indien niet geïnitialiseerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string|null
|
||||
*/
|
||||
private static $dbPath = null;
|
||||
|
||||
/**
|
||||
* Initialize the log manager with the logging config section.
|
||||
* Initialiseert de log-manager met de logging-configuratie.
|
||||
*
|
||||
* @param array $loggingConfig The "logging" section from config.json
|
||||
* Slaat de configuratie op en stelt het pad naar de SQLite-database in
|
||||
* onder `admin/storage/logs`.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $loggingConfig De "logging" sectie uit config.json.
|
||||
* @return void
|
||||
*/
|
||||
public static function init(array $loggingConfig): void
|
||||
{
|
||||
@@ -37,7 +108,11 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether logging is enabled at all.
|
||||
* Geeft aan of logging in het algemeen ingeschakeld is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien logging ingeschakeld is, anders false.
|
||||
*/
|
||||
public static function isEnabled(): bool
|
||||
{
|
||||
@@ -45,9 +120,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a given event type should be recorded.
|
||||
* Geeft aan of een bepaald event-type geregistreerd moet worden.
|
||||
*
|
||||
* @param string $event One of the EVENT_* constants
|
||||
* Controleert eerst of logging in het algemeen ingeschakeld is en
|
||||
* daarna of het specifieke event aan staat in de configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $event Een van de EVENT_* constanten.
|
||||
* @return bool True indien het event geregistreerd moet worden.
|
||||
*/
|
||||
public static function isEventEnabled(string $event): bool
|
||||
{
|
||||
@@ -59,9 +140,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local storage driver: 'sqlite' (falling back to 'file' if
|
||||
* SQLite is unavailable). Syslog is an additional output, not a storage
|
||||
* driver, so it never replaces local storage.
|
||||
* Geeft de actieve lokale opslag-driver.
|
||||
*
|
||||
* Retourneert 'sqlite' wanneer SQLite beschikbaar is, anders 'file'.
|
||||
* Syslog is een aanvullende output en vervangt nooit de lokale
|
||||
* opslag, en komt dus nooit als driver terug.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string 'sqlite' of 'file'.
|
||||
*/
|
||||
public static function getDriver(): string
|
||||
{
|
||||
@@ -75,12 +162,20 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a log entry if the event type is enabled.
|
||||
* Slaat een log-entry op indien het event-type ingeschakeld is.
|
||||
*
|
||||
* @param string $event Event type (EVENT_* constant)
|
||||
* @param string $level Log level (info, warning, error, debug)
|
||||
* @param string $message Log message
|
||||
* @param array $context Additional structured context
|
||||
* De entry wordt altijd lokaal opgeslagen (SQLite of file-fallback) zodat
|
||||
* de dynamische admin-log altijd entries bevat. Wanneer een syslog-host
|
||||
* geconfigureerd is, wordt de entry tevens naar de remote syslog-server
|
||||
* gestuurd. Het IP-adres wordt uit de context of de RequestLogger gehaald.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $event Event-type (een EVENT_* constante).
|
||||
* @param string $level Loglevel (info, warning, error, debug).
|
||||
* @param string $message Logbericht.
|
||||
* @param array $context Aanvullende gestructureerde context. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
public static function log(string $event, string $level, string $message, array $context = []): void
|
||||
{
|
||||
@@ -115,7 +210,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a log entry to a remote syslog server over UDP.
|
||||
* Stuurt een log-entry naar een remote syslog-server over UDP.
|
||||
*
|
||||
* Bouwt een syslog-bericht op met facility, severity en ident en stuurt
|
||||
* dit via een UDP-socket. Verbindings- en schrijffouten worden gesilenced.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $entry De te versturen log-entry.
|
||||
* @return void
|
||||
*/
|
||||
private static function writeSyslog(array $entry): void
|
||||
{
|
||||
@@ -138,7 +241,16 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a log entry in the SQLite database.
|
||||
* Slaat een log-entry op in de SQLite-database.
|
||||
*
|
||||
* De context-array wordt als JSON opgeslagen. Wanneer de PDO-verbinding
|
||||
* niet beschikbaar is of het schrijven faalt, wordt teruggevallen op
|
||||
* de file-driver.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $entry De op te slaan log-entry.
|
||||
* @return void
|
||||
*/
|
||||
private static function writeSqlite(array $entry): void
|
||||
{
|
||||
@@ -167,7 +279,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a log entry to a plain text file (fallback driver).
|
||||
* Voegt een log-entry toe aan een platte-tekstbestand (fallback-driver).
|
||||
*
|
||||
* Zorgt dat de doelmap bestaat en schrijft de entry met een vaste
|
||||
* opmaak. Schrijffouten worden gesilenced.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $entry De toe te voegen log-entry.
|
||||
* @return void
|
||||
*/
|
||||
private static function writeFile(array $entry): void
|
||||
{
|
||||
@@ -182,7 +302,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get (and lazily create) the PDO connection to the SQLite database.
|
||||
* Geeft (en creëert lazy) de PDO-verbinding met de SQLite-database.
|
||||
*
|
||||
* Maakt de tabel en indices aan indien deze nog niet bestaan. Bij een
|
||||
* fout wordt de interne verbinding op null gezet en null teruggegeven,
|
||||
* zodat de aanroeper kan terugvallen op de file-driver.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return \PDO|null PDO-verbinding of null indien SQLite niet beschikbaar/faalt.
|
||||
*/
|
||||
private static function getPdo(): ?\PDO
|
||||
{
|
||||
@@ -222,7 +350,11 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the SQLite PDO driver is available.
|
||||
* Geeft aan of de SQLite PDO-driver beschikbaar is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien PDO en de sqlite-driver beschikbaar zijn.
|
||||
*/
|
||||
private static function sqliteAvailable(): bool
|
||||
{
|
||||
@@ -230,7 +362,16 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a facility name to its syslog numeric value.
|
||||
* Vertaalt een facility-naam naar de bijbehorende numerieke syslog-waarde.
|
||||
*
|
||||
* Ondersteunt de standaard facilities (kern, user, mail, daemon, auth,
|
||||
* syslog, lpr, news, uucp, cron, authpriv, ftp) en local0 t/m local7.
|
||||
* Onbekende faciliteiten vallen terug op local0 (16).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $facility Naam van de facility. Default 'local0' wordt door aanroeper gezet.
|
||||
* @return int Numerieke facility-waarde.
|
||||
*/
|
||||
private static function syslogFacility(string $facility): int
|
||||
{
|
||||
@@ -245,7 +386,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a log level to its syslog severity value.
|
||||
* Vertaalt een loglevel naar de bijbehorende syslog-severity.
|
||||
*
|
||||
* Ondersteunt debug t/m emergency. Onbekende levels vallen terug op
|
||||
* info (6). De invoer wordt case-insensitive vergeleken.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $level Loglevel (debug, info, notice, warning, error, ...).
|
||||
* @return int Numerieke severity-waarde (0 t/m 7).
|
||||
*/
|
||||
private static function syslogSeverity(string $level): int
|
||||
{
|
||||
@@ -263,13 +412,22 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Query recent log entries from the active store.
|
||||
* Zoekt recente log-entries op uit de actieve opslag.
|
||||
*
|
||||
* @param int $limit Number of entries to return
|
||||
* @param string|null $event Optional event filter
|
||||
* @param string|null $level Optional level filter (info, warning, error, ...)
|
||||
* @param string|null $search Optional text search on the message
|
||||
* @return array List of log entries (newest first)
|
||||
* Bij de SQLite-driver wordt een geparametriseerde query uitgevoerd met
|
||||
* optionele filters op event, level en een zoekterm op het bericht.
|
||||
* Bij de file-driver wordt het logbestand uitgelezen en met een regex
|
||||
* geparseerd; de resultaten worden gefilterd en oudste eerst teruggegeven.
|
||||
* Resultaten worden altijd nieuwste eerst geretourneerd (SQLite via
|
||||
* ORDER BY id DESC, file via array_reverse).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $limit Aantal terug te geven entries. Default 200.
|
||||
* @param string|null $event Optioneel event-filter. Default null (geen filter).
|
||||
* @param string|null $level Optioneel levelfilter (info, warning, error, ...). Default null.
|
||||
* @param string|null $search Optionele tekstzoekopdracht op het bericht. Default null.
|
||||
* @return array<int, array<string, mixed>> Lijst met log-entries (nieuwste eerst).
|
||||
*/
|
||||
public static function getLogs(int $limit = 200, ?string $event = null, ?string $level = null, ?string $search = null): array
|
||||
{
|
||||
@@ -340,7 +498,15 @@ class LogManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stored log entries.
|
||||
* Wist alle opgeslagen log-entries.
|
||||
*
|
||||
* Bij de SQLite-driver worden alle rijen uit de logs-tabel verwijderd.
|
||||
* Bij de file-driver wordt het logbestand leeggemaakt. Schrijffouten
|
||||
* worden gesilenced.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear(): void
|
||||
{
|
||||
|
||||
+119
-43
@@ -1,27 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Simple Logger Class for CodePress CMS
|
||||
*
|
||||
* Provides structured logging with log levels and file output.
|
||||
*
|
||||
* @package CodePress
|
||||
* @version 1.0.0
|
||||
* Eenvoudige logger voor CodePress CMS.
|
||||
*
|
||||
* Voorziet in gestructureerde logging met loglevels en bestandsuitvoer.
|
||||
* Logregels worden naar een tekstbestand geschreven en, indien aanwezig,
|
||||
* tevens door de dynamische LogManager gerouteerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
|
||||
class Logger {
|
||||
/**
|
||||
* Loglevel voor debug-boodschappen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const DEBUG = 'DEBUG';
|
||||
|
||||
/**
|
||||
* Loglevel voor informatieve boodschappen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const INFO = 'INFO';
|
||||
|
||||
/**
|
||||
* Loglevel voor waarschuwingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const WARNING = 'WARNING';
|
||||
|
||||
/**
|
||||
* Loglevel voor fouten.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
const ERROR = 'ERROR';
|
||||
|
||||
|
||||
/**
|
||||
* Pad naar het logbestand, of null indien nog niet geïnitialiseerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string|null
|
||||
*/
|
||||
private static $logFile = null;
|
||||
|
||||
/**
|
||||
* Of debug-logging actief is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var bool
|
||||
*/
|
||||
private static $debugMode = false;
|
||||
|
||||
/**
|
||||
* Initialize logger
|
||||
*
|
||||
* @param string $logFile Path to log file
|
||||
* @param bool $debugMode Enable debug logging
|
||||
* Initialiseert de logger.
|
||||
*
|
||||
* Stelt het pad naar het logbestand in en zorgt dat de bijbehorende
|
||||
* map bestaat. Indien geen pad wordt opgegeven valt de logger terug
|
||||
* op een standaardpad onder de core-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string|null $logFile Pad naar het logbestand. Default null (standaardlocatie).
|
||||
* @param bool $debugMode Of debug-logging actief is. Default false.
|
||||
* @return void
|
||||
*/
|
||||
public static function init($logFile = null, $debugMode = false) {
|
||||
if ($logFile === null) {
|
||||
@@ -39,10 +87,13 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug message (only in debug mode)
|
||||
*
|
||||
* @param string $message Message to log
|
||||
* @param array $context Additional context
|
||||
* Logt een debug-boodschap (enkel in debug-modus).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $message Boodschap die gelogd moet worden.
|
||||
* @param array $context Aanvullende contextuele data. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
public static function debug($message, $context = []) {
|
||||
if (self::$debugMode) {
|
||||
@@ -51,41 +102,59 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message
|
||||
*
|
||||
* @param string $message Message to log
|
||||
* @param array $context Additional context
|
||||
* Logt een informatieve boodschap.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $message Boodschap die gelogd moet worden.
|
||||
* @param array $context Aanvullende contextuele data. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
public static function info($message, $context = []) {
|
||||
self::write(self::INFO, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning message
|
||||
*
|
||||
* @param string $message Message to log
|
||||
* @param array $context Additional context
|
||||
* Logt een waarschuwing.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $message Boodschap die gelogd moet worden.
|
||||
* @param array $context Aanvullende contextuele data. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
public static function warning($message, $context = []) {
|
||||
self::write(self::WARNING, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error message
|
||||
*
|
||||
* @param string $message Message to log
|
||||
* @param array $context Additional context
|
||||
* Logt een fout.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $message Boodschap die gelogd moet worden.
|
||||
* @param array $context Aanvullende contextuele data. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
public static function error($message, $context = []) {
|
||||
self::write(self::ERROR, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write log entry to file
|
||||
*
|
||||
* @param string $level Log level
|
||||
* @param string $message Message to log
|
||||
* @param array $context Additional context
|
||||
* Schrijft een logregel naar het bestand en routeert deze door LogManager.
|
||||
*
|
||||
* De context-array wordt als JSON aan de logregel toegevoegd. Fouten bij
|
||||
* het schrijven worden gesilenced (graceful degradation). Wanneer de
|
||||
* LogManager class beschikbaar is, wordt de entry tevens naar de
|
||||
* dynamische log gestuurd (errors/warnings naar EVENT_ERRORS, verder
|
||||
* naar EVENT_SYSTEM).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $level Loglevel (DEBUG, INFO, WARNING, ERROR).
|
||||
* @param string $message Boodschap die gelogd moet worden.
|
||||
* @param array $context Aanvullende contextuele data. Default lege array.
|
||||
* @return void
|
||||
*/
|
||||
private static function write($level, $message, $context = []) {
|
||||
if (self::$logFile === null) {
|
||||
@@ -109,18 +178,22 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log file path
|
||||
*
|
||||
* @return string Log file path
|
||||
* Geeft het pad naar het logbestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string|null Pad naar het logbestand of null indien niet geïnitialiseerd.
|
||||
*/
|
||||
public static function getLogFile() {
|
||||
return self::$logFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear log file
|
||||
*
|
||||
* @return bool Success status
|
||||
* Verwijdert het logbestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien het bestand succesvol verwijderd is, anders false.
|
||||
*/
|
||||
public static function clear() {
|
||||
if (self::$logFile && file_exists(self::$logFile)) {
|
||||
@@ -130,13 +203,16 @@ class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last N lines from log file
|
||||
* Geeft de laatste N regels uit het logbestand.
|
||||
*
|
||||
* Reads the file backwards in chunks so large log files never have to be
|
||||
* loaded into memory in their entirety.
|
||||
* Leest het bestand achterwaarts in chunks zodat grote logbestanden
|
||||
* nooit volledig in het geheugen geladen hoeven te worden. De regels
|
||||
* worden inclusief afsluitende newlines teruggegeven, oudste eerst.
|
||||
*
|
||||
* @param int $lines Number of lines to retrieve
|
||||
* @return array Log lines (including trailing newlines, oldest first)
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $lines Aantal terug te halen regels. Default 100.
|
||||
* @return array<string> Logregels (oudste eerst) of lege array indien niet beschikbaar.
|
||||
*/
|
||||
public static function tail($lines = 100) {
|
||||
if (!self::$logFile || !file_exists(self::$logFile)) {
|
||||
|
||||
@@ -1,16 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Limiteert het aantal acties per identifier binnen een tijdvenster.
|
||||
*
|
||||
* Houdt per identifier (zoals een IP of gebruikersnaam) bij hoeveel pogingen
|
||||
* binnen een geconfigureerd tijdvenster zijn gedaan. Oudere pogingen vallen
|
||||
* buiten het venster en worden genegeerd. De teller wordt opgeslagen in een
|
||||
* CacheInterface-implementatie, standaard FileCache.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class RateLimiter {
|
||||
/**
|
||||
* Maximum aantal toegestane pogingen binnen het tijdvenster.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var int
|
||||
*/
|
||||
private int $maxAttempts;
|
||||
|
||||
/**
|
||||
* Lengte van het tijdvenster in seconden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var int
|
||||
*/
|
||||
private int $timeWindow;
|
||||
|
||||
/**
|
||||
* Cache-implementatie waarin de pogingen worden bijgehouden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var CacheInterface
|
||||
*/
|
||||
private CacheInterface $cache;
|
||||
|
||||
/**
|
||||
* Maak een nieuwe RateLimiter aan.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $maxAttempts Maximum aantal pogingen binnen het tijdvenster. Default 10.
|
||||
* @param int $timeWindow Lengte van het tijdvenster in seconden. Default 60.
|
||||
* @param CacheInterface|null $cache Optioneel een eigen cache-implementatie; bij null wordt FileCache gebruikt.
|
||||
*/
|
||||
public function __construct(int $maxAttempts = 10, int $timeWindow = 60, ?CacheInterface $cache = null) {
|
||||
$this->maxAttempts = $maxAttempts;
|
||||
$this->timeWindow = $timeWindow;
|
||||
$this->cache = $cache ?? new FileCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bepaal of een nieuwe poging voor de identifier is toegestaan.
|
||||
*
|
||||
* Verwijder eerst pogingen die buiten het tijdvenster vallen. Wanneer het
|
||||
* aantal resterende pogingen onder het maximum ligt wordt de huidige
|
||||
* poging geregistreerd en true teruggegeven; anders false.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $identifier Unieke sleutel voor de te beperken actor (bijv. IP of gebruikersnaam).
|
||||
* @return bool True wanneer de poging is toegestaan, false wanneer het limiet is bereikt.
|
||||
*/
|
||||
public function isAllowed(string $identifier): bool {
|
||||
$key = 'ratelimit_' . md5($identifier);
|
||||
$attempts = $this->cache->get($key) ?? [];
|
||||
@@ -32,6 +83,17 @@ class RateLimiter {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geef het aantal nog resterende pogingen voor de identifier.
|
||||
*
|
||||
* Telt alleen pogingen die binnen het tijdvenster vallen. Het resultaat
|
||||
* is nooit negatief.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $identifier Unieke sleutel voor de te beperken actor.
|
||||
* @return int Aantal resterende pogingen; minimaal 0.
|
||||
*/
|
||||
public function getRemainingAttempts(string $identifier): int {
|
||||
$key = 'ratelimit_' . md5($identifier);
|
||||
$attempts = $this->cache->get($key) ?? [];
|
||||
@@ -44,6 +106,14 @@ class RateLimiter {
|
||||
return max(0, $this->maxAttempts - count($attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wis alle geregistreerde pogingen voor een identifier.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $identifier Unieke sleutel voor de te beperken actor.
|
||||
* @return void
|
||||
*/
|
||||
public function reset(string $identifier): void {
|
||||
$key = 'ratelimit_' . md5($identifier);
|
||||
$this->cache->delete($key);
|
||||
|
||||
@@ -1,14 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Logging en analyse van inkomende requests.
|
||||
*
|
||||
* Biedt functionaliteit om inkomende requests naar een logbestand te
|
||||
* schrijven, IP-adressen te anonimiseren, IP's tegen lijsten/CIDR-ranges
|
||||
* te matchen, het client-IP te bepalen en bezoekerstype (bot/mens/AI)
|
||||
* te detecteren.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class RequestLogger
|
||||
{
|
||||
/**
|
||||
* Pad naar het logbestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $logFile;
|
||||
|
||||
/**
|
||||
* Maak een nieuwe RequestLogger aan.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $logFile Pad naar het te gebruiken logbestand.
|
||||
*/
|
||||
public function __construct(string $logFile)
|
||||
{
|
||||
$this->logFile = $logFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schrijf een request-regel naar het logbestand.
|
||||
*
|
||||
* De gebruikersagent en referrer worden ontdaan van controle-tekens en
|
||||
* afgekapt tot 500 tekens. De landcode wordt alleen opgenomen wanneer
|
||||
* deze exact twee tekens lang is. De mapstructuur wordt automatisch
|
||||
* aangemaakt indien deze nog niet bestaat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $page Opgevraagde pagina of route.
|
||||
* @param string $ip Client IP-adres.
|
||||
* @param string $userAgent HTTP User-Agent header.
|
||||
* @param string $referrer HTTP Referer header.
|
||||
* @param string $host Hostnaam van het request.
|
||||
* @param string $acceptLanguage HTTP Accept-Language header.
|
||||
* @param string $status Statusaanduiding van het request. Default 'ok'.
|
||||
* @param string|null $country Tweeletterige landcode of null. Default null.
|
||||
* @return void
|
||||
*/
|
||||
public function log(string $page, string $ip, string $userAgent, string $referrer, string $host, string $acceptLanguage, string $status = 'ok', ?string $country = null): void
|
||||
{
|
||||
$dir = dirname($this->logFile);
|
||||
@@ -25,7 +68,16 @@ class RequestLogger
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask the last octet (IPv4) or last block (IPv6) of an IP address
|
||||
* Anonimiseer het laatste deel van een IP-adres.
|
||||
*
|
||||
* Voor IPv4 wordt het laatste octet vervangen door 'x'. Voor IPv6 worden
|
||||
* alleen de eerste vier blokken behouden en de rest vervangen door '::x'.
|
||||
* Ongeldige adressen worden ongewijzigd teruggegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip Het te anonimiseren IP-adres.
|
||||
* @return string Het geanonimiseerde IP-adres of het origineel bij een ongeldig adres.
|
||||
*/
|
||||
public static function anonymizeIp(string $ip): string
|
||||
{
|
||||
@@ -45,13 +97,17 @@ class RequestLogger
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an IP matches any entry in a list of IPs/CIDR ranges.
|
||||
* Controleer of een IP overeenkomt met een entry uit een lijst van IP's of CIDR-ranges.
|
||||
*
|
||||
* Supports exact IPv4/IPv6 addresses and CIDR notation (e.g. 192.168.0.0/16).
|
||||
* Ondersteunt exacte IPv4/IPv6-adressen en CIDR-notatie (bijv. 192.168.0.0/16).
|
||||
* Bij een prefix-lengte van 0 komt ieder IP van dezelfde adresfamilie overeen.
|
||||
* Adresfamilies (IPv4 vs IPv6) worden niet onderling gemengd.
|
||||
*
|
||||
* @param string $ip The client IP to test
|
||||
* @param array $list List of IPs and/or CIDR ranges
|
||||
* @return bool True if the IP matches any entry
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ip Het te testen client IP-adres.
|
||||
* @param string[] $list Lijst van IP-adressen en/of CIDR-ranges.
|
||||
* @return bool True wanneer het IP met een entry overeenkomt.
|
||||
*/
|
||||
public static function ipMatchesList(string $ip, array $list): bool
|
||||
{
|
||||
@@ -117,6 +173,18 @@ class RequestLogger
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bepaal het client IP-adres uit de beschikbare request-headers.
|
||||
*
|
||||
* Doorloopt in twee passes een vaste set headers (zoals CF-Connecting-IP,
|
||||
* X-Real-IP, X-Forwarded-For en REMOTE_ADDR). De eerste pass geeft
|
||||
* voorrang aan geldige publieke IP's en slaat interne/reserved ranges
|
||||
* over; de tweede pass is een fallback voor lokale ontwikkelomgevingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Het gedetecteerde client IP-adres, of '127.0.0.1' als fallback.
|
||||
*/
|
||||
public static function getClientIp(): string
|
||||
{
|
||||
$headerKeys = [
|
||||
@@ -165,6 +233,21 @@ class RequestLogger
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detecteer het type bezoeker aan de hand van de User-Agent.
|
||||
*
|
||||
* Wanneer de BotGuard-class beschikbaar is wordt de UA geïdentificeerd
|
||||
* en een passend label, badge-kleur en Bootstrap-icon teruggegeven
|
||||
* (ai, search, scraper, generieke bot, lege UA of mens). Bij een
|
||||
* ingelogde gebruiker wordt de naam aan het label toegevoegd. Zonder
|
||||
* BotGuard wordt een algemene bezoeker teruggegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $ua De User-Agent string.
|
||||
* @param string $user Optionele gebruikersnaam; bij 'Gast' of leeg wordt geen naam toegevoegd. Default ''.
|
||||
* @return array<string,string> Associatieve array met keys: type, label, badge, icon.
|
||||
*/
|
||||
public static function detectVisitorInfo(string $ua, string $user = ''): array
|
||||
{
|
||||
if (class_exists('BotGuard')) {
|
||||
@@ -202,6 +285,18 @@ class RequestLogger
|
||||
return ['type' => 'unknown', 'label' => 'Bezoeker', 'badge' => 'secondary', 'icon' => 'bi-person'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detecteer of het huidige request afkomstig is van een bekende bot.
|
||||
*
|
||||
* Leest de User-Agent uit de request-headers en identificeert deze via
|
||||
* BotGuard wanneer beschikbaar. Alleen de categorieën 'ai', 'search'
|
||||
* en 'scraper' worden als bot beschouwd en als uppercase string
|
||||
* teruggegeven; anders null.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string|null Boticategorie ('AI', 'SEARCH' of 'SCRAPER') of null bij geen bot.
|
||||
*/
|
||||
public static function detectBot(): ?string
|
||||
{
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
@@ -214,6 +309,20 @@ class RequestLogger
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lees de laatste regels uit het logbestand en parse deze.
|
||||
*
|
||||
* De regels worden geparseerd volgens het vaste logformaat en omgezet
|
||||
* naar associatieve arrays met tijd, IP, gebruiker, bezoeker-info, taal,
|
||||
* pagina, UA, referrer, status en landcode. Regels waarvan de host leeg
|
||||
* is of een IP/cli-waarde bevat worden als gebruiker 'Gast' gemarkeerd.
|
||||
* Het resultaat is nieuwste-regel-eerst.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param int $lines Aantal te lezen regels vanaf het einde. Default 100.
|
||||
* @return array<int,array<string,mixed>> Geparseerde logregels, nieuwste eerst. Lege array bij ontbrekend bestand.
|
||||
*/
|
||||
public function getLogs(int $lines = 100): array
|
||||
{
|
||||
if (!file_exists($this->logFile)) {
|
||||
|
||||
+169
-45
@@ -5,24 +5,62 @@ use Twig\Environment;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
/**
|
||||
* ThemeManager - Resolves and renders the active theme
|
||||
* Beheert het actieve thema en de rendering hiervan.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Resolve the active theme directory from config
|
||||
* - Load theme.json (title, default_layout, template mapping, colors)
|
||||
* - Build a Twig environment rooted at the theme directory
|
||||
* - Compile theme SCSS to CSS (in assets/css_compiled/)
|
||||
* - Map a requested layout to a concrete .twig template, falling back
|
||||
* to the theme's default_layout when the layout is unknown
|
||||
* Verantwoordelijkheden:
|
||||
* - Bepaalt de actieve thema-map aan de hand van de configuratie.
|
||||
* - Laadt theme.json (titel, default_layout, template-mapping, kleuren).
|
||||
* - Bouwt een Twig-omgeving geworteld in de thema-map.
|
||||
* - Compileert thema-SCSS naar CSS (in assets/css_compiled/).
|
||||
* - Mapt een aangevraagde layout naar een concreet .twig-template, met
|
||||
* fallback naar de default_layout van het thema wanneer de layout
|
||||
* onbekend is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class ThemeManager {
|
||||
/**
|
||||
* Volledige CMS-configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array Bevat o.a. 'theme_dir' en 'theme'.
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Absoluut pad naar de actieve thema-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private $themeDir;
|
||||
|
||||
/**
|
||||
* Ruwe theme.json configuratie-array.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array
|
||||
*/
|
||||
private $themeConfig;
|
||||
|
||||
/**
|
||||
* Twig-omgeving voor het renderen van thema-templates.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var Environment
|
||||
*/
|
||||
private $twig;
|
||||
|
||||
/**
|
||||
* @param array $config Full CMS config (must contain 'theme_dir' and 'theme')
|
||||
* Constructor: initialiseert de thema-manager met de CMS-configuratie.
|
||||
*
|
||||
* Stelt de thema-map, theme.json-configuratie en een Twig-omgeving in.
|
||||
* De Twig-cache staat uit en auto-escape is uitgeschakeld (de thema's
|
||||
* beheren eigen escaping waar nodig).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array $config Volledige CMS-configuratie; moet 'theme_dir' en 'theme' bevatten.
|
||||
*/
|
||||
public function __construct(array $config) {
|
||||
$this->config = $config;
|
||||
@@ -37,55 +75,85 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute path of the active theme directory
|
||||
* Geeft het absolute pad van de actieve thema-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de thema-map.
|
||||
*/
|
||||
public function getThemeDir(): string {
|
||||
return $this->themeDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw theme.json config array
|
||||
* Geeft de ruwe theme.json configuratie-array.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array Theme.json inhoud, of lege array bij afwezigheid.
|
||||
*/
|
||||
public function getThemeConfig(): array {
|
||||
return $this->themeConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme title (from theme.json 'title' or 'name')
|
||||
* Geeft de thema-titel.
|
||||
*
|
||||
* Leest eerst 'title' uit theme.json, dan 'name', en als laatste
|
||||
* fallback de mapnaam van het thema.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Thematische titel.
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return $this->themeConfig['title'] ?? $this->themeConfig['name'] ?? basename($this->themeDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme config section (default_template, background settings, etc.)
|
||||
* Geeft de 'config'-sectie van theme.json.
|
||||
*
|
||||
* Bevat o.a. default_template en achtergrondinstellingen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array Config-sectie, of lege array bij afwezigheid.
|
||||
*/
|
||||
public function getConfig(): array {
|
||||
return $this->themeConfig['config'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme template section (layout key => .twig file mapping)
|
||||
* Geeft de 'template'-sectie van theme.json.
|
||||
*
|
||||
* Bevat de mapping van layout-key naar .twig-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,string> Layout-key => .twig-bestand mapping.
|
||||
*/
|
||||
public function getTemplates(): array {
|
||||
return $this->themeConfig['template'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the .twig template file for a requested layout.
|
||||
* Bepaalt het .twig-templatebestand voor een aangevraagde layout.
|
||||
*
|
||||
* Templates are defined in theme.json under the "template" section:
|
||||
* Templates worden gedefinieerd in theme.json onder de "template"-sectie:
|
||||
* { "template": { "full_content": "full_content.twig", ... } }
|
||||
* The default template is defined in the "config" section:
|
||||
* De standaardtemplate staat in de "config"-sectie:
|
||||
* { "config": { "default_template": "full_content", ... } }
|
||||
*
|
||||
* Priority:
|
||||
* 1. If the layout is a known key in the "template" section, use its mapped file.
|
||||
* 2. Otherwise fall back to config.default_template.
|
||||
* 3. Final safety net: full_content.twig.
|
||||
* Prioriteit:
|
||||
* 1. Als de layout een bekende key is in de "template"-sectie, gebruik
|
||||
* het bijbehorende bestand.
|
||||
* 2. Anders fallback naar config.default_template.
|
||||
* 3. Laatste veiligheidsnet: full_content.twig.
|
||||
*
|
||||
* @param string $layout Requested layout key (e.g. 'left_sidebar')
|
||||
* @return string Template name usable by the Twig loader
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $layout Aangevraagde layout-key (bijv. 'left_sidebar').
|
||||
* @return string Templacenaam bruikbaar voor de Twig-loader.
|
||||
*/
|
||||
public function getTemplateForLayout(string $layout): string {
|
||||
$layout = trim($layout);
|
||||
@@ -111,16 +179,23 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available layout keys defined in the "template" section.
|
||||
* Geeft de lijst met beschikbare layout-keys uit de "template"-sectie.
|
||||
*
|
||||
* @return array List of layout keys
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string> Lijst met layout-keys.
|
||||
*/
|
||||
public function getLayouts(): array {
|
||||
return array_keys($this->getTemplates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a template file exists in the theme directory
|
||||
* Controleert of een templatebestand bestaat in de thema-map.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $file Bestandsnaam van het template (relatief t.o.v. thema-map).
|
||||
* @return bool True als het bestand bestaat.
|
||||
*/
|
||||
private function templateExists(string $file): bool {
|
||||
$path = $this->themeDir . '/' . ltrim($file, '/');
|
||||
@@ -128,11 +203,13 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a layout template with the given data.
|
||||
* Rendert een layout-template met de gegeven data.
|
||||
*
|
||||
* @param string $layout Requested layout key
|
||||
* @param array $data Template variables
|
||||
* @return string Rendered HTML
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $layout Aangevraagde layout-key.
|
||||
* @param array $data Template-variabelen.
|
||||
* @return string Gerenderde HTML.
|
||||
*/
|
||||
public function render(string $layout, array $data): string {
|
||||
$template = $this->getTemplateForLayout($layout);
|
||||
@@ -140,11 +217,17 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the theme's SCSS to CSS (cached by source mtime).
|
||||
* Compiles from assets/scss/theme.scss to assets/css_compiled/theme.css
|
||||
* Compileert de SCSS van het thema naar CSS (gecacht op bron-mtime).
|
||||
*
|
||||
* @param bool $force Force recompilation
|
||||
* @return string|null Absolute path to the compiled CSS, or null if none
|
||||
* Compileert vanuit assets/scss/theme.scss naar assets/css_compiled/theme.css.
|
||||
* De gecompileerde CSS en een .mtime-cachebestand worden op alleen-lezen
|
||||
* (0444) gezet om handmatige aanpassingen te voorkomen. Bij een fout
|
||||
* wordt deze gelogd en null teruggegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param bool $force Forceer hercompilatie ongeacht de cache. Default false.
|
||||
* @return string|null Absoluut pad naar de gecompileerde CSS, of null bij fout/afwezigheid.
|
||||
*/
|
||||
public function compileCss(bool $force = false): ?string {
|
||||
$scssFile = $this->themeDir . '/assets/scss/theme.scss';
|
||||
@@ -193,8 +276,15 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public URL for the theme CSS.
|
||||
* Uses assets/css_compiled/theme.css (compiled from SCSS by scssphp).
|
||||
* Geeft de publieke URL voor de thema-CSS.
|
||||
*
|
||||
* Gebruikt assets/css_compiled/theme.css (gecompileerd vanuit SCSS door
|
||||
* scssphp). Indien de gecompileerde CSS nog niet bestaat, wordt deze
|
||||
* alsnog gecompileerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string|null Publieke CSS-URL, of null indien niet beschikbaar.
|
||||
*/
|
||||
public function getCssUrl(): ?string {
|
||||
$compiledCss = $this->themeDir . '/assets/css_compiled/theme.css';
|
||||
@@ -212,7 +302,11 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public URL for the theme JS, or null if unavailable.
|
||||
* Geeft de publieke URL voor de thema-JS, of null indien niet beschikbaar.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string|null Publieke JS-URL, of null indien niet beschikbaar.
|
||||
*/
|
||||
public function getJsUrl(): ?string {
|
||||
$jsFile = $this->themeDir . '/assets/js/theme.js';
|
||||
@@ -223,7 +317,12 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public URL for a theme asset.
|
||||
* Geeft de publieke URL voor een thema-asset.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $assetPath Pad naar de asset binnen de assets/-map van het thema.
|
||||
* @return string Publieke URL in de vorm '/themes/<naam>/assets/<path>'.
|
||||
*/
|
||||
private function getThemeAssetUrl(string $assetPath): string {
|
||||
$themeName = basename($this->themeDir);
|
||||
@@ -231,14 +330,22 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if theme has SCSS source file.
|
||||
* Controleert of het thema een SCSS-bronbestand heeft.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True als assets/scss/theme.scss bestaat.
|
||||
*/
|
||||
public function hasScss(): bool {
|
||||
return is_file($this->themeDir . '/assets/scss/theme.scss');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if compiled CSS is newer than SCSS source.
|
||||
* Controleert of de gecompileerde CSS nieuwer is dan de SCSS-bron.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True als de CSS-_mtime >= SCSS-mtime, anders false.
|
||||
*/
|
||||
public function isScssCompiled(): bool {
|
||||
$scssFile = $this->themeDir . '/assets/scss/theme.scss';
|
||||
@@ -252,15 +359,24 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if theme has manual CSS file.
|
||||
* Controleert of het thema een handmatig CSS-bestand heeft.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True als assets/css/theme.css bestaat.
|
||||
*/
|
||||
public function hasManualCss(): bool {
|
||||
return is_file($this->themeDir . '/assets/css/theme.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of CSS files in theme assets/css/ directory.
|
||||
* Excludes css_compiled directory.
|
||||
* Geeft een lijst met CSS-bestanden in de assets/css/-map van het thema.
|
||||
*
|
||||
* De css_compiled-map wordt uitgesloten.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string> Lijst met publieke CSS-URLs.
|
||||
*/
|
||||
public function getCssFiles(): array {
|
||||
$cssDir = $this->themeDir . '/assets/css';
|
||||
@@ -281,7 +397,11 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of JS files in theme assets/js/ directory.
|
||||
* Geeft een lijst met JS-bestanden in de assets/js/-map van het thema.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string> Lijst met publieke JS-URLs.
|
||||
*/
|
||||
public function getJsFiles(): array {
|
||||
$jsDir = $this->themeDir . '/assets/js';
|
||||
@@ -302,7 +422,11 @@ class ThemeManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for favicon if it exists.
|
||||
* Geeft de URL voor de favicon indien deze bestaat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string|null Publieke favicon-URL, of null indien niet beschikbaar.
|
||||
*/
|
||||
public function getFaviconUrl(): ?string {
|
||||
$faviconFile = $this->themeDir . '/assets/img/favicon.svg';
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuratie-loader voor CodePress CMS.
|
||||
*
|
||||
* Laadt config.json (of maakt deze aan op basis van config.json.example of
|
||||
* een ingebouwde standaardconfiguratie), voegt ontbrekende secties
|
||||
* (security, analytics, logging) samen met defaults, converteert relatieve
|
||||
* content_dir-paden naar absoluut, laadt de actieve theme-configuratie en
|
||||
* returnt de uiteindelijke configuratie-array. Bij falen volgt een
|
||||
* minimale fallback-configuratie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @package CodePress
|
||||
*/
|
||||
|
||||
// Simple configuration loader
|
||||
$configJsonPath = __DIR__ . '/../../config.json';
|
||||
$configExamplePath = __DIR__ . '/../../config.json.example';
|
||||
|
||||
/**
|
||||
* config.json automatisch aanmaken als deze nog niet bestaat.
|
||||
*
|
||||
* Kopieert config.json.example indien aanwezig; anders wordt een ingebouwde
|
||||
* standaardconfiguratie weggeschreven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Auto-create config.json if it does not exist
|
||||
if (!file_exists($configJsonPath)) {
|
||||
if (file_exists($configExamplePath)) {
|
||||
@@ -83,6 +104,15 @@ if (!file_exists($configJsonPath)) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* config.json inladen en ontbrekende secties aanvullen met defaults.
|
||||
*
|
||||
* Voegt default-waarden samen voor security, analytics en logging,
|
||||
* garandeert de excluded_ips-standaardlijst en converteert relatieve
|
||||
* content_dir-paden naar absoluut.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
if (file_exists($configJsonPath)) {
|
||||
$jsonContent = file_get_contents($configJsonPath);
|
||||
$config = json_decode($jsonContent, true);
|
||||
@@ -150,6 +180,11 @@ if (file_exists($configJsonPath)) {
|
||||
$config['content_dir'] = $projectRoot . $config['content_dir'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Actieve theme-configuratie laden vanuit theme.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load active theme
|
||||
$activeTheme = $config['active_theme'] ?? 'default';
|
||||
$themeDir = __DIR__ . '/../../themes/' . $activeTheme;
|
||||
@@ -166,6 +201,11 @@ if (file_exists($configJsonPath)) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimale fallback-configuratie wanneer config.json niet geladen kon worden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Fallback to minimal config
|
||||
return [
|
||||
'site_title' => 'CodePress',
|
||||
|
||||
+60
-20
@@ -1,37 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CodePress CMS Core Loader
|
||||
*
|
||||
* This file serves as the central entry point for the CodePress CMS core system.
|
||||
* It loads all essential classes and configuration needed for the CMS to function.
|
||||
*
|
||||
* Architecture:
|
||||
* - config.php: Configuration loader that merges default settings with config.json
|
||||
* - ThemeManager.php: Twig-based theme rendering + SCSS compilation
|
||||
* - CodePressCMS.php: Main CMS class handling content, navigation, search, and rendering
|
||||
*
|
||||
* Usage:
|
||||
* This file is included by public/index.php, which then:
|
||||
* 1. Loads configuration from config.php
|
||||
* 2. Creates a new CodePressCMS instance with the config
|
||||
* 3. Calls render() to output the complete page
|
||||
*
|
||||
* The separation allows for:
|
||||
* - Clean public entry point (public/index.php)
|
||||
* - Reusable core components
|
||||
* - Proper class organization with PHPDoc documentation
|
||||
* CodePress CMS Core Loader.
|
||||
*
|
||||
* Centraal bootstrap-bestand voor de CodePress CMS-core. Laadt alle
|
||||
* essentiële classes en configuratie die nodig zijn voor het CMS:
|
||||
* configuratie, Composer-autoloader, core utility classes, logger,
|
||||
* plugin-systeem, ContentAPI en de hoofdclass CodePressCMS.
|
||||
*
|
||||
* Architectuur:
|
||||
* - config.php: configuratie-loader die defaults samenvoegt met config.json.
|
||||
* - ThemeManager.php: Twig-based theme rendering + SCSS-compilatie.
|
||||
* - CodePressCMS.php: hoofdclass voor content, navigation, search en rendering.
|
||||
*
|
||||
* Gebruik:
|
||||
* Dit bestand wordt geïnclude door public/index.php, dat vervolgens:
|
||||
* 1. Configuratie laadt vanuit config.php.
|
||||
* 2. Een CodePressCMS-instantie aanmaakt met de config.
|
||||
* 3. render() aanroept om de pagina uit te voeren.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @package CodePress
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuratie-systeem laden: defaults en config.json-merge.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load configuration system - handles default settings and config.json merging
|
||||
require_once 'config.php';
|
||||
|
||||
/**
|
||||
* Composer-autoloader laden indien aanwezig.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load Composer autoloader
|
||||
$autoloader = dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
if (file_exists($autoloader)) {
|
||||
require_once $autoloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core utility classes laden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load core utility classes
|
||||
require_once 'class/Cache.php';
|
||||
require_once 'class/RateLimiter.php';
|
||||
@@ -41,21 +56,46 @@ require_once 'class/GeoIP.php';
|
||||
require_once 'class/Analytics.php';
|
||||
require_once 'class/ThemeManager.php';
|
||||
|
||||
/**
|
||||
* Logger-klassen laden voor gestructureerde logging met log levels.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load Logger class - structured logging with log levels
|
||||
require_once 'class/Logger.php';
|
||||
require_once 'class/LogManager.php';
|
||||
|
||||
/**
|
||||
* Plugin-systeem laden (interfaces, API's en PluginManager).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load Plugin system
|
||||
require_once 'plugin/PluginAPIInterface.php';
|
||||
require_once 'plugin/CMSAPI.php';
|
||||
require_once 'plugin/AdminPluginAPI.php';
|
||||
require_once 'plugin/PluginManager.php';
|
||||
|
||||
/**
|
||||
* ContentAPI laden: CMS-data-toegang voor PHP content-bestanden.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load ContentAPI class - provides CMS data access for PHP content files
|
||||
require_once 'class/ContentAPI.php';
|
||||
|
||||
/**
|
||||
* Hoofdclass laden: content parsing, navigation, search en page rendering.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Load main CMS class - handles content parsing, navigation, search, and page rendering
|
||||
require_once 'class/CodePressCMS.php';
|
||||
|
||||
/**
|
||||
* Logger initialiseren (debug-mode kan via config worden ingeschakeld).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Initialize logger (debug mode can be enabled in config)
|
||||
Logger::init();
|
||||
@@ -1,16 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lightweight API wrapper for plugins in the admin context.
|
||||
* Provides read-only access to the site config (no CMS instance needed).
|
||||
* Lichtgewicht API-wrapper voor plugins in de admin-context.
|
||||
*
|
||||
* Biedt alleen-lezen toegang tot de site-config (zonder CMS-instance nodig).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class AdminPluginAPI implements PluginAPIInterface
|
||||
{
|
||||
/**
|
||||
* De site-configuratie als associatieve array.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
private array $config;
|
||||
|
||||
/**
|
||||
* Het absolute pad naar de project-root.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $projectRoot;
|
||||
|
||||
/**
|
||||
* De actieve admin-taalcode (bijv. 'nl', 'en').
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $adminLanguage;
|
||||
|
||||
/**
|
||||
* De admin PluginManager-instantie, optioneel via setPluginManager geïnjecteerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var PluginManager|null
|
||||
*/
|
||||
private ?PluginManager $pluginManager = null;
|
||||
|
||||
/**
|
||||
* Construeer een AdminPluginAPI-instantie met de opgegeven site-config.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array<string,mixed> $siteConfig De site-configuratie.
|
||||
* @param string $projectRoot Optioneel absoluut pad naar de project-root.
|
||||
*/
|
||||
public function __construct(array $siteConfig, string $projectRoot = '')
|
||||
{
|
||||
$this->config = $siteConfig;
|
||||
@@ -20,8 +58,13 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the admin PluginManager so plugins can resolve their own
|
||||
* translations through the same fallback chain.
|
||||
* Injecteer de admin PluginManager zodat plugins hun eigen vertalingen
|
||||
* via dezelfde fallback-keten kunnen resolven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param PluginManager $pm De admin PluginManager-instantie.
|
||||
* @return void
|
||||
*/
|
||||
public function setPluginManager(PluginManager $pm): void
|
||||
{
|
||||
@@ -29,7 +72,13 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value using dot notation.
|
||||
* Haal een configuratiewaarde op via dot-notatie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Configuratie-sleutel in dot-notatie (bijv. 'language.default').
|
||||
* @param mixed $default Standaardwaarde indien de sleutel niet bestaat.
|
||||
* @return mixed De gevonden waarde of $default indien niet gevonden.
|
||||
*/
|
||||
public function getConfig(string $key, $default = null)
|
||||
{
|
||||
@@ -47,7 +96,11 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project root directory.
|
||||
* Haal de project-rootdirectory op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de project-root.
|
||||
*/
|
||||
public function getProjectRoot(): string
|
||||
{
|
||||
@@ -55,15 +108,30 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content directory path.
|
||||
* Haal het pad naar de contentdirectory op als absoluut pad.
|
||||
*
|
||||
* Relatieve paden uit config.json (bijv. 'content') worden afgezet tegen
|
||||
* de project-root, conform het gedrag van cms/core/config.php.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de contentdirectory.
|
||||
*/
|
||||
public function getContentDir(): string
|
||||
{
|
||||
return $this->config['content_dir'] ?? ($this->projectRoot . '/content');
|
||||
$dir = $this->config['content_dir'] ?? ($this->projectRoot . '/content');
|
||||
if ($dir !== '' && $dir[0] !== DIRECTORY_SEPARATOR && !preg_match('#^[A-Za-z]:[/\\\\]#', $dir)) {
|
||||
$dir = $this->projectRoot . '/' . $dir;
|
||||
}
|
||||
return rtrim($dir, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugins directory path.
|
||||
* Haal het pad naar de pluginsdirectory op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de pluginsdirectory.
|
||||
*/
|
||||
public function getPluginsDir(): string
|
||||
{
|
||||
@@ -71,7 +139,11 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of enabled plugins.
|
||||
* Haal de lijst met ingeschakelde plugins op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,string> Lijst met ingeschakelde plugin-namen.
|
||||
*/
|
||||
public function getEnabledPlugins(): array
|
||||
{
|
||||
@@ -79,7 +151,11 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version info from version.php.
|
||||
* Haal de versie-informatie uit version.php op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,mixed> Versie-informatie met minimaal de sleutel 'version'.
|
||||
*/
|
||||
public function getVersionInfo(): array
|
||||
{
|
||||
@@ -94,8 +170,13 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active admin language code (e.g. 'nl', 'en').
|
||||
* System plugins should use this to resolve their translations.
|
||||
* Haal de actieve admin-taalcode op (bijv. 'nl', 'en').
|
||||
*
|
||||
* System-plugins gebruiken deze om hun vertalingen te resolven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De actieve admin-taalcode.
|
||||
*/
|
||||
public function getAdminLanguage(): string
|
||||
{
|
||||
@@ -103,14 +184,16 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translations for a plugin in the admin context.
|
||||
* Haal de vertalingen voor een plugin op in de admin-context.
|
||||
*
|
||||
* Fallback chain (handled by PluginManager):
|
||||
* requested language -> plugin default_language -> empty array.
|
||||
* Fallback-keten (afgehandeld door PluginManager):
|
||||
* aangevraagde taal -> default_language van de plugin -> lege array.
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language; defaults to the active admin language
|
||||
* @return array Translations [key => value]
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Override-taal; standaard de actieve admin-taal.
|
||||
* @return array<string,string> Vertalingen als [key => value].
|
||||
*/
|
||||
public function getPluginTranslations(string $pluginName, ?string $lang = null): array
|
||||
{
|
||||
@@ -122,12 +205,14 @@ class AdminPluginAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a single key for a plugin in the admin context.
|
||||
* Vertaal een enkele sleutel voor een plugin in de admin-context.
|
||||
*
|
||||
* @param string $key Translation key
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language; defaults to the active admin language
|
||||
* @return string Translated string, or $key if not found
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Vertaalsleutel.
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Override-taal; standaard de actieve admin-taal.
|
||||
* @return string Vertaalde string, of $key indien niet gevonden.
|
||||
*/
|
||||
public function t(string $key, string $pluginName, ?string $lang = null): string
|
||||
{
|
||||
|
||||
+225
-38
@@ -1,18 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Front-end plugin-API voor content-plugins.
|
||||
*
|
||||
* Biedt toegang tot de CodePressCMS-instance, pagina-informatie, menu, zoekresultaten,
|
||||
* vertalingen en bestandsoperaties binnen de front-end context.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class CMSAPI implements PluginAPIInterface
|
||||
{
|
||||
/**
|
||||
* De CodePressCMS-hoofdinstantie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var CodePressCMS
|
||||
*/
|
||||
private CodePressCMS $cms;
|
||||
|
||||
/**
|
||||
* De front-end PluginManager-instantie, optioneel via setPluginManager geïnjecteerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var PluginManager|null
|
||||
*/
|
||||
private ?PluginManager $pluginManager = null;
|
||||
|
||||
/**
|
||||
* Construeer een CMSAPI-instantie met de opgegeven CodePressCMS.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param CodePressCMS $cms De CodePressCMS-hoofdinstantie.
|
||||
*/
|
||||
public function __construct(CodePressCMS $cms)
|
||||
{
|
||||
$this->cms = $cms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the front-end PluginManager so content plugins can resolve
|
||||
* their own translations through the same fallback chain.
|
||||
* Injecteer de front-end PluginManager zodat content-plugins hun eigen
|
||||
* vertalingen via dezelfde fallback-keten kunnen resolven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param PluginManager $pm De front-end PluginManager-instantie.
|
||||
* @return void
|
||||
*/
|
||||
public function setPluginManager(PluginManager $pm): void
|
||||
{
|
||||
@@ -20,7 +53,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page information
|
||||
* Haal de informatie van de huidige pagina op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,mixed> Pagina-informatie van de huidige pagina.
|
||||
*/
|
||||
public function getCurrentPage(): array
|
||||
{
|
||||
@@ -28,7 +65,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page title
|
||||
* Haal de titel van de huidige pagina op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De titel van de huidige pagina, of lege string.
|
||||
*/
|
||||
public function getCurrentPageTitle(): string
|
||||
{
|
||||
@@ -37,7 +78,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page content
|
||||
* Haal de inhoud van de huidige pagina op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De HTML-inhoud van de huidige pagina, of lege string.
|
||||
*/
|
||||
public function getCurrentPageContent(): string
|
||||
{
|
||||
@@ -46,7 +91,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page URL
|
||||
* Haal de URL van de huidige pagina op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De URL van de huidige pagina inclusief query-parameters.
|
||||
*/
|
||||
public function getCurrentPageUrl(): string
|
||||
{
|
||||
@@ -56,7 +105,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu structure
|
||||
* Haal de menustructuur op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,array<string,mixed>> De menustructuur.
|
||||
*/
|
||||
public function getMenu(): array
|
||||
{
|
||||
@@ -64,7 +117,13 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration value
|
||||
* Haal een configuratiewaarde op via dot-notatie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Configuratie-sleutel in dot-notatie (bijv. 'language.default').
|
||||
* @param mixed $default Standaardwaarde indien de sleutel niet bestaat.
|
||||
* @return mixed De gevonden waarde of $default indien niet gevonden.
|
||||
*/
|
||||
public function getConfig(string $key, $default = null)
|
||||
{
|
||||
@@ -82,7 +141,70 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation
|
||||
* Haal de project-rootdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de project-root.
|
||||
*/
|
||||
public function getProjectRoot(): string
|
||||
{
|
||||
return dirname(__DIR__, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal het pad naar de contentdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* De CMS-config lost relatieve content_dir-waarden al op
|
||||
* (zie cms/core/config.php), dus deze wordt as-is teruggegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de contentdirectory.
|
||||
*/
|
||||
public function getContentDir(): string
|
||||
{
|
||||
return rtrim($this->cms->config['content_dir'] ?? ($this->getProjectRoot() . '/content'), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal het pad naar de pluginsdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de pluginsdirectory.
|
||||
*/
|
||||
public function getPluginsDir(): string
|
||||
{
|
||||
return $this->getProjectRoot() . '/plugins';
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal de versie-informatie uit version.php op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,mixed> Versie-informatie met minimaal de sleutel 'version'.
|
||||
*/
|
||||
public function getVersionInfo(): array
|
||||
{
|
||||
$versionFile = $this->getProjectRoot() . '/version.php';
|
||||
if (file_exists($versionFile)) {
|
||||
$data = include $versionFile;
|
||||
if (is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
return ['version' => '0.0.0'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal een vertaling op voor de opgegeven sleutel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Vertaalsleutel.
|
||||
* @return string De vertaalde string, of $key indien niet gevonden.
|
||||
*/
|
||||
public function translate(string $key): string
|
||||
{
|
||||
@@ -90,7 +212,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language
|
||||
* Haal de huidige content-taal op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De huidige taalcode (bijv. 'nl', 'en').
|
||||
*/
|
||||
public function getCurrentLanguage(): string
|
||||
{
|
||||
@@ -98,7 +224,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is on homepage
|
||||
* Controleer of de gebruiker zich op de homepage bevindt.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien de huidige pagina de default_page is.
|
||||
*/
|
||||
public function isHomepage(): bool
|
||||
{
|
||||
@@ -108,7 +238,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file info for current page
|
||||
* Haal de bestandsinformatie van de huidige pagina op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,mixed>|null Bestandsinformatie of null indien niet aanwezig.
|
||||
*/
|
||||
public function getCurrentPageFileInfo(): ?array
|
||||
{
|
||||
@@ -117,7 +251,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get breadcrumb data
|
||||
* Haal de breadcrumb-HTML op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string De gegenereerde breadcrumb-HTML.
|
||||
*/
|
||||
public function getBreadcrumb(): string
|
||||
{
|
||||
@@ -125,7 +263,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content directory has content
|
||||
* Controleer of de contentdirectory inhoud bevat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien de contentdirectory niet leeg is.
|
||||
*/
|
||||
public function hasContent(): bool
|
||||
{
|
||||
@@ -133,7 +275,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get search results if searching
|
||||
* Haal de zoekresultaten op indien een zoekopdracht actief is.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,array<string,mixed>> De zoekresultaten, of een lege array.
|
||||
*/
|
||||
public function getSearchResults(): array
|
||||
{
|
||||
@@ -144,7 +290,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if currently searching
|
||||
* Controleer of momenteel een zoekopdracht wordt uitgevoerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return bool True indien de search-query-parameter aanwezig is.
|
||||
*/
|
||||
public function isSearching(): bool
|
||||
{
|
||||
@@ -152,7 +302,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available languages
|
||||
* Haal de beschikbare talen op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,string> Lijst met beschikbare taalcodes.
|
||||
*/
|
||||
public function getAvailableLanguages(): array
|
||||
{
|
||||
@@ -160,7 +314,13 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Create URL for page
|
||||
* Maak een URL voor een pagina.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $page Pagina-identifier.
|
||||
* @param string|null $lang Taalcode; standaard de huidige content-taal.
|
||||
* @return string De gegenereerde pagina-URL met query-parameters.
|
||||
*/
|
||||
public function createUrl(string $page, ?string $lang = null): string
|
||||
{
|
||||
@@ -169,7 +329,15 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute PHP file and capture output
|
||||
* Voer een PHP-bestand uit en vang de output op.
|
||||
*
|
||||
* Het bestand moet binnen de CMS-directory liggen om willekeurige
|
||||
* file-inclusion te voorkomen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filePath Absoluut pad naar het PHP-bestand.
|
||||
* @return string De opgevangen output, of lege string indien ongeldig.
|
||||
*/
|
||||
public function executePhpFile(string $filePath): string
|
||||
{
|
||||
@@ -190,7 +358,12 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content from PHP/HTML/Markdown file
|
||||
* Haal de inhoud op uit een PHP-, HTML- of Markdown-bestand.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filePath Pad naar het bestand.
|
||||
* @return string De bestandsinhoud (gerenderd indien PHP/Markdown), of lege string.
|
||||
*/
|
||||
public function getFileContent(string $filePath): string
|
||||
{
|
||||
@@ -215,7 +388,12 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exists in content directory
|
||||
* Controleer of een bestand in de contentdirectory bestaat.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $filename Bestandsnaam relatief ten opzichte van de contentdirectory.
|
||||
* @return bool True indien het bestand bestaat.
|
||||
*/
|
||||
public function contentFileExists(string $filename): bool
|
||||
{
|
||||
@@ -224,9 +402,11 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all content entries (mappen + bestanden) as a list of entry arrays.
|
||||
* Haal alle content-items (mappen + bestanden) op als lijst met entry-arrays.
|
||||
*
|
||||
* @return array List of ['path' => ..., 'title' => ..., 'type' => ...]
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,array<string,mixed>> Lijst met entries ['path' => ..., 'title' => ..., 'type' => ...].
|
||||
*/
|
||||
public function getAllPages(): array
|
||||
{
|
||||
@@ -234,10 +414,13 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author metadata for the current page.
|
||||
* Returns author_name, author_email and created from the page frontmatter.
|
||||
* Haal de auteursmetadata van de huidige pagina op.
|
||||
*
|
||||
* @return array Author metadata with keys: author_name, author_email, created
|
||||
* Geeft author_name, author_email en created terug uit de pagina-frontmatter.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,string> Auteursmetadata met sleutels: author_name, author_email, created.
|
||||
*/
|
||||
public function getPageAuthor(): array
|
||||
{
|
||||
@@ -251,15 +434,17 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translations for a plugin in the front-end context.
|
||||
* Haal de vertalingen voor een plugin op in de front-end context.
|
||||
*
|
||||
* Content plugins follow the current content language. Fallback chain
|
||||
* (handled by PluginManager): requested language -> plugin
|
||||
* default_language -> empty array.
|
||||
* Content-plugins volgen de huidige content-taal. Fallback-keten
|
||||
* (afgehandeld door PluginManager): aangevraagde taal ->
|
||||
* default_language van de plugin -> lege array.
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language; defaults to the current content language
|
||||
* @return array Translations [key => value]
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Override-taal; standaard de huidige content-taal.
|
||||
* @return array<string,string> Vertalingen als [key => value].
|
||||
*/
|
||||
public function getPluginTranslations(string $pluginName, ?string $lang = null): array
|
||||
{
|
||||
@@ -271,12 +456,14 @@ class CMSAPI implements PluginAPIInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a single key for a plugin in the front-end context.
|
||||
* Vertaal een enkele sleutel voor een plugin in de front-end context.
|
||||
*
|
||||
* @param string $key Translation key
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language; defaults to the current content language
|
||||
* @return string Translated string, or $key if not found
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Vertaalsleutel.
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Override-taal; standaard de huidige content-taal.
|
||||
* @return string Vertaalde string, of $key indien niet gevonden.
|
||||
*/
|
||||
public function t(string $key, string $pluginName, ?string $lang = null): string
|
||||
{
|
||||
|
||||
@@ -1,33 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Interface for plugin API objects.
|
||||
* Both CMSAPI (front-end) and AdminPluginAPI (admin) implement this.
|
||||
* Interface voor plugin-API-objecten.
|
||||
*
|
||||
* Zowel CMSAPI (front-end) als AdminPluginAPI (admin) implementeren deze interface.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
interface PluginAPIInterface
|
||||
{
|
||||
/**
|
||||
* Haal een configuratiewaarde op via dot-notatie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Configuratie-sleutel in dot-notatie (bijv. 'language.default').
|
||||
* @param mixed $default Standaardwaarde indien de sleutel niet bestaat.
|
||||
* @return mixed De gevonden waarde of $default indien niet gevonden.
|
||||
*/
|
||||
public function getConfig(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* Get the translations for a plugin in the current context.
|
||||
* Haal de project-rootdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* System plugins resolve against the admin language; content plugins
|
||||
* against the current content language. The implementation handles the
|
||||
* fallback to the plugin's default_language.
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language (optional)
|
||||
* @return array Translations [key => value]
|
||||
* @return string Absoluut pad naar de project-root.
|
||||
*/
|
||||
public function getProjectRoot(): string;
|
||||
|
||||
/**
|
||||
* Haal het pad naar de contentdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* Bij een relatieve configuratiewaarde wordt deze afgezet tegen de project-root.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de contentdirectory.
|
||||
*/
|
||||
public function getContentDir(): string;
|
||||
|
||||
/**
|
||||
* Haal het pad naar de pluginsdirectory op (absoluut, genormaliseerd).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return string Absoluut pad naar de pluginsdirectory.
|
||||
*/
|
||||
public function getPluginsDir(): string;
|
||||
|
||||
/**
|
||||
* Haal de versie-informatie uit version.php op als associatieve array.
|
||||
*
|
||||
* Bevat altijd minimaal de sleutel 'version'.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,mixed> Versie-informatie met minimaal de sleutel 'version'.
|
||||
*/
|
||||
public function getVersionInfo(): array;
|
||||
|
||||
/**
|
||||
* Haal de vertalingen voor een plugin op in de huidige context.
|
||||
*
|
||||
* System-plugins resolven tegen de admin-taal; content-plugins tegen de
|
||||
* huidige content-taal. De implementatie regelt de fallback naar de
|
||||
* default_language van de plugin.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Optionele override-taal.
|
||||
* @return array<string,string> Vertalingen als [key => value].
|
||||
*/
|
||||
public function getPluginTranslations(string $pluginName, ?string $lang = null): array;
|
||||
|
||||
/**
|
||||
* Translate a single key for a plugin in the current context.
|
||||
* Vertaal een enkele sleutel voor een plugin in de huidige context.
|
||||
*
|
||||
* @param string $key Translation key
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string|null $lang Override language (optional)
|
||||
* @return string Translated string, or $key if not found
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $key Vertaalsleutel.
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string|null $lang Optionele override-taal.
|
||||
* @return string Vertaalde string, of $key indien niet gevonden.
|
||||
*/
|
||||
public function t(string $key, string $pluginName, ?string $lang = null): string;
|
||||
}
|
||||
@@ -1,15 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Beheert het laden en hooks-systeem van plugins.
|
||||
*
|
||||
* Laadt ingeschakelde plugins uit de pluginsdirectory, registreert automatisch
|
||||
* action- en filter-hooks, en biedt methoden voor plugin-configuratie,
|
||||
* vertalingen, admin-routes en sidebar-content.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
class PluginManager
|
||||
{
|
||||
/**
|
||||
* Geladen plugin-instanties, geïndexeerd op plugin-naam.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string,object>
|
||||
*/
|
||||
private array $plugins = [];
|
||||
|
||||
/**
|
||||
* Het absolute pad naar de pluginsdirectory.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $pluginsPath;
|
||||
|
||||
/**
|
||||
* De geïnjecteerde plugin-API-instantie (CMSAPI of AdminPluginAPI).
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var PluginAPIInterface|null
|
||||
*/
|
||||
private $api = null;
|
||||
|
||||
/**
|
||||
* Lijst met ingeschakelde plugin-namen.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<int,string>
|
||||
*/
|
||||
private array $enabledPlugins = [];
|
||||
|
||||
/**
|
||||
* Geregistreerde action-hooks, gegroepeerd per hook-naam en prioriteit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string,array<int,array<int,callable>>>
|
||||
*/
|
||||
private array $actions = [];
|
||||
|
||||
/**
|
||||
* Geregistreerde filter-hooks, gegroepeerd per hook-naam en prioriteit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var array<string,array<int,array<int,callable>>>
|
||||
*/
|
||||
private array $filters = [];
|
||||
|
||||
/**
|
||||
* De standaardtaal van de site, gebruikt als fallback voor plugins
|
||||
* zonder eigen default_language in plugin.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @var string
|
||||
*/
|
||||
private string $siteDefaultLanguage = 'nl';
|
||||
|
||||
/**
|
||||
* Construeer een PluginManager en laadt direct de ingeschakelde plugins.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginsPath Absoluut pad naar de pluginsdirectory.
|
||||
* @param array<int,string> $enabledPlugins Lijst met ingeschakelde plugin-namen.
|
||||
* @param string $siteDefaultLanguage Standaardtaal van de site.
|
||||
*/
|
||||
public function __construct(string $pluginsPath, array $enabledPlugins = [], string $siteDefaultLanguage = 'nl')
|
||||
{
|
||||
$this->pluginsPath = $pluginsPath;
|
||||
@@ -19,14 +86,31 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CMS site default language. Used as a fallback when a plugin
|
||||
* does not declare its own `default_language` in plugin.json.
|
||||
* Stel de standaardtaal van de site in.
|
||||
*
|
||||
* Wordt gebruikt als fallback wanneer een plugin geen eigen `default_language`
|
||||
* declareert in plugin.json.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $lang Taalcode (bijv. 'nl', 'en').
|
||||
* @return void
|
||||
*/
|
||||
public function setSiteDefaultLanguage(string $lang): void
|
||||
{
|
||||
$this->siteDefaultLanguage = $lang !== '' ? $lang : 'nl';
|
||||
}
|
||||
|
||||
/**
|
||||
* Stel de plugin-API in en geef deze door aan alle geladen plugins.
|
||||
*
|
||||
* Plugins die een setAPI()-methode implementeren, ontvangen de API-instantie.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param PluginAPIInterface $api De plugin-API-instantie.
|
||||
* @return void
|
||||
*/
|
||||
public function setAPI($api): void
|
||||
{
|
||||
$this->api = $api;
|
||||
@@ -38,6 +122,16 @@ class PluginManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laad alle ingeschakelde plugins uit de pluginsdirectory.
|
||||
*
|
||||
* Per plugin wordt het hoofdbestand (<naam>.php) geïncludeerd, de plugin-class
|
||||
* geïnstantieerd, en action- en filter-hooks automatisch geregistreerd.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadPlugins(): void
|
||||
{
|
||||
if (!is_dir($this->pluginsPath)) {
|
||||
@@ -82,16 +176,45 @@ class PluginManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registreer een action-callback voor een hook.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $hook Naam van de action-hook.
|
||||
* @param callable $callback De callback die bij de hook wordt uitgevoerd.
|
||||
* @param int $priority Uitvoer-volgorde; lager wordt eerder uitgevoerd.
|
||||
* @return void
|
||||
*/
|
||||
public function addAction(string $hook, callable $callback, int $priority = 10): void
|
||||
{
|
||||
$this->actions[$hook][$priority][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registreer een filter-callback voor een hook.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $hook Naam van de filter-hook.
|
||||
* @param callable $callback De callback die de waarde filtert.
|
||||
* @param int $priority Uitvoer-volgorde; lager wordt eerder uitgevoerd.
|
||||
* @return void
|
||||
*/
|
||||
public function addFilter(string $hook, callable $callback, int $priority = 10): void
|
||||
{
|
||||
$this->filters[$hook][$priority][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Voer alle geregistreerde callbacks voor een action-hook uit.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $hook Naam van de action-hook.
|
||||
* @param mixed ...$args Argumenten die aan elke callback worden doorgegeven.
|
||||
* @return void
|
||||
*/
|
||||
public function doAction(string $hook, ...$args): void
|
||||
{
|
||||
if (!isset($this->actions[$hook])) return;
|
||||
@@ -103,6 +226,16 @@ class PluginManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pas alle geregistreerde filter-callbacks toe op een waarde.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $hook Naam van de filter-hook.
|
||||
* @param mixed $value De initiële waarde die wordt gefilterd.
|
||||
* @param mixed ...$args Extra argumenten voor elke filter-callback.
|
||||
* @return mixed De gefilterde waarde.
|
||||
*/
|
||||
public function applyFilters(string $hook, $value, ...$args)
|
||||
{
|
||||
if (!isset($this->filters[$hook])) return $value;
|
||||
@@ -115,32 +248,66 @@ class PluginManager
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal een specifieke plugin-instantie op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $name Plugin-naam (directorynaam).
|
||||
* @return object|null De plugin-instantie of null indien niet geladen.
|
||||
*/
|
||||
public function getPlugin(string $name): ?object
|
||||
{
|
||||
return $this->plugins[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal alle geladen plugin-instanties op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<string,object> Plugin-instanties geïndexeerd op naam.
|
||||
*/
|
||||
public function getAllPlugins(): array
|
||||
{
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal de lijst met ingeschakelde plugin-namen op.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,string> Lijst met ingeschakelde plugin-namen.
|
||||
*/
|
||||
public function getEnabledPlugins(): array
|
||||
{
|
||||
return $this->enabledPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controleer of een plugin is ingeschakeld.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-naam (directorynaam).
|
||||
* @return bool True indien de plugin is ingeschakeld.
|
||||
*/
|
||||
public function isEnabled(string $pluginName): bool
|
||||
{
|
||||
return in_array($pluginName, $this->enabledPlugins, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the runtime config for a plugin: defaults from plugin.json `settings`
|
||||
* merged with overrides from the plugin's config.json.
|
||||
* Haal de runtime-configuratie van een plugin op.
|
||||
*
|
||||
* @param string $pluginName Plugin name (directory name)
|
||||
* @return array Resolved config: [key => value, ...]
|
||||
* Standaardwaarden uit plugin.json `settings` worden samengevoegd met
|
||||
* overrides uit het config.json van de plugin.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-naam (directorynaam).
|
||||
* @return array<string,mixed> Opgeloste configuratie als [key => value].
|
||||
*/
|
||||
public function getPluginConfig(string $pluginName): array
|
||||
{
|
||||
@@ -167,15 +334,17 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default (fallback) language declared by a plugin.
|
||||
* Haal de standaard (fallback) taal van een plugin op.
|
||||
*
|
||||
* Resolved from (in order):
|
||||
* Resolved in volgorde van:
|
||||
* 1. plugin.json `default_language`
|
||||
* 2. CMS site config `language.default`
|
||||
* 2. CMS site-config `language.default`
|
||||
* 3. 'nl'
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @return string Language code (e.g. 'nl', 'en')
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @return string Taalcode (bijv. 'nl', 'en').
|
||||
*/
|
||||
public function getPluginDefaultLanguage(string $pluginName): string
|
||||
{
|
||||
@@ -190,15 +359,17 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the translations for a plugin for the requested language.
|
||||
* Laad de vertalingen voor een plugin voor de aangevraagde taal.
|
||||
*
|
||||
* Fallback chain: requested language -> plugin default language -> empty array.
|
||||
* Fallback-keten: aangevraagde taal -> standaardtaal van de plugin -> lege array.
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string $lang Requested language code
|
||||
* @param string $context Translation context: 'admin' or 'site' (front-end).
|
||||
* Defaults to 'admin' for system plugins, 'site' for content plugins.
|
||||
* @return array Translations [key => value]
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-directorynaam.
|
||||
* @param string $lang Aangevraagde taalcode.
|
||||
* @param string $context Vertaalcontext: 'admin' of 'site' (front-end).
|
||||
* Standaard 'admin' voor system-plugins, 'site' voor content-plugins.
|
||||
* @return array<string,string> Vertalingen als [key => value].
|
||||
*/
|
||||
public function getPluginTranslations(string $pluginName, string $lang, string $context = 'admin'): array
|
||||
{
|
||||
@@ -232,14 +403,16 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect translations for every loaded plugin for the requested language.
|
||||
* Verzamel vertalingen voor alle geladen plugins voor de aangevraagde taal.
|
||||
*
|
||||
* Returns an associative array keyed by plugin name:
|
||||
* Geeft een associatieve array terug, geïndexeerd op plugin-naam:
|
||||
* ['Statistics' => [...], 'Logs' => [...], ...]
|
||||
*
|
||||
* @param string $lang Requested language code
|
||||
* @param string $context 'admin' or 'site'
|
||||
* @return array Plugin translations keyed by plugin name
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $lang Aangevraagde taalcode.
|
||||
* @param string $context 'admin' of 'site'.
|
||||
* @return array<string,array<string,string>> Plugin-vertalingen geïndexeerd op plugin-naam.
|
||||
*/
|
||||
public function getAllPluginTranslations(string $lang, string $context = 'admin'): array
|
||||
{
|
||||
@@ -252,6 +425,16 @@ class PluginManager
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controleer of een plugin viewable is.
|
||||
*
|
||||
* Een plugin is viewable tenzij zijn configuratie 'viewable' expliciet op false zet.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param object $plugin De plugin-instantie.
|
||||
* @return bool True indien de plugin viewable is.
|
||||
*/
|
||||
public function isPluginViewable(object $plugin): bool
|
||||
{
|
||||
if (method_exists($plugin, 'getConfig')) {
|
||||
@@ -261,6 +444,17 @@ class PluginManager
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Haal de samengevoegde sidebar-content van alle viewable plugins op.
|
||||
*
|
||||
* Elke plugin met een getSidebarContent()-methode levert HTML die wordt
|
||||
* ingepakt in een Bootstrap-card met de plugin-titel.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param array<int,string>|null $allowedPlugins Optionele whitelist van plugin-namen.
|
||||
* @return string De samengevoegde sidebar-HTML.
|
||||
*/
|
||||
public function getSidebarContent(?array $allowedPlugins = null): string
|
||||
{
|
||||
$sidebarContent = '';
|
||||
@@ -300,9 +494,11 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS URLs from all enabled plugins that provide getCssUrl().
|
||||
* Haal CSS-URL's op van alle ingeschakelde plugins die getCssUrl() implementeren.
|
||||
*
|
||||
* @return array List of CSS URLs
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,string> Lijst met CSS-URL's.
|
||||
*/
|
||||
public function getPluginCssUrls(): array
|
||||
{
|
||||
@@ -319,10 +515,13 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect admin menu items from all enabled plugins that implement getAdminMenu().
|
||||
* Each plugin returns [['route' => 'plugin-name/action', 'label' => 'Label', 'icon' => 'bi-icon']].
|
||||
* Verzamel admin-menu-items van alle plugins die getAdminMenu() implementeren.
|
||||
*
|
||||
* @return array Admin menu items from all plugins
|
||||
* Elke plugin levert [['route' => 'plugin-name/action', 'label' => 'Label', 'icon' => 'bi-icon']].
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @return array<int,array<string,mixed>> Admin-menu-items van alle plugins.
|
||||
*/
|
||||
public function getAdminMenuItems(): array
|
||||
{
|
||||
@@ -341,12 +540,16 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an admin route to a plugin that handles it.
|
||||
* Plugins implement handleAdminRoute(string $action): ?string (returns rendered HTML or null).
|
||||
* Dispatch een admin-route naar de plugin die deze afhandelt.
|
||||
*
|
||||
* @param string $pluginName Plugin name
|
||||
* @param string $action Action/sub-route within the plugin
|
||||
* @return string|null Rendered HTML, or null if plugin doesn't handle it
|
||||
* Plugins implementeren handleAdminRoute(string $action): ?string
|
||||
* (geeft gerenderde HTML of null terug).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $pluginName Plugin-naam.
|
||||
* @param string $action Actie/sub-route binnen de plugin.
|
||||
* @return string|null Gerenderde HTML, of null indien de plugin deze niet afhandelt.
|
||||
*/
|
||||
public function dispatchAdminRoute(string $pluginName, string $action): ?string
|
||||
{
|
||||
@@ -361,10 +564,12 @@ class PluginManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Find which plugin handles a given admin route.
|
||||
* Bepaal welke plugin een opgegeven admin-route afhandelt.
|
||||
*
|
||||
* @param string $route The admin route (e.g. 'statistics' or 'statistics/details')
|
||||
* @return array|null ['plugin' => name, 'action' => action, 'permission' => required permission] or null
|
||||
* @since 2.6.4
|
||||
*
|
||||
* @param string $route De admin-route (bijv. 'statistics' of 'statistics/details').
|
||||
* @return array<string,mixed>|null ['plugin' => naam, 'action' => actie, 'permission' => vereiste permissie] of null.
|
||||
*/
|
||||
public function resolveAdminRoute(string $route): ?array
|
||||
{
|
||||
@@ -382,4 +587,4 @@ class PluginManager
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Router voor de PHP development-server: clean-URL support en statische bestanden.
|
||||
*
|
||||
* Wordt gebruikt via `php -S localhost:8080 cms/router.php`. Houdt de request-
|
||||
* URI tegen de public/-map voor statische bestanden, serveert theme-, admin-
|
||||
* theme- en plugin-assets buiten public/, en routeert /admin en taal-geprefixte
|
||||
* paden (/nl, /en) door naar admin.php respectievelijk index.php.
|
||||
*
|
||||
* @since 2.6.4
|
||||
* @package CodePress
|
||||
*/
|
||||
// Router file for PHP development server - clean URL support + static file serving
|
||||
|
||||
$requestUri = $_SERVER['REQUEST_URI'];
|
||||
@@ -19,6 +30,11 @@ $mimeTypes = [
|
||||
'json' => 'application/json',
|
||||
];
|
||||
|
||||
/**
|
||||
* Statische bestanden uit public/ serveren met juiste MIME-type.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Serve static files from public/
|
||||
$filePath = $publicDir . $path;
|
||||
if (is_file($filePath)) {
|
||||
@@ -30,6 +46,13 @@ if (is_file($filePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme-assets uit themes/<naam>/ serveren (bijv. /themes/default/js/theme.js).
|
||||
*
|
||||
* Path-traversal wordt afgedwongen via realpath() + prefix-controle.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Serve theme assets from the themes/ directory (e.g. /themes/default/js/theme.js)
|
||||
if (preg_match('#^/themes/([^/]+)/(.+)$#', $path, $m)) {
|
||||
$themeName = $m[1];
|
||||
@@ -50,6 +73,13 @@ if (preg_match('#^/themes/([^/]+)/(.+)$#', $path, $m)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin-theme-assets serveren (bijv. /admin/assets/css/bootstrap.min.js).
|
||||
*
|
||||
* Bronmap: admin/theme/default/assets/.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Serve admin theme assets (e.g. /admin/assets/css/bootstrap.min.js)
|
||||
// Served from admin/theme/default/assets/
|
||||
if (preg_match('#^/admin/assets/(.+)$#', $path, $m)) {
|
||||
@@ -70,6 +100,11 @@ if (preg_match('#^/admin/assets/(.+)$#', $path, $m)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin-assets serveren (bijv. /plugins/Navigation/assets/css/navigation.css).
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Serve plugin assets (e.g. /plugins/Navigation/assets/css/navigation.css)
|
||||
if (preg_match('#^/plugins/([^/]+)/assets/(.+)$#', $path, $m)) {
|
||||
$pluginName = $m[1];
|
||||
@@ -90,6 +125,13 @@ if (preg_match('#^/plugins/([^/]+)/assets/(.+)$#', $path, $m)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin-routes doorsturen: /admin/<route> → admin.php?route=<route>.
|
||||
*
|
||||
* Default route is 'dashboard' indien geen subpad opgegeven.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Admin routes: /admin/login → admin.php?route=login
|
||||
if (preg_match('#^/admin(?:/(.+))?$#', $path, $m)) {
|
||||
$_GET['route'] = $m[1] ?? 'dashboard';
|
||||
@@ -97,6 +139,14 @@ if (preg_match('#^/admin(?:/(.+))?$#', $path, $m)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taal-geprefixte routes: /nl/<page> of /en/<page> → index.php?lang=...&page=...
|
||||
*
|
||||
* /nl/guide activeert de guide-flag met bestaande ?page=. Default valt
|
||||
* door naar index.php.
|
||||
*
|
||||
* @since 2.6.4
|
||||
*/
|
||||
// Language-prefixed routes: /nl/page/path → index.php?lang=nl&page=page/path
|
||||
if (preg_match('#^/(nl|en)(?:/(.+))?$#', $path, $m)) {
|
||||
$_GET['lang'] = $m[1];
|
||||
|
||||
Reference in New Issue
Block a user