RadioCMS DEV
Getting Started Plugins Themes Referenz EN

# Entwickler-Dokumentation

Alles was du brauchst, um Plugins, Themes und eigene Module für RadioCMS Community Edition zu entwickeln. Das System basiert auf dem WordPress-Prinzip, Actions, Filters und Widgets, ist aber deutlich schlanker.

#### Plugins

Erweitere RadioCMS um neue Features, ohne den Core anzufassen.

#### Module

Core-Features deaktivieren und durch eigene Plugins ersetzen.

#### Themes

Eigene Designs per CSS, kein PHP, keine Angst vor kaputten Seiten.

#### Safe by Default

Plugin-Crashes legen nie die ganze Seite lahm, Fehler werden gefangen.

## Übersicht

RadioCMS Community Edition ist ein Open-Source-CMS speziell für Webradios, lizenziert unter MIT. Du darfst es kostenlos nutzen, ändern und weitergeben, auch kommerziell.

Das Erweiterungs-System besteht aus drei Bausteinen:

- Plugins, PHP-Code der sich in Aktionen und Filter einhakt
- Module, Core-Features die man an- und ausschalten kann
- Themes, CSS-basierte Designs die man einfach hochlädt

Community-First

Plugins und Themes dürfen und sollen geteilt werden. Wenn du etwas Gutes baust, teile es im Community-Discord oder auf GitHub.

## Getting Started

Ein minimales Plugin besteht aus zwei Dateien:

plugins/mein-plugin/plugin.json

    {
     "id": "mein-plugin",
     "name": "Mein Plugin",
     "version": "1.0.0",
     "description": "Kurze Beschreibung.",
     "author": "Dein Name",
     "main": "index.php",
     "enabled": false,
     "requires": "1.0.0"
    }

plugins/mein-plugin/index.php

    <?php
    // Nachricht am Seitenende ausgeben
    radiocms_add_action('footer', function() {
     echo '<!-- Mein Plugin ist aktiv -->';
    });

Das war's. Ordner hochladen, im Admin unter `Plugins` aktivieren, fertig.

## Architektur

RadioCMS lädt Plugins während der Initialisierung in `config/config.php`. Jedes Plugin wird in einem `try/catch` geladen, ein fehlerhaftes Plugin kann die Seite nicht lahmlegen.

    config.php
     ├─ database.php (getDB)
     ├─ themes.php (Theme-Helper)
     └─ plugins.php
     └─ radiocms_load_plugins()
     ├─ plugins/a/plugin.json → try: require index.php
     ├─ plugins/b/plugin.json → catch: log, skip
     └─ plugins/c/plugin.json → try: require index.php

Nach dem Laden liegen alle registrierten Actions, Filters und Widgets im RAM bereit. Werden Hook-Punkte im Frontend erreicht, werden die entsprechenden Callbacks (wieder in `try/catch`) ausgeführt.

## Was ist ein Plugin?

Ein Plugin ist ein Ordner unter `/plugins/` mit mindestens einer `plugin.json` und einer Haupt-PHP-Datei (meist `index.php`).

### Plugin-Struktur

    plugins/
    └── mein-plugin/
     ├── plugin.json # Metadaten (Pflicht)
     ├── index.php # Einstiegspunkt (Pflicht)
     ├── assets/ # CSS/JS/Bilder (optional)
     │ └── style.css
     └── includes/ # Eigene Helper (optional)
     └── helper.php

#### plugin.json, Felder

Feld | Typ | Pflicht | Beschreibung
--- | --- | --- | ---
`id` | string | ✓ | Eindeutige ID, `[a-z0-9-]+`
`name` | string | ✓ | Anzeigename
`version` | string | ✓ | SemVer (z.B. `1.0.0`)
`author` | string | ✓ | Autor
`description` | string | - | Kurze Beschreibung
`main` | string | - | Einstiegsdatei (default: `index.php`)
`enabled` | bool | - | Standard-Status (wird beim Installieren verwendet)
`requires` | string | - | Mindest-RadioCMS-Version
`author_url` | string | - | Autor-Website

## Actions Action

Actions sind Ereignis-Hooks. Du registrierst einen Callback, der an einer bestimmten Stelle ausgeführt wird (z.B. im Footer, nach dem Player, …).

#### Hinzufügen

    radiocms_add_action(string $hook, callable $callback, int $priority = 10): void

Niedrigere `$priority` = früher ausgeführt. Default ist 10.

