Skip to content

Das Feld einbinden ​

Der Editor ist ein October-Formular-Widget, registriert als contenteditor. Er funktioniert in jedem Formular: Tailor-Blueprints, Backend-Formulare von Plugins, eigene Models.

Backend-Formulare (fields.yaml) ​

yaml
fields:
    content:
        label: Content
        type: contenteditor
        # all optional:
        allowBlocks: [heading, richtext, image]   # whitelist of block handles (default: all)
        maxDepth: 3                               # nesting cap (default: 20)
        previewCss: [/themes/demo/assets/css/theme.css]  # default: the global "Preview stylesheets" setting
        fillHeight: false                         # default: fill the remaining viewport height
        showPreview: false                        # default: show the live-preview column
        blockSettings: false                      # default: the global "Show block settings in the editor" setting
        seoTab: false                             # default: the global "Show the SEO tab in the editor" setting
        previewLayout: default                    # CMS layout of the fallback preview (default: the theme's 'default')
        previewWrapClass: 'container'             # class(es) around the document in the fallback preview
        previewVar: article                       # extra page variable for the record in a Tailor real-page preview (default: post/entry)
        draftContext: article                     # AI drafts fit an article column: no hero, no full-width sections (default: page)

draftContext: article passt für Felder, deren Seite Titel, Titelbild und eine schmale Spalte selbst rendert, etwa Blogbeiträge: KI-Entwürfe verzichten dann auf Heros und Sektionen in voller Breite. allowBlocks und maxDepth gelten auch beim Speichern über DocumentSaveGuard. Verstöße, die schon im gespeicherten Dokument stehen, genießen Bestandsschutz.

Praxisnahe Live-Vorschau ​

Die Vorschau rendert die Blöcke in einem echten Theme-Layout (previewLayout) oder, wenn dieses scheitert, in einer Minimalseite. Links sind wirkungslos, und das iframe ist Script-sandboxed. Static Pages von RainLab.Pages nutzen das Layout der Seite.

Tailor-Blueprints ​

Derselbe Feldtyp contenteditor (mit allowBlocks / maxDepth) funktioniert in Blueprints. Er trägt sich in die Jsonables des Models ein und legt eine nullable longText-Spalte an.

Vorschau über die echte Seite ​

Ein Tailor-Eintrag wird über die Seite gerendert, die seinen Blueprint mit [section] isDefault = 1 bindet, samt deren Container. Das funktioniert für Entwürfe und neue Einträge und zeigt ungespeicherte Änderungen, ohne zu speichern. Der Datensatz wird als post und entry injiziert (weiterer Name über previewVar); previewWrapClass greift hier nicht.

Ohne eine solche Seite oder wenn sie nicht rendert, nutzt die Vorschau ein Layout: previewLayout, sonst das Layout der Section-Seite, sonst das der RainLab.Pages-Seite, sonst das default-Layout des Themes.

Was die Echte-Seite-Vorschau nicht reproduziert

Das PHP-onStart der Seite läuft nicht, daher fehlen SEO-Tags, Vorher/Nachher-Links und andere berechnete Variablen. Überschrift und Titelbild stammen aus den zuletzt gespeicherten skalaren Feldern.

Eigene Models ​

Lege eine nullable longText-Spalte an und trage das Feld in $jsonable ein. Das Widget speichert normalisiertes JSON; beim Lesen erhältst du das dekodierte Array.

php
public $jsonable = ['content'];

// migration
$table->longText('content')->nullable();

Das Dokument rendern ​

