# VITEC Content Blocks Setup — Step-by-Step **Strategie:** Content Blocks (YAML-basiert, deklarativ) als Haupt-Pattern für alle VITEC Content Elements. `nb-headless-content-blocks` übernimmt automatisch das JSON-Mapping. **Stack-Basis bei dir (bereits vorhanden):** - `evomedien/vitec` — deine Extension - Site Set `evomedien/vitecset` - `friendsoftypo3/headless` (`headless: 1`) - `nb-headless-content-blocks` (Bridge) - `b13/container` + `itplusx/headless-container` (für Layout-Wrapper, optional) --- ## 0. Aufräumen (zwei kleine Issues vorab) ### 0.1 `nb-headless-content-blocks` ins Site Set migrieren Aktuell steht die Extension nur in der **Site-Config**, nicht im **Site Set**. Das sollte konsistent sein, damit das Set in sich abgeschlossen ist. **Datei:** `packages/vitec/Configuration/Sets/VitecSet/config.yaml` (oder wo dein Set liegt) ```yaml name: evomedien/vitecset label: VITEC Set settings: website: background: color: '#386492' dependencies: - typo3/fluid-styled-content - friendsoftypo3/headless - b13/container - itplusx/headless-container - nb-headless-content-blocks/headless-content-blocks # NEU ``` Dann aus `config/sites//config.yaml` die Zeile `- nb-headless-content-blocks/headless-content-blocks` wieder rausnehmen — sie kommt jetzt transitiv über dein Set. ### 0.2 Version-Mismatch fixen - `composer.json` sagt `1.0.1` - `ext_emconf.php` sagt `0.0.1` Einfach beide auf den gleichen Wert bringen (Empfehlung: `1.0.0` und bei jedem Deployment hochzählen). Nicht dringend, aber sauber: ```php // ext_emconf.php 'version' => '1.0.0', 'state' => 'stable', // 'alpha' wirkt im Produktivbetrieb komisch ``` ```json // composer.json "version": "1.0.0" ``` --- ## 1. Ordnerstruktur anlegen Content Blocks leben in der Extension unter einer festen Pfad-Konvention: ``` packages/vitec/ ├── ContentBlocks/ │ └── ContentElements/ │ ├── hero-section/ ← erster Content Block (Beispiel) │ │ ├── config.yaml ← Feld-Definitionen │ │ ├── assets/ │ │ │ └── icon.svg ← Icon für CE-Wizard │ │ ├── language/ │ │ │ └── labels.xlf ← Übersetzungen │ │ └── templates/ │ │ └── EditorPreview.html ← optional: Backend-Preview │ ├── feature-teaser/ │ ├── cta-banner/ │ └── ... ├── Classes/ ├── Configuration/ ├── composer.json └── ext_emconf.php ``` Ein Content Block = ein Ordner. Name = Ordnername (kleingeschrieben, mit Bindestrichen). --- ## 2. Erster Content Block: VITEC Hero Section Nachgebaut nach einem typischen Template aus deinen Figma-Wireframes: Eyebrow + Heading + Subline + Body + CTA + Hero-Image + Background-Variant. ### 2.1 `config.yaml` **Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml` ```yaml name: vitec/hero-section group: vitec prefixFields: true prefixType: full fields: - identifier: eyebrow type: Text max: 50 - identifier: header useExistingField: true required: true - identifier: subheader useExistingField: true - identifier: bodytext useExistingField: true enableRichtext: true - identifier: cta type: Link allowedTypes: - page - url - file - email - identifier: cta_label type: Text default: 'Learn more' max: 30 - identifier: hero_image type: File minitems: 0 maxitems: 1 allowed: common-image-types extendedPalette: true - identifier: background_variant type: Select renderType: selectSingle default: none items: - label: None value: none - label: Orange value: orange - label: Blue value: blue - label: Graphite value: graphite - label: Midnight value: midnight - identifier: show_logo_wall type: Checkbox default: 0 ``` **Was passiert hier:** - `name: vitec/hero-section` → TCA-Typ `vitec_hero_section`, Tabelle `tt_content` - `group: vitec` → eigene Sektion im CE-Wizard (muss noch registriert werden — siehe 2.5) - `useExistingField: true` → recycled TYPO3-Core-Felder (`header`, `subheader`, `bodytext`). Keine neuen DB-Spalten, konsistente Bedeutung - `prefixFields: true` + `prefixType: full` → neue Felder bekommen `tx_vitec_hero_section_*` als DB-Prefix (vermeidet Kollisionen) - **Keine TCA-Override-Datei nötig** — Content Blocks generiert das TCA automatisch ### 2.2 `language/labels.xlf` Content Blocks erwartet XLF-Files nach einer festen Namens-Konvention. Jedes Feld bekommt automatisch einen Key `...label`. **Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf` ```xml VITEC · Hero Section Haupt-Hero mit Headline, CTA, Background-Variante Eyebrow Text Kleiner Label-Text über der Headline CTA Link CTA Button Text Hero Image Background Variant Show Logo Wall below ``` > Die Core-Felder (`header`, `subheader`, `bodytext`) brauchen keine Labels — sie erben die vom TYPO3-Core. ### 2.3 `assets/icon.svg` Content Blocks erkennt Icons automatisch, wenn sie unter `assets/icon.svg` (oder `icon.png`) liegen. **Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg` ```xml ``` Platzhalter — tausch später gegen die echten VITEC-Icons aus. ### 2.4 `templates/EditorPreview.html` (optional aber empfohlen) Im **Headless-Mode** wird kein Frontend-Template gerendert. Aber für die Redakteure ist eine Backend-Preview sinnvoll, damit sie im Seiten-Modul sehen, was sie gerade anlegen. **Datei:** `packages/vitec/ContentBlocks/ContentElements/hero-section/templates/EditorPreview.html` ```html
VITEC · Hero Section — {data.background_variant}
{data.eyebrow}

