Security: verwijder hardcoded wachtwoord, voeg random-wachtwoord-generator toe bij eerste installatie
This commit is contained in:
+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/
|
||||
|
||||
Reference in New Issue
Block a user