twig
{# CMS component: also injects the frontend CSS/JS (includeAssets, includeBootstrap) #}
[renickContent]
==
{% component 'renickContent' document=post.content %}

{# Layout without its own container: root blocks with no stored width get one #}
{% component 'renickContent' document=entry.content rootWidth='container' %}

{# Twig function and filter: HTML only #}
{{ content_render(post.content)|raw }}
{{ post.content|rc_blocks }}

Alle drei rufen BlockRenderer::renderDocument() auf und akzeptieren einen JSON-String oder ein dekodiertes Array. Das Rendern wirft nie: Ein defekter Block wird zu einem HTML-Kommentar (im Debug-Modus zu einer sichtbaren Karte).

Der Editor zeigt Container als Standardbreite jedes Blocks, speichert aber nichts; ein Block bleibt also volle Breite, solange das Layout ihn nicht einrahmt. Ein Layout ohne äußeren Container (wie Dawns Seiten) übergibt rootWidth (container oder narrow): Blöcke auf oberster Ebene ohne gespeicherte Breite erhalten sie, ebenso die Kinder einer obersten Sektion ohne Breite. Blöcke mit fullBleed: true (Hero- und CTA-Bänder) bleiben volle Breite.

Update auf 1.0.7: Aus |content wird |rc_blocks

Der alte Filter verdeckte Octobers eigenen |content-Filter. Benenne {{ record.content|content }} (mit oder ohne |raw) überall, wo es ein Block-Dokument rendert, in {{ record.content|rc_blocks }} um und führe dann php artisan cache:clear aus. Bei Rich-Editor-HTML bleibt |content. Ein Dokument, das auf |content bleibt, gibt rohes JSON aus oder wirft bei einem dekodierten Array (einem Tailor-Feld) einen Fehler.

Site-beschränkte Blöcke verhalten sich fail-closed

Ein Block mit visibility.sites rendert nur, wenn die aktive Site bekannt und gelistet ist. In CLI oder Tests übergib sie: BlockRenderer::renderDocument($doc, ['siteId' => 1]).

Plugin-Integrationen ​

Alle Integrationen sind standardmäßig aus und ohne ihr Plugin wirkungslos. Ein Block, dessen Integration fehlt oder aus ist, rendert nichts; die Editor-Vorschau nennt den Grund.

Die Schalter für RainLab.Pages und RainLab.Blog liegen unter Einstellungen → Content → Content-Editor → Editor → Integrationen. Sie fassen bestehende Inhalte nie an: Nur leere Felder und Block-Dokumente bekommen den Block-Editor, und Block-Dokumente behalten ihn, wenn der Schalter wieder aus ist.

Static Pages (RainLab.Pages) ​

Layout-Variable (RainLab.Pages 2.x und 3.x) ​

twig
[staticPage]
useContent = 0
==
{variable type="contenteditor" name="content" label="Content" tab="Content"}{/variable}
{% page %}
  • Die einzige Option unter Pages 3.x, dessen Editor den Einstellungs-Schalter ignoriert. Dawns static-Layout bringt sie mit.
  • Eine contenteditor-Variable pro Layout. Das Dokument steht im Markup der Seite, also rendert {% page %} es, und die Assets werden eingebunden.
  • Seiten, deren Inhalt noch HTML ist, öffnen schreibgeschützt und behalten ihr HTML.
  • Unter 3.x speichern Übersetzungen in content/static-pages-<locale>/; eine, die der Basisseite gleicht, bleibt leer. Die Historie gilt pro Site.
  • Pages 3.x warnt nicht vor ungespeicherten Änderungen in Layout-Variablen.

Einstellungs-Schalter (RainLab.Pages 2.x) ​

„Block-Editor für statische Seiten verwenden“ ersetzt das Inhaltsfeld bei Layouts ohne die Variable.

Blogbeiträge (RainLab.Blog) ​

„Block-Editor für Blogbeiträge verwenden“ bearbeitet content als Blöcke und rendert daraus beim Speichern und Laden content_html, sodass blogPost und blogPosts weiter funktionieren. Markdown-Beiträge behalten den Markdown-Editor. KI-Entwürfe nutzen dort den Artikel-Kontext (draftContext: article).

Shop (OFFLINE.Mall) ​

Einstellungen → Content → Mall-Integration (nur solange OFFLINE.Mall installiert ist) ergänzt die Blöcke Mall-Produkte und Mall-Kategorie, Darstellungs-Voreinstellungen und eine Statusliste der Seiten, die Mall braucht.

  • Shop-Scaffold (Button oder renick:content:mall-scaffold): kopiert ein shop-Layout, die Produkt-, Kategorie-, Warenkorb-, Checkout-, Konto-, Adress- und Login-Seiten sowie Partial-Overrides aus templates/mall/ der Theme-Lineage (sonst neutrale Bootstrap-5-Vorlagen) und verknüpft sie in Malls Einstellungen. Vorhandene Dateien bleiben erhalten.
  • Produktdaten werden in renick.content.beforeRenderBlock aufgelöst, pro Währung und Kundengruppe gecacht und beim Speichern von Produkten, Varianten, Preisen und Kategorien geleert.
  • Mall liefert kein Frontend-CSS. Dawns Shop-Layout lädt offline-mall-integration.css und .js; andere Themes bleiben ungestylt.

Einsendungen von Besuchern (Tailor) ​

Ab October CMS 4.4 ergänzt Einstellungen → Content → Einsendeformulare den Block Einsendeformular und die Komponente tailorSubmission, die Tailor\Components\SubmissionComponent erweitert. Beim Blueprint bleiben Validierung, Moderationsstatus, Spam-Bereinigung, Benachrichtigungen sowie Octobers Drossel und Honeypot. Das Plugin ergänzt:

  • das lokale Captcha pro Block (Immer an funktioniert auch bei ausgeschalteter globaler Einstellung),
  • Pflichtfelder, die sich nicht ausblenden lassen, und einen Block, der nicht rendert, wenn ein Pflichtfeld nicht darstellbar ist,
  • private Uploads (uploads/protected, nur im Backend), deren Typ, Anzahl und Größe vor dem Absenden geprüft werden,
  • ein HMAC-signiertes Feld _rc_tsf, geprüft bei cms.page.init, damit gecachtes Block-HTML den Handler erreicht.

Zum Theming partials/renick-content/tailor-submission-form.htm überschreiben; {% framework %} ist nicht nötig. php artisan theme:seed dawn installiert eine Demo unter /en/appointment und schaltet die Integration ein (nur ab 4.4).

Eigene Integration schreiben ​

Erweitere Integration und gib sie aus renick.content.registerIntegrations zurück; eine eingebaute mit gleichem code() gewinnt. Pflicht sind nur code() und isAvailable(), und nur verfügbare Integrationen booten oder registrieren Komponenten. Optional: boot(), settingsPage(), blockDefinitions() (solange aktiv), blockHandles(), unavailableNotice(), components(), extendSettingsController(), cacheKeySuffix(), flushCaches(). isEnabled() liest <code>_enabled (Bindestriche als Unterstriche) aus den Content-Editor-Einstellungen, außer du überschreibst settingsModel() / settingKey().

php
use Renick\Content\Classes\Integrations\Integration;

final class AcmeShopIntegration extends Integration
{
    public function code(): string { return 'acme-shop'; }

    public function isAvailable(): bool
    {
        return \System\Classes\PluginManager::instance()->hasPlugin('Acme.Shop');
    }

    public function blockDefinitions(): array
    {
        return [/* renick.content.registerBlocks payload */];
    }
}

// Plugin.php: register(), not boot(), because Renick.Content collects integrations in its own boot()
public function register()
{
    Event::listen('renick.content.registerIntegrations', fn () => [new AcmeShopIntegration()]);
}