Beispiel, etwas in den Footer schreiben

    radiocms_add_action('footer', function() {
     echo '<script>console.log("Plugin loaded");</script>';
    });

#### Auslösen (nur im Core)

    radiocms_do_action(string $hook, mixed ...$args): void

Der Core ruft diese an den definierten Stellen auf, du machst das normalerweise nicht selbst.

#### Verfügbare Actions

Hook | Ort | Args
--- | --- | ---
`wp_head` | Vor `</head>` | -
`footer` | Vor `</body>` | -
`home_top` | Homepage, vor Hero | -
`home_after_player` | Homepage, nach Hero | -
`home_bottom` | Homepage, vor Footer | -

Mehr Hook-Punkte kommen

Wir erweitern die Hook-Liste laufend. Brauchst du einen bestimmten Hook? Schreib uns, wir fügen sinnvolle hinzu.

## Filters Filter

Filters verändern einen Wert. Sie bekommen einen Input, dein Callback gibt einen (möglicherweise veränderten) Wert zurück.

    radiocms_add_filter(string $hook, callable $callback, int $priority = 10): void

Beispiel, Stationsnamen erweitern

    radiocms_add_filter('station_name', function(string $name): string {
     return $name . ' 🎵';
    });

Beispiel, Navbar-Eintrag hinzufügen

    radiocms_add_filter('navbar_items', function(array $items): array {
     $items[] = [
     'label' => 'Blog',
     'url' => '/blog.php',
     'slug' => 'blog',
     ];
     return $items;
    });

#### Verfügbare Filters

Hook | Input | Beschreibung
--- | --- | ---
`station_name` | string | Name des Radios
`navbar_items` | array | Zusätzliche Navbar-Einträge

## Widgets

Widgets sind UI-Blöcke, die Admins frei an Positionen (Footer, Sidebar, Homepage, …) platzieren können. Dein Plugin registriert sie, der Admin entscheidet wo sie erscheinen.

    radiocms_register_widget(
     string $id,
     string $name,
     callable $render,
     array $options = []
    ): void

Beispiel, Wetter-Widget

    radiocms_register_widget(
     id: 'wetter-widget',
     name: 'Wetter',
     render: function() {
     echo '<div class="card p-3">';
     echo '<h5>🌤️ 22°C in Berlin</h5>';
     echo '<p class="text-muted">Sonnig, leichter Wind</p>';
     echo '</div>';
     },
     options: [
     'description' => 'Zeigt aktuelles Wetter an.',
     'icon' => 'bi-cloud-sun',
     'plugin' => 'wetter-plugin',
     ]
    );

Der Render-Callback kann beliebiges HTML ausgeben, auch Bootstrap, eigenes CSS, JS-Snippets etc.

#### Widget-Positionen

Position | Wo wird gerendert
--- | ---
`home_top` | Startseite, ganz oben
`home_after_player` | Startseite, nach dem Player
`home_bottom` | Startseite, vor dem Footer
`footer` | Footer (jede Seite)
`admin_dashboard` | Admin-Dashboard

## Fehlerbehandlung

Du musst dich nicht um Try/Catch kümmern, der Core fängt alle Fehler. Wenn dein Plugin eine Exception wirft, wird sie geloggt und das Plugin übersprungen.

Error-Log: `/logs/plugin_errors.log`

    [2026-04-16 14:32:01] [PLUGIN:action:footer] Undefined variable $foo

Im Admin unter `Plugins` siehst du Fehlermeldungen der Plugins direkt.

## Core-Module

Core-Module sind die eingebauten Features: Sendeplan, News, DJs, Team, Partner, Song-Wünsche, Podcasts, Chat, DJ-Bewerbungen, Kontakt.

Admins können sie unter `Admin → Module` einzeln aktivieren oder deaktivieren. Deaktivierte Module:

- Verschwinden aus der Frontend-Navigation
- Verschwinden aus der Admin-Sidebar
- Zeigen 404 wenn man die URL direkt aufruft
- Verschwinden aus den Footer-Links

### Ein Modul durch ein Plugin ersetzen

Du willst den eingebauten Sendeplan durch deinen eigenen ersetzen?

1. Im Admin unter Module das Modul `schedule` deaktivieren
2. In deinem Plugin einen eigenen Endpoint anbieten, z.B. `/mein-sendeplan.php` oder via Plugin-Router
3. Über `navbar_items`-Filter einen eigenen Nav-Eintrag hinzufügen

