Version 0.21.0

July 24, 2026

Schließt die letzte offene Lücke der Copy-&-Rebuild-Vision: Die Startseite war bis hierher als Kopiervorlage strukturell unerreichbar. /duplicate-page klonte immer in die Section der Quelle zurück, und die Startseite liegt in einer single-Section, die per Definition genau einen Entry fasst — ein Klon dorthin hätte die bestehende Seite verdrängt. Der Worker musste single-Sections deshalb korrekt ausschließen und landete auf einer beliebigen anderen Section, deren Beispiel-Entry oft nur einen einzigen Block hat. Ergebnis beim Kunden: eine Seite, die nichts vom Design der Startseite hat. Der in v0.19.0 als Brücke gebaute Endpoint /build-template-page legte seine Vorlagen-Section zudem mit hasUrls: true und template: null an — Craft validiert template nie, die Section speichert also klaglos, und jede daraus erzeugte Seite lief beim Rendern in einen 404.

Fixed

  • Kritisch — /build-template-page legte eine dauerhaft nicht renderbare Section an. Section_SiteSettings wurde mit hasUrls: true, uriFormat: 'deon-vorlagen/{slug}' und template: null erzeugt. Gegen den Quelltext von craftcms/cms 5.10.10 geprüft verlangt Section_SiteSettings::defineRules() bei hasUrls ausschließlich uriFormattemplate wird nie validiert. Die Section speicherte damit fehlerfrei und der Defekt zeigte sich erst beim Aufruf der erzeugten Seite als 404. Jetzt erbt die Vorlagen-Section die Site-Settings der Quell-Section (nur wenn deren Template per View::doesTemplateExist() tatsächlich existiert); ist dort kein nutzbares Template hinterlegt, wird die Vorlagen-Section bewusst ganz ohne URLs angelegt — eine Vorlage ohne URL ist korrekt, eine Vorlage mit kaputter URL ist eine Falle.
  • Selbstheilung für bereits betroffene Sites. Wurde die Section deonTemplates von v0.19.0 oder v0.20.x bereits im kaputten Zustand angelegt, repariert /build-template-page sie beim nächsten Aufruf automatisch (neues Response-Feld section_repaired: true). Ohne diesen Schritt bliebe der 404 dort dauerhaft bestehen, weil der Endpoint idempotent ist und eine vorhandene Section nie wieder anfasste.

Added

  • POST /deon-ai/duplicate-page — neue optionale Body-Keys target_section_handle und target_entry_type_handle. Damit landet der Klon in einer anderen Section als die Quelle; sectionId/typeId gehen als $newAttributes direkt in Elements::duplicateElement() (nachträglich wäre der Klon bereits in der Quell-Section gespeichert). Das macht die Startseite erstmals zu einer legalen Klonquelle: Craft-Feld-Handles sind global eindeutig, dasselbe Matrix-Feld hängt daher in aller Regel auch am Entry-Type der regulären Unterseiten — die komplette Block-Palette der Startseite ist dort gültig. Ohne die neuen Keys ist das Verhalten unverändert (voll rückwärtskompatibel). Validierung vor dem Klon, jeweils mit eigenem Fehlercode statt einer 500er-Exception: target_section_not_found (404), target_section_is_single (409 — ein Klon in eine Single-Section würde die dortige Seite verdrängen), target_section_has_no_entry_type (409), target_entry_type_not_in_section (409, inkl. available_entry_types), title_field_missing (422, bestehender Check) und target_block_field_missing (409, inkl. source_block_fields/target_block_fields — der Klon wäre sonst ohne Sektionen). Ohne expliziten target_entry_type_handle wird der erste Entry-Type der Ziel-Section gewählt, der ein Block-Feld mit der Quelle teilt.
  • POST /deon-ai/duplicate-page liefert zusätzlich section_handle, entry_type_handle und cloned_into_section (bool). Alle bestehenden Felder inkl. success bleiben unverändert; der Worker erkennt die neue Fähigkeit an typeof body.cloned_into_section === "boolean".
  • GET /deon-ai/site-inventory liefert pro Section jetzt has_urls, uri_format, template, template_exists, renderable sowie entry_types[{handle, name, has_title_field, block_field_handles[]}] (dazu plugin_version auf oberster Ebene). Bisher standen dort nur Handle, Typ, Name, Entry-Zahl und ein Beispiel-Entry — zu wenig, um ein Klonziel zu wählen, ohne zu raten. renderable ist bewusst die Konjunktion aus URL und existierendem Template, weil genau deren Auseinanderfallen den 404-Defekt oben verursacht hat.
  • Neue Einträge im capabilities-Array von /deon-ai/ping: clone_into_section und site_inventory_targets.

Hinweis — Worker-Gegenpart erforderlich

Minor-Release: neue Response-Felder und ein neuer Body-Key, keine neue Migration (schemaVersion bleibt 1.5.0). Der Worker deon-mvp muss die Zielwahl aus dem angereicherten Inventory treffen und target_section_handle mitschicken — hinter einem Capability-Gate, sonst brechen Sites mit älterer Plugin-Version. Reihenfolge zwingend: erst dieses Plugin-Release beim Kunden installieren und smoke-testen, dann den Worker deployen. Wie v0.19.0/v0.20.1 ist auch dieser Stand nur mit php -l syntaxgeprüft, nicht gegen eine laufende Craft-Instanz ausgeführt.

Version 0.20.1

July 24, 2026

Ersetzt den nie veröffentlichten internen Entwurf 0.20.0 vollständig. Ein Craft-Review gegen den echten Quelltext von craftcms/cms 5.10.10 hat in diesem Entwurf zwei Defekte gefunden, einer davon mit echtem Datenverlust-Potenzial. Beide sind unten dokumentiert und behoben; der 0.20.0-Handoff darf nicht angewendet werden.

