# BreznGEO





🇬🇧 [English version → README.md](README.md)
**Website:** [brezngeo.com](https://brezngeo.com) · [How To](https://brezngeo.com/howto.html) · [FAQ](https://brezngeo.com/faq.html) · [Changelog](https://brezngeo.com/changelog.html)
---
BreznGEO ist ein schlankes SEO- & GEO-Plugin für WordPress. Es generiert KI-Metabeschreibungen, gibt Schema.org-Strukturdaten aus, erstellt GEO-Inhaltsblöcke für KI-Engines und verwaltet den Crawler-Zugriff über robots.txt und llms.txt — alles in einem Plugin, ohne dass etwas hinter einer Paywall versteckt wird.
Es funktioniert mit oder ohne KI-Key. Es integriert sich ohne Konflikte in Rank Math, Yoast, AIOSEO und SEOPress. Kein SaaS. Keine Telemetrie. Keine Upsells.
---
## Warum dieses Plugin existiert
Die meisten WordPress-SEO-Plugins haben sich in die gleiche Richtung entwickelt: aufgeblähte Feature-Sets, Dashboards voller Metriken, die niemand gebraucht hat, und ein Preismodell, das die nützlichen Funktionen hinter einem monatlichen Abo versteckt.
Die KI-Welle hat es schlimmer gemacht. Plugins fingen an, „KI-gestützte" Features anzubieten — aber als Proxy-Dienst. Man zahlt eine monatliche Gebühr, die Inhalte werden über deren Server geleitet, sie rufen die KI-API im eigenen Namen auf und schlagen eine Marge drauf.
BreznGEO verfolgt einen anderen Ansatz:
- **Direkter API-Zugriff.** Du hinterlegst deinen eigenen Key von OpenAI, Anthropic, Google oder xAI. BreznGEO ruft die API direkt auf. Kein Mittelsmann, keine Marge, keine Daten über Server Dritter.
- **Klarer Output, kein Lärm.** Metabeschreibungen, Strukturdaten, KI-Inhaltsblöcke für GEO, Bot-Steuerung. Keine Lesbarkeits-Scores, keine Keyword-Dichte-Meter, keine Upsell-Banner.
- **Keine Subscription.** GPL-2.0. Kostenlos auf beliebig vielen Sites nutzbar. Die einzigen Kosten sind die API-Nutzung — typischerweise Bruchteile eines Cents pro Beitrag.
- **Keine Telemetrie.** BreznGEO sendet keine Daten nach Hause. Kein Usage-Tracking, kein Remote-Logging, keine Analytics, die den eigenen Server verlassen.
- **Funktioniert ohne KI.** Kein API-Key? Der Fallback-Extraktor erzeugt eine brauchbare Metabeschreibung aus dem Artikelinhalt per Satzgrenzenerkennung. Jeder Beitrag bekommt eine Beschreibung.
Entwickelt in Passau, Bayern — für [Donau2Space](https://donau2space.de), einen persönlichen KI-Blog, für den ich genau das gebraucht habe — und nichts mehr.
---
## Inhaltsverzeichnis
- [Warum dieses Plugin existiert](#warum-dieses-plugin-existiert)
- [Verzeichnisstruktur](#verzeichnisstruktur)
- [Features](#features)
- [Datenspeicherung](#datenspeicherung)
- [Sicherheit](#sicherheit)
- [KI-Provider](#ki-provider)
- [Hooks & Erweiterbarkeit](#hooks--erweiterbarkeit)
- [AJAX-Schnittstellen](#ajax-schnittstellen)
- [Installation](#installation)
- [Technischer Stack](#technischer-stack)
- [Lizenz](#lizenz)
---
## Verzeichnisstruktur
```
brezngeo/
├── brezngeo.php # Plugin-Header, Konstanten (BREZNGEO_VERSION, BREZNGEO_DIR, BREZNGEO_URL)
├── uninstall.php # Aufräumen bei Plugin-Löschung
├── assets/
│ ├── admin.css # Gemeinsames Admin-Stylesheet
│ ├── admin.js # Provider-Selektor, Verbindungstest
│ ├── bulk.js # Bulk-Generator AJAX-Loop + Progress-UI
│ ├── editor-meta.js # Meta Editor Box: Live-Zähler, KI-Regen-Button
│ ├── geo-editor.js # GEO Block Editor: Generieren / Löschen Button
│ ├── geo-frontend.css # Minimales Stylesheet für .brezngeo-geo auf dem Frontend
│ ├── link-suggest.js # Interne Link-Vorschläge: Trigger, UI, Apply (Gutenberg + Classic)
│ └── seo-widget.js # SEO Analyse Widget: Live-Auswertung im Editor
├── includes/
│ ├── Core.php # Singleton-Bootstrap, lädt alle Abhängigkeiten
│ ├── Admin/
│ │ ├── AdminMenu.php # Menüstruktur + Dashboard-Render
│ │ ├── BulkPage.php # Bulk Generator Admin-Seite
│ │ ├── GeoEditorBox.php # GEO Block Meta-Box im Post-Editor
│ │ ├── GeoPage.php # GEO Block Einstellungsseite
│ │ ├── LinkAnalysis.php # AJAX-Handler für Link-Analyse Dashboard
│ │ ├── LinkSuggestPage.php # Einstellungsseite für interne Link-Vorschläge
│ │ ├── MetaEditorBox.php # Meta Description Meta-Box im Post-Editor
│ │ ├── MetaPage.php # Meta Generator Einstellungsseite
│ │ ├── ProviderPage.php # AI Provider Einstellungsseite
│ │ ├── SchemaMetaBox.php # Schema.org per-Post Meta-Box
│ │ ├── TxtPage.php # TXT-Dateien-Seite: llms.txt + robots.txt (Tabs)
│ │ ├── SchemaPage.php # Schema.org Einstellungsseite
│ │ ├── SeoWidget.php # SEO Analyse Sidebar Widget
│ │ ├── SettingsPage.php # Zentrales getSettings() — mergt alle Option-Keys
│ │ └── views/ # PHP-Templates für alle Admin-Seiten
│ ├── Features/
│ │ ├── CrawlerLog.php # KI-Bot-Besuche loggen (eigene DB-Tabelle)
│ │ ├── GeoBlock.php # GEO Quick Overview Block (Frontend-Ausgabe)
│ │ ├── LlmsTxt.php # /llms.txt Endpunkt mit ETag/Cache
│ │ ├── LinkSuggest.php # Interne Link-Vorschläge: Matching-Engine + AJAX-Handler + Meta-Box
│ │ ├── MetaGenerator.php # Kernlogik: KI-Aufruf, Speichern, Bulk, AJAX
│ │ ├── RobotsTxt.php # robots.txt Bot-Blocking via WP-Filter
│ │ └── SchemaEnhancer.php # JSON-LD Schema.org Ausgabe in wp_head
│ ├── Helpers/
│ │ ├── BulkQueue.php # Mutex-Lock für Bulk-Prozesse (Transient-basiert)
│ │ ├── FallbackMeta.php # Meta-Extraktion aus Post-Content ohne KI
│ │ ├── KeyVault.php # API-Key Verschleierung vor dem Schreiben in die DB
│ │ └── TokenEstimator.php # Grobe Token-Schätzung für Kostenvorschau im Bulk
│ └── Providers/
│ ├── ProviderInterface.php # Interface: getId, getName, getModels, testConnection, generateText
│ ├── ProviderRegistry.php # Registry-Pattern: Provider registrieren und abrufen
│ ├── AnthropicProvider.php # Claude API (Messages API)
│ ├── GeminiProvider.php # Google Gemini (generateContent API)
│ ├── GrokProvider.php # xAI Grok (OpenAI-kompatibler Endpunkt)
│ └── OpenAIProvider.php # OpenAI GPT (Chat Completions API)
└── vendor/ # Composer-Abhängigkeiten (nur Produktionsstand)
```
---
## Features
### AI Meta Generator
Generiert SEO-optimierte Meta-Beschreibungen (150–160 Zeichen) automatisch beim Veröffentlichen eines Beitrags. Der Prompt ist vollständig anpassbar; unterstützte Platzhalter: `{title}`, `{content}`, `{excerpt}`, `{language}`.
**Spracherkennung:** Die Zielsprache wird automatisch aus Polylang, WPML oder dem WordPress-Locale ermittelt und im Prompt übergeben — ohne Konfiguration.
**SEO-Plugin-Integration:** Generierte Beschreibungen landen nicht nur in `_bre_meta_description`, sondern auch direkt im nativen Feld des aktiven SEO-Plugins:
| SEO-Plugin | Meta-Feld |
|---|---|
| Rank Math | `rank_math_description` |
| Yoast SEO | `_yoast_wpseo_metadesc` |
| AIOSEO | `_aioseo_description` |
| SEOPress | `_seopress_titles_desc` |
| (keins aktiv) | BreznGEO gibt `` selbst aus |
**Token-Modus:** Wahlweise wird der gesamte Artikelinhalt gesendet (`full`) oder auf eine konfigurierbare Token-Anzahl (100–8000) gekürzt (`limit`). Das Kürzen erfolgt über `TokenEstimator` — eine wortbasierte Schätzung ohne externe Bibliothek.
**Fallback ohne KI:** `FallbackMeta::extract()` liefert immer eine brauchbare Beschreibung — auch ohne API-Key oder bei Fehlern. Vollständig multibyte-safe via `mb_substr` / `mb_strrpos`.
---
### GEO Block (Quick Overview)
Generiert KI-gestützte Inhaltsblöcke direkt im Artikeltext für Generative Engine Optimization:
- **Summary** — Kurzüberblick des Artikels
- **Key Points** — Stichpunktliste der wichtigsten Aussagen
- **FAQ** — Frage-Antwort-Paare (nur ab konfiguriertem Wort-Schwellenwert, Standard: 350 Wörter)
**Einfügeposition** (konfigurierbar): nach dem ersten Absatz (Standard), oben, unten.
**Ausgabe-Modi:**
| Modus | Verhalten |
|---|---|
| `details_collapsible` | Natives HTML `` — zugeklappt, kein JavaScript nötig |
| `open_always` | Block immer sichtbar |
| `store_only_no_frontend` | Nur in DB speichern, keine Frontend-Ausgabe (z.B. für FAQPage-Schema) |
Alle Labels, Akzentfarbe, Farbschema (Auto/Hell/Dunkel) und Custom CSS sind über die Admin-Seite konfigurierbar — ohne Code.
---
### Schema.org Enhancer
Gibt JSON-LD-Strukturdaten und Meta-Tags in `` aus. Einstellungen unter **BreznGEO → Schema.org**. Jeder Typ ist einzeln aktivierbar:
| Typ | Schema.org-Type | Hinweis |
|---|---|---|
| `organization` | `Organization` | Name, URL, Logo, `sameAs`-Links |
| `author` | `Person` | Autorenname, Profil-URL, optionaler Twitter-`sameAs` |
| `speakable` | `WebPage` + `SpeakableSpecification` | CSS-Selektoren auf H1 und ersten Absatz |
| `article_about` | `Article` | Headline, Publish/Modified, Description, Publisher |
| `breadcrumb` | `BreadcrumbList` | Automatisch unterdrückt wenn Rank Math oder Yoast aktiv |
| `ai_meta_tags` | — | `` mit `max-snippet:-1` |
| `faq_schema` | `FAQPage` | Automatisch aus GEO Block Daten befüllt |
| `blog_posting` | `BlogPosting` / `Article` | Mit eingebettetem `author` und Featured Image |
| `image_object` | `ImageObject` | Featured Image mit Dimensionen und Caption |
| `video_object` | `VideoObject` | YouTube/Vimeo wird automatisch erkannt |
| `howto` | `HowTo` | Schrittweise Anleitung — Daten per Metabox |
| `review` | `Review` | Bewertung mit `ratingValue` — Daten per Metabox |
| `recipe` | `Recipe` | Zutaten, Zeiten, Nährwerte — Daten per Metabox |
| `event` | `Event` | Datum, Ort, Veranstalter — Daten per Metabox |
---
### llms.txt
Bedient `/llms.txt` und paginierte Folgedateien über einen `parse_request`-Hook mit Priorität 1 — vor WordPress-Routing, kein Rewrite-Rule-Flush nötig.
**HTTP-Caching:** ETag, Last-Modified, Cache-Control. Transient-Cache wird bei jeder Einstellungsänderung automatisch invalidiert.
**Rank Math Konfliktwarnung:** Falls Rank Math ebenfalls eine llms.txt ausliefern will, zeigt BreznGEO einen Admin-Hinweis an — BreznGEO hat wegen Priorität 1 automatisch Vorrang.
---
### robots.txt Manager
Hängt `Disallow`-Blöcke über den WordPress-Filter `robots_txt` an — die WordPress-eigene robots.txt bleibt erhalten. 13 KI-Bots einzeln steuerbar: GPTBot, ClaudeBot, Google-Extended, PerplexityBot, CCBot, Applebot-Extended, Bytespider, DataForSeoBot, ImagesiftBot, omgili, Diffbot, FacebookBot, Amazonbot.
---
### Bulk Generator
Batch-Verarbeitung aller veröffentlichten Beiträge ohne Meta-Beschreibung. Läuft als AJAX-Request im Browser — kein WP-Cron, keine CLI nötig. 1–20 Beiträge pro Batch, 6s Delay, bis zu 3 Versuche je Post, Mutex-Lock via Transient.
---
### Crawler Log
Loggt Besuche bekannter KI-Bots in der Tabelle `{prefix}brezngeo_crawler_log` (bot_name, ip_hash SHA-256, url, visited_at). Einträge älter als 90 Tage werden automatisch bereinigt. Dashboard zeigt 30-Tage-Zusammenfassung.
---
## Datenspeicherung
### WordPress Options (wp_options)
| Option-Key | Inhalt |
|---|---|
| `brezngeo_settings` | Aktiver Provider, API-Keys (verschleiert), Modell-Auswahl, Token-Kosten, `ai_enabled`-Flag |
| `brezngeo_meta_settings` | Meta Generator: Auto-Modus, Post-Types, Token-Modus, Prompt |
| `brezngeo_schema_settings` | Schema.org: aktivierte Typen, Organization sameAs-URLs |
| `brezngeo_geo_settings` | GEO Block: Modus, Position, Labels, CSS, Prompt, Farbschema |
| `brezngeo_robots_settings` | robots.txt: blockierte Bots |
| `brezngeo_llms_settings` | llms.txt: Titel, Beschreibung, Featured-Links, Footer, Seitenanzahl |
| `brezngeo_usage_stats` | Akkumulierte Token-Nutzung: `tokens_in`, `tokens_out`, `count` |
| `brezngeo_first_activated` | Unix-Timestamp der Erstaktivierung (für Welcome Notice) |
### Post Meta (wp_postmeta)
| Meta-Key | Inhalt |
|---|---|
| `_bre_meta_description` | Generierte Meta-Beschreibung |
| `_bre_meta_source` | Quelle: `ai` / `fallback` / `manual` |
| `_bre_bulk_failed` | Letzter Fehler beim Bulk-Versuch |
| `_bre_geo_summary` | GEO Block Summary |
| `_bre_geo_bullets` | GEO Block Key Points (JSON-Array) |
| `_bre_geo_faq` | GEO Block FAQ (JSON-Array) |
### Transients
| Transient | TTL | Zweck |
|---|---|---|
| `brezngeo_llms_cache_{n}` | 1 Stunde | Gecachter llms.txt Inhalt je Seite |
| `brezngeo_link_analysis` | 1 Stunde | Dashboard Link-Analyse Ergebnis |
| `brezngeo_bulk_running` | 15 Minuten | Mutex-Lock für den Bulk Generator |
| `brezngeo_meta_stats` | 5 Minuten | Dashboard Meta-Coverage-Abfrage |
| `brezngeo_crawler_summary` | 5 Minuten | Dashboard Crawler-Zusammenfassung (letzte 30 Tage) |
> **Uninstall:** `uninstall.php` löscht `brezngeo_settings` und `_bre_meta_description` für alle Posts. Die übrigen Option-Keys und die `brezngeo_crawler_log`-Tabelle müssen manuell gelöscht werden.
---
## Sicherheit
### API-Key Verschleierung (KeyVault)
```
Klartextkey → XOR(key, sha256(AUTH_KEY . SECURE_AUTH_KEY)) → base64 → "bre1:"
```
Kein `openssl_*` oder externe Extension nötig — läuft auf jeder PHP 8.0+ Installation. Das Präfix `bre1:` ermöglicht spätere Migration ohne Breaking Change.
**Sicherheitsgrenzen:** XOR mit statischem Salt ist Verschleierung, keine kryptografische Verschlüsselung. Für maximale Sicherheit können Keys als `wp-config.php`-Konstanten definiert werden:
```php
define( 'BREZNGEO_OPENAI_KEY', 'sk-...' );
define( 'BREZNGEO_ANTHROPIC_KEY', 'sk-ant-...' );
define( 'BREZNGEO_GEMINI_KEY', 'AI...' );
define( 'BREZNGEO_GROK_KEY', 'xai-...' );
```
### CSRF-Schutz und Capability Checks
Jeder AJAX-Handler ohne Ausnahme:
```php
check_ajax_referer( 'brezngeo_admin', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'Unauthorized', 403 );
}
```
Kein `wp_ajax_nopriv_`-Handler — alle Endpunkte erfordern `manage_options`.
### CSS-Sanitierung (GEO Block)
Das Custom-CSS-Feld des GEO-Blocks wird durch `Helpers\Css::sanitize_declarations()` bereinigt — entfernt Kommentare, geschweifte Klammern, At-Regeln (`@import`, `@media` usw.), `url()`, `expression()` und `javascript:`, bevor die Ausgabe über `wp_add_inline_style()` injiziert wird.
### Datenschutz (DSGVO)
CrawlerLog speichert IPs ausschließlich als SHA-256-Hash. Originalwert wird nie persistiert. Einträge nach 90 Tagen automatisch gelöscht.
---
## KI-Provider
| Provider | Klasse | API-Basis-URL |
|---|---|---|
| OpenAI | `OpenAIProvider` | `https://api.openai.com/v1/chat/completions` |
| Anthropic | `AnthropicProvider` | `https://api.anthropic.com/v1/messages` |
| Google Gemini | `GeminiProvider` | `https://generativelanguage.googleapis.com/...` |
| xAI Grok | `GrokProvider` | `https://api.x.ai/v1/chat/completions` |
Neuen Provider hinzufügen: `ProviderInterface` implementieren, in `Core.php` via `$registry->register()` eintragen — erscheint automatisch in allen Dropdowns.
---
## Hooks & Erweiterbarkeit
### `brezngeo_prompt` (Filter)
```php
add_filter( 'brezngeo_prompt', function( string $prompt, WP_Post $post ): string {
$keyword = get_post_meta( $post->ID, 'focus_keyword', true );
return $keyword ? $prompt . "\nFokus-Keyword: {$keyword}" : $prompt;
}, 10, 2 );
```
### `brezngeo_meta_saved` (Action)
```php
add_action( 'brezngeo_meta_saved', function( int $post_id, string $description ): void {
my_cdn_purge( get_permalink( $post_id ) );
}, 10, 2 );
```
---
## AJAX-Schnittstellen
Alle Endpunkte erfordern `manage_options` (kein `nopriv`).
| Action | Handler | Beschreibung |
|---|---|---|
| `brezngeo_regen_meta` | `MetaEditorBox::ajax_regen` | Meta-Beschreibung für einzelnen Post neu generieren |
| `brezngeo_test_connection` | `ProviderPage::ajax_test_connection` | API-Key und Verbindung testen |
| `brezngeo_get_default_prompt` | `ProviderPage::ajax_get_default_prompt` | Standard-Prompt zurücksetzen |
| `brezngeo_link_analysis` | `LinkAnalysis::ajax_analyse` | Link-Analyse ausführen |
| `brezngeo_link_suggestions` | `LinkSuggest::ajax_suggest` | Top-10 interne Link-Vorschläge für aktuellen Beitrag zurückgeben |
| `brezngeo_geo_generate` | `GeoEditorBox::ajax_generate` | GEO Block generieren |
| `brezngeo_geo_clear` | `GeoEditorBox::ajax_clear` | GEO Block löschen |
| `brezngeo_llms_clear_cache` | `TxtPage::ajax_clear_cache` | llms.txt Cache leeren |
| `brezngeo_dismiss_llms_notice` | `LlmsTxt::ajax_dismiss_notice` | Rank-Math-Hinweis ausblenden |
| `brezngeo_dismiss_welcome` | `AdminMenu::ajax_dismiss_welcome` | Welcome Notice per User ausblenden |
| `brezngeo_bulk_generate` | `MetaGenerator::ajaxBulkGenerate` | Nächsten Batch verarbeiten |
| `brezngeo_bulk_stats` | `MetaGenerator::ajaxBulkStats` | Fortschritt abrufen |
| `brezngeo_bulk_release` | `MetaGenerator::ajaxBulkRelease` | Mutex-Lock manuell freigeben |
| `brezngeo_bulk_status` | `MetaGenerator::ajaxBulkStatus` | Lock-Status prüfen |
---
## Installation
**Via GitHub Release (empfohlen):**
1. `brezngeo.zip` vom [neuesten Release](https://github.com/noschmarrn/brezngeo/releases/latest) herunterladen
2. In WordPress unter *Plugins → Installieren → Plugin hochladen* einspielen
**Manuell (clone):**
```bash
cd /path/to/wordpress/wp-content/plugins/
git clone https://github.com/noschmarrn/brezngeo.git brezngeo
wp plugin activate brezngeo
```
**Nach der Aktivierung:**
1. *BreznGEO → AI Provider* — Provider wählen, API-Key hinterlegen, Verbindungstest
2. *Meta Generator* — Auto-Modus aktivieren, Post-Types auswählen
Kein JavaScript-Build-Step. Alle Assets unter `assets/` sind direkte JS/CSS-Dateien.
---
## Technischer Stack
| Komponente | Technologie |
|---|---|
| Backend | PHP 8.0+, WordPress Plugin API |
| Namespace | `BreznGEO\` |
| Architektur | Singleton-Core, Registry-Pattern (Provider), Feature-Klassen mit `register()` |
| Datenbank | WordPress Options API, `wpdb` (eigene Tabelle für CrawlerLog) |
| Caching | WordPress Transients |
| Frontend | Vanilla JS + jQuery (WordPress-integriert), kein Build-Step |
| I18n | `.pot`-File, Text-Domain `brezngeo` |
| Tests | PHPUnit (102 Tests, 216 Assertions) |
| Coding Standard | WordPress PHPCS |
| Lizenz | GPL-2.0-or-later |
---
## Lizenz
GPL-2.0-or-later — [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html)
Copyright (c) 2025–2026 [Donau2Space](https://donau2space.de)