plugin.php, Beispiel: Eigener Sendeplan

    <?php

    // Eigenen Navbar-Eintrag
    radiocms_add_filter('navbar_items', function(array $items): array {
     if (!radiocms_module_active('schedule')) {
     // Nur wenn Core-Modul deaktiviert
     $items[] = [
     'label' => 'Sendeplan',
     'url' => '/plugins/mein-sendeplan/view.php',
     'slug' => 'schedule',
     ];
     }
     return $items;
    });

#### Alle Core-Module

Module-ID | Feature
--- | ---
`schedule` | Sendeplan
`news` | News
`djs` | DJs
`team` | Team
`partners` | Partner
`requests` | Song-Wünsche
`podcasts` | Podcasts
`chat` | Chat
`applications` | DJ-Bewerbungen
`contact` | Kontakt

## Theme-Entwicklung

Themes in RadioCMS sind bewusst minimalistisch: nur CSS und Bilder, kein PHP. So können Themes die Seite nicht kaputt machen.

### Theme-Struktur

    mein-theme.zip
    ├── theme.json # Metadaten (Pflicht)
    ├── style.css # CSS-Overrides (Pflicht)
    ├── screenshot.png # Vorschaubild (optional, 16:9 empfohlen)
    └── assets/ # Bilder, Fonts (optional)
     ├── logo.png
     └── background.jpg

#### theme.json

theme.json

    {
     "id": "mein-theme",
     "name": "Mein Theme",
     "version": "1.0.0",
     "author": "Dein Name",
     "author_url": "https://example.com",
     "description": "Dunkles Theme mit orangenen Akzenten.",
     "requires": "1.0.0",
     "tags": ["dark", "orange", "modern"]
    }

### CSS-Variablen

RadioCMS nutzt CSS-Custom-Properties für alle Farben. Dein Theme überschreibt sie einfach:

style.css, Einfachstes Theme

    :root {
     --primary: #ff6b35; /* Hauptfarbe (z.B. Buttons) */
     --primary-dark: #e0551d; /* Hover-Farbe */
     --secondary: #ffd23f; /* Akzentfarbe */
     --bg-primary: #0d0d1a; /* Haupt-Hintergrund */
     --bg-card: #1a1a2e; /* Karten-Hintergrund */
     --text-main: #ffffff; /* Haupt-Textfarbe */
     --text-muted: #94a3b8; /* Sekundärer Text */
     --border-color: #2a2f42; /* Rahmen */
    }

    /* Navbar-Logo in Theme-Farbe */
    .navbar-brand {
     color: var(--primary) !important;
    }

    /* Hero-Bereich mit Gradient */
    .hero-section {
     background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
    }

### Upload & Validierung

Der Admin lädt dein Theme als ZIP unter `Admin → Themes` hoch. RadioCMS validiert streng und zeigt eine klare Fehlermeldung wenn etwas nicht passt:

Prüfung | Fehlermeldung bei Verstoß
--- | ---
Nur `.zip` | „Datei muss eine .zip sein"
Max 5 MB | „ZIP zu groß: 12 MB. Maximum: 5 MB"
`theme.json` vorhanden | „theme.json fehlt im ZIP-Archiv"
JSON valide | „theme.json ist kein valides JSON: …"
Pflichtfelder | „theme.json fehlt Pflichtfeld: author"
ID-Pattern | „Theme-ID 'My Theme!' ist ungültig"
`style.css` vorhanden | „style.css fehlt im ZIP-Archiv"
Keine PHP-Dateien | „Unerlaubte Datei im ZIP: evil.php"
Keine `.htaccess` | „Unerlaubte Datei im ZIP: .htaccess"
Kein Path-Traversal | „Verdächtiger Dateipfad im ZIP: ../../../etc/passwd"

Sicherheit

PHP-Dateien werden aus Sicherheitsgründen komplett verweigert. Der `/themes/`-Ordner hat zusätzlich eine `.htaccess` die PHP-Ausführung blockiert, mehrfacher Schutz.

## API-Referenz

### Plugin-Funktionen