{data.header}

{data.subheader}
{data.cta_label} →
``` ### 2.5 Backend-Gruppe "VITEC" registrieren Damit `group: vitec` aus der YAML eine saubere Sektion im CE-Wizard wird, muss die Gruppe einmal registriert sein: **Datei:** `packages/vitec/Configuration/page.tsconfig` (deine existierende Datei — aktuell leer) ```tsconfig mod.wizards.newContentElement.wizardItems.vitec { header = VITEC show = * elements { } } ``` Das reicht — Content Blocks hängt die einzelnen Elemente dann automatisch unter `elements` ein. --- ## 3. Installation & Test ### 3.1 Caches und TCA flushen Nach jeder Content-Block-Änderung: ```bash vendor/bin/typo3 cache:flush ``` Oder im Backend: Admin Tools → Maintenance → Flush all caches. ### 3.2 Datenbank-Schema updaten Content Blocks erstellt automatisch neue Spalten (z.B. `tx_vitec_hero_section_eyebrow`, `tx_vitec_hero_section_background_variant` etc.): ```bash vendor/bin/typo3 database:updateschema ``` Oder Backend: Admin Tools → Maintenance → Analyze Database Structure → Run. ### 3.3 Im Backend anlegen 1. Seite im Seitenbaum öffnen → Page-Modul 2. "Neues Inhaltselement" → Tab **VITEC** → "VITEC · Hero Section" 3. Felder ausfüllen (Eyebrow, Heading, CTA, Image, Background-Variant "orange") 4. Speichern ### 3.4 JSON prüfen ```bash curl -s https://dev.vitec.com/testseite | jq '.content.colPos0[] | select(.type == "vitec_hero_section")' ``` **Erwarteter JSON-Output** (durch `nb-headless-content-blocks` automatisch erzeugt): ```json { "id": 42, "type": "vitec_hero_section", "colPos": 0, "categories": "", "appearance": { "layout": "default", "frameClass": "default", "spaceBefore": "", "spaceAfter": "" }, "content": { "header": "Enterprise Video Solutions", "subheader": "Reliable. Scalable. Proven.", "bodytext": "

VITEC delivers mission-critical video...

