Major changes: - New ThemeManager with Twig templating and SCSS compilation - Dynamic themes system (themes/default, themes/demo) - LogManager with SQLite storage and syslog forwarding - RequestLogger with static helper methods - Admin UI overhaul (Bootstrap 5, dark mode) - Admin config page with logging and theme settings - Admin logs page with filters and search - Removed legacy Mustache templates - Removed test plugin and theme - Composer dependencies: Twig, scssphp, CommonMark, MaxMind GeoIP
24 KiB
CodePress CMS Guide
Table of Contents
Overview
CodePress CMS is a lightweight, file-based content management system built with PHP (>=8.0). Works without a database.
Installation
- Upload files to web server
- Set permissions for web server
- Run
composer installfor CommonMark dependency - Configure
config.jsonif needed - Access website via browser
- PHP development server:
php -S localhost:8080 -t public(usescms/router.php)
Project Structure
codepress/
├── cms/ # Core CMS engine
│ ├── core/
│ │ ├── class/
│ │ │ ├── CodePressCMS.php # Main CMS class (content, navigation, search)
│ │ │ ├── ThemeManager.php # Theme resolver + Twig render + SCSS compile
│ │ │ ├── Logger.php # Structured logging system
│ │ │ ├── Analytics.php # Visitor statistics
│ │ │ ├── BotGuard.php # Bot/AI/scraper detection
│ │ │ ├── GeoIP.php # Country lookup by IP
│ │ │ ├── Cache.php # File-based caching
│ │ │ └── RateLimiter.php # Per-IP rate limiting
│ │ ├── plugin/
│ │ │ ├── PluginManager.php # Plugin loader and manager
│ │ │ └── CMSAPI.php # API for plugin developers
│ │ ├── config.php # Configuration loader (merge with config.json)
│ │ └── index.php # Bootstrap (autoloader, requires)
│ ├── lang/ # Language files
│ │ ├── nl.php # Dutch translations
│ │ └── en.php # English translations
│ └── router.php # PHP dev server router (also serves /themes/)
├── themes/ # Dynamic themes (fully self-contained)
│ ├── default/ # Default theme
│ │ ├── theme.json # { title, config.default_template, template→.twig mapping }
│ │ ├── base.twig # Main layout (head, header, nav, footer)
│ │ ├── full_content.twig # Layout: full width
│ │ ├── left_sidebar.twig # Layout: sidebar left
│ │ ├── right_sidebar.twig # Layout: sidebar right
│ │ ├── custom1.twig # Layout: custom
│ │ ├── partials/ # header.twig, navigation.twig, footer.twig
│ │ ├── css/theme.scss # Colors, heights, background (compiled at runtime)
│ │ ├── js/theme.js # Theme JavaScript
│ │ └── theme.png # Preview image
│ ├── demo/ # Demo theme (same structure, different look)
│ └── test/ # Test theme
├── admin/ # Admin panel
│ ├── config/
│ │ ├── app.php # Admin app configuration (paths, timezone)
│ │ └── admin.json # Users & security (bcrypt hashes)
│ ├── src/
│ │ └── AdminAuth.php # Authentication (sessions, bcrypt, CSRF, lockout)
│ ├── templates/
│ │ ├── login.php # Login page
│ │ ├── layout.php # Admin layout with sidebar navigation
│ │ └── pages/
│ │ ├── dashboard.php # Dashboard with statistics
│ │ ├── content.php # Content overview with file upload
│ │ ├── content-edit.php # CodeMirror editor with toolbar and rename
│ │ ├── content-new.php # Create new content
│ │ ├── content-dir-form.php # Create/edit directory
│ │ ├── content-move-form.php # Move content
│ │ ├── config.php # Configuration editor
│ │ ├── security.php # Security settings
│ │ ├── statistics.php # Statistics dashboard
│ │ ├── plugins.php # Plugin overview
│ │ ├── plugins-edit.php # Plugin PHP source code editor
│ │ ├── plugins-new.php # Create new plugin
│ │ ├── plugin-config.php # Plugin configuration editor
│ │ ├── theme.php # Theme management
│ │ ├── users.php # User management
│ │ ├── logs.php # Log viewer
│ │ ├── update.php # System update
│ │ └── guide.php # Guide
│ └── storage/logs/ # Admin logs
├── cli/ # CLI scripts & tests
├── content/ # Content files
│ ├── -assets/ # Uploaded media files
│ ├── index.md # Default homepage
│ └── ... # Other content
├── plugins/ # CMS plugins
│ ├── HTMLBlock/ # Custom HTML blocks in sidebar
│ └── MQTTTracker/ # Real-time analytics and tracking
├── public/ # Web root
│ ├── index.php # Website entry point (media serving + CMS)
│ ├── admin.php # Admin entry point + routing
│ ├── .htaccess # Apache rewrite/security rules
│ ├── assets/ # CSS, JS, favicons
│ │ ├── codemirror/ # CodeMirror editor (minified JS/CSS)
│ │ └── css/js/ # Bootstrap, icons, app CSS/JS
│ ├── themes/ # Runtime compiled theme CSS (public/themes)
│ └── manifest.json / sw.js # PWA support
├── themes/ # Dynamic themes (fully self-contained)
│ ├── default/ # Default theme
│ │ ├── theme.json # Title, default template, template mapping
│ │ ├── base.twig # Main layout
│ │ ├── *.twig # Layout templates (full_content, left_sidebar, ...)
│ │ ├── partials/ # header, navigation, footer
│ │ ├── css/theme.scss # Colors, heights, background
│ │ ├── js/theme.js # Theme JavaScript
│ │ └── theme.png # Preview image
│ ├── demo/ # Demo theme
│ └── test/ # Test theme
├── config.json # Site configuration
├── version.php # Version information
└── vendor/ # Composer dependencies
Content
Content Structure
File Structure
content/
├── folder1/
│ ├── subfolder1/
│ │ ├── nl.page1.md
│ │ └── en.page1.md
│ └── page3.html
├── folder2/
│ └── page4.md
├── index.md
└── -assets/
├── image.jpg
└── document.pdf
File Naming
- Use lowercase filenames
- No spaces - use
-or_ - Logical extensions -
.md,.php,.html - Unique names - no duplicates
- Language prefixes -
nl.file.mdanden.file.md
Media Files
Media files (images, PDFs, video, audio) can be placed in any content/ subdirectory and are served via:
/-media/path/file.jpg- Media from any content subdirectory/-assets/file.jpg- Backward compatibility (old URLs)- Uploads via the admin panel go to
content/-assets/
Content API (for PHP content files)
PHP content files (.php in the content/ directory) have access to an $api variable with the following methods:
Getting pages
// Get all pages with titles
$pages = $api->getAllPages();
// Result: ['index' => 'Home', 'about' => 'About Us', ...]
// Get a specific page's content
$page = $api->getPage('about');
// $page['title'], $page['content'], $page['path'], $page['layout'], $page['metadata']
// Check if a page exists
if ($api->pageExists('contact')) {
// ...
}
Navigation
// Get menu structure
$menu = $api->getMenu();
// Nested array with 'title', 'path', 'url', 'children'
Configuration
// Get config value (dot notation)
$title = $api->getConfig('site_title');
$lang = $api->getConfig('language.default');
$seoDesc = $api->getConfig('seo.description', 'Default description');
Current page
// Current page title
$pageTitle = $api->getCurrentPageTitle();
// Current page path
$pagePath = $api->getCurrentPagePath();
// Check if this is the homepage
if ($api->isHomepage()) {
echo 'Welcome!';
}
URLs and language
// Build URL for a page
$url = $api->buildUrl('about', 'en');
// Current language
$lang = $api->getCurrentLanguage();
// Available languages
$languages = $api->getAvailableLanguages();
// Site title
$title = $api->getSiteTitle();
Translations and search
// Get translation
$label = $api->t('home');
// Search results (if searching)
if ($api->isSearching()) {
$results = $api->getSearchResults();
}
Example PHP content file
---
title: Page Overview
layout: content
---
<h1>All Pages</h1>
<ul>
<?php foreach ($api->getAllPages() as $path => $title): ?>
<li><a href="<?= $api->buildUrl($path) ?>"><?= htmlspecialchars($title) ?></a></li>
<?php endforeach; ?>
</ul>
Settings
Configuration
The site configuration is managed via the admin panel at /admin/config. The form includes:
- General settings - Site title and homepage (dropdown with available pages)
- Language - Default language and available languages
- SEO - Meta description and keywords
- Author - Name and website
- Features - Auto-link pages, search, breadcrumbs, show version
- IP Exclusions - Exclude IP addresses from statistics and security checks
The configuration is stored in config.json. You can also edit this file manually for advanced options.
IP Exclusions
Under Configuration in the admin panel, the "IP Exclusions" field lets you specify IP addresses that will be:
- Excluded from visitor statistics
- Skipped during all security checks (bot detection, rate limiting, IP blocklist)
This is useful for your own IP address or internal monitoring tools.
Example config.json
{
"site_title": "CodePress",
"content_dir": "content",
"default_page": "index",
"active_theme": "default",
"language": {
"default": "en",
"available": ["nl", "en"]
},
"seo": {
"description": "CodePress CMS - Lightweight file-based content management system",
"keywords": "cms, php, content management, file-based"
},
"author": {
"name": "E. Noorlander",
"website": "https:\/\/noorlander.info"
},
"features": {
"auto_link_pages": true,
"search_enabled": true,
"breadcrumbs_enabled": true
},
"analytics": {
"enabled": true,
"excluded_ips": ["127.0.0.1", "::1"]
},
"security": {
"block_ai_bots": true,
"block_scrapers": true,
"block_empty_user_agent": true,
"rate_limit_enabled": true
}
}
Themes
Themes are managed via the admin panel at /admin/theme. This is a selection page: pick the active theme and click "Activate theme". Each theme is a fully self-contained folder in themes/ with its own Twig templates, SCSS and JavaScript.
Theme structure (themes/<name>/)
themes/<name>/
├── theme.json # Title, default template, template mapping
├── base.twig # Main layout (head, header, nav, footer)
├── full_content.twig # Layout: full width
├── left_sidebar.twig # Layout: sidebar left
├── right_sidebar.twig # Layout: sidebar right
├── custom1.twig # Layout: custom
├── partials/ # header.twig, navigation.twig, footer.twig
├── css/theme.scss # Colors, heights, background (compiled at runtime)
├── js/theme.js # Theme JavaScript
└── theme.png # Preview image (shown in admin)
Theme Configuration (themes/<name>/theme.json)
{
"title": "default",
"config": {
"default_template": "full_content"
},
"template": {
"full_content": "full_content.twig",
"left_sidebar": "left_sidebar.twig",
"right_sidebar": "right_sidebar.twig",
"custom1": "custom1.twig"
}
}
config.default_template: the default template used when a page requests an unknown layout.template: the layout-key →.twigfile mapping. A theme can have multiple template pages.
Colors, heights and background are not set in theme.json but in css/theme.scss:
$header-bg: #0a369d;
$header-font: #ffffff;
$header-height: 56px;
$nav-bg: #2754b4;
$nav-font: #ffffff;
$nav-height: 42px;
$sidebar-bg: #f8f9fa;
$sidebar-border: #dee2e6;
$header-bg-image: none; // optional header background
$header-bg-opacity: 1;
The SCSS is compiled at runtime into public/themes/<name>/theme.css.
How to create a new theme
Themes are created manually: copy the themes/default/ folder to themes/<name>/, adjust the SCSS colors and templates, and add a theme.png preview. The theme is then available on /admin/theme to activate.
Security
Security settings are managed via /admin/security. Includes:
Bot, AI & Scraper Blocking
Incoming requests are checked against known bot and AI crawler patterns via the User-Agent header. Detected bots receive a 403 Forbidden response.
| Category | Examples |
|---|---|
| AI Crawlers | GPTBot, ChatGPT-User, Claude-Web, ClaudeBot, Google-Extended, CCBot, PerplexityBot |
| Search Engines | Googlebot, Bingbot, BingPreview, DuckDuckBot, YandexBot, Baiduspider |
| Scrapers | HTTrack, Scrapy, PhantomJS |
Rate Limiting
Prevents IPs from overloading the site. Returns HTTP 429 on exceedance.
IP Lists
- IP Whitelist - IPs on the whitelist are never blocked
- IP Blocklist - IPs on the blocklist always receive a 403 Forbidden
Dynamic robots.txt
The system automatically generates a robots.txt based on your security settings, available at /robots.txt.
Data
Statistics & Analytics
The statistics dashboard is available at /admin/statistics and provides:
- KPI cards - Page views, unique visitors, human/bot ratio, blocked requests
- World map - Visual representation of visitors per country with color intensity
- Countries list - Top 25 countries with percentage
- Most viewed pages - Top 25 pages
- Daily chart - Bar chart of visitors per day
- Referring sites - Top 15 referrers
Periods and export
Filter by 7, 30, 90 days or all time. Export data as CSV or JSON.
GeoIP
Country detection via three sources:
- Local (DB-IP Lite) - Offline, privacy-friendly, auto-updated
- MaxMind database (.mmdb) - Custom MMDB file
- External API - Custom API URL and key
Logging
The admin console maintains logs, viewable at /admin/logs:
- Activity log (
admin/storage/logs/admin.log) — admin actions like creating, editing, deleting pages, enabling/disabling plugins, changing configuration. - Request log (
admin/storage/logs/requests.log) — every page view on the website, including IP, page, domain, language, user agent, and referrer. - Dynamic log — structured log entries via
LogManager, with event type, level, IP, and message.
Configuring dynamic logging
Via /admin/config → Logging you can configure how and what is recorded:
- Storage:
SQLite(default) orSyslog. - Syslog server: if a host is provided, log entries are sent to that server over UDP. Leave empty to use SQLite.
- Facility: the category of the log source in syslog.
local0–local7are for your own applications;daemon,user, andauthare standard system categories. - Syslog ident: the name that appears in the log message (e.g.
codepress). - Events: choose which types are recorded —
admin,requests,errors,security,content,system.
If no syslog server is configured, SQLite is always used (with a file fallback if SQLite is unavailable).
The dashboard shows the last 20 entries of each log. Click "View all →" for the full list, where you can also download or clear.
System
Plugin System
Plugin Structure
plugins/
├── HTMLBlock/
│ ├── HTMLBlock.php # Plugin class (required)
│ ├── config.json # Configuration (optional)
│ └── README.md # Documentation (optional)
├── MQTTTracker/
│ ├── MQTTTracker.php
│ ├── config.json
│ └── README.md
Plugin Development
- API access via
CMSAPIclass - gives access to CMS configuration, templates, menu - Sidebar content with
getSidebarContent()- returns HTML for sidebar - Metadata access from YAML frontmatter via
CMSAPI - Configuration via
config.json- editable through admin panel - viewable field in config.json determines if plugin is visible in sidebar
- Per-page visibility - via the editor plugin selector per page
Plugin Boilerplate
<?php
class MyPlugin
{
private ?CMSAPI $api = null;
private array $config;
public function __construct()
{
$this->config = [
'viewable' => true,
];
}
public function setAPI(CMSAPI $api): void
{
$this->api = $api;
}
public function getSidebarContent(): string
{
return '';
}
public function getConfig(): array
{
return $this->config;
}
public function setConfig(array $config): void
{
$this->config = array_merge($this->config, $config);
}
}
Known Issue: MQTTTracker Credentials
The MQTTTracker plugin stores broker_host, broker_port, client_id, username and password in plain text in plugins/MQTTTracker/config.json. This is a known open security issue - in a production environment it is recommended to externalize these credentials to environment variables or a separate credential manager.
User Management
Users are managed via /admin/users. Features:
- Add user with username, password and role
- Delete user
- Change password for other users (admin)
- Change own password (requires current password)
Passwords are stored as bcrypt hashes in admin/config/admin.json.
Update
Via /admin/update the system can be updated in one click via Git pull. The page shows the current version and git branch, and executes git pull origin <branch> on confirmation.
Guide (in Admin)
This guide is also built into the admin panel via /admin/guide, with support for Dutch and English.
Other
Templates
Templates are Twig files per theme in themes/<name>/. ThemeManager renders them and compiles css/theme.scss at runtime into public/themes/<name>/theme.css.
Template Variables
Site Info - site_title, author_name, author_website, author_git
Page Info - page_title, content, file_info, is_homepage
Navigation - menu, breadcrumb, homepage
Theme - theme_title, theme_css_url, theme_js_url, theme_config (config from theme.json)
Language - current_lang, current_lang_upper, t_* (translated strings)
Layout Options
Use YAML frontmatter to select the template. The layout key references a template in the active theme:
---
title: My Page
layout: left_sidebar
plugins: HTMLBlock
---
Available Layouts
The available layouts are defined by the template section of the active theme (themes/<name>/theme.json). The default theme includes:
full_content- Content only (full width)left_sidebar- Sidebar left, content rightright_sidebar- Content left, sidebar rightcustom1- Custom layout
If a page requests an unknown layout, the default_template from the theme's config is used.
Meta Data
---
title: Page Title
layout: left_sidebar
description: Page description
author: Author Name
date: 2025-11-26
plugins: HTMLBlock, MQTTTracker
---
URL Structure
Frontend Page URLs
- Home:
/or/en/ - Page:
/en/folder/page - Search:
?search=query(via search form)
Media URLs
- Media:
/-media/path/to/file.jpg(from any content subdirectory) - Assets:
/-assets/file.jpg(from content/-assets/, backward compatible)
Admin URLs
- Admin:
/admin - Dashboard:
/admin/dashboard - Content:
/admin/content - Configuration:
/admin/config - Security:
/admin/security - Statistics:
/admin/statistics - Theme:
/admin/theme - Plugins:
/admin/plugins - Users:
/admin/users - Logs:
/admin/logs - Update:
/admin/update - Guide:
/admin/guide
SEO Optimization
Meta Tags
The CMS automatically adds meta tags:
<meta name="generator" content="CodePress CMS">
<meta name="author" content="E. Noorlander">
<meta name="description" content="...">
<meta name="keywords" content="...">
Security Headers
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; ...
Frequently Asked Questions
How do I set the homepage?
- Go to Configuration in the admin panel (
/admin/config) - Select the desired page in the Default/homepage dropdown
- Click Save configuration
How does navigation work?
- Directories become dropdown menus
- Files become direct links
- Sub-directories become nested dropdowns
- Only files without a language prefix show in the menu
How do I add new content?
- Via the admin panel:
/admin/content-new - Or upload files to the
content/directory - Organize in logical directories
- Use correct filenames and extensions
How do I move a file or directory?
- Go to
/admin/content - Click "Move" next to the item
- Select the target directory
- Confirm the move
How do I exclude my own IP from statistics?
- Go to Configuration in the admin panel (
/admin/config) - Scroll to the "IP Exclusions" field
- Enter your IP address (one per line)
- Click Save configuration
Troubleshooting
Page not found (404)
- Check filename and path
- Check file extension (.md, .php, .html)
- Check file permissions
- Check if the file has the correct language prefix (
nl.oren.)
Navigation not updated
- Reload the page
- Check content directory structure
- Check filenames (no spaces)
- Files with language prefix only show in the correct language mode
Admin panel not accessible
- Check if the session is still valid
- On lockout: wait 15 minutes or clear lockout data in
admin/config/admin.json - Check CSRF token (reload the page)
Version
Current version: 1.9.1 Release date: 2026-07-29
Support
For technical support:
- Git: https://git.noorlander.info/E.Noorlander/CodePress
- Website: https://noorlander.info
- Issues: Report problems via Git issues
License
CodePress CMS is open-source software under dual-license: AGPL v3 for open-source use, commercial license for proprietary use.