Funktion | Beschreibung
--- | ---
`radiocms_add_action($hook, $cb, $priority=10)` | Action-Hook registrieren
`radiocms_do_action($hook...$args)` | Alle Actions eines Hooks ausführen (Core)
`radiocms_add_filter($hook, $cb, $priority=10)` | Filter-Hook registrieren
`radiocms_apply_filters($hook, $value...$args)` | Filter anwenden (Core)
`radiocms_register_widget($id, $name, $render, $options=[])` | Widget registrieren
`radiocms_render_widgets($position)` | Alle Widgets einer Position rendern (Core)
`radiocms_plugin_loaded($id): bool` | Ist ein bestimmtes Plugin aktiv?
`radiocms_log_plugin_error($context, $msg)` | Eigenen Fehler loggen

### Modul-Funktionen

Funktion | Beschreibung
--- | ---
`radiocms_module_active($id): bool` | Ist ein Core-Modul aktiv?
`radiocms_require_module($id)` | 404 wenn Modul deaktiviert (für Seitenschutz)
`radiocms_get_core_modules(): array` | Liste aller Core-Module

### Theme-Funktionen

Funktion | Beschreibung
--- | ---
`radiocms_get_active_theme(): string` | ID des aktuell aktiven Themes
`radiocms_get_theme_url(): string` | URL-Pfad zum aktiven Theme
`radiocms_get_all_themes(): array` | Liste aller installierten Themes

### Nützliche Core-Funktionen

Funktion | Beschreibung
--- | ---
`getSetting($key, $default=''): string` | Einstellung aus `settings`-Tabelle lesen
`getDB(): PDO` | Datenbank-Verbindung (PDO)
`t($key): string` | Übersetzung (nur in Callbacks, nicht beim Plugin-Laden!)

## Hook-Übersicht (Spickzettel)

#### Actions

    // ── Frontend ──
    radiocms_add_action('wp_head', fn() => /* CSS/JS in Head */);
    radiocms_add_action('footer', fn() => /* Skripte am Ende */);
    radiocms_add_action('home_top', fn() => /* Oben auf Startseite */);
    radiocms_add_action('home_after_player', fn() => /* Nach Player */);
    radiocms_add_action('home_bottom', fn() => /* Vor Footer */);

#### Filters

    radiocms_add_filter('station_name', fn(string $n) => $n);
    radiocms_add_filter('navbar_items', fn(array $items) => $items);

## Tutorial: Mein erstes Plugin

Wir bauen ein Plugin das einen Gruß in den Footer schreibt.

#### 1. Ordner anlegen

    plugins/
    └── greetings/
     ├── plugin.json
     └── index.php

#### 2. Plugin-Manifest

plugins/greetings/plugin.json

    {
     "id": "greetings",
     "name": "Greetings",
     "version": "1.0.0",
     "author": "Du",
     "description": "Zeigt einen Gruß im Footer.",
     "main": "index.php"
    }

#### 3. Logik

plugins/greetings/index.php

    <?php
    radiocms_add_action('footer', function() {
     $hour = (int)date('H');
     if ($hour < 12) $greet = '🌅 Guten Morgen';
     elseif ($hour < 18) $greet = '☀️ Guten Tag';
     else $greet = '🌙 Guten Abend';

     echo '<div class="alert alert-info text-center m-3">';
     echo htmlspecialchars($greet) . ', schön dass du da bist!';
     echo '</div>';
    });

#### 4. Im Admin aktivieren

Gehe zu `Admin → Plugins`, finde „Greetings", klick auf Aktivieren. Fertig.

## Tutorial: Ein Widget bauen

Wir bauen ein Stream-Statistik-Widget das die aktuelle Hörerzahl zeigt.

plugins/listener-stats/index.php

    <?php
    radiocms_register_widget(
     id: 'listener-stats',
     name: 'Hörer-Statistik',
     render: function() {
     // Hörerzahl aus Icecast-Status lesen (gecacht)
     $status = @file_get_contents('http://localhost:8000/status-json.xsl');
     $data = @json_decode($status, true);
     $listeners = $data['icestats']['source']['listeners'] ?? '?';

     ?>
     <div class="card bg-dark text-white p-3 mb-3">
     <div class="d-flex align-items-center gap-3">
     <i class="bi bi-people-fill fs-2 text-primary"></i>
     <div>
     <div class="small text-muted">Hören gerade zu</div>
     <div class="fw-bold fs-4"><?= htmlspecialchars((string)$listeners) ?></div>
     </div>
     </div>
     </div>
     <?php
     },
     options: [
     'description' => 'Zeigt aktuelle Hörerzahl.',
     'icon' => 'bi-people-fill',
     'plugin' => 'listener-stats',
     ]
    );

