# Agent Instructions for CodePress CMS ## AI Model - **Huidig model**: GLM-5.2 max (Ollama cloud) - Sessie gestart: 16 feb 2026 ## Build & Run - **Run Server**: `php -S localhost:8080 cms/router.php` (router nodig voor clean URLs) - **Lint PHP**: `find . -name "*.php" -not -path "./vendor/*" -not -path "./var/*" -exec php -l {} \;` - **Dependencies**: Composer vereist voor CommonMark, Twig en scssphp. Geen NPM. - **Admin Console**: Toegankelijk op `/admin` (standaard login: `admin` / `admin`) ## Project Structuur ``` codepress/ ├── cms/ # Core CMS engine │ ├── core/ │ │ ├── class/ │ │ │ ├── CodePressCMS.php # Hoofd CMS class (routing, rendering, breadcrumb, guide) │ │ │ ├── ThemeManager.php # Thema-resolver + Twig render + SCSS compile │ │ │ ├── ContentAPI.php # Read-only API voor PHP content bestanden │ │ │ ├── ContentSecurityPolicy.php # CSP header management │ │ │ ├── Analytics.php # Bezoekersstatistieken │ │ │ ├── BotGuard.php # Bot/AI detectie │ │ │ ├── Cache.php # Cache systeem │ │ │ ├── GeoIP.php # GeoIP lookup (land, vlag) │ │ │ ├── Logger.php # Basis logging │ │ │ ├── LogManager.php # Dynamisch logging systeem (SQLite/syslog) │ │ │ ├── RateLimiter.php # Rate limiting per IP │ │ │ ├── RequestLogger.php # Request logging + visitor info │ │ │ ├── SearchEngine.php # Volledige tekst zoekfunctie │ │ │ ├── SimpleTemplate.php # Legacy Mustache-style engine (niet meer gebruikt) │ │ │ └── AccessibilityManager.php # Accessibility features │ │ ├── plugin/ │ │ │ ├── PluginManager.php # Plugin loader (laadt, hooks, filters, sidebar) │ │ │ └── CMSAPI.php # API voor plugins (getPage, getConfig, etc.) │ │ ├── config.php # Config loader (leest config.json) │ │ └── index.php # Bootstrap (autoloader, requires) │ ├── lang/ # Taalbestanden (nl.php, en.php) │ └── router.php # PHP dev server router (serveert themes/, admin/assets/, plugins/assets/) ├── themes/ # Dynamische thema's (volledig zelfstandig) │ ├── default/ # Standaard thema │ │ ├── theme.json # { title, config.default_template, template: layout→.twig mapping } │ │ ├── base.twig # Hoofd layout (head, header, nav, breadcrumb, footer) │ │ ├── full_content.twig # Layout: volledige breedte │ │ ├── left_sidebar.twig # Layout: sidebar links │ │ ├── right_sidebar.twig # Layout: sidebar rechts │ │ ├── custom1.twig # Layout: custom │ │ ├── guide.twig # Layout: handleiding met sidebar (Navigation plugin) │ │ ├── partials/ # header.twig, navigation.twig, footer.twig │ │ └── assets/ │ │ ├── scss/theme.scss # SCSS bron (runtime gecompileerd) │ │ ├── css/theme.css # Gecompileerde CSS │ │ ├── css/style.css # Extra CSS (search fix, etc.) │ │ ├── css/bootstrap.min.css # Bootstrap 5 │ │ ├── css/bootstrap-icons.css # Bootstrap Icons │ │ ├── css/mobile.css # Mobile styles │ │ ├── js/app.js # Thema JavaScript │ │ ├── js/bootstrap.bundle.min.js │ │ ├── fonts/ # bootstrap-icons.woff, woff2 │ │ └── img/ # favicon, icon, world-map │ └── demo/ # Demo thema (zelfde structuur, andere look) ├── admin/ # Admin paneel │ ├── config/ │ │ ├── app.php # Admin app configuratie (paths, config_json, etc.) │ │ └── admin.json # Gebruikers & security (file-based, .gitignore'd) │ ├── src/ │ │ └── AdminAuth.php # Authenticatie (sessies, bcrypt, CSRF, lockout, RBAC) │ ├── theme/default/ # Admin thema │ │ ├── theme.json # Admin thema configuratie │ │ ├── assets/ │ │ │ ├── css/ │ │ │ │ ├── style.css # Admin styles (code blocks, guide nav) │ │ │ │ ├── editor.css # CodeMirror editor styles │ │ │ │ ├── bootstrap.min.css │ │ │ │ └── bootstrap-icons.css │ │ │ ├── js/ │ │ │ │ ├── app.js │ │ │ │ ├── editor-toolbar.js # CodeMirror toolbar + shortcuts │ │ │ │ ├── keyboard-navigation.js │ │ │ │ └── bootstrap.bundle.min.js │ │ │ ├── codemirror/ # CodeMirror editor (modes, addons) │ │ │ ├── fonts/ # bootstrap-icons.woff, woff2 │ │ │ └── img/ # favicon, world-map │ │ └── views/ │ │ ├── login.twig # Login pagina │ │ ├── layouts/ │ │ │ └── admin.twig # Admin layout met sidebar (role-based zichtbaarheid) │ │ └── pages/ │ │ ├── dashboard.twig # Dashboard (role-based inhoud) │ │ ├── content.twig # Content beheer │ │ ├── content-edit.twig # Content bewerken (layout select, plugins, CodeMirror) │ │ ├── content-new.twig # Nieuwe content │ │ ├── content-move-form.twig # Content verplaatsen │ │ ├── content-dir-form.twig # Map hernoemen │ │ ├── config.twig # Site configuratie │ │ ├── security.twig # Beveiliging instellingen │ │ ├── theme.twig # Thema beheer │ │ ├── theme-new.twig # Nieuw thema │ │ ├── plugins.twig # Plugin beheer (beschermd: essentiële plugins) │ │ ├── plugins-edit.twig # Plugin bewerken (CodeMirror) │ │ ├── plugins-new.twig # Nieuwe plugin │ │ ├── plugin-config.twig # Plugin configuratie │ │ ├── users.twig # Gebruikersbeheer (rollen, wachtwoord) │ │ ├── statistics.twig # Statistieken │ │ ├── logs.twig # Log viewer │ │ ├── media.twig # Media beheer │ │ ├── guide.twig # Handleiding (met Navigation sidebar) │ │ ├── update.twig # Update pagina │ │ └── error.twig # 403/404 fout pagina │ └── storage/ # Logs, cache, geoip ├── plugins/ # CMS plugins │ ├── Navigation/ # Essentiële navigatie plugin (beschermd) │ │ ├── Navigation.php # Plugin code (guide + content navigatie) │ │ ├── plugin.json # Plugin metadata │ │ ├── assets/ │ │ │ ├── scss/navigation.scss # SCSS bron │ │ │ └── css/navigation.css # Gecompileerde CSS │ │ └── views/ # Plugin Twig templates (toekomstig) │ └── HTMLBlock/ # Voorbeeld sidebar plugin │ └── HTMLBlock.php ├── content/ # Website content (.md, .php, .html) ├── public/ # Web root │ ├── index.php # Website entry point │ ├── admin.php # Admin entry point + routing │ ├── favicon.ico │ └── robots.txt ├── guide/ # Handleidingen (nl/en, gesplitst in mappen) │ ├── nl/ # Nederlandse handleidingen │ │ ├── index.md # Index (verwijst naar onderwerpen) │ │ ├── admin-beheerder.md # Admin handleiding (inleiding) │ │ ├── admin-beheerder/ # Sub-onderdelen │ │ ├── content-beheerder.md │ │ ├── content-beheerder/ │ │ ├── codepress-developer.md │ │ ├── codepress-developer/ │ │ ├── theme-developer.md │ │ └── theme-developer/ │ ├── en/ # Engelse handleidingen (zelfde structuur als nl/) │ └── README.md ├── cli/ # CLI scripts & tests │ └── test/ │ ├── accessibility.sh # WCAG 2.1 AA test suite │ ├── enhanced-suite.sh # Enhanced test suite │ ├── functional/ # Functionele testen │ └── pentest/ # Penetratietesten ├── var/ # Cache (twig) — .gitignore'd ├── config.json # Site configuratie — .gitignore'd ├── composer.json # PHP dependencies ├── version.php # Versie informatie (huidige: 2.5.2) ├── .gitignore # Negeert: var/, config.json, admin/config/admin.json, content/ └── AGENTS.md # Dit bestand ``` ## Code Style & Conventions - **PHP Standards**: Follow PSR-12. Use 4 spaces for indentation. - **Naming**: Classes `PascalCase` (e.g., `CodePressCMS`), methods `camelCase` (e.g., `renderMenu`), variables `camelCase`, config keys `snake_case`. - **Architecture**: - Core CMS logic in `cms/core/class/CodePressCMS.php` - Bootstrap/requires in `cms/core/index.php` - Configuration loaded from `config.json` via `cms/core/config.php` - Public website entry point: `public/index.php` - Admin entry point + routing: `public/admin.php` - Admin authentication + RBAC: `admin/src/AdminAuth.php` - Admin theme: `admin/theme/default/` (views + assets) - Plugin assets: `plugins//assets/` (served via router `/plugins//assets/`) - **Content**: Stored in `content/`. Supports `.md` (Markdown), `.php` (Dynamic), `.html` (Static). - **Templating**: Twig templates in `themes//`. `ThemeManager` rendert via Twig en compileert `assets/scss/theme.scss` runtime naar `assets/css_compiled/theme.css` via scssphp. Layout gekozen via frontmatter `layout:` key; onbekende layouts vallen terug op `config.default_template` in `theme.json`. - **Plugin CSS**: Plugins hebben eigen `assets/scss/` en `assets/css/`. Plugin CSS wordt automatisch geladen na theme CSS (in `base.twig`), zodat thema's plugin styling kunnen overschrijven. Plugin assets worden geserveerd via `cms/router.php` op URL `/plugins//assets/...`. - **Navigation**: Auto-generated from directory structure. Folders require an index file to be clickable in breadcrumbs. Breadcrumb is dynamisch: Home > [submappen] > [pagina]. Homepage toont altijd het pad. - **Security**: - Always use `htmlspecialchars()` for outputting user/content data - Use `realpath()` + prefix-check for path traversal prevention - Admin forms require CSRF tokens via `AdminAuth::verifyCsrf()` - Passwords stored as bcrypt hashes in `admin/config/admin.json` - Role-based access control (RBAC) via `AdminAuth::hasPermission()` - **Git**: `main` is the clean CMS core. `development` is de actieve development branch. `e.noorlander` bevat persoonlijke content. Niet mixen. ## Admin Console - **File-based**: Geen database. Gebruikers opgeslagen in `admin/config/admin.json` - **Routing**: Via clean URLs `/admin/` (omgezet naar `?route=` door `cms/router.php`) - **Routes**: `login`, `logout`, `dashboard`, `content`, `content-edit`, `content-new`, `content-delete`, `content-dir-create`, `content-dir-rename`, `content-dir-delete`, `content-move`, `config`, `security`, `theme`, `theme-new`, `plugins`, `plugins-new`, `plugins-edit`, `plugins-config`, `plugins-toggle`, `plugins-delete`, `users`, `statistics`, `logs`, `media`, `guide`, `update` - **Auth**: Session-based. `AdminAuth` class handelt login, logout, CSRF, brute-force lockout af - **Templates**: Twig templates in `admin/theme/default/views/pages/`. Layout in `admin/theme/default/views/layouts/admin.twig` - **Essentiële plugins**: Gedefinieerd in `getProtectedPlugins()` in `public/admin.php`. Deze plugins kunnen niet worden gedeactiveerd, bewerkt of verwijderd. Huidige essentiële plugins: `Navigation`. ## Gebruikersrollen (RBAC) Gedefinieerd in `AdminAuth::ROLE_PERMISSIONS` als een mapping van rol → toegestane routes. | Rol | Label | Permissies | |-----|-------|-----------| | `admin` | Admin | Alles (`*`) | | `content-manager` | Content Beheerder | Content beheer, handleiding | | `bi-manager` | BI Beheerder | Statistieken, logs, handleiding | | `site-admin` | Site Admin | Thema, plugins, statistieken, logs, update, handleiding | - Sidebar items worden conditioneel getoond via `has_permission()` Twig function - Dashboard inhoud is role-based (statistieken, content stats, systeem info, quick actions) - Route access control in `public/admin.php`: onbevoegde routes geven 403 error ## Guide Systeem - Handleidingen in `guide//` met sub-onderdelen in aparte mappen - NL en EN hebben identieke structuur (bestandsnamen zijn gelijk voor fallback) - `getGuidePage()` in `CodePressCMS.php` laadt guide content en injecteert `plugins: Navigation` in metadata - Navigation plugin genereert zijbalk navigatie vanuit de mapstructuur (guide + content) - Guide titles worden uit H1 van markdown bestanden gehaald (via `getTitleFromFile()`) - Admin guide laadt Navigation plugin direct in `handleGuide()` in `public/admin.php` ## Important: Title vs File/Directory Name Logic - **CRITICAL**: When user asks for "title" corrections, they usually mean **FILE/DIRECTORY NAME WITHOUT LANGUAGE PREFIX AND EXTENSIONS**, not the HTML title from content! - **Examples**: - `nl.test.md` → display as "Test" (not content title) - `nl.test/` directory → display as "Test" (not H1 content) - `en.php-testen` → display as "Php Testen" (not "ICT") - **Method**: Use `formatDisplayName()` to process file/directory names correctly - **Priority**: Directory names take precedence over file names when both exist - **Language prefixes**: Dynamisch verwijderd op basis van beschikbare talen via `getAvailableLanguages()` ## Bekende aandachtspunten - LSP errors over "Undefined function" in PHP files zijn vals-positief (standaard PHP functies worden niet herkend door de LSP). Negeer deze. - Zie `TODO.md` voor alle openstaande verbeteringen en nieuwe features. - `vendor/` map bevat Composer dependencies (CommonMark, Twig, scssphp). Niet handmatig wijzigen. - `admin/config/admin.json` bevat wachtwoord-hashes. Niet committen met echte productie-wachtwoorden. - `config.json` staat in `.gitignore`. Niet committen. - `var/` (Twig cache) staat in `.gitignore`. Kan veilig worden gewist: `rm -rf var/cache/twig/*` - `content/` staat in `.gitignore`. Content wordt lokaal beheerd. - Pad-referenties vanuit `public/admin.php`: gebruik `__DIR__ . "/../themes/..."` (één niveau omhoog), NIET `../../` (twee niveaus). ## CRITICAL: SCSS is de enige CSS bron — NIET handmatig CSS aanpassen - **NOOIT** `themes//assets/css/theme.css` handmatig aanpassen. Dit bestand mag niet bestaan. - **ALTIJD** `themes//assets/scss/theme.scss` aanpassen. scssphp compileert dit automatisch naar `assets/css_compiled/theme.css`. - **ALTIJD** `themes//assets/css_compiled/theme.css` verwijderen na SCSS wijzigingen: `rm themes//assets/css_compiled/theme.css themes//assets/css_compiled/.mtime` - **REDEN**: `ThemeManager::getCssUrl()` heeft prioriteit: 1) `assets/css/theme.css` (handmatig), 2) `assets/css_compiled/theme.css` (gecompileerd). Als `theme.css` bestaat, wordt de SCSS negeren en worden SCSS wijzigingen NIET zichtbaar. - **Plugin CSS**: Plugin SCSS in `plugins//assets/scss/` wordt nog niet automatisch gecompileerd. Plugin CSS in `plugins//assets/css/` is handmatig te onderhouden. - **Bootstrap CSS**: `themes//assets/css/bootstrap.min.css` is een externe dependency. NIET aanpassen. ## CRITICAL: Bootstrap 5 overrides — alle CSS variabelen EN properties overschrijven - Bootstrap 5 gebruikt CSS custom properties (variables) op `:root` en op component niveau. Deze overschrijven met alleen `!important` op properties is NIET voldoende. - **ALTIJD** zowel de Bootstrap CSS variabelen als de properties overschrijven: - `.nav-tabs`: set `--bs-nav-tabs-border-width`, `--bs-nav-tabs-border-color`, `--bs-nav-tabs-border-radius` EN `border`, `border-radius`, `border-bottom` - `.dropdown-menu`: set `--bs-dropdown-min-width`, `--bs-dropdown-border-width`, `--bs-dropdown-border-radius`, `--bs-dropdown-box-shadow`, `--bs-dropdown-padding-x/y` EN `border`, `border-radius`, `padding`, `margin`, `min-width`, `box-shadow` - `.nav-link`: set `border`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius` (alle vier de hoeken!) - **NOOIT** aannemen dat een enkele `border: none !important` voldoende is. Bootstrap zet `border-top-left-radius` en `border-top-right-radius` apart op `.nav-tabs .nav-link`. ## CRITICAL: Pad-referenties vanuit public/admin.php - `__DIR__` in `public/admin.php` is `public/`. - Gebruik `__DIR__ . "/../themes/..."` (één niveau omhoog naar project root). - **NOOIT** `__DIR__ . "/../../themes/..."` (twee niveaus — dit gaat naar de parent van het project). - Dit geldt voor alle paden naar `themes/`, `plugins/`, `guide/`, `admin/`. ## CRITICAL: Twig syntaxis — geen PHP functions in Twig - Twig heeft geen `dirname` filter. Gebruik PHP `dirname()` in de handler en geef het resultaat als aparte variabele door aan Twig. - Twig `??` (null coalescing) werkt NIET op filter expressies zoals `(stats.pages|length ?? 0)`. Gebruik `|default(0)` in plaats daarvan. - Twig `??` werkt WEL op simpele variable access zoals `(config.value ?? 'default')`. ## CRITICAL: CodeMirror mode laadvolgorde - CodeMirror modes hebben dependencies. De laadvolgorde in `admin.twig` moet zijn: 1. `codemirror.min.js` (core) 2. `xml.min.js` (basis voor htmlmixed) 3. `css.min.js` (dependency van htmlmixed) 4. `javascript.min.js` (dependency van htmlmixed) 5. `htmlmixed.min.js` (dependency van php) 6. `php.min.js` (gebruikt htmlmixed) 7. `clike.min.js` 8. `markdown.min.js` - **NOOIT** `php.min.js` laden vóór `htmlmixed.min.js` — dit veroorzaakt `c.indent is not a function` errors. ## CRITICAL: Plugin bestandsnaam conventie - Plugin PHP bestanden heten `.php` (bijv. `Navigation.php`, `HTMLBlock.php`), NIET `plugin.php`. - `PluginManager` laadt `$pluginDir . '/' . $pluginName . '.php'` (regel 46 in PluginManager.php). - `handlePluginsEdit` in `admin.php` moet `$pluginDir . '/' . $plugin . '.php'` gebruiken, NIET `$pluginDir . '/plugin.php'`. - `handlePluginsNew` moet het bestand aanmaken als `.php`, NIET `plugin.php`. ## CRITICAL: Essentiële plugins beschermen - Essentiële plugins gedefinieerd in `getProtectedPlugins()` in `public/admin.php` (huidige: `Navigation`). - Deze plugins kunnen NIET worden: gedeactiveerd (`handlePluginsToggle`), verwijderd (`handlePluginsDelete`), bewerkt (`handlePluginsEdit`). - De `plugins.twig` template toont een "Essentieel" badge i.p.v. de actieknoppen. - **ALTIJD** de `isProtectedPlugin()` check toevoegen aan nieuwe plugin handlers. ## CRITICAL: Live server (Apache) — asset serving - Op de live server (Apache) staan themes, plugins en admin assets buiten de webroot (`public/`). - `public/.htaccess` stuurt `/themes/`, `/admin/assets/`, `/plugins/` URLs door naar `public/asset.php`. - `public/asset.php` serveert de bestanden vanuit de juiste mappen met het juiste MIME-type. - **NOOIT** aannemen dat de PHP dev server router (`cms/router.php`) op de live server draait. - Na wijzigingen aan assets: commit EN deploy naar live server.