Version 0.22.0
Bis v0.21.1 konnte Deon AI eine Seite lesen und bestehende Sektionen umschreiben, aus-/einblenden und umsortieren — aber es konnte weder eine Sektion anlegen noch eine entfernen.
op:"disable"blendet einen Block nur im Frontend aus; im Craft-CP steht er weiterhin ausgegraut in der Liste. Genau das war die berechtigte Kundenrückmeldung („es wurden keine Sektionen entfernt oder neu hinzugefügt"). v0.22.0 schließt diese Lücke und macht damit den eigentlichen Produktgedanken erst möglich: Deon AI wählt aus den vorhandenen Skeletten des Kundendesigns dasjenige aus, das zu einer neuen H2 bzw. zum Inhalt passt, legt es an und befüllt es. Es wird nie neues Design erfunden — es wird immer mit dem gearbeitet, was der Kunde bereits hat.
Added
GET /deon-ai/section-catalogundGET /deon-ai/section-catalog/<id>— der Skelett-Katalog. Ohne diesen Katalog kann die AI keinen Block anlegen, weilblock_typeein existierender Handle sein muss. Site-Scope listet jedes Matrix-/Neo-Feld einmal (inkl.used_in), Entry-Scope listet nur die Block-Felder dieses Entries, dafür mit je einem Beispielblock pro Typ aus dem Entry selbst — damit sieht die AI, wie ein Skelett beim Kunden tatsächlich befüllt aussieht, statt es zu raten. Pro Block-Typ werden Handle, Name,has_title_field,can_addund die Sub-Felder mit Typ und Rollen-Hinweis geliefert. Mögliche Werte vonrole_hint:headline,subline,body,cta_label,cta_url,media— odernull, wenn der Handle zu keiner Rolle passt.can_addist bei Neo-Feldern bewusstfalsestatt eines Fehlers beim Schreiben.op:"add"in/deon-ai/entry-sections— Skelett nehmen und befüllen. Body:{"op":"add","field_handle":"…","block_type":"faqSection","position":5,"enabled":true, "title":"…","fields":{"headline":"…","text":"…"}}.positionist optional (ohne wird hinten angehängt). Nicht setzbare Sub-Felder werden nicht still verschluckt, sondern inskipped_fieldsmit Begründung zurückgemeldet (sub_field_not_found,value_not_string,field_not_text_writable). Ein unbekannterblock_typeantwortet mitblock_type_not_foundplus der Liste der verfügbaren Handles.op:"remove"in/deon-ai/entry-sections— strukturelle Entfernung, aber umkehrbar. Der Block verschwindet auch im CP aus der Liste, technisch aber als Soft-Delete (deleteElement($el, false)setzt nurdateDeleted). Vor dem Löschen wird ein vollständiger Snapshot (Typ, Enabled-State, Position, alle Textwerte) in den Change-Log geschrieben.- Rollback für beide neuen Operationen.
entry-section-added→ der erzeugte Block wird wieder soft-gelöscht;entry-section-removed→restoreElement(). Beide Richtungen sind idempotent: ist der Zielzustand schon erreicht (already_absent/already_present), gilt der Rollback als erfolgreich, nicht als Fehler. Die Rollback-Vorschau (/rollback/<id>/preview) hat eigene Sätze für beide Fälle statt des generischen „wird zurückgesetzt". - Capabilities
section_catalog,entry_section_add,entry_section_removein/ping. Der Worker ruft die neuen Wege ausschließlich gegatet auf — ältere Installationen laufen unverändert weiter.
Notes zur Sicherheit der Implementierung
- Kein serialisiertes
['entries' => …]-Delta im Add-Pfad. Angelegt wird über einen direktensaveElement()auf dem neuen Block-Element. Der Delta-Pfad ist exakt der, der in v0.20.0 echten Datenverlust verursacht hat (Craft behandelt alles Nichtgenannte als gelöscht); ein direkter Element-Save kann bestehende Blöcke strukturell nicht verlieren. - Craft 4 und 5 getrennt behandelt, aber nicht über
version_compare, sondern über den tatsächlichen Typ des Block-Typ-Objekts. Craft 5: der Block ist ein Entry,saveOwnership()hängt ihn mitsortOrder = MAX+1automatisch an —sortOrderwird deshalb bewusst nicht gesetzt. Craft 4:MatrixBlock::afterSave()schreibtsortOrder ?? 0, es gibt kein Auto-Append —sortOrderwird deshalb explizit gesetzt. - Soft-Delete ist gegen späteres Hard-Delete abgesichert. Weder
NestedElementManager::deleteOtherNestedElements()(Craft 5) nochMatrix::_deleteOtherBlocks()(Craft 4) queryen mit->trashed(null); ein soft-gelöschter Block ist für sie unsichtbar und kann durch einen späteren Reorder oder Owner-Save nicht nachträglich hart gelöscht werden. - Das optionale
position-Delta läuft nie auf dem Entry-Objekt der laufenden Schleife, sondern auf einer frisch geladenen Kopie — sonst würde der memoisierte Matrix-Wert die Blockliste der nachfolgenden Patches im selben Request vergiften. Und es ist nach Regel 3 ein reines['sortOrder' => [...]]ohneentries-Key. patches[]ist batchfest gemacht. Craft memoisiert den Wert eines Matrix-Feldes auf der Entry-Instanz. Nach einemadd/removeim selben Request wäre dieser Wert veraltet: der neue Block fehlte darin, der entfernte stünde noch drin, undblock_indexwäre um eins verschoben. Der Endpoint merkt sich deshalb pro Feld, ob strukturell eingegriffen wurde, und liest die Blockliste ab dann aus einer frisch geladenen Kopie (nur als Lesequelle — gespeichert wird weiterhin ausschließlich das jeweilige Block-Element). Das ist kein Randfall, sondern die Normalform der Aufrufe: „Sektion anlegen, dann ihre Felder füllen" in einem Request.reorder{}nach einemadd/removeim selben Request läuft ebenfalls auf frischen Daten. Das ist der kritischste Pfad des Endpoints, weilreorderals einziger den Owner-Entry speichert: ein aus veralteter Blockliste gebautessortOrderhätte den soeben angelegten Block nicht enthalten — und nicht gelistete Blöcke entfernt CraftsdeleteOtherNestedElements(). Lässt sich die frische Kopie nicht laden, wird der Reorder mitreorder_skipped_stale_entryabgebrochen statt geraten; die bereits angewendeten Patches bleiben gültig und werden normal zurückgemeldet.- Keine Migration,
schemaVersionbleibt1.5.0. Alle Änderungen sind additiv; bestehende Response-Felder wurden nicht umbenannt.
Version 0.21.1
Behebt die Klasse von Ausfällen, bei denen das Plugin nach einem Self-Update funktionsfähig installiert war und trotzdem nichts mehr tat. Ursache ist kein Fehler in der Update-Kette, sondern eine Eigenschaft von Craft: Plugin-Settings liegen in der Project Config. Läuft die Installation mit
allowAdminChanges = false— der Produktions-Standard — schreibtsavePluginSettings()nur in dieplugins-Tabelle, und der auf jedes Self-Update folgendephp craft up→project-config/applybügelt die YAML-Werte darüber. Auf der Pilot-Installation standen nach0.20.1 → 0.21.0alle fünf von/setup-bloggeschriebenen Handles wieder auf ihren Klassen-Defaults (blog,pages,body,'',''), die dort nichts auflösen./pingmeldetesections_okundfields_okvollständigfalse,/entrieslief in ein 422 — und/entryund/pageüberlebten nur, weil der Worker beisection_not_foundreaktiv/setup-blognachfeuert. Alles, was an diesem Self-Heal vorbeiläuft, war schlicht tot. Ein erneuter/setup-blog-Aufruf repariert das zwar, aber nur bis zum nächsten Update — deshalb löst v0.21.1 das Problem dort, wo es dauerhaft verschwindet: beim Lesen.
Fixed
- Kritisch — alle Endpoints arbeiten mit aufgelösten statt mit rohen Handle-Settings.
Neue private Resolver im
ApiController(resolvedBlogSectionHandle(),resolvedPagesSectionHandle(),resolvedBodyFieldHandle(),resolvedImageFieldHandle(),resolvedAssetVolumeHandle()) lösen je Handle in fester Reihenfolge auf: expliziter Request-Parameter → Plugin-Setting, sofern es auf ein existierendes Objekt zeigt → Deon-Bootstrap-Handle (deonBlog/deonPages/deonBody/deonFeaturedImage) →null. Damit ist ein Setting ab sofort eine Präferenz, kein Fakt: ein verlorenes Setting kostet höchstens noch die Präferenz, nie die Funktion. Betroffen sind/ping,/entries,/entry,/page,/asset, der FAQ-Inject und der Restore-Point-Snapshot. Die Auflösung wird pro Request gecacht. nullheißt 422, nicht „irgendwas bauen". Löst kein Kandidat auf, existiert das Ziel wirklich nicht — dann antwortet der Endpoint mit einem sauberen, benannten Fehler (/assetetwa mitasset_volume_not_configuredplus Hinweis), statt eine halbfertige Seite zu erzeugen. Der Volume-Resolver spiegelt exakt die Ableitung aus/setup-blog(Restriktion des Deon-Bildfelds → erstes vorhandenes Volume) und legt wie dort nie selbst ein Volume an, weil Filesystem und Storage hosting-abhängig sind.sections_ok/fields_okin/pingprüfen jetzt die aufgelösten Handles. Ein zurückgesetztes Setting erscheint dort nicht mehr als „Section fehlt".falsebedeutet ab v0.21.1 verlässlich, dass das Objekt tatsächlich nicht existiert.
Added
- Capability
handle_fallbackin/ping. Der Worker darf daraus schließen, dasssections_ok/fields_okbei dieser Plugin-Version echte Existenz-Aussagen sind. /pingliefert additivhandlesundhandles_repaired.handlesnennt die in diesem Request tatsächlich benutzten Handles (blog_section,pages_section,body_field,featured_image_field,asset_volume),handles_repaireddie Setting-Properties, die von den aufgelösten Werten abwichen und zurückgeschrieben wurden. Ein nicht-leereshandles_repaireddirekt nach einem Update ist der Beleg für den Project-Config-Revert — kein Fehler. Beide Felder sind rein additiv; ältere Worker ignorieren sie.- Opportunistische Selbstheilung der Settings.
/pingschreibt abgedriftete Handles persaveSettingsWithoutBootstrap()zurück. Ausdrücklich best effort: schlägt das fehl oder wird es beim nächstencraft uperneut überschrieben, ist das kein Fehlerfall — die Endpoints laufen ohnehin über die Resolver. Die Methode wirft nie, sie loggt viaCraft::warning().
Notes
- Keine Migration,
schemaVersionbleibt1.5.0. Alle Änderungen sind additiv;/setup-blogliest bewusst weiterhin die rohen Settings, weil genau dort die Abweichung erkannt werden muss, umsettings_updatedzu berechnen.
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)