", "tx_vitec_hero_section_eyebrow": "Why VITEC", "tx_vitec_hero_section_cta": { "href": "/success-stories", "target": null, "class": null, "title": null, "linkText": "t3://page?uid=15", "additionalAttributes": [] }, "tx_vitec_hero_section_cta_label": "Learn more", "tx_vitec_hero_section_hero_image": [ { "publicUrl": "https://dev.vitec.com/fileadmin/.../hero.png", "properties": { ... } } ], "tx_vitec_hero_section_background_variant": "orange", "tx_vitec_hero_section_show_logo_wall": false } } ``` Struktur passt zu deinem existierenden `textpic`-Pattern — `content.*` mit allen Feldern nebeneinander. ✅ --- ## 4. JSON-Schema aufräumen (Field-Namen kürzen) Du hast sicher gemerkt: die Feldnamen im JSON sind lang (`tx_vitec_hero_section_eyebrow`). Das kommt vom `prefixFields: true` — nötig für DB-Konsistenz, aber unschön fürs Frontend. **Lösung:** Via `nb-headless-content-blocks` EventListener die Keys umbenennen. ### 4.1 EventListener anlegen **Datei:** `packages/vitec/Classes/EventListener/NormalizeContentBlockKeys.php` ```php getKey(); // Strip prefix "tx_vitec__" from field names // e.g. "tx_vitec_hero_section_eyebrow" → "eyebrow" if (preg_match('/^tx_vitec_[a-z_]+?_([a-z_]+)$/', $key, $matches)) { $event->setKey($matches[1]); } } } ``` ### 4.2 Services.yaml **Datei:** `packages/vitec/Configuration/Services.yaml` (anlegen falls nicht vorhanden) ```yaml services: _defaults: autowire: true autoconfigure: true public: false Evomedien\Vitec\: resource: '../Classes/*' exclude: '../Classes/Domain/Model/*' ``` > `AsEventListener` Attribute + autoconfigure = Event-Listener wird automatisch registriert. Keine weitere Konfiguration nötig. ### 4.3 Resultat nach Cache-Flush ```json { "type": "vitec_hero_section", "content": { "header": "Enterprise Video Solutions", "eyebrow": "Why VITEC", "cta": { ... }, "cta_label": "Learn more", "hero_image": [ ... ], "background_variant": "orange", "show_logo_wall": false } } ``` Saubere, lesbare Keys — React-Kollege glücklich. --- ## 5. Pattern für weitere Content Blocks Für jeden neuen Content Block brauchst du: ``` packages/vitec/ContentBlocks/ContentElements// ├── config.yaml ← Felder definieren ├── language/labels.xlf ← Labels übersetzen ├── assets/icon.svg ← Icon └── templates/EditorPreview.html ← optional ``` Dann: ```bash vendor/bin/typo3 database:updateschema vendor/bin/typo3 cache:flush ``` Fertig. Keine PHP-Boilerplate, keine TypoScript-Overrides, kein TCA-Gefummel. ### Template-Vorschlag für deine 20 Figma-Templates Nummerierung/Naming-Vorschlag an den Figma-Templates entlang: | # | Name | Content Block | |---|---|---| | 01 | Hero | `vitec/hero-section` | | 02 | Feature-Teaser Grid | `vitec/feature-teaser-grid` | | 03 | CTA Banner | `vitec/cta-banner` | | 04 | Quote / Testimonial | `vitec/testimonial` | | 05 | Logo Wall | `vitec/logo-wall` | | 06 | Product Card Grid | `vitec/product-card-grid` | | ... | ... | ... | | 16 | Content Page V1 | `vitec/content-page-v1` | Wenn du einen Block gebaut hast, ist jeder weitere 10-20 Minuten Arbeit. --- ## 6. Wann brauchst du trotzdem `b13/container`? **Antwort:** Für echte Layout-Wrapper mit nested Content Elements. Beispiele: - **2-Spalten-Section:** Ein Container, in dessen linker Spalte ein `hero-section` + rechts ein `testimonial` liegt - **Tabs / Accordion:** ein Tab-Container mit mehreren Content Blocks je Tab - **Grid mit freier CE-Wahl:** 3-column-grid, wo der Redakteur pro Spalte frei wählt Für diese Fälle: 1. `b13/container` Container registrieren (wie in der vorherigen Anleitung beschrieben, per PHP TCA-Override) 2. `itplusx/headless-container` mappt ihn automatisch ins JSON 3. Die Kinder sind dann Content Blocks → das Pattern spielt sauber zusammen Laut Doku von `nb-headless-content-blocks`: **"Support for EXT:container"** ist eingebaut — die zwei Extensions beißen sich nicht. --- ## 7. Advanced: Sammlungen (Collections) Für wiederkehrende Items (z.B. 3 Teaser-Cards in einem Grid) gibt es den `Collection`-Type: ```yaml name: vitec/feature-teaser-grid group: vitec fields: - identifier: header useExistingField: true - identifier: teasers type: Collection minitems: 1 maxitems: 6 fields: - identifier: icon type: File maxitems: 1 allowed: common-image-types - identifier: title type: Text required: true - identifier: description type: Textarea - identifier: link type: Link ``` Im JSON kommt das dann als Array raus: ```json { "type": "vitec_feature_teaser_grid", "content": { "header": "Our Solutions", "teasers": [ { "title": "...", "description": "...", "icon": [...], "link": {...} }, { "title": "...", "description": "...", "icon": [...], "link": {...} } ] } } ``` --- ## 8. Cheatsheet: Field-Types | YAML `type` | Zweck | JSON-Output-Typ | |---|---|---| | `Text` | Einzeiliger Text | string | | `Textarea` | Mehrzeilig; mit `enableRichtext: true` → RTE | string (HTML bei RTE) | | `Number` | int/float | number | | `Checkbox` | Boolean | boolean | | `Select` `renderType: selectSingle` | Dropdown | string (value) | | `Select` `renderType: selectMultipleSideBySide` | Multi-Select | string (comma-sep) | | `Radio` | Radio-Button-Gruppe | string | | `Link` | TYPO3-Link (Page/URL/File/Email) | object (`href`, `target`, `linkText`…) | | `File` | File-Reference | array of file-objects | | `Color` | Color-Picker | string (hex) | | `DateTime` | Datum/Zeit | string (ISO) | | `Collection` | Wiederholbare Feldgruppen | array of objects | | `Category` | TYPO3-Kategorien | array | | `Relation` | Referenz zu anderen Records | array | --- ## 9. Fehlerbild-Cheatsheet | Symptom | Ursache | Fix | |---|---|---| | Content Block erscheint nicht im CE-Wizard | Cache oder Gruppe nicht registriert | `cache:flush`, dann Backend-User-Session refresh | | Spalten fehlen in DB (`column not found`) | `database:updateschema` nicht ausgeführt | `vendor/bin/typo3 database:updateschema` | | Feld-Label bleibt englisch/identifier | XLF-Key-Konvention falsch | Key muss exakt `.label` heißen (ohne Vendor-Prefix) | | JSON enthält veraltete Struktur | `nb-headless-content-blocks` nicht geladen oder Site-Set greift nicht | TypoScript-Analyzer prüfen | | EventListener greift nicht | `Services.yaml` nicht geladen oder `autoconfigure: false` | Services.yaml prüfen, Cache flush | | Icon wird nicht angezeigt | falsche Position oder SVG-Fehler | Pfad muss `assets/icon.svg` relativ zum CB-Ordner sein | | "Type does not exist" nach Rename | TCA-Cache veraltet | `cache:flush --group=system` | --- ## 10. Quickstart ```bash # 1. Aufräumen (einmalig) # - nb-headless-content-blocks ins Site Set migrieren # - Version in ext_emconf.php / composer.json angleichen # 2. Ordnerstruktur für ersten Block mkdir -p packages/vitec/ContentBlocks/ContentElements/hero-section/{assets,language,templates} # 3. Files anlegen: # - config.yaml # - language/labels.xlf # - assets/icon.svg # - templates/EditorPreview.html (optional) # 4. DB + Cache vendor/bin/typo3 database:updateschema vendor/bin/typo3 cache:flush # 5. Backend: CE anlegen, speichern # 6. Test curl -s https://dev.vitec.com/testseite | jq '.content.colPos0' ``` --- ## 11. Nächste Schritte Nachdem der erste Content Block läuft: 1. **EventListener** für saubere Keys einbauen (Schritt 4) 2. Die 20 Templates aus Figma durchgehen → pro Template ein Content Block 3. Wenn Layouts mit nested CEs gebraucht werden → `b13/container` dazupacken 4. **Custom Records** (`tx_vitec_market`, `_solution`, `_product`, `_story`) — Content Blocks kann auch Record Types. Siehe YAML reference → RecordTypes 5. **m:n Relationen** zwischen Records — via `Relation`-Field-Type (mit `allowed` und `maxitems`) Sobald der Hero-Section-Block JSON liefert, melden — dann bauen wir zusammen den ersten Record Type (`tx_vitec_market`) und die bidirektionalen Relationen. --- ## Referenzen - **Content Blocks:** https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/ - **Field Types:** https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/YamlReference/FieldTypes/Index.html - **nb-headless-content-blocks:** https://github.com/Netzbewegung-Backend/nb_headless_content_blocks - **Headless Docs:** https://docs.typo3.org/p/friendsoftypo3/headless/main/en-us/ - **Beispiel-Repo (Content Blocks):** https://github.com/friendsoftypo3/content-blocks/tree/main/Build/content_blocks_examples