# Bavarian Rank Engine




Ein WordPress-Plugin für AI-gestützte Meta-Beschreibungen, GEO-optimierte Strukturdaten, llms.txt und KI-Crawler-Management — entwickelt von [Donau2Space](https://donau2space.de).
---
## Was ist das Plugin?
**Bavarian Rank Engine (BRE)** ist ein All-in-One-Plugin für SEO und GEO (Generative Engine Optimization). Es verbindet WordPress mit führenden KI-Anbietern, um Meta-Beschreibungen vollautomatisch beim Veröffentlichen eines Beitrags zu generieren, reichert Seiten mit schema.org-Strukturdaten an, stellt einen maschinenlesbaren Inhaltsindex unter `/llms.txt` bereit und ermöglicht die gezielte Steuerung von KI-Crawlern über `robots.txt` — alles aus einer einheitlichen Admin-Oberfläche heraus.
Das Plugin ist so konzipiert, dass es ohne Reibung neben Rank Math, Yoast SEO, AIOSEO und SEOPress arbeitet, deren native Meta-Felder beschreibt und bestehende Beschreibungen niemals überschreibt.
---
## Features
### AI Meta Generator
Generiert beim Veröffentlichen automatisch eine SEO-optimierte Meta-Beschreibung (150–160 Zeichen). Der Prompt ist vollständig anpassbar und unterstützt die Platzhalter `{title}`, `{content}`, `{excerpt}` und `{language}`. Die Sprache wird automatisch aus Polylang, WPML oder dem WordPress-Locale ermittelt und an die KI weitergegeben.
Generierte Beschreibungen werden sowohl in BREs eigenem Post-Meta-Feld `_bre_meta_description` als auch direkt im nativen Feld des aktiven SEO-Plugins gespeichert (Rank Math: `rank_math_description`, Yoast: `_yoast_wpseo_metadesc`, AIOSEO: `_aioseo_description`, SEOPress: `_seopress_titles_desc`).
Zwei Hooks stehen für Entwickler bereit:
- **`bre_prompt`** (Filter): Erlaubt es, den fertigen Prompt vor dem API-Aufruf zu modifizieren — z. B. um ein Focus-Keyword anzuhängen.
- **`bre_meta_saved`** (Action): Wird nach dem Speichern einer Beschreibung gefeuert — z. B. für Synchronisation mit externen Systemen.
### Fallback-Meta (FallbackMeta)
Wenn kein API-Key hinterlegt ist oder ein KI-Aufruf fehlschlägt, extrahiert `FallbackMeta::extract()` automatisch eine saubere 150–160-Zeichen-Beschreibung aus dem Post-Content. Der Extraktor schneidet bevorzugt an Satzgrenzen (`.`, `!`, `?`) ab und fällt erst dann auf Wortgrenzen und schließlich auf einen harten Schnitt mit Ellipse zurück. Kein HTML im Ergebnis.
### Bulk Generator
Batch-Verarbeitung aller veröffentlichten Beiträge ohne Meta-Beschreibung. Der Bulk-Prozess läuft per wiederholtem AJAX-Request im Browser, verarbeitet 1–20 Beiträge pro Batch mit einem fest eingebautem 6-Sekunden-Delay zwischen Batches (Rate-Limiting). Jeder Beitrag wird bis zu dreimal versucht. Ein Live-Progress-Log und eine laufende Kostenabschätzung werden direkt in der Admin-UI angezeigt.
**BulkQueue-Lock:** Ein Transient-basierter Mutex (`bre_bulk_running`, TTL 15 Minuten) verhindert gleichzeitige Bulk-Runs — auch über mehrere Browser-Tabs oder Admin-Nutzer hinweg. Das Lock wird beim Start des ersten Batches gesetzt und beim letzten Batch oder manuell über einen Button freigegeben.
### Schema.org Enhancer (GEO)
Gibt JSON-LD-Strukturdaten und Meta-Tags in `wp_head` aus. Jeder Typ ist individuell aktivierbar:
| Typ | Schema.org-Type | Besonderheit |
|---|---|---|
| `organization` | `Organization` | Name, URL, Logo, `sameAs`-Links (Social) |
| `article_about` | `Article` | Headline, Publish/Modified, Description, Publisher |
| `author` | `Person` | Name, Autor-URL, optionaler Twitter-`sameAs` |
| `speakable` | `WebPage` + `SpeakableSpecification` | CSS-Selektoren auf H1 und ersten Absatz |
| `breadcrumb` | `BreadcrumbList` | Übersprungen wenn Rank Math oder Yoast aktiv |
| `ai_meta_tags` | — | `` und `` mit `max-snippet:-1` |
Die eigenständige ``-Ausgabe wird unterdrückt, wenn Rank Math, Yoast oder AIOSEO aktiv sind, um Doppelausgaben zu vermeiden.
### llms.txt (mit ETag/Cache)
Bedient `/llms.txt` (und paginierte Folgedateien `/llms-2.txt`, `/llms-3.txt` ...) über einen `parse_request`-Hook mit Priorität 1. Der Inhalt wird via WordPress-Transients gecacht und beim Speichern der Einstellungen automatisch invalidiert. HTTP-Caching-Header werden korrekt gesetzt:
- `ETag: "md5(content)"` — HTTP 304 bei übereinstimmendem If-None-Match
- `Last-Modified:` basierend auf dem neuesten `post_modified_gmt` in der Datenbank
- `Cache-Control: public, max-age=3600`
Die llms.txt enthält: Titel, Beschreibungs-Block vor dem Inhaltsverzeichnis, Featured-Links-Sektion, Content-Liste mit Datum, More-Sektion für Folgeseiten und optional einen Footer-Block.
Wenn Rank Math ebenfalls eine llms.txt ausliefern will, zeigt BRE einen Admin-Hinweis an, dass BRE Priorität hat.
### robots.txt Manager
Hängt `User-agent: \nDisallow: /\n`-Blöcke über den WordPress-Filter `robots_txt` an. Unterstützte Bots:
GPTBot, ClaudeBot, Google-Extended, PerplexityBot, CCBot, Applebot-Extended, Bytespider, DataForSeoBot, ImagesiftBot, omgili, Diffbot, FacebookBot, Amazonbot.
Jeder Bot kann einzeln über Checkboxen im Admin aktiviert oder deaktiviert werden.
### CrawlerLog (Privacy-konform)
Loggt Besuche bekannter KI-Bots in eine eigene Datenbanktabelle (`{prefix}bre_crawler_log`):
| Spalte | Inhalt |
|---|---|
| `bot_name` | Name des Bots (z. B. `GPTBot`) |
| `ip_hash` | SHA-256-Hash der Besucher-IP — keine Original-IP wird gespeichert |
| `url` | Aufgerufene URL (max. 512 Zeichen) |
| `visited_at` | Zeitstempel des Besuchs |
Einträge älter als 90 Tage werden automatisch per Weekly-Cron bereinigt. Das Dashboard zeigt eine 30-Tage-Zusammenfassung pro Bot.
### Meta Editor Box
Fügt einen Meta-Box-Block "Meta Description (BRE)" in den Post-Editor ein. Zeigt:
- Eine Quellen-Badge (KI generiert / Fallback / Manuell / Noch nicht generiert)
- Ein Textfeld mit `maxlength="160"` und Live-Zeichenzähler
- Einen "Mit KI neu generieren"-Button (nur sichtbar wenn ein API-Key hinterlegt ist), der inline eine neue Beschreibung abruft
### SEO Analyse Widget
Eine Sidebar-Meta-Box im Post-Editor mit Live-Statistiken:
- Titel-Länge (Ziel: 60 Zeichen)
- Wortanzahl und geschätzte Lesezeit
- Überschriften-Hierarchie (H1–H6)
- Intern- und Externlinks-Zähler
- Inline-Warnungen (z. B. kein H2, keine internen Links)
Stats werden per JavaScript live aktualisiert, während der Redakteur schreibt.
### Link-Analyse (Dashboard)
Ein AJAX-Bereich auf dem Dashboard, der identifiziert:
- Beiträge ohne einen einzigen internen Link
- Beiträge mit überdurchschnittlich vielen externen Links (konfigurierbarer Schwellenwert)
- Die Top-5-Pillar-Pages nach Anzahl eingehender interner Links
Ergebnisse werden 1 Stunde im Transient-Cache gehalten.
---
## API-Key-Sicherheit (KeyVault)
```
Klartextkey → XOR(key, sha256(AUTH_KEY . SECURE_AUTH_KEY)) → base64 → "bre1:"
```
Die Klasse `BavarianRankEngine\Helpers\KeyVault` verschleiert API-Keys vor dem Schreiben in die WordPress-Optionstabelle:
1. Ein 64-Zeichen-Hex-Salt wird aus den WordPress-Konstanten `AUTH_KEY` und `SECURE_AUTH_KEY` via `hash('sha256', AUTH_KEY . SECURE_AUTH_KEY)` abgeleitet.
2. Der Klartext-Key wird byte-für-byte XOR-verknüpft (Salt wird bei Bedarf wiederholt).
3. Das Ergebnis wird base64-kodiert und mit dem Präfix `bre1:` gespeichert.
**Kein OpenSSL, keine PHP-Extension** außer der PHP-Standardbibliothek erforderlich.
**Wichtiger Hinweis:** XOR mit einem statischen, abgeleiteten Salt ist Verschleierung, keine kryptografische Verschlüsselung. Ein Angreifer mit Zugriff auf sowohl die Datenbank als auch `wp-config.php` kann den Key rekonstruieren. Für maximale Sicherheit können Keys als Konstanten in `wp-config.php` definiert werden:
```php
define( 'BRE_OPENAI_KEY', 'sk-...' );
define( 'BRE_ANTHROPIC_KEY', 'sk-ant-...' );
define( 'BRE_GEMINI_KEY', 'AI...' );
define( 'BRE_GROK_KEY', 'xai-...' );
```
In der Admin-UI werden Keys stets maskiert angezeigt: `••••••Ab3c9` (letzte 5 Zeichen sichtbar).
---
## Technischer Stack
| Komponente | Technologie |
|---|---|
| Backend | PHP 8.0+, WordPress Plugin API |
| Namespace | `BavarianRankEngine\` |
| Architektur | Singleton-Core, Registry-Pattern für Provider, Feature-Klassen mit `register()` |
| Datenbank | WordPress Options API, wpdb (eigene Tabelle für CrawlerLog) |
| Caching | WordPress Transients (llms.txt, Link-Analyse, Bulk-Lock) |
| Frontend | Vanilla JS + jQuery (WordPress-integriert), kein Build-Step |
| I18n | `.pot`-File, Text-Domain `bavarian-rank-engine` |
| Tests | PHPUnit via Composer |
| Lizenz | GPL-2.0-or-later |
---
## Besonderheiten
**llms.txt mit ETag/Cache-Kontrolle**
Vollständige HTTP-Caching-Semantik inklusive 304-Not-Modified-Unterstützung. Der Content-Hash wird per `md5()` als ETag generiert. `Last-Modified` basiert auf dem neuesten `post_modified_gmt`-Timestamp in der Datenbank. Der Transient-Cache wird bei jeder Einstellungsänderung automatisch invalidiert.
**Bulk mit Rate-Limiting und Mutex-Lock**
Der 6-Sekunden-Inter-Batch-Delay und der 15-Minuten-Transient-Lock sind gezielt so implementiert, dass kein Produkt-API-Ratelimit ausgelöst wird und keine Race Conditions zwischen parallelen Admin-Sessions entstehen können.
**Fallback-Meta mit Satzgrenzerkennung**
`FallbackMeta::extract()` versucht aktiv, am Ende eines vollständigen Satzes (`. `, `! `, `? `) zu schneiden, bevor es auf Wortgrenzen und schließlich auf einen harten Schnitt zurückfällt. Multibyte-safe via `mb_substr` / `mb_strrpos`.
**CrawlerLog ohne Personenbezug**
IP-Adressen werden vor dem Speichern mit SHA-256 gehasht. Die Originale werden nie persistiert, was die Anforderungen der DSGVO an Datenminimierung erfüllt.
**Keine Doppel-Ausgaben bei SEO-Plugin-Koexistenz**
`hasExistingMeta()` prüft alle bekannten Meta-Felder (BRE, Rank Math, Yoast, AIOSEO, SEOPress) und überspringt Posts, die in einem davon bereits eine Beschreibung haben. `outputMetaDescription()` unterdrückt die eigene ``-Ausgabe wenn ein kompatibles SEO-Plugin erkannt wird.
---
## Installation für Entwickler
```bash
# Repository klonen
git clone https://git.donau2space.de/Michael/bavarianrankengine.git
cd bavarianrankengine
# Dev-Abhängigkeiten installieren (PHPUnit)
composer install
# Testsuite ausführen
php vendor/bin/phpunit --testdox
```
Für eine lokale WordPress-Instanz (z. B. mit wp-env oder Local by Flywheel):
```bash
# Plugin-Verzeichnis verlinken oder kopieren
ln -s $(pwd) /path/to/wordpress/wp-content/plugins/bavarian-rank-engine
# Plugin in WordPress aktivieren
wp plugin activate bavarian-rank-engine
```
Das Plugin hat keinen JavaScript-Build-Step. Alle Assets unter `assets/` sind direkte JS/CSS-Dateien.
---
## Contributing / Extending
### Neuen KI-Provider hinzufügen
1. Datei `includes/Providers/YourProvider.php` erstellen und `ProviderInterface` implementieren:
```php
namespace BavarianRankEngine\Providers;
class YourProvider implements ProviderInterface {
public function getId(): string { return 'yourprovider'; }
public function getName(): string { return 'Your Provider'; }
public function getModels(): array { return [ 'model-id' => 'Model Name' ]; }
public function testConnection( string $api_key ): array {
// Minimaler API-Call, gibt ['success' => bool, 'message' => string] zurück
}
public function generateText( string $prompt, string $api_key, string $model, int $max_tokens = 300 ): string {
// API aufrufen, Text zurückgeben, \RuntimeException bei Fehler werfen
}
}
```
2. In `includes/Core.php` in `register_hooks()` registrieren:
```php
$registry->register( new Providers\YourProvider() );
```
Der Provider erscheint automatisch in allen Admin-Dropdowns.
### Neues Feature hinzufügen
1. `includes/Features/YourFeature.php` mit einer `register()`-Methode erstellen, die WordPress-Hooks anhängt.
2. `require_once BRE_DIR . 'includes/Features/YourFeature.php';` in `Core::load_dependencies()` ergänzen.
3. `( new Features\YourFeature() )->register();` in `Core::register_hooks()` ergänzen.
### Hooks für externe Nutzung
```php
// Prompt vor dem API-Aufruf anpassen
add_filter( 'bre_prompt', function( string $prompt, \WP_Post $post ): string {
return $prompt . "\nFokus-Keyword: " . get_post_meta( $post->ID, 'focus_keyword', true );
}, 10, 2 );
// Nach dem Speichern einer Meta-Beschreibung reagieren
add_action( 'bre_meta_saved', function( int $post_id, string $description ): void {
my_external_sync( $post_id, $description );
}, 10, 2 );
```
---
## Lizenz
GPL-2.0-or-later — siehe [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html)
Copyright (c) 2025 [Donau2Space](https://donau2space.de)