Fixed (gegenüber dem 0.20.0-Entwurf)

  • Kritisch — reorder hätte alle Blöcke der Sektion löschen können. Der Entwurf übergab Entry::setFieldValue($fieldHandle, $orderedBlocks) ein Array instanziierter Entry-Objekte. Gegen craft\fields\Matrix (5.10.10) geprüft ist das kein gültiges Wertformat: _normalizeValueInternal() reicht ein Array an _createEntriesFromSerializedData() weiter, wo ein Objekt-Array im Legacy-Zweig mit numerischen Schlüsseln 0,1,2… landet, zu keiner echten Entry-ID passt, in den "neuer Block"-Zweig fällt (der ein type verlangt) und per continue für jeden Block verworfen wird. Übrig bleibt ein leeres Set, das NestedElementManager::saveNestedElements() an deleteOtherNestedElements() weitergibt — womit sämtliche Blöcke des Feldes gelöscht worden wären. Jetzt wird das korrekte Delta-Format ['sortOrder' => [id, id, …]] (reine Integer-IDs, kein entries-Key) verwendet; Craft führt damit ausschließlich ein UPDATE auf elements_owners.sortOrder aus — kein Anlegen, kein Löschen. Zusätzliches Sicherheitsnetz: hat nicht jeder vorhandene Block eine verwertbare ID, bricht der Aufruf mit reorder_incomplete_block_ids ab, statt mit unvollständiger Liste zu speichern. Betraf applyBlockReorder() und rollbackEntrySectionOrder().
  • Kritisch — deaktivierte Blöcke waren für das Plugin unsichtbar. Matrix::createEntryQuery() baut den Feldwert als normale EntryQuery mit ->fieldId()->siteId(), aber ohne ->status(null) — der Default-Statusfilter enabled blendet also genau die Blöcke aus, die per op:"disable" ausgeblendet wurden. Folgen im Entwurf: op:"enable" konnte einen zuvor deaktivierten Block nie wiederfinden (block_not_found), das neue enabled-Feld im GET-Read wäre strukturell immer true gewesen, und — am gefährlichsten — reorder hätte deaktivierte Blöcke nicht in die sortOrder aufgenommen, woraufhin Craft sie beim Speichern gelöscht hätte. Alle Block-Queries laufen jetzt über den neuen Helper blockQueryAll(), der ->status(null) setzt, bevor gelesen wird (Query wird bewusst mutiert statt geklont: Craft definiert auf ElementQuery/Query kein __clone, und NestedElementManager::getValue() erweitert denselben memoisierten Feldwert intern um exakt dieselben Kriterien). Betraf resolveMatrixBlocks(), resolveNeoBlocks(), die Neo-getChildren()-Rekursion, applyEntrySectionPatches(), applyBlockReorder(), rollbackEntrySection(), rollbackEntrySectionEnabled() und rollbackEntrySectionOrder().

Changed

  • GET /deon-ai/entry-sections/<id> listet als Folge des Status-Fixes jetzt auch deaktivierte Blöcke auf (erkennbar an enabled: false). Das ist beabsichtigt und Voraussetzung dafür, dass op:"enable" und reorder überhaupt korrekt arbeiten können — Aufrufer, die nur die sichtbaren Sektionen brauchen, filtern auf enabled === true.

Added

  • POST /deon-ai/entry-sections/<id> — neue Patch-Operation op je Eintrag in patches[] (Default weiterhin "set", unverändertes Text-Patch-Verhalten aus v0.18.0 — voll rückwärtskompatibel). Neu: op: "disable"/"enable" — schaltet einen einzelnen Matrix-/Neo-Block sichtbar aus bzw. wieder ein ({ field_handle, block_id?, block_index?, op: "disable" }, kein field/value nötig). Bewusst KEIN Hard-Delete: der Block bleibt als Element erhalten, nur enabled=false — reversibel über op:"enable" oder /deon-ai/rollback, konsistent mit dem Rest des Plugins, das nie destruktiv löscht. Schließt den "Sektion aus der kopierten Seite rausnehmen"-Teil der Copy-&-Rebuild-Vision.
  • POST /deon-ai/entry-sections/<id> — neuer optionaler Body-Key reorder: { "field_handle": "heroSection", "block_ids": [123, 456, 789] } bringt die Blöcke eines Matrix-Feldes in eine neu vorgegebene Reihenfolge (nicht erwähnte Blöcke werden ans Ende angehängt statt zu verschwinden). Kann zusammen mit patches[] oder allein aufgerufen werden (patches[] ist dafür nicht mehr Pflicht). Nutzt dasselbe non-destruktive "Feldwert neu setzen"-Prinzip wie duplicateElement() in /build-template-page — keine Elemente werden neu erstellt oder gelöscht, nur umsortiert. Aktuell nur für Matrix-Felder (Neo liefert reorder_neo_not_supported — Neo verschachtelt Kind-Blöcke strukturell anders, dafür bräuchte es ein eigenes, hier noch nicht gebautes Vorgehen). Schließt den "Sektionen umstrukturieren"-Teil der Vision.
  • GET /deon-ai/entry-sections/<id> liefert pro Block jetzt zusätzlich enabled (bool) — Voraussetzung dafür, dass ein Aufrufer vor einem disable/enable-Patch den aktuellen Stand kennt.
  • Beide neuen Operationen sind einzeln über /deon-ai/rollback rückgängig machbar (rollbackEntrySectionEnabled(), rollbackEntrySectionOrder() — eigene Rollback-Pfade statt Wiederverwendung von rollbackEntrySection(), da dessen Ziel-Key-Format zwingend ein echtes Custom-Field im 4. Segment erwartet).
  • /deon-ai/ping-capabilities um entry_section_write, entry_section_visibility, entry_section_reorder, template_page_build ergänzt (fehlte im ursprünglichen Handoff für dieses Feature sowie rückwirkend für den entry-sections-Write aus v0.18.0).

Hinweis — noch nicht live smoke-getestet

Das Reorder-Wertformat ['sortOrder' => [ids]] ist gegen den echten Quelltext von craftcms/cms 5.10.10 verifiziert (Matrix::_createEntriesFromSerializedData() + NestedElementManager::saveNestedElements()), aber wie der sectionId/typeId-Override in /build-template-page in dieser Session nur mit php -l syntaxgeprüft, nicht gegen eine laufende Craft-Instanz ausgeführt. disable/enable ist risikoärmer (nur $block->enabled + saveElement(), dieselbe Mechanik, die das Plugin an vielen Stellen bereits produktiv nutzt), aber ebenfalls ungetestet gegen den echten Content dieser Site. Vor produktivem Einsatz einmal manuell smoke-testen (siehe Handoff-README) — reorder sinnvollerweise zuerst an der disabled Vorlagen-Section, nicht an einer Live-Seite.

Version 0.19.0

July 24, 2026

Added

  • POST /deon-ai/build-template-page — baut vollautomatisch EINE Vorlagenseite mit den echten nativen Design-Blöcken der Site (demselben Matrix-/Neo-Feld, das z. B. die Startseite nutzt). Body: { source_entry_id, field_handle?, title?, force? }. Hintergrund: /duplicate-page klont eine Seite bereits strukturell perfekt im echten Theme, aber auf frisch angebundenen Sites gibt es oft noch KEINEN Entry mit echten Blöcken außer der Startseite — und die ist als Single-Section keine sichere Massen-Kopiervorlage (URI-Format hat i. d. R. kein {slug}, mehrere Entries würden kollidieren). Dieser Endpoint klont die gewünschten Blöcke stattdessen in eine dedizierte, automatisch angelegte Section (deonTemplates, Channel-Typ, eigenes {slug}-URI-Format, immer disabled — landet nie live) und macht sie so zur sicheren Kopiervorlage für künftige Standort-/Leistungsseiten. Nutzt für den eigentlichen Klon dieselbe native, nicht-destruktive duplicateElement()-API wie /duplicate-page (kein handgebautes Matrix-/Neo-Block-Array) — die Quelle (z. B. die Startseite) wird dabei nur gelesen, nie verändert, auch wenn der Section-Wechsel fehlschlägt. Gate: page_create (dieselbe Freigabe wie /duplicate-page//page). Idempotent: liefert ohne force=true bei wiederholten Aufrufen den bereits vorhandenen Vorlagen-Entry zurück statt Duplikate anzuhäufen.

Version 0.18.0

July 24, 2026

Added

  • POST /deon-ai/entry-sections/<id> — Text-Werte in nativen Matrix-/Neo-Blöcken patchen (dieselbe Route wie der bestehende GET-Read, per HTTP-Methode verzweigt). Body: { patches: [{ field_handle, block_id?, block_index?, field, value }] }. Schließt die im "Kopieren & Umbauen"-Konzept beschriebene Lücke: /duplicate-page klont eine Seite bereits strukturell perfekt (native duplicateElement(), inkl. aller Matrix-/Neo-Blöcke im Original-Theme-Template), aber bisher gab es keinen Weg, den Text in diesen geklonten Blöcken für die neue Seite (z. B. eine andere Stadt/Leistung) zu ersetzen — nur das eine Legacy-deonBody-HTML-Feld war beschreibbar. Schreibt bewusst nur "textartige" Felder (PlainText/CKEditor/Redactor sowie jedes Feld mit einfachem String-Wert — bewusst is_string() statt is_scalar(), sonst wären auch Number-/Lightswitch-/Date-Felder fälschlich als "textartig" durchgerutscht) — Assets/Relationen/verschachtelte Matrix-Neo-Felder werden nie angefasst. Gate: content_edit (dieselbe Freigabe wie /deon-ai/faq). Jeder Patch wird einzeln im Change-Log erfasst und ist über /deon-ai/rollback einzeln rückgängig machbar (rollbackEntrySection(), Ziel-Typ entry-section).
  • GET /deon-ai/entry-sections/<id> liefert pro Block jetzt zusätzlich block_id (stabile Element-ID, nicht nur der positionsabhängige block_index) — Voraussetzung für zuverlässiges Patchen nach einem separaten Read. Bei genesteten Neo-Blöcken block_id statt block_index verwenden (block_index zählt beim GET pro Verschachtelungsebene neu bei 0, beim POST-Fallback indiziert er die flache Blockliste — beide Nummerierungen sind bei genesteten Neo-Strukturen nicht deckungsgleich; für Matrix und flache Neo-Strukturen kein Unterschied).

Version 0.17.0

July 23, 2026

Fixed

  • /deon-ai/faq konnte generierten FAQ-Text stillschweigend korrumpieren. Der FAQ-HTML-Block wurde als Replacement-String an preg_replace() übergeben — PHP interpretiert darin $1/\1 etc. als Backreferences. Enthielt der generierte Text z. B. einen Preis ("Kosten: $29/Monat"), wurde "$29" durch einen Leerstring ersetzt. Jetzt über preg_replace_callback() eingefügt, keine Backreference-Interpretation mehr.
  • /deon-ai/seoenabled wurde bei jedem Teil-Update ungewollt zurückgesetzt. Wurde ein Override zuvor bewusst deaktiviert (enabled: false), reaktivierte ihn jedes spätere Update von z. B. nur title stillschweigend wieder, weil enabled ohne explizites Feld im Request immer auf true statt auf den bestehenden Wert zurückfiel.
  • /deon-ai/entry und /deon-ai/page legten bei ungültiger entry_id still ein Duplikat an. Eine nicht mehr existierende entry_id (gelöschter Entry, Worker-Retry mit veralteter ID) führte zum kommentarlosen Anlegen eines komplett neuen Entries statt eines 404. Beide Endpoints geben jetzt 404 entry_not_found zurück, wenn eine explizit übergebene entry_id nicht auflösbar ist.
  • actionRollbackCreateRestorePoint/-Restore — der Entry-slug wurde beim Wiederherstellen eines Sicherungspunkts nie zurückgeschrieben, obwohl der Snapshot ihn sicherte (im Gegensatz zum korrekten Einzel-Rollback rollbackEntry()). Ein Restore-Point-Restore meldete Erfolg, stellte die URL aber nicht wieder her.
  • setup-blog konnte auf Multi-Site-Sections funktionierende Custom-Templates überschreiben. Hatte eine Section mindestens eine Site mit leerem Template und gleichzeitig eine andere Site mit einem funktionierenden, individuellen Custom-Template, wurden ohne fix_template-Flag trotzdem alle Site-Templates überschrieben — der eigene Docblock-Anspruch ("überschreibt nie stillschweigend eine funktionierende Custom-Konfiguration") war für diesen Fall nicht erfüllt. Jetzt werden ohne force nur Sites mit leerem Template befüllt.
  • createDeonSection() konnte bei einem Section-Speicherfehler eine Datenleiche hinterlassen. saveEntryType() und saveSection() liefen ohne Transaktion; schlug saveSection() fehl, blieb der bereits gespeicherte EntryType mit dem gewählten Handle (deonBlog/deonPages) in der DB zurück — ein erneuter setup-blog-Lauf scheiterte dann dauerhaft an der Handle-Kollision statt sich selbst zu heilen. Beide Saves laufen jetzt in einer gemeinsamen DB-Transaktion.

Alle sechs Funde stammen aus demselben Code-Audit wie v0.16.0 (Daten-Integrität-/Robustheit-Teil der zwölf Funde).

Version 0.16.0

July 23, 2026

Security

  • /deon-ai/publish-lp — Slug ohne Zeichen-Whitelist konnte Kern-Routen kapern. Der Slug wurde in Plugin::init() 1:1 als Array-Key einer Yii-UrlManager-Rule verwendet, deren Key-Syntax Platzhalter wie <id:\d+> unterstützt — ungefiltert also nicht nur eine Kollisionsgefahr, sondern potenzielles Routing-Pattern-Injection. Da die LP-Routen in derselben $event->rules-Array-Instanz nach allen Kern-Routen registriert werden (PHP überschreibt Array-Keys bei Kollision), konnte ein Slug wie "deon-ai/self-update" die gleichnamige Kern-Route kapern. Slug jetzt strikt auf [a-z0-9-/] beschränkt, gegen die deon-ai/*-Reserved-Liste sowie gegen bestehende Entry-URIs geprüft.
  • /deon-ai/seo — Homepage-Schutz war wirkungslos. Die Klausel "Homepage nur mit explizitem allow_homepage-Flag" griff nur, wenn uri komplett fehlte — ein Request mit {"uri":"/"} (ohne das Flag) überschrieb die Homepage-SEO trotzdem. Jetzt greift der Schutz unabhängig vom Rohwert, sobald die normalisierte URI / ist.
  • /deon-ai/configure-ab und /deon-ai/configure-tracker hatten gar keine Berechtigungsprüfung. Jeder gültige API-Key konnte A/B-Testing/Tracking site-weit an-/abschalten, ohne dass es dafür einen Consent-Schalter gab. Neue Berechtigung allowAbTest (Default aus) gattert jetzt beide Endpoints; CP-Settings um den entsprechenden Schalter ergänzt.
  • /deon-ai/publish-winner konnte SEO-Overrides ohne seo_meta-Freigabe schreiben. Die Methode prüfte nur content_edit, ihr seo_meta-Change-Type schrieb aber direkt in dieselbe Tabelle, die /deon-ai/seo nur nach separater seo_meta-Freigabe beschreiben darf. Wird jetzt zusätzlich geprüft; fehlt die Freigabe, wird der seo_meta-Change übersprungen (errors: ["seo_meta: consent_required"]") statt den Rest des Requests abzubrechen.
  • fetchUrlBytes() (Bild-URL-Fetch für Featured Images/Asset-Uploads) war per DNS-Rebinding gegen den SSRF-Schutz umgehbar. Der Host wurde per gethostbyname() gegen private/reservierte IP-Ranges geprüft, der eigentliche Fetch löste den Hostnamen aber erneut auf — bei kurzer TTL könnte der DNS-Eintrag zwischen Check und Fetch auf eine interne IP wechseln (TOCTOU). Die geprüfte IP wird jetzt direkt für die Verbindung verwendet (Host-Header + ssl.peer_name halten virtuelles Hosting/TLS-Zertifikatsprüfung weiterhin korrekt).

Alle fünf Funde stammen aus einem vollständigen Code-Audit der Plugin-Funktionen gegen echten Craft-Core-/Neo-Plugin-Quellcode. Nicht übernommen: der Befund "Rollback-Restore-Endpoints ohne Berechtigungsprüfung" — das ist laut README (Zeile 65) bewusstes Design ("Rückgängig machen funktioniert unabhängig von diesen Schaltern immer"), kein Bug.

Version 0.15.0

July 23, 2026

Added

  • GET /deon-ai/site-inventory — Überblick über alle Sections der Site (handle, type, name, entry_count). Der Worker kann damit erstmals selbst herausfinden, welche Seitentypen eine Kunden-Site überhaupt hat (Leistungen, Branchen, Use-Cases, Über uns, …), statt Section-Handles zu raten oder fest zu verdrahten.
  • GET /deon-ai/entry-sections/<id> — strukturierte Block-/Sektionsinhalte eines beliebigen Entries: native Matrix-Felder (Craft 5: verschachtelte Entries, Craft 4: MatrixBlocks) sowie Neo-Felder (spicyweb/craft-neo, falls installiert), rekursiv aufgelöst, jeweils mit Block-Typ-Handle/-Name und den einzelnen Feldwerten (Text, Bilder inkl. Alt-Text, Entry-/Category-Relationen). Feature-detected über die tatsächliche Feld-Klasse — rät nicht anhand von Feld- oder Block-Namen, da jede Kunden-Site eigene Handles verwendet. Grund: /deon-ai/page-structure (bisher der einzige Content-Lese-Endpoint) kennt ausschließlich das eine deonBody-HTML-Feld; echte, handgebaute Seiten (Startseite, Leistungsseiten, Branchenseiten) bestehen aber i. d. R. aus benannten Block-Typen (Hero, FAQ, CTA, …), nicht aus einem HTML-Blob — für diese Struktur gab es bislang keinen Lese-Pfad. Hat der Entry-Type kein Matrix-/Neo-Feld (heutiger Fall bei Deons eigenen Blog-/Standortseiten), greift legacy_body_fallback (identisch zu /page-structure), damit nichts kaputt geht, was heute schon funktioniert. Tiefen-/Blockzahl-Limit (4 Ebenen / 200 Blöcke) gegen teure, tief verschachtelte Neo-Strukturen. Beide Endpoints read-only, nicht consent-gated (wie /pages, /media, /page-structure). /deon-ai/ping-capabilities um site_inventory, entry_sections ergänzt.

Version 0.14.0

July 23, 2026

Fixed

  • Kritisch: Featured Images wurden nie angehängt. /deon-ai/entry und /deon-ai/page hängen ein Bild nur an, wenn beide Settings gesetzt sind — featuredImageFieldHandle und assetVolumeHandle. setup-blog hat aber ausschließlich featuredImageFieldHandle in den Settings verdrahtet; assetVolumeHandle blieb auf jeder Site leer, außer jemand hat es manuell im CP eingetragen. hero_image_url/image_url vom Worker wurde dadurch bei jedem Blog-/Seiten-Publish stillschweigend ignoriert. setup-blog schreibt assetVolumeHandle jetzt mit — für neu angelegte Felder aus dem verwendeten Volume, für bereits bestehende deonFeaturedImage-Felder aus deren restrictedLocationSource (rückwirkende Heilung von Alt-Setups, ohne das Feld anzufassen).
  • /deon-ai/pings fields_ok.featured_image prüfte bisher nur featuredImageFieldHandle und meldete dadurch true, obwohl Bild-Uploads faktisch nie liefen. Da der Worker-Self-Heal genau dieses Flag als Gate nutzt (setup-blog wird nur erneut aufgerufen, wenn es false ist), blieb der Bug auf bereits betroffenen Sites für die automatische Reparatur dauerhaft unsichtbar. fields_ok.featured_image prüft jetzt zusätzlich assetVolumeHandle samt echter Volume-Existenz.

Version 0.13.0

July 21, 2026

Added

  • GET /deon-ai/media — Bild-Bibliothek der Site über alle Asset-Volumes (url, alt, title, filename, w, h), Pendant zu WPs wp-json/wp/v2/media?media_type=image. Der Worker matcht damit passende Bilder in generierte Sektionen — bislang lief der Aufruf für Craft ins Leere, weil kein Endpoint existierte. Read-only, nicht consent-gated. /deon-ai/ping-capabilities um media_inventory ergänzt.
  • setup-blog prüft und repariert jetzt zusätzlich, ob der Entry-Type der Blog-/Seiten-Section überhaupt ein Titel-Attribut tragen kann. Response neu: title_field/title_field_pages ("ok"|"missing"|"fixed"), Reparatur nur mit { "fix_template": true } (dieselbe Freigabe wie für die Template-Reparatur).
  • /deon-ai/entry und /deon-ai/page brechen jetzt mit 422 title_field_missing ab, statt eine Seite zu veröffentlichen, deren Titel Craft beim Speichern automatisch leert.

Fixed

  • Ursache von „Eintrag ohne Titel" im CP gefunden und behoben (nicht der im Handoff vermutete Ort): /entry und /page haben entry->title schon immer korrekt gesetzt. Der eigentliche Grund liegt in Craft selbst — craft\elements\Entry::updateTitle() läuft unumgehbar in jedem beforeSave() und überschreibt den gesetzten Titel automatisch mit leer, sobald der Entry-Type kein Titel-Feld (hasTitleField) und kein titleFormat hat. Betroffen sind ausschließlich extern (nicht über setup-blog) angelegte Sections — die vom Plugin selbst erzeugten (deonBlog/deonPages) hatten hasTitleField schon immer aktiv.

Version 0.12.0

July 21, 2026

Added

  • Plugin liefert jetzt zusätzlich zum Artikel-Template (v0.11.0) ein eigenes, self-contained Seiten-Template (templates/page.twig, adressierbar als deon-ai/page) für die per POST /deon-ai/page erzeugten Standort-/Leistungsseiten. Rendert entry.title immer als H1 (der Worker liefert bewusst keinen H1 im body_html) und gibt das vom Worker gelieferte, bereits gestylte HTML (FAQ-Akkordeon, CTA-Button etc.) unverändert |raw aus — kein RTE-Sanitizer, kein Theme-Dependency.
  • setup-blog setzt/repariert das Template jetzt auch für die pages-Section — gleiches idempotentes Verhalten wie beim Blog: neue Sections bekommen deon-ai/page direkt, bestehende Sections mit leerem Template werden automatisch repariert, bestehende Sections mit gesetztem Template nur mit { "fix_template": true }. template_pages/previous_template_pages in der Response (additiv neben template/previous_template für Blog).

Fixed

  • Kritisch: setup-blog prüfte/reparierte bislang ausschließlich die eigenen Bootstrap-Sections deonBlog/deonPages — unabhängig davon, ob settings.blogSectionHandle/pagesSectionHandle (Standard "blog"/"pages") bereits auf eine andere, tatsächlich existierende Section zeigten. /deon-ai/entry und /deon-ai/page bespielen aber genau diese konfigurierten Handles, nicht die Bootstrap-Konstanten. Auf Installationen, deren Blog-/Seiten-Section nicht deonBlog/deonPages heißt (z. B. der Default "pages"), reparierte setup-blog dadurch eine ungenutzte Section, während die tatsächlich ausgelieferte Seite weiterhin ohne Template/Styling blieb. setup-blog löst den Ziel-Handle jetzt zuerst aus den aktuellen Settings auf (nur Fallback auf deonBlog/deonPages, wenn der konfigurierte Handle leer oder ungültig ist) und wirkt dadurch immer auf die Section, die auch wirklich ausliefert.

Version 0.11.0

July 20, 2026

Added

  • Plugin liefert jetzt ein eigenes, self-contained Artikel-Template (templates/entry.twig, adressierbar als deon-ai/entry über einen registrierten Site-Template-Root) — kein Theme-Dependency, kein {% extends %}, eigenes Minimal-CSS, keine Deon-Werbung. Behebt den Pilot-Fall, dass ein Custom-Theme-Template Entry-Variablen gar nicht ausgibt und Blog-Artikel dadurch leer bleiben (Titel/Body/Bild liegen korrekt im Entry, nur das Section-Template rendert nichts davon).
  • setup-blog setzt dieses Template jetzt direkt auf neu angelegte Sections (deonBlog/deonPages), statt sie leer zu lassen und nur template_missing zu melden.
  • setup-blog repariert außerdem bestehende Blog-/Seiten-Sections mit leerem Template automatisch — und mit optionalem Body-Flag { "fix_template": true } auch Sections mit einem gesetzten, aber offensichtlich kaputten Template (der Worker prüft das serverseitig, bevor er das Flag sendet). Eine funktionierende Custom-Konfiguration wird nie stillschweigend überschrieben. Response neu: template/previous_template (Blog-Section, primärer Contract-Wert) sowie additiv template_pages/previous_template_pages. Jede Template-Änderung ist rollback-fähig (neuer Change-Log-Typ section_template).

Fixed

  • Kritisch, seit v0.1.0: Sämtliche Section-Operationen (getSectionByHandle, saveSection, saveEntryType, getAllSections) riefen Craft::$app->getEntries() auf. Das funktioniert nur auf Craft 5 — dort wurden Section-Methoden in den Entries-Service gemergt. Auf Craft 4 existieren sie ausschließlich im separaten craft\services\Sections (Craft::$app->getSections()); craft\services\Entries kennt dort nur Entry-Element-Methoden. Jeder schreibende Endpoint, der Sections anfasst (/entry, /page, /entries, /setup-blog, /duplicate-page, /pages, /nav-Structure-Fallback u. a.), war auf Craft 4 dadurch ein Fatal Error — trotz composer.json-Anspruch craftcms/cms: ^4.0.0|^5.0.0. Neuer versionsübergreifender Helper sectionsService() löst den richtigen Service anhand des registrierten Component-Namens auf, ohne version_compare.

Version 0.10.0

July 19, 2026

Added

  • POST /deon-ai/audit-fix — Content-Write-Fixes vom Deon-AI-Worker (Contract = WP /audit-fix, beschränkt auf die zwei für Craft nötigen Actions; alle anderen Fix-Actions laufen bereits über /deon-ai/seo bzw. /deon-ai/faq):
    • replace_content — kompletten Body-HTML ersetzen (Freshness-Refresh). Bewusst ohne HTML-Stripping — authentifizierter Plugin-Kontext, der CKEditor-/Redactor-Purifier greift beim Rendern.
    • append_html_box — HTML-Box idempotent anhängen (interne Verlinkung, Pillar-Backrefs): existiert bereits ein <aside class="…{box_marker}…">-Block (Default-Marker deon-cluster-ref, konfigurierbar über payload.box_marker), wird er ersetzt statt dupliziert — Marker-Sanitize und Regex identisch zum WP-Plugin.
    • Beide Actions mit Rollback-Snapshot (Pflichtfeld rollback_id in der Response), Entry-Auflösung per page_url (URI, Fallback Slug), Berechtigung allowContentEdit.
    • Response liefert meta_keys_applied (exakter WP-Key) und applied als Alias, inkl. no_change-Erkennung wie im WP-Original.

Version 0.9.0

July 17, 2026

Added

Section-Tests + A/B-Varianten — der letzte fehlende Funktionsblock aus dem WordPress-Plugin, Craft-nativ adaptiert (WP arbeitet auf Gutenberg-/Elementor-/Fusion-Strukturen; in Craft sind die „Sections" die Top-Level-Elemente des Body-HTML, builder html):

  • POST /deon-ai/section-test/create — Variante als geklonter, deaktivierter Entry (duplicateElement), Section-Änderungen (insert/remove/move/replace, Selector = Index oder tag[n]) per DOM-Engine auf den Body angewandt. Neue Tabelle deonai_section_tests.
  • Server-seitiger 50/50-Split beim Ausspielen: Cookie aideon_st_<id> (Name identisch zu WP, damit die SDK-Conversion-Attribution gleich funktioniert), Bot-Ausschluss, Cache-Control: no-store + Vary: Cookie, Besucher-Zähler. Variante B ersetzt den Original-Body im gerenderten HTML.
  • GET /deon-ai/section-test/list/<id>, POST /deon-ai/section-test/preview (anwenden ohne speichern), POST /deon-ai/section-test/stop — Winner B wird mit Rollback-Snapshot ins Original gemerged, die Variante wandert in den Craft-Papierkorb (weiches Löschen statt WP-Force-Delete).
  • POST /deon-ai/publish-winner — Änderungs-Liste anwenden: seo_meta → SEO-Override, content_replace → Body-Austausch, html_section → Section-Replace (Craft-Pendant zu elementor_section, das einen klaren Fehler meldet). Immer mit Rollback-Snapshot.
  • POST /deon-ai/ab-variant/create, GET /deon-ai/ab-variant/list/<id>, POST /deon-ai/ab-variant/stop — Selector-basierte A/B-Varianten (alle WP-Modi: text/html/attr/link/style/form), neue Tabelle deonai_ab_variants. Ausspielung über ein 1:1 portiertes Frontend-Snippet (Cookie aideon_ab_assign, ?aideon_force=-Preview mit Banner, sendBeacon-Impression-Tracking).
  • POST /deon-ai/configure-ab + GET /deon-ai/ab-status, POST /deon-ai/configure-tracker + GET /deon-ai/tracker-status — Remote-Konfiguration (WP-Shapes); tracker_enabled=false schaltet zusätzlich zur CP-Einstellung die SDK-Injection ab.
  • /deon-ai/ping-capabilities um content_replace, section_test, ab_variant_split, ab_script_inject, tracker_inject erweitert.

Version 0.8.0

July 17, 2026

Added

Seiten-Anbindung — Contract-Parität zum WordPress-Plugin aideon-connect (v3.67.0), damit Deon AI Craft-Seiten lesen, im Original-Design klonen und texturieren kann. Neue Endpoints (Response-Shapes bewusst identisch zu WP, damit der Worker 1:1 durchreichen kann):

  • GET /deon-ai/match-url?url= — Entry per URL finden (inkl. Slug-Fallback)
  • GET /deon-ai/pages — Entries aller Sections, nach Änderungsdatum (ergänzt /entries, das nur eine Section pro Aufruf listet)
  • GET /deon-ai/page-structure/<id> — kompletter Seiteninhalt: Titel, Slug, Body-HTML, SEO-Override, plus walkbare Text-Blöcke (content_blocks mit pc-N-IDs: h1–h3 = title, p = editor — identisch zum builder-agnostischen WP-Contract)
  • POST /deon-ai/set-widget-texts — gezielte Text-Sets per pc-N auf den Entry-Body (SEO-Texturierung geklonter Standortseiten), mit Rollback-Protokoll
  • POST /deon-ai/duplicate-page — 1:1-Seiten-Klon mit find/replace-Textaustausch, h1_override, SEO-Metas, idempotent per page_id (der Standortseiten-Pfad im Original-Design)
  • GET /deon-ai/render-preview — gerendertes Frontend-HTML für die Dashboard-Preview; HMAC-Preview-Token (gleiches Format wie WP: 60s TTL) + Same-Origin-Guard
  • POST /deon-ai/publish-lp — Full-Page-Landingpage aus Roh-HTML (inkl. <style>/<script>), neue Tabelle deonai_landing_pages, ausgeliefert über eigene Route pro Slug, idempotent per page_id/Slug
  • GET /deon-ai/theme-tokens — Design-Tokens (Farben, Fonts, Radius, Palette). Craft hat kein theme.json, daher CSS-Extraktion aus der gerenderten Startseite inkl. einstufiger var(--x)-Auflösung (source: "css_extract")
  • POST /deon-ai/site-schema — Site-weites JSON-LD (Organization/LocalBusiness), ausgespielt im <head> aller Seiten
  • GET /deon-ai/sitemap-discover — Sitemap-URL-Kandidaten
  • GET|POST /deon-ai/footer-links — Plugin-eigener Footer-Block („Servicegebiete") vor </body>, Markup identisch zum WP-Pendant
  • /deon-ai/ping liefert jetzt eine capabilities-Liste (Namensschema wie WP /capabilities) für einheitliches Feature-Gating im Worker

Changed

  • /deon-ai/hygiene-list liefert nur noch die Typen robots/llms (die Tabelle speichert jetzt zusätzlich site_schema/footer_links)

Version 0.7.0

July 17, 2026

Added

  • POST /deon-ai/setup-blog — Blog-/Seiten-Bootstrap für Pilotinnen ohne bestehendes Schema: legt bei Bedarf ein Body-Feld (deonBody, CKEditor falls installiert → Redactor falls installiert → PlainText multiline), ein Featured-Image-Feld (deonFeaturedImage, auf das erste vorhandene Volume beschränkt, wird übersprungen statt ein Volume zu erfinden) sowie die Sections deonBlog (Channel, blog/{slug}) und deonPages (Structure, {slug}) an — jeweils idempotent, bestehende Handles werden nie überschrieben. Verdrahtet leere/ungültige Plugin-Settings automatisch mit den neuen Handles. Meldet fehlende Section-Templates statt sie selbst anzulegen (liefert stattdessen ein Beispiel-Template im Response mit).
  • /deon-ai/entry und /deon-ai/page unterstützen jetzt beide image_url/asset_id fürs Featured Image (bisher nur /entry) — fail-soft, ein Bildfehler blockiert den Entry/die Seite nie.
  • /deon-ai/ping liefert zusätzlich fields_ok: { body, featured_image } (echte Feld-Existenz, nicht nur ob das Setting gesetzt ist) sowie nav: { verbb, editable }.
  • POST /deon-ai/nav (DEO-80) — verlinkt eine generierte Seite in Hauptnavigation oder Footer. Neuer Consent-Schalter allowNavEdit (Standard aus). Strategie-Kaskade, da Craft keine Kern-Navigation hat: (1) verbb/navigation, falls installiert — Nav per Handle/Name-Heuristik wählen, Node anlegen, Dedupe über URL/verlinkte Entry; (2) Structure-Section mit Handle nav/menu und einem linkUrl/url-Feld, falls vorhanden; (3) sonst 422 nav_not_automatable mit Hinweis zur manuellen Verlinkung bzw. Tipp auf das verbb-Plugin.

Version 0.6.0

July 16, 2026

Added

  • Berechtigungen: 6 neue Schalter im Control Panel („Berechtigungen — was darf Deon AI ändern?"), mit denen der Kunde selbst entscheidet, was Deon AI ändern darf — allowSeoMeta (Standard an), allowContentEdit, allowPageCreate, allowFiles, allowAssets (Standard jeweils aus), allowSelfUpdate (Standard an, siehe Remote-Self-Update)
  • Schreibende Endpoints prüfen jetzt die passende Berechtigung und liefern 403 { ok: false, error: "consent_required", permission: "…" }, solange sie nicht freigegeben ist: /deon-ai/seoseo_meta, /deon-ai/faqcontent_edit, /deon-ai/entry + /deon-ai/pagepage_create, /deon-ai/files + /deon-ai/hygienefiles, /deon-ai/assetassets, /deon-ai/self-updateself_update. Lese-Endpoints (ping, seo-list, entries, hygiene-list, rollback/*) bleiben ungegated — Rückgängig machen funktioniert immer.
  • /deon-ai/ping liefert jetzt ein permissions-Objekt mit dem aktuellen Freigabe-Stand aller sechs Kategorien, damit Deon AI nicht freigegebene 1-Klick-Fixes im Dashboard ausgrauen kann. Zusätzlich plugin_version/craft_version/php_version (Aliase der bestehenden Felder), ein Fähigkeits-Flag self_update (kann dieser Server technisch überhaupt selbst updaten — proc_open, Speicher, composer.phar) und sections_ok (prüft, ob die konfigurierten Section-Handles für Blog/Seiten tatsächlich existieren).
  • Remote-Self-Update: POST /deon-ai/self-update ({ version }) hebt das Plugin per Composer auf eine konkrete Zielversion an (nur das eigene Paket, nie craft update all), danach POST /deon-ai/up in einem neuen Request, um die Plugin-Migrationen auszuführen — zwei Phasen, weil nach dem Composer-Swap im selben Request noch der alte Klassen-Code geladen ist. Preflight prüft proc_open, memory_limit und composer.phar, bevor überhaupt etwas angefasst wird; schlägt er fehl, bleibt die Installation unverändert (422 self_update_unavailable). Fail-soft DB-Backup vor dem Swap. Konsolen-Fallback php craft deon-ai-connect/update <version> für Hosting, auf dem Composer im Web-Request an exec-/Speicher-Limits scheitert.

Version 0.5.0

July 16, 2026

Added

  • /deon-ai/files — robots.txt/llms.txt direkt im Webroot lesen/schreiben (strikte Dateinamen-Whitelist, Backup vor jedem Schreiben)
  • /deon-ai/faq — FAQ-Block sichtbar in den Entry-Body einbauen, idempotent über einen data-deon-faq-Marker (ersetzt statt zu duplizieren)
  • /deon-ai/page — native Seiten anlegen (Standortseiten, KI-Faktenseite), eigene Section-Auflösung über neues Setting pagesSectionHandle, geht standardmäßig als Entwurf raus
  • Neue Tabelle deonai_content_backups — Vorher-Inhalt wird vor jeder /files- oder /faq-Änderung fail-soft gesichert

Fixed

  • Install.php enthielt nur die ursprüngliche deonai_seo_overrides-Tabelle. Da Craft bei einer Neuinstallation ausschließlich Install.php ausführt und alle zu dem Zeitpunkt bereits vorhandenen nummerierten Migrationen ungeprüft als "erledigt" markiert (ohne sie laufen zu lassen), fehlten frischen Installationen bisher deonai_seo_hygiene und deonai_change_log komplett. Install.php enthält jetzt den vollständigen Tabellenstand.

Version 0.4.2

July 15, 2026

Changed

  • Plugin-Store-Vorbereitung: composer.json ohne version-Feld (Versionen kommen aus Git-Tags), support.source ergänzt; LICENSE.mdLICENSE.txt; .github/workflows/create-release.yml für automatische GitHub-Releases bei neuen, vom Craft Plugin Store erkannten Tags. Keine funktionale Änderung am Plugin selbst.

Version 0.4.1

July 15, 2026

Fixed

  • Kritisch: Bootstrap-Save (v0.4.0) hat beim Speichern nur siteId/sdkKey/verificationUuid an savePluginSettings() übergeben — Craft merged dabei nicht mit den bestehenden Settings, wodurch apiKey (und alle anderen Felder) auf ihre Defaults zurückgesetzt wurden. Der Connection-Key erschien danach leer, obwohl die Verbindung erfolgreich war. Betroffene Installationen (Key wurde geleert) müssen den Connection-Key einmal neu eintragen; ab diesem Fix bleibt er erhalten.

Version 0.4.0

July 15, 2026

Changed

  • Ein-Key-Onboarding: Setup verlangt jetzt nur noch den "Deon AI Connection-Key" statt vier separaten Feldern (API-Key, Site-ID, SDK-Key, Verifizierungs-UUID). Beim Speichern holt das Plugin Site-ID, SDK-Key und Verifizierungs-UUID automatisch per Bootstrap-Call (GET https://audit.deon-ai.de/api/plugin/craft/bootstrap, authentifiziert über denselben Key) — analog zum WordPress-Plugin-Flow. Fail-soft: schlägt der Bootstrap fehl, bleiben gespeicherte Settings unverändert, nur eine CP-Meldung informiert.

Version 0.3.0

July 15, 2026

Added

  • Änderungsprotokoll mit Rollback: jede Deon-AI-Änderung (SEO-Override, Entry, robots.txt/llms.txt) speichert automatisch den Vorher-Zustand — kein separater Backup-Schritt, funktioniert auf jedem Hosting (reines SQL, kein shell_exec/mysqldump nötig)
  • /deon-ai/rollback/list, /rollback/<rb_id>, /rollback/<rb_id>/preview, /rollback/<rb_id>/restore — folgt derselben Proxy-Konvention wie das WordPress-/TYPO3-Plugin, erscheint damit im bestehenden "Änderungs-Journal"-Tab des Dashboards statt eines eigenen, unverbundenen Endpoints
  • /deon-ai/rollback/restore-point — kompletter Sicherungspunkt (Snapshot aller SEO-Overrides, robots.txt/llms.txt, Entries) als reines SQL-Snapshot
  • Konflikt-Erkennung: restore bricht ab (HTTP 409), wenn der Live-Zustand seit der Deon-AI-Änderung manuell verändert wurde — überschreibbar mit force: true
  • Alle schreibenden Endpoints (/deon-ai/seo, /deon-ai/entry, /deon-ai/hygiene) akzeptieren optional note und geben rollback_id in der Antwort zurück

Version 0.2.0

July 14, 2026

Added

  • robots.txt/llms.txt-Verwaltung am Origin: neues Setting manageRobotsLlms, Endpoints /deon-ai/hygiene (setzen) und /deon-ai/hygiene-list (auslesen), ausgeliefert über /robots.txt und /llms.txt
  • /deon-ai/entries — bestehende Entries einer Section auflisten (Duplikat-Check vor dem Anlegen)
  • /deon-ai/asset — Bild-Upload (URL oder Base64) für Featured Images, Settings assetVolumeHandle + featuredImageFieldHandle
  • /deon-ai/entry unterstützt jetzt section/body_field-Override sowie image_url/asset_id für Featured Images — Multi-Section-Publishing ohne separate Plugin-Installation

Changed

  • Feature-Parität zum WordPress-Plugin (AideonConnect) angenähert: SEO-Hygiene (robots/llms) und erweiterte Content-API waren zuvor WP/TYPO3-exklusiv

Version 0.1.1

July 10, 2026

Fixed

  • Falsche Repo-URLs in composer.json (support.issues, extra.changelogUrl) korrigiert — der 0.1.0-Tag zeigte noch auf github.com/baestmarketing/craft-connect statt baestmarketing-dot/craft-connect

Version 0.1.0

July 2, 2026

Added

  • SEO-Overrides (Title, Meta-Description, Canonical, Schema.org) — serverseitig ins Frontend-HTML gepatcht, sichtbar für alle Crawler inkl. KI-Bots
  • Automatische SDK- und Verifizierungs-Tag-Injection (kein Template-Edit nötig)
  • REST-Endpoints für Deon AI: /deon-ai/ping, /deon-ai/seo, /deon-ai/seo-list, /deon-ai/entry
  • Blog-Publishing in konfigurierbare Section (Entwurf/live)
  • Settings-Seite im Control Panel (Env-Var-Support)