Version 0.21.0
Schließt die letzte offene Lücke der Copy-&-Rebuild-Vision: Die Startseite war bis hierher als Kopiervorlage strukturell unerreichbar.
/duplicate-pageklonte immer in die Section der Quelle zurück, und die Startseite liegt in einersingle-Section, die per Definition genau einen Entry fasst — ein Klon dorthin hätte die bestehende Seite verdrängt. Der Worker musstesingle-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-pagelegte seine Vorlagen-Section zudem mithasUrls: trueundtemplate: nullan — Craft validierttemplatenie, die Section speichert also klaglos, und jede daraus erzeugte Seite lief beim Rendern in einen 404.
Fixed
- Kritisch —
/build-template-pagelegte eine dauerhaft nicht renderbare Section an.Section_SiteSettingswurde mithasUrls: true,uriFormat: 'deon-vorlagen/{slug}'undtemplate: nullerzeugt. Gegen den Quelltext voncraftcms/cms5.10.10 geprüft verlangtSection_SiteSettings::defineRules()beihasUrlsausschließlichuriFormat—templatewird 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 perView::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
deonTemplatesvon v0.19.0 oder v0.20.x bereits im kaputten Zustand angelegt, repariert/build-template-pagesie beim nächsten Aufruf automatisch (neues Response-Feldsection_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-Keystarget_section_handleundtarget_entry_type_handle. Damit landet der Klon in einer anderen Section als die Quelle;sectionId/typeIdgehen als$newAttributesdirekt inElements::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) undtarget_block_field_missing(409, inkl.source_block_fields/target_block_fields— der Klon wäre sonst ohne Sektionen). Ohne explizitentarget_entry_type_handlewird der erste Entry-Type der Ziel-Section gewählt, der ein Block-Feld mit der Quelle teilt.POST /deon-ai/duplicate-pageliefert zusätzlichsection_handle,entry_type_handleundcloned_into_section(bool). Alle bestehenden Felder inkl.successbleiben unverändert; der Worker erkennt die neue Fähigkeit antypeof body.cloned_into_section === "boolean".GET /deon-ai/site-inventoryliefert pro Section jetzthas_urls,uri_format,template,template_exists,renderablesowieentry_types[{handle, name, has_title_field, block_field_handles[]}](dazuplugin_versionauf 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.renderableist 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_sectionundsite_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
Ersetzt den nie veröffentlichten internen Entwurf 0.20.0 vollständig. Ein Craft-Review gegen den echten Quelltext von
craftcms/cms5.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 —
reorderhätte alle Blöcke der Sektion löschen können. Der Entwurf übergabEntry::setFieldValue($fieldHandle, $orderedBlocks)ein Array instanziierterEntry-Objekte. Gegencraft\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üsseln0,1,2…landet, zu keiner echten Entry-ID passt, in den "neuer Block"-Zweig fällt (der eintypeverlangt) und percontinuefür jeden Block verworfen wird. Übrig bleibt ein leeres Set, dasNestedElementManager::saveNestedElements()andeleteOtherNestedElements()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, keinentries-Key) verwendet; Craft führt damit ausschließlich einUPDATEaufelements_owners.sortOrderaus — kein Anlegen, kein Löschen. Zusätzliches Sicherheitsnetz: hat nicht jeder vorhandene Block eine verwertbare ID, bricht der Aufruf mitreorder_incomplete_block_idsab, statt mit unvollständiger Liste zu speichern. BetrafapplyBlockReorder()undrollbackEntrySectionOrder(). - Kritisch — deaktivierte Blöcke waren für das Plugin unsichtbar.
Matrix::createEntryQuery()baut den Feldwert als normaleEntryQuerymit->fieldId()->siteId(), aber ohne->status(null)— der Default-Statusfilterenabledblendet also genau die Blöcke aus, die perop:"disable"ausgeblendet wurden. Folgen im Entwurf:op:"enable"konnte einen zuvor deaktivierten Block nie wiederfinden (block_not_found), das neueenabled-Feld im GET-Read wäre strukturell immertruegewesen, und — am gefährlichsten —reorderhätte deaktivierte Blöcke nicht in diesortOrderaufgenommen, woraufhin Craft sie beim Speichern gelöscht hätte. Alle Block-Queries laufen jetzt über den neuen HelperblockQueryAll(), der->status(null)setzt, bevor gelesen wird (Query wird bewusst mutiert statt geklont: Craft definiert aufElementQuery/Querykein__clone, undNestedElementManager::getValue()erweitert denselben memoisierten Feldwert intern um exakt dieselben Kriterien). BetrafresolveMatrixBlocks(),resolveNeoBlocks(), die Neo-getChildren()-Rekursion,applyEntrySectionPatches(),applyBlockReorder(),rollbackEntrySection(),rollbackEntrySectionEnabled()undrollbackEntrySectionOrder().
Changed
GET /deon-ai/entry-sections/<id>listet als Folge des Status-Fixes jetzt auch deaktivierte Blöcke auf (erkennbar anenabled: false). Das ist beabsichtigt und Voraussetzung dafür, dassop:"enable"undreorderüberhaupt korrekt arbeiten können — Aufrufer, die nur die sichtbaren Sektionen brauchen, filtern aufenabled === true.
Added
POST /deon-ai/entry-sections/<id>— neue Patch-Operationopje Eintrag inpatches[](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" }, keinfield/valuenötig). Bewusst KEIN Hard-Delete: der Block bleibt als Element erhalten, nurenabled=false— reversibel überop:"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-Keyreorder:{ "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 mitpatches[]oder allein aufgerufen werden (patches[] ist dafür nicht mehr Pflicht). Nutzt dasselbe non-destruktive "Feldwert neu setzen"-Prinzip wieduplicateElement()in/build-template-page— keine Elemente werden neu erstellt oder gelöscht, nur umsortiert. Aktuell nur für Matrix-Felder (Neo liefertreorder_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ätzlichenabled(bool) — Voraussetzung dafür, dass ein Aufrufer vor einemdisable/enable-Patch den aktuellen Stand kennt.- Beide neuen Operationen sind einzeln über
/deon-ai/rollbackrückgängig machbar (rollbackEntrySectionEnabled(),rollbackEntrySectionOrder()— eigene Rollback-Pfade statt Wiederverwendung vonrollbackEntrySection(), da dessen Ziel-Key-Format zwingend ein echtes Custom-Field im 4. Segment erwartet). /deon-ai/ping-capabilitiesumentry_section_write,entry_section_visibility,entry_section_reorder,template_page_buildergänzt (fehlte im ursprünglichen Handoff für dieses Feature sowie rückwirkend für denentry-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
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-pageklont 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, immerdisabled— 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-destruktiveduplicateElement()-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 ohneforce=truebei wiederholten Aufrufen den bereits vorhandenen Vorlagen-Entry zurück statt Duplikate anzuhäufen.
Version 0.18.0
Added
POST /deon-ai/entry-sections/<id>— Text-Werte in nativen Matrix-/Neo-Blöcken patchen (dieselbe Route wie der bestehendeGET-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-pageklont eine Seite bereits strukturell perfekt (nativeduplicateElement(), 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 — bewusstis_string()stattis_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/rollbackeinzeln rückgängig machbar (rollbackEntrySection(), Ziel-Typentry-section).GET /deon-ai/entry-sections/<id>liefert pro Block jetzt zusätzlichblock_id(stabile Element-ID, nicht nur der positionsabhängigeblock_index) — Voraussetzung für zuverlässiges Patchen nach einem separaten Read. Bei genesteten Neo-Blöckenblock_idstattblock_indexverwenden (block_indexzä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
Fixed
/deon-ai/faqkonnte generierten FAQ-Text stillschweigend korrumpieren. Der FAQ-HTML-Block wurde als Replacement-String anpreg_replace()übergeben — PHP interpretiert darin$1/\1etc. als Backreferences. Enthielt der generierte Text z. B. einen Preis ("Kosten: $29/Monat"), wurde"$29"durch einen Leerstring ersetzt. Jetzt überpreg_replace_callback()eingefügt, keine Backreference-Interpretation mehr./deon-ai/seo—enabledwurde bei jedem Teil-Update ungewollt zurückgesetzt. Wurde ein Override zuvor bewusst deaktiviert (enabled: false), reaktivierte ihn jedes spätere Update von z. B. nurtitlestillschweigend wieder, weilenabledohne explizites Feld im Request immer auftruestatt auf den bestehenden Wert zurückfiel./deon-ai/entryund/deon-ai/pagelegten bei ungültigerentry_idstill ein Duplikat an. Eine nicht mehr existierendeentry_id(gelöschter Entry, Worker-Retry mit veralteter ID) führte zum kommentarlosen Anlegen eines komplett neuen Entries statt eines404. Beide Endpoints geben jetzt404 entry_not_foundzurück, wenn eine explizit übergebeneentry_idnicht auflösbar ist.actionRollbackCreateRestorePoint/-Restore— der Entry-slugwurde beim Wiederherstellen eines Sicherungspunkts nie zurückgeschrieben, obwohl der Snapshot ihn sicherte (im Gegensatz zum korrekten Einzel-RollbackrollbackEntry()). Ein Restore-Point-Restore meldete Erfolg, stellte die URL aber nicht wieder her.setup-blogkonnte 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 ohnefix_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 ohneforcenur Sites mit leerem Template befüllt.createDeonSection()konnte bei einem Section-Speicherfehler eine Datenleiche hinterlassen.saveEntryType()undsaveSection()liefen ohne Transaktion; schlugsaveSection()fehl, blieb der bereits gespeicherte EntryType mit dem gewählten Handle (deonBlog/deonPages) in der DB zurück — ein erneutersetup-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
Security
/deon-ai/publish-lp— Slug ohne Zeichen-Whitelist konnte Kern-Routen kapern. Der Slug wurde inPlugin::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 diedeon-ai/*-Reserved-Liste sowie gegen bestehende Entry-URIs geprüft./deon-ai/seo— Homepage-Schutz war wirkungslos. Die Klausel "Homepage nur mit explizitemallow_homepage-Flag" griff nur, wennurikomplett 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-abund/deon-ai/configure-trackerhatten 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 BerechtigungallowAbTest(Default aus) gattert jetzt beide Endpoints; CP-Settings um den entsprechenden Schalter ergänzt./deon-ai/publish-winnerkonnte SEO-Overrides ohneseo_meta-Freigabe schreiben. Die Methode prüfte nurcontent_edit, ihrseo_meta-Change-Type schrieb aber direkt in dieselbe Tabelle, die/deon-ai/seonur nach separaterseo_meta-Freigabe beschreiben darf. Wird jetzt zusätzlich geprüft; fehlt die Freigabe, wird derseo_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 pergethostbyname()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_namehalten 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
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 einedeonBody-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), greiftlegacy_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-capabilitiesumsite_inventory,entry_sectionsergänzt.
Version 0.14.0
Fixed
- Kritisch: Featured Images wurden nie angehängt.
/deon-ai/entryund/deon-ai/pagehängen ein Bild nur an, wenn beide Settings gesetzt sind —featuredImageFieldHandleundassetVolumeHandle.setup-bloghat aber ausschließlichfeaturedImageFieldHandlein den Settings verdrahtet;assetVolumeHandleblieb auf jeder Site leer, außer jemand hat es manuell im CP eingetragen.hero_image_url/image_urlvom Worker wurde dadurch bei jedem Blog-/Seiten-Publish stillschweigend ignoriert.setup-blogschreibtassetVolumeHandlejetzt mit — für neu angelegte Felder aus dem verwendeten Volume, für bereits bestehendedeonFeaturedImage-Felder aus derenrestrictedLocationSource(rückwirkende Heilung von Alt-Setups, ohne das Feld anzufassen). /deon-ai/pingsfields_ok.featured_imageprüfte bisher nurfeaturedImageFieldHandleund meldete dadurchtrue, obwohl Bild-Uploads faktisch nie liefen. Da der Worker-Self-Heal genau dieses Flag als Gate nutzt (setup-blogwird nur erneut aufgerufen, wenn esfalseist), blieb der Bug auf bereits betroffenen Sites für die automatische Reparatur dauerhaft unsichtbar.fields_ok.featured_imageprüft jetzt zusätzlichassetVolumeHandlesamt echter Volume-Existenz.
Version 0.13.0
Added
GET /deon-ai/media— Bild-Bibliothek der Site über alle Asset-Volumes (url,alt,title,filename,w,h), Pendant zu WPswp-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-capabilitiesummedia_inventoryergänzt.setup-blogprü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/entryund/deon-ai/pagebrechen jetzt mit422 title_field_missingab, 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):
/entryund/pagehabenentry->titleschon immer korrekt gesetzt. Der eigentliche Grund liegt in Craft selbst —craft\elements\Entry::updateTitle()läuft unumgehbar in jedembeforeSave()und überschreibt den gesetzten Titel automatisch mit leer, sobald der Entry-Type kein Titel-Feld (hasTitleField) und keintitleFormathat. Betroffen sind ausschließlich extern (nicht übersetup-blog) angelegte Sections — die vom Plugin selbst erzeugten (deonBlog/deonPages) hattenhasTitleFieldschon immer aktiv.
Version 0.12.0
Added
- Plugin liefert jetzt zusätzlich zum Artikel-Template (v0.11.0) ein eigenes, self-contained Seiten-Template (
templates/page.twig, adressierbar alsdeon-ai/page) für die perPOST /deon-ai/pageerzeugten Standort-/Leistungsseiten. Rendertentry.titleimmer als H1 (der Worker liefert bewusst keinen H1 imbody_html) und gibt das vom Worker gelieferte, bereits gestylte HTML (FAQ-Akkordeon, CTA-Button etc.) unverändert|rawaus — kein RTE-Sanitizer, kein Theme-Dependency. setup-blogsetzt/repariert das Template jetzt auch für diepages-Section — gleiches idempotentes Verhalten wie beim Blog: neue Sections bekommendeon-ai/pagedirekt, bestehende Sections mit leerem Template werden automatisch repariert, bestehende Sections mit gesetztem Template nur mit{ "fix_template": true }.template_pages/previous_template_pagesin der Response (additiv nebentemplate/previous_templatefür Blog).
Fixed
- Kritisch:
setup-blogprüfte/reparierte bislang ausschließlich die eigenen Bootstrap-SectionsdeonBlog/deonPages— unabhängig davon, obsettings.blogSectionHandle/pagesSectionHandle(Standard"blog"/"pages") bereits auf eine andere, tatsächlich existierende Section zeigten./deon-ai/entryund/deon-ai/pagebespielen aber genau diese konfigurierten Handles, nicht die Bootstrap-Konstanten. Auf Installationen, deren Blog-/Seiten-Section nichtdeonBlog/deonPagesheißt (z. B. der Default"pages"), repariertesetup-blogdadurch eine ungenutzte Section, während die tatsächlich ausgelieferte Seite weiterhin ohne Template/Styling blieb.setup-bloglöst den Ziel-Handle jetzt zuerst aus den aktuellen Settings auf (nur Fallback aufdeonBlog/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
Added
- Plugin liefert jetzt ein eigenes, self-contained Artikel-Template (
templates/entry.twig, adressierbar alsdeon-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-blogsetzt dieses Template jetzt direkt auf neu angelegte Sections (deonBlog/deonPages), statt sie leer zu lassen und nurtemplate_missingzu melden.setup-blogrepariert 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 additivtemplate_pages/previous_template_pages. Jede Template-Änderung ist rollback-fähig (neuer Change-Log-Typsection_template).
Fixed
- Kritisch, seit v0.1.0: Sämtliche Section-Operationen (
getSectionByHandle,saveSection,saveEntryType,getAllSections) riefenCraft::$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 separatencraft\services\Sections(Craft::$app->getSections());craft\services\Entrieskennt 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 — trotzcomposer.json-Anspruchcraftcms/cms: ^4.0.0|^5.0.0. Neuer versionsübergreifender HelpersectionsService()löst den richtigen Service anhand des registrierten Component-Namens auf, ohneversion_compare.
Version 0.10.0
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/seobzw./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-Markerdeon-cluster-ref, konfigurierbar überpayload.box_marker), wird er ersetzt statt dupliziert — Marker-Sanitize und Regex identisch zum WP-Plugin.- Beide Actions mit Rollback-Snapshot (Pflichtfeld
rollback_idin der Response), Entry-Auflösung perpage_url(URI, Fallback Slug), BerechtigungallowContentEdit. - Response liefert
meta_keys_applied(exakter WP-Key) undappliedals Alias, inkl.no_change-Erkennung wie im WP-Original.
Version 0.9.0
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 odertag[n]) per DOM-Engine auf den Body angewandt. Neue Tabelledeonai_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 zuelementor_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 Tabelledeonai_ab_variants. Ausspielung über ein 1:1 portiertes Frontend-Snippet (Cookieaideon_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=falseschaltet zusätzlich zur CP-Einstellung die SDK-Injection ab./deon-ai/ping-capabilitiesumcontent_replace,section_test,ab_variant_split,ab_script_inject,tracker_injecterweitert.
Version 0.8.0
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_blocksmitpc-N-IDs: h1–h3 =title, p =editor— identisch zum builder-agnostischen WP-Contract)POST /deon-ai/set-widget-texts— gezielte Text-Sets perpc-Nauf den Entry-Body (SEO-Texturierung geklonter Standortseiten), mit Rollback-ProtokollPOST /deon-ai/duplicate-page— 1:1-Seiten-Klon mit find/replace-Textaustausch,h1_override, SEO-Metas, idempotent perpage_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-GuardPOST /deon-ai/publish-lp— Full-Page-Landingpage aus Roh-HTML (inkl.<style>/<script>), neue Tabelledeonai_landing_pages, ausgeliefert über eigene Route pro Slug, idempotent perpage_id/SlugGET /deon-ai/theme-tokens— Design-Tokens (Farben, Fonts, Radius, Palette). Craft hat kein theme.json, daher CSS-Extraktion aus der gerenderten Startseite inkl. einstufigervar(--x)-Auflösung (source: "css_extract")POST /deon-ai/site-schema— Site-weites JSON-LD (Organization/LocalBusiness), ausgespielt im<head>aller SeitenGET /deon-ai/sitemap-discover— Sitemap-URL-KandidatenGET|POST /deon-ai/footer-links— Plugin-eigener Footer-Block („Servicegebiete") vor</body>, Markup identisch zum WP-Pendant/deon-ai/pingliefert jetzt einecapabilities-Liste (Namensschema wie WP/capabilities) für einheitliches Feature-Gating im Worker
Changed
/deon-ai/hygiene-listliefert nur noch die Typenrobots/llms(die Tabelle speichert jetzt zusätzlichsite_schema/footer_links)
Version 0.7.0
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 SectionsdeonBlog(Channel,blog/{slug}) unddeonPages(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/entryund/deon-ai/pageunterstützen jetzt beideimage_url/asset_idfürs Featured Image (bisher nur/entry) — fail-soft, ein Bildfehler blockiert den Entry/die Seite nie./deon-ai/pingliefert zusätzlichfields_ok: { body, featured_image }(echte Feld-Existenz, nicht nur ob das Setting gesetzt ist) sowienav: { verbb, editable }.POST /deon-ai/nav(DEO-80) — verlinkt eine generierte Seite in Hauptnavigation oder Footer. Neuer Consent-SchalterallowNavEdit(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 Handlenav/menuund einemlinkUrl/url-Feld, falls vorhanden; (3) sonst422 nav_not_automatablemit Hinweis zur manuellen Verlinkung bzw. Tipp auf das verbb-Plugin.
Version 0.6.0
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/seo→seo_meta,/deon-ai/faq→content_edit,/deon-ai/entry+/deon-ai/page→page_create,/deon-ai/files+/deon-ai/hygiene→files,/deon-ai/asset→assets,/deon-ai/self-update→self_update. Lese-Endpoints (ping,seo-list,entries,hygiene-list,rollback/*) bleiben ungegated — Rückgängig machen funktioniert immer. /deon-ai/pingliefert jetzt einpermissions-Objekt mit dem aktuellen Freigabe-Stand aller sechs Kategorien, damit Deon AI nicht freigegebene 1-Klick-Fixes im Dashboard ausgrauen kann. Zusätzlichplugin_version/craft_version/php_version(Aliase der bestehenden Felder), ein Fähigkeits-Flagself_update(kann dieser Server technisch überhaupt selbst updaten — proc_open, Speicher, composer.phar) undsections_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, niecraft update all), danachPOST /deon-ai/upin 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üftproc_open,memory_limitundcomposer.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-Fallbackphp craft deon-ai-connect/update <version>für Hosting, auf dem Composer im Web-Request an exec-/Speicher-Limits scheitert.
Version 0.5.0
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 einendata-deon-faq-Marker (ersetzt statt zu duplizieren)/deon-ai/page— native Seiten anlegen (Standortseiten, KI-Faktenseite), eigene Section-Auflösung über neues SettingpagesSectionHandle, 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ßlichInstall.phpausführt und alle zu dem Zeitpunkt bereits vorhandenen nummerierten Migrationen ungeprüft als "erledigt" markiert (ohne sie laufen zu lassen), fehlten frischen Installationen bisherdeonai_seo_hygieneunddeonai_change_logkomplett.Install.phpenthält jetzt den vollständigen Tabellenstand.
Version 0.4.2
Changed
- Plugin-Store-Vorbereitung:
composer.jsonohneversion-Feld (Versionen kommen aus Git-Tags),support.sourceergänzt;LICENSE.md→LICENSE.txt;.github/workflows/create-release.ymlfür automatische GitHub-Releases bei neuen, vom Craft Plugin Store erkannten Tags. Keine funktionale Änderung am Plugin selbst.
Version 0.4.1
Fixed
- Kritisch: Bootstrap-Save (v0.4.0) hat beim Speichern nur
siteId/sdkKey/verificationUuidansavePluginSettings()übergeben — Craft merged dabei nicht mit den bestehenden Settings, wodurchapiKey(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
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
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/mysqldumpnö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:
restorebricht ab (HTTP 409), wenn der Live-Zustand seit der Deon-AI-Änderung manuell verändert wurde — überschreibbar mitforce: true - Alle schreibenden Endpoints (
/deon-ai/seo,/deon-ai/entry,/deon-ai/hygiene) akzeptieren optionalnoteund gebenrollback_idin der Antwort zurück
Version 0.2.0
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.txtund/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, SettingsassetVolumeHandle+featuredImageFieldHandle/deon-ai/entryunterstützt jetztsection/body_field-Override sowieimage_url/asset_idfü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
Fixed
- Falsche Repo-URLs in composer.json (
support.issues,extra.changelogUrl) korrigiert — der 0.1.0-Tag zeigte noch aufgithub.com/baestmarketing/craft-connectstattbaestmarketing-dot/craft-connect
Version 0.1.0
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)