Der Admin geht unter `Plugins → Widgets`, wählt das Widget, die Position (z.B. `sidebar`) und speichert.

## Tutorial: Eigenes Theme

Wir bauen ein einfaches „Sunset" Theme mit orangen Akzenten.

#### 1. Dateien erstellen

    sunset/
    ├── theme.json
    ├── style.css
    └── screenshot.png

#### 2. theme.json

    {
     "id": "sunset",
     "name": "Sunset",
     "version": "1.0.0",
     "author": "Du",
     "description": "Warmes Theme mit orangen und gelben Tönen.",
     "tags": ["warm", "orange", "sunset"]
    }

#### 3. style.css

    :root {
     --primary: #ff6b35;
     --primary-light: #ff9b7a;
     --secondary: #ffd23f;
     --bg-primary: #1a0f0a;
     --bg-card: #2d1810;
     --border-color: #4a2817;
    }

    /* Hero mit Sonnenuntergang-Gradient */
    .hero-section {
     background:
     linear-gradient(180deg, rgba(26, 15, 10, 0.4), rgba(26, 15, 10, 0.9)),
     linear-gradient(135deg, #ff6b35, #ffd23f);
    }

    /* Buttons warm */
    .btn-primary-radio.btn-hero-primary {
     background: linear-gradient(135deg, var(--primary), var(--secondary));
     color: #1a0f0a;
     font-weight: 700;
    }

    /* Logo orange */
    .brand-icon i { color: var(--primary); }

    /* Player-Akzente */
    .player-play-btn {
     background: linear-gradient(135deg, var(--primary), var(--secondary));
     color: #1a0f0a;
    }

#### 4. ZIP erstellen & hochladen

1. Ordner `sunset/` zippen (wichtig: ordnerinhalt zippen, nicht den Ordner selbst rein)
2. Im Admin unter `Themes` hochladen
3. Aktivieren & fertig

Tipp

Ein `screenshot.png` (Größe 1280×720 oder 16:9) macht dein Theme im Admin deutlich ansprechender.

## FAQ

#### Ist RadioCMS Community wirklich komplett kostenlos?

Ja, MIT-Lizenz. Du darfst es kostenlos nutzen, anpassen, weitergeben, auch kommerziell einsetzen.

#### Darf ich Plugins verkaufen?

Ja, absolut. Deine Plugins gehören dir, du entscheidest über deren Lizenz.

#### Wer baut Custom Plugins auf Anfrage?

Der RadioCMS-Autor bietet das als bezahlten Service an, siehe radio.dgnshop.com. Die Community wächst ebenfalls.

#### Kann ich mehrere Themes gleichzeitig haben?

Installieren: ja. Aktiv: nur eins. Du kannst aber jederzeit wechseln.

#### Was passiert wenn mein aktives Theme plötzlich kaputt ist?

RadioCMS fällt automatisch auf das Default-Theme zurück. Deine Seite bleibt online.

#### Kann ich Hooks in eigenen Templates definieren?

Ja, benutze `radiocms_do_action('mein_hook')` in deinen Plugin-Templates. Andere Plugins können sich dann einhaken.

#### Warum keine PHP-Dateien in Themes?

Sicherheit. Ein kaputtes PHP-Theme könnte die Seite sprengen oder als Einfallstor für Angreifer dienen. CSS allein reicht für 99% aller Designwünsche, für komplexe Logik nimmst du ein Plugin.

## Hilfe & Support

#### GitHub

Issues melden, Pull Requests einreichen, Quellcode browsen.

#### Community-Discord

Austausch mit anderen Entwicklern & Radio-Betreibern. (bald)

#### Custom Plugins

Brauchst du ein spezielles Feature? Wir bauen Custom Plugins auf Anfrage.

#### Beispiel-Plugin

In der Installation: `plugins/hello-world/`, Starterpunkt zum Kopieren.

### Starter-Templates zum Download

Fertige Boilerplates mit Beispiel-Code und Kommentaren, Zip runterladen, anpassen, loslegen.

#### Plugin-Template

plugin.json + index.php mit Action-, Filter- und Widget-Beispielen als Kommentar.

plugin-template.zip

#### Theme-Template

theme.json + style.css mit allen CSS-Variablen und auskommentierten Beispielen.

theme-template.zip

### Bereit loszulegen?

Lad dir RadioCMS herunter, schnapp dir ein Starter-Template und bau dein erstes Plugin in 10 Minuten.

RadioCMS herunterladen