# CodePress CMS

**[🇳🇱 Dutch](README.md) | [🇬🇧 English](#)**

A lightweight, file-based content management system built with PHP (≥8.0).

**Version:** 2.6.1 | **License:** AGPL v3 / Commercial

## ✨ Features

- 📝 **Multi-format Content** - Markdown, PHP and HTML files
- 🧭 **Dynamic Navigation** - Automatic menu generation
- 🌍 **Multi-language** - NL/EN/DE support
- 🔍 **Search** - Full-text search
- 📱 **Responsive** - Bootstrap 5 themes
- 🔒 **Security** - 100/100 pentest score
- 🛡️ **Admin Console** - CodeMirror editor, media management, themes, plugins
- 👥 **User Roles** - Admin, Content Manager, BI Manager, Site Admin
- 📊 **Analytics** - Visitor statistics with GeoIP
- 🤖 **BotGuard** - Bot/AI protection
- 📈 **Logging** - Comprehensive logging system
- 🔌 **Plugin System** - Sidebar plugins with own CSS/SCSS, Twig templates

## 🚀 Quick Start

```bash
# Install dependencies
composer install

# Start server with router for clean URLs (local only)
php -S localhost:8080 cms/router.php
```

**Website:** `http://localhost:8080`  
**Admin:** `http://localhost:8080/admin` (login: `admin` / `admin`)

## 📦 Installation

### Requirements

- **PHP** ≥ 8.0 with extensions: `json`, `mbstring`
- **Composer** (PHP dependency manager)
- Web server: **Apache 2.4+** with `mod_rewrite` or **Nginx** with PHP-FPM
- Optional: `opcache` (recommended for performance), `git` (for content versioning), `zip` extension (for ZIP backup/restore)

### Step 1 — Code and dependencies

```bash
git clone <repository-url> codepress
cd codepress
composer install
```

### Step 2 — Configuration

```bash
cp config.json.example config.json
cp admin/config/admin.json.example admin/config/admin.json
```

Edit `config.json` with your site title, language and plugins. Change the admin password in `admin/config/admin.json` (default `admin`/`admin`).

### Step 3a — Apache 2.4+

The webroot is the `public/` directory. Example vhost (`/etc/apache2/sites-available/codepress.conf`):

```apache
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/codepress/public

    <Directory /var/www/codepress/public>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/codepress_error.log
    CustomLog ${APACHE_LOG_DIR}/codepress_access.log combined
</VirtualHost>
```

Required Apache modules:

```bash
sudo a2enmod rewrite headers
sudo systemctl restart apache2
```

- `mod_rewrite` — for clean URLs (`/nl/page`) and asset-serving
- `mod_headers` — for security headers
- `AllowOverride All` — so the `.htaccess` in `public/` is applied

### Step 3b — Nginx

Example server block (`/etc/nginx/sites-available/codepress`):

```nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/codepress/public;
    index index.php;

    # Clean URLs: language-prefixed pages
    location ~ ^/(nl|en|de)(/(.+))?$ {
        try_files $uri /index.php?lang=$1&page=$2;
    }

    # Admin routes
    location /admin {
        try_files $uri /admin.php?$args;
    }

    # Asset-serving via asset.php (themes/plugins/admin outside webroot)
    location ~ ^/(themes|plugins)/([^/]+)/assets/(.+)$ {
        try_files $uri /asset.php;
    }
    location ~ ^/admin/assets/(.+)$ {
        try_files $uri /asset.php;
    }

    # PHP via FPM
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.0-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Security: block access to sensitive directories
    location ~ ^/(content|cms|admin/src|admin/config|admin/storage|var|vendor)/ {
        deny all;
        return 403;
    }

    location ~ /\.(git|htaccess) {
        deny all;
    }
}
```

**Note:** Nginx does not use `.htaccess`. Security headers must be set in the Nginx config:

```nginx
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';";
```

### Step 4 — Directory permissions

Make sure the web server has write access to the runtime directories:

```bash
chown -R www-data:www-data var/ admin/storage/ content/
chmod -R 755 .
```

### Step 5 — Test

Open the website in your browser. With an empty content directory you'll see a welcome page. The admin console is available at `/admin` (login `admin`/`admin`).

## 📚 Documentation

See **[guide/](guide/)** for extensive documentation per role:

| Role | Guide |
|------|-------|
| 📝 Content Editor | [Content Manager](guide/en/content-beheerder.md) |
| ⚙️ Administrator | [Admin Manager](guide/en/admin-beheerder.md) |
| 🎨 Theme Developer | [Theme Developer](guide/en/theme-developer.md) |
| 💻 Developer | [CodePress Developer](guide/en/codepress-developer.md) |

Each guide has sub-topics in separate folders with sidebar navigation.

## 👥 User Roles

| Role | Permissions |
|------|------------|
| **Admin** | Full access (everything) |
| **Content Manager** | Content management, guide |
| **BI Manager** | Statistics, logs, guide |
| **Site Admin** | Theme, plugins, statistics, logs, update, guide |

## 📁 Project Structure

```
codepress/
├── cms/                        # Core CMS engine
│   ├── core/class/             # CMS classes (CodePressCMS, ThemeManager, etc.)
│   ├── core/plugin/            # Plugin system (PluginManager, CMSAPI)
│   └── router.php              # PHP dev server router (clean URLs)
├── language/                   # Translation files (nl/, en/, de/ — each with site.php + admin.php)
├── admin/                      # Admin console
│   ├── config/                 # Admin configuration (admin.json)
│   ├── src/AdminAuth.php       # Authentication, roles, permissions
│   ├── static/                 # Static files (404.html)
│   ├── storage/                # Logs, cache, geoip
│   └── theme/default/          # Admin theme
│       ├── assets/             # CSS, JS, fonts, codemirror
│       ├── views/              # Twig templates (layouts, pages)
│       └── theme.json          # Admin theme configuration
├── themes/                     # Website themes
│   ├── default/                # Default theme
│   │   ├── theme.json          # Layout mapping, colors
│   │   ├── base.twig          # Main layout
│   │   ├── *.twig             # Layout templates
│   │   ├── partials/           # Header, navigation, footer
│   │   └── assets/            # SCSS, CSS, JS, img
├── plugins/                    # Plugins
│   ├── HTMLBlock/              # Example sidebar plugin
│   └── Navigation/            # Essential navigation plugin (protected)
│       ├── Navigation.php      # Plugin code
│       ├── plugin.json         # Plugin metadata
│       ├── assets/scss/        # Plugin SCSS source
│       └── assets/css/         # Plugin CSS
├── content/                    # Website content (.md, .php, .html)
├── public/                     # Web root
│   ├── index.php               # Website entry point
│   └── admin.php               # Admin entry point + routing
├── guide/                      # Documentation (nl/en)
│   ├── nl/                     # Dutch guides
│   └── en/                     # English guides
├── cli/test/                   # Test suites
├── var/                        # Cache (twig)
├── config.json                 # Site configuration
├── composer.json               # PHP dependencies
└── version.php                 # Version information
```

## ⚙️ Configuration

### config.json

```json
{
    "site_title": "CodePress",
    "active_theme": "default",
    "default_page": "auto",
    "language": {
        "default": "nl",
        "available": ["nl", "en"]
    },
    "enabled_plugins": ["HTMLBlock", "Navigation"],
    "features": {
        "search_enabled": true,
        "breadcrumbs_enabled": true
    },
    "security": {
        "block_ai_bots": true,
        "rate_limit_enabled": true
    },
    "analytics": { "enabled": true },
    "logging": { "enabled": true }
}
```

## 🔧 Dependencies

- **PHP ≥8.0** with extensions: json, mbstring
- **Composer** packages:
  - twig/twig (templating)
  - scssphp/scssphp (SCSS compilation)
  - league/commonmark (Markdown with HeadingPermalinks)
  - maxmind-db/reader (GeoIP)

## 🔐 Security

- ✅ XSS prevention (htmlspecialchars)
- ✅ CSRF tokens (admin forms)
- ✅ Path traversal prevention (realpath checks)
- ✅ Secure cookies (HttpOnly, SameSite)
- ✅ Security headers (X-Frame-Options, CSP)
- ✅ Bot/AI protection (BotGuard)
- ✅ Rate limiting per IP
- ✅ Role-based access control (RBAC)

## 🔌 Plugins

### Plugin structure

```
plugins/MyPlugin/
├── MyPlugin.php             # Plugin code (name = plugin name)
├── plugin.json              # Plugin metadata
├── assets/scss/            # Plugin SCSS source
└── assets/css/             # Plugin CSS (after compilation)
```

### Essential plugins

The **Navigation** plugin is an essential plugin and cannot be disabled, edited, or deleted. This plugin automatically generates sidebar navigation for guides and content.

### Plugin CSS

Plugin CSS is automatically loaded after theme CSS, so themes can override plugin styling.

## 📝 Content Examples

### Markdown with frontmatter

```markdown
---
layout: full_content
plugins: HTMLBlock, Navigation
---

# Page title

Content in Markdown format...
```

### PHP content

```php
<?php
/** @var ContentAPI $api */
$pages = $api->getAllPages();
echo "<h1>My Page</h1>";
echo "<p>Number of pages: " . count($pages) . "</p>";
```

## 🧪 Testing

```bash
# Penetration tests
cli/test/pentest/security-test.sh

# Accessibility tests (WCAG 2.1 AA)
cli/test/accessibility.sh

# Functional tests
cli/test/functional/*.sh
```

## 📞 Support

- **Documentation:** [guide/](guide/)
- **Issues:** Git repository
- **Contact:** commercial@noorlander.info

## 📄 License

**Dual-licensed:**

- **AGPL v3** - For open-source projects
- **Commercial** - For proprietary use

See [LICENSE](LICENSE) for details.

---

**CodePress CMS** - Built by E.Noorlander / CodePress Development Team