diff --git a/Documentation/CustomFrameClasses.md b/Documentation/CustomFrameClasses.md new file mode 100644 index 0000000..8e9d03c --- /dev/null +++ b/Documentation/CustomFrameClasses.md @@ -0,0 +1,107 @@ +# Custom Frame Classes for TYPO3 Content Elements + +## Overview + +Custom frame class options have been added to the TYPO3 backend, allowing editors to select Vitec-specific styling options for content elements. + +## Location + +File: `/packages/vitec/Configuration/TCA/Overrides/tt_content.php` + +## Available Frame Classes + +The following custom frame classes are available in the backend under **Appearance → Frame**: + +| Label | CSS Class | Description | +|-------|-----------|-------------| +| Vitec: Full Width | `vitec-full-width` | Content spans full width of the viewport | +| Vitec: Centered Container | `vitec-centered` | Content is centered with constrained width | +| Vitec: Card Style | `vitec-card` | Content displayed as a card with shadow/border | +| Vitec: Dark Background | `vitec-dark` | Content with dark background styling | +| Vitec: Highlight Box | `vitec-highlight` | Content with highlighted/accent styling | + +## Implementation + +The frame classes are added using TCA overrides, with the field configured to allow **multiple selections**: + +```php +// Change frame_class to allow multiple selections +$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['renderType'] = 'selectCheckBox'; +$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['maxitems'] = 999; + +// Add custom frame_class options for Vitec +$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['items'] = array_merge( + $GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['items'], + [ + ['label' => 'Vitec: Full Width', 'value' => 'vitec-full-width'], + ['label' => 'Vitec: Centered Container', 'value' => 'vitec-centered'], + ['label' => 'Vitec: Card Style', 'value' => 'vitec-card'], + ['label' => 'Vitec: Dark Background', 'value' => 'vitec-dark'], + ['label' => 'Vitec: Highlight Box', 'value' => 'vitec-highlight'], + ] +); +``` + +## Usage + +### In Backend + +1. Edit any content element +2. Navigate to the **Appearance** tab +3. **Select one or more options** using checkboxes from the **Frame** field +4. Multiple classes can be combined (e.g., "Card Style" + "Dark Background") +5. Save the content element + +### In Frontend/CSS + +The selected frame class is added to the content element wrapper. Style these classes in your CSS: + +```css +/* Example CSS styling */ +.vitec-full-width { + width: 100vw; + margin-left: calc(-50vw + 50%); +} + +.vitec-centered { + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + +.vitec-card { + background: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + padding: 2rem; +} + +.vitec-dark { + background-color: #1a1a1a; + color: white; +} + +.vitec-highlight { + background-color: #f0f7ff; + border-left: 4px solid #0066cc; + padding: 1.5rem; +} +``` + +## Adding More Options + +To add additional frame class options, edit `/packages/vitec/Configuration/TCA/Overrides/tt_content.php` and add new array entries: + +```php +['label' => 'Your Label', 'value' => 'your-css-class'], +``` + +After making changes, clear the TYPO3 cache: + +```bash +./vendor/bin/typo3 cache:flush +``` + +## Date Added + +November 19, 2025 diff --git a/VITEC-ContentBlocks-Setup.md b/VITEC-ContentBlocks-Setup.md new file mode 100644 index 0000000..ba7ec02 --- /dev/null +++ b/VITEC-ContentBlocks-Setup.md @@ -0,0 +1,624 @@ +# 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 diff --git a/composer.json b/composer.json index fc05064..24c8111 100644 --- a/composer.json +++ b/composer.json @@ -15,11 +15,13 @@ "sort-packages": true }, "require": { + "b13/container": "^3.1", "b13/typo3-updater": "^1.1", "co-stack/logs": "^5.3", "evomedien/vitec": "^1.0.1", "friendsoftypo3/content-blocks": "^1.3", - "friendsoftypo3/headless": "^4.7", + "friendsoftypo3/headless": "^4.2", + "itplusx/headless-container": "^3.0", "netzbewegung/nb-headless-content-blocks": "^0.0.21", "nitsan/ns-license": "^13.0", "typo3/cms-backend": "^13.4", diff --git a/composer.lock b/composer.lock index ff69136..7b2e3c2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,70 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "724002ccf875139e7f2ceb28954d3869", + "content-hash": "c6c4d1d82c761620488ccd3a9f1e5789", "packages": [ + { + "name": "b13/container", + "version": "3.2.3", + "source": { + "type": "git", + "url": "https://github.com/b13/container.git", + "reference": "bc3e47686115fa98ed9dd439ec507547bd3d59ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/b13/container/zipball/bc3e47686115fa98ed9dd439ec507547bd3d59ea", + "reference": "bc3e47686115fa98ed9dd439ec507547bd3d59ea", + "shasum": "" + }, + "require": { + "typo3/cms-backend": "^11.5 || ^12.4 || ^13.4 || ^14.0" + }, + "replace": { + "typo3-ter/container": "self.version" + }, + "require-dev": { + "b13/container-example": "dev-task/v14-backend-template", + "codeception/codeception": "^4.1 || ^5.1", + "codeception/module-asserts": "^1.0 || ^3.0", + "codeception/module-db": "^1.0 || ^3.1", + "codeception/module-webdriver": "^1.0 || ^4.0", + "friendsofphp/php-cs-fixer": "^3.51", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "9.6 || ^10.5 || ^11.3", + "typo3/cms-fluid-styled-content": "^11.5 || ^12.4 || ^13.4 || ^14.0", + "typo3/cms-info": "^11.5 || ^12.4 || ^13.4 || ^14.0", + "typo3/cms-install": "^11.5 || ^12.4 || ^13.4 || ^14.0", + "typo3/cms-workspaces": "^11.5 || ^12.4 || ^13.4 || ^14.0", + "typo3/coding-standards": "^0.5.5", + "typo3/testing-framework": "^7.1.1 || ^8.2.7 || ^9.1" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "app-dir": ".Build", + "web-dir": ".Build/Web", + "extension-key": "container", + "cms-package-dir": "{$vendor-dir}/typo3/cms" + } + }, + "autoload": { + "psr-4": { + "B13\\Container\\": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Create Custom Container Content Elements for TYPO3", + "homepage": "https://b13.com", + "support": { + "issues": "https://github.com/b13/container/issues", + "source": "https://github.com/b13/container/tree/3.2.3" + }, + "time": "2026-03-17T15:00:20+00:00" + }, { "name": "b13/typo3-updater", "version": "1.1.0", @@ -294,16 +356,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b" + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/6a9c2f0970022ab00dc58c07d0685dd712f2231b", - "reference": "6a9c2f0970022ab00dc58c07d0685dd712f2231b", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f", + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f", "shasum": "" }, "require": { @@ -347,7 +409,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.7.2" + "source": "https://github.com/composer/class-map-generator/tree/1.7.3" }, "funding": [ { @@ -359,7 +421,7 @@ "type": "github" } ], - "time": "2026-03-30T15:36:56+00:00" + "time": "2026-05-05T09:17:07+00:00" }, { "name": "composer/composer", @@ -1477,7 +1539,7 @@ "dist": { "type": "path", "url": "./packages/vitec", - "reference": "e877a4520781c02657b7740eb77996cf9ec81596" + "reference": "4c53c0e0e5a9a10369459cc9deaa39ebc0c8fd04" }, "require": { "typo3/cms-core": "^13.4" @@ -1576,16 +1638,16 @@ }, { "name": "friendsoftypo3/content-blocks", - "version": "1.4.6", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/FriendsOfTYPO3/content-blocks.git", - "reference": "40634174c8d4ac8bafd6ee23c69f00384015d951" + "reference": "1a940c40345f76e320ef527de3a558b2c6341d38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfTYPO3/content-blocks/zipball/40634174c8d4ac8bafd6ee23c69f00384015d951", - "reference": "40634174c8d4ac8bafd6ee23c69f00384015d951", + "url": "https://api.github.com/repos/FriendsOfTYPO3/content-blocks/zipball/1a940c40345f76e320ef527de3a558b2c6341d38", + "reference": "1a940c40345f76e320ef527de3a558b2c6341d38", "shasum": "" }, "require": { @@ -1595,7 +1657,7 @@ "symfony/dependency-injection": "^7.2", "symfony/filesystem": "^7.2", "symfony/finder": "^7.2", - "symfony/var-exporter": "^7.2", + "symfony/var-exporter": "^7.2 || ^8.0", "symfony/yaml": "^7.2", "typo3/cms-backend": "^13.4.19", "typo3/cms-core": "^13.4.19", @@ -1652,12 +1714,12 @@ "description": "TYPO3 CMS Content Blocks - Content Types API | Define reusable components via YAML", "homepage": "https://typo3.org", "support": { - "chat": "https://typo3.org/help", - "docs": "https://docs.typo3.org/p/friendsoftypo3/content-blocks/main/en-us/", - "issues": "https://forge.typo3.org", - "source": "https://github.com/typo3/typo3" + "chat": "https://typo3.slack.com/archives/C8Z2UM50Q", + "docs": "https://docs.typo3.org/p/friendsoftypo3/content-blocks/1.4/en-us/", + "issues": "https://github.com/FriendsOfTYPO3/content-blocks/issues", + "source": "https://github.com/FriendsOfTYPO3/content-blocks" }, - "time": "2026-04-17T08:35:51+00:00" + "time": "2026-04-30T07:01:48+00:00" }, { "name": "friendsoftypo3/headless", @@ -2075,17 +2137,85 @@ "time": "2026-03-10T16:41:02+00:00" }, { - "name": "justinrainbow/json-schema", - "version": "6.8.0", + "name": "itplusx/headless-container", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc" + "url": "https://github.com/itplusx/headless-container.git", + "reference": "70b6a44e156b248c8306f7a4d8d14883f7bb18d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", - "reference": "89ac92bcfe5d0a8a4433c7b89d394553ae7250cc", + "url": "https://api.github.com/repos/itplusx/headless-container/zipball/70b6a44e156b248c8306f7a4d8d14883f7bb18d7", + "reference": "70b6a44e156b248c8306f7a4d8d14883f7bb18d7", + "shasum": "" + }, + "require": { + "b13/container": ">=3.1.2", + "friendsoftypo3/headless": "^4.0", + "php": "^8.1", + "typo3/cms-core": "^12.4 || ^13.4" + }, + "replace": { + "typo3-ter/headless-container": "self.version" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "app-dir": ".Build", + "web-dir": ".Build/public", + "extension-key": "headless_container" + }, + "branch-alias": { + "dev-main": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "ITplusX\\HeadlessContainer\\": "Classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ramón Schlosser", + "email": "schlosser@itplusx.de", + "homepage": "https://itplusx.de", + "role": "Developer" + } + ], + "description": "Container Content Elements (EXT:container) json output for EXT:headless", + "homepage": "https://itplusx.de", + "keywords": [ + "Container Content Elements", + "TYPO3 CMS", + "container", + "extension", + "headless", + "typo3" + ], + "support": { + "email": "typo3@itplusx.de", + "issues": "https://github.com/itplusx/headless-container/issues", + "source": "https://github.com/itplusx/headless-container/tree/v3.0.0" + }, + "time": "2025-07-01T13:46:20+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "6.8.2", + "source": { + "type": "git", + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/2c89ebb95ca9cedc9347f780333f7b25792dcb76", + "reference": "2c89ebb95ca9cedc9347f780333f7b25792dcb76", "shasum": "" }, "require": { @@ -2095,7 +2225,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "^23.2", + "json-schema/json-schema-test-suite": "dev-main", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -2145,9 +2275,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.0" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.8.2" }, - "time": "2026-04-02T12:43:11+00:00" + "time": "2026-05-05T05:39:01+00:00" }, { "name": "lolli42/finediff", @@ -3772,16 +3902,16 @@ }, { "name": "symfony/cache", - "version": "v8.0.8", + "version": "v8.0.10", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78" + "reference": "8ff96cde73684bfa32b702f5cff1eb83b1fac429" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78", - "reference": "8abf3ccbeae9d3071b81a3ae7ee11b209f9e1e78", + "url": "https://api.github.com/repos/symfony/cache/zipball/8ff96cde73684bfa32b702f5cff1eb83b1fac429", + "reference": "8ff96cde73684bfa32b702f5cff1eb83b1fac429", "shasum": "" }, "require": { @@ -3793,7 +3923,6 @@ "symfony/var-exporter": "^7.4|^8.0" }, "conflict": { - "doctrine/dbal": "<4.3", "ext-redis": "<6.1", "ext-relay": "<0.12.1" }, @@ -3848,7 +3977,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v8.0.8" + "source": "https://github.com/symfony/cache/tree/v8.0.10" }, "funding": [ { @@ -3868,20 +3997,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:18:51+00:00" + "time": "2026-05-05T08:24:00+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + "reference": "225e8a254166bd3442e370c6f50145465db63831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", - "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/225e8a254166bd3442e370c6f50145465db63831", + "reference": "225e8a254166bd3442e370c6f50145465db63831", "shasum": "" }, "require": { @@ -3895,7 +4024,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -3928,7 +4057,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.7.0" }, "funding": [ { @@ -3939,12 +4068,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-13T15:25:07+00:00" + "time": "2026-05-05T15:33:14+00:00" }, { "name": "symfony/clock", @@ -4025,16 +4158,16 @@ }, { "name": "symfony/config", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2d19dde43fa2ff720b9a40763ace7226594f503b" + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2d19dde43fa2ff720b9a40763ace7226594f503b", - "reference": "2d19dde43fa2ff720b9a40763ace7226594f503b", + "url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", + "reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57", "shasum": "" }, "require": { @@ -4080,7 +4213,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.8" + "source": "https://github.com/symfony/config/tree/v7.4.10" }, "funding": [ { @@ -4100,20 +4233,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-03T14:20:49+00:00" }, { "name": "symfony/console", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707" + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", - "reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707", + "url": "https://api.github.com/repos/symfony/console/zipball/d7d2b64a45a89d607865927b176fa51c33ddbb58", + "reference": "d7d2b64a45a89d607865927b176fa51c33ddbb58", "shasum": "" }, "require": { @@ -4178,7 +4311,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.8" + "source": "https://github.com/symfony/console/tree/v7.4.9" }, "funding": [ { @@ -4198,20 +4331,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T13:54:39+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "f7025fd7b687c240426562f86ada06a93b1e771d" + "reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f7025fd7b687c240426562f86ada06a93b1e771d", - "reference": "f7025fd7b687c240426562f86ada06a93b1e771d", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d", + "reference": "4eb0d9dfa9d4f7c59216baf49b3ed6b1fb72293d", "shasum": "" }, "require": { @@ -4262,7 +4395,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.8" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.10" }, "funding": [ { @@ -4282,20 +4415,20 @@ "type": "tidelift" } ], - "time": "2026-03-31T06:50:29+00:00" + "time": "2026-05-06T11:55:30+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -4308,7 +4441,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4333,7 +4466,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -4344,12 +4477,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/doctrine-messenger", @@ -4429,16 +4566,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v8.0.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6" + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0c3c1a17604c4dbbec4b93fe162c538482096e1f", + "reference": "0c3c1a17604c4dbbec4b93fe162c538482096e1f", "shasum": "" }, "require": { @@ -4490,7 +4627,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.9" }, "funding": [ { @@ -4510,20 +4647,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/ccba7060602b7fed0b03c85bf025257f76d9ef32", + "reference": "ccba7060602b7fed0b03c85bf025257f76d9ef32", "shasum": "" }, "require": { @@ -4537,7 +4674,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -4570,7 +4707,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.0" }, "funding": [ { @@ -4581,12 +4718,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-01-05T13:30:16+00:00" }, { "name": "symfony/expression-language", @@ -4658,16 +4799,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5" + "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/58b9790d12f9670b7f53a1c1738febd3108970a5", - "reference": "58b9790d12f9670b7f53a1c1738febd3108970a5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/dcd8f96bcdc0f128ec406c765cc066c6035d1be3", + "reference": "dcd8f96bcdc0f128ec406c765cc066c6035d1be3", "shasum": "" }, "require": { @@ -4704,7 +4845,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.8" + "source": "https://github.com/symfony/filesystem/tree/v7.4.9" }, "funding": [ { @@ -4724,7 +4865,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-18T13:18:21+00:00" }, { "name": "symfony/finder", @@ -4962,16 +5103,16 @@ }, { "name": "symfony/messenger", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/messenger.git", - "reference": "ddf5ab29bc0329ece30e16f01c86abb6241e92d8" + "reference": "8538bd43f3c928ab3400250b1e973698d28286fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/messenger/zipball/ddf5ab29bc0329ece30e16f01c86abb6241e92d8", - "reference": "ddf5ab29bc0329ece30e16f01c86abb6241e92d8", + "url": "https://api.github.com/repos/symfony/messenger/zipball/8538bd43f3c928ab3400250b1e973698d28286fd", + "reference": "8538bd43f3c928ab3400250b1e973698d28286fd", "shasum": "" }, "require": { @@ -5032,7 +5173,7 @@ "description": "Helps applications send and receive messages to/from other applications or via message queues", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/messenger/tree/v7.4.8" + "source": "https://github.com/symfony/messenger/tree/v7.4.10" }, "funding": [ { @@ -5052,20 +5193,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T12:55:43+00:00" + "time": "2026-05-06T11:21:16+00:00" }, { "name": "symfony/mime", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379" + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6df02f99998081032da3407a8d6c4e1dcb5d4379", - "reference": "6df02f99998081032da3407a8d6c4e1dcb5d4379", + "url": "https://api.github.com/repos/symfony/mime/zipball/2d550c4758ba4c47519a6667c36553d535705b0c", + "reference": "2d550c4758ba4c47519a6667c36553d535705b0c", "shasum": "" }, "require": { @@ -5121,7 +5262,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.8" + "source": "https://github.com/symfony/mime/tree/v7.4.9" }, "funding": [ { @@ -5141,7 +5282,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T14:11:46+00:00" + "time": "2026-04-29T13:21:53+00:00" }, { "name": "symfony/options-resolver", @@ -5216,7 +5357,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5275,7 +5416,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -5299,16 +5440,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df" + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df", - "reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", + "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", "shasum": "" }, "require": { @@ -5357,7 +5498,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" }, "funding": [ { @@ -5377,11 +5518,11 @@ "type": "tidelift" } ], - "time": "2026-04-10T16:19:22+00:00" + "time": "2026-04-26T13:13:48+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", @@ -5444,7 +5585,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" }, "funding": [ { @@ -5468,7 +5609,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5529,7 +5670,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" }, "funding": [ { @@ -5553,7 +5694,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -5614,7 +5755,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" }, "funding": [ { @@ -5638,7 +5779,7 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -5694,7 +5835,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.37.0" }, "funding": [ { @@ -5718,7 +5859,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -5778,7 +5919,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { @@ -5802,7 +5943,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -5858,7 +5999,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0" }, "funding": [ { @@ -5882,7 +6023,7 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", @@ -5938,7 +6079,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" }, "funding": [ { @@ -5962,7 +6103,7 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", @@ -6018,7 +6159,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" }, "funding": [ { @@ -6042,7 +6183,7 @@ }, { "name": "symfony/polyfill-uuid", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", @@ -6101,7 +6242,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.36.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.37.0" }, "funding": [ { @@ -6361,16 +6502,16 @@ }, { "name": "symfony/rate-limiter", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "d55de9ec479418f58464e122e68d33886cf6f1fb" + "reference": "778c5239c7fd6bf9b886dedf3d84ddb156ddb888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/d55de9ec479418f58464e122e68d33886cf6f1fb", - "reference": "d55de9ec479418f58464e122e68d33886cf6f1fb", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/778c5239c7fd6bf9b886dedf3d84ddb156ddb888", + "reference": "778c5239c7fd6bf9b886dedf3d84ddb156ddb888", "shasum": "" }, "require": { @@ -6411,7 +6552,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v7.4.8" + "source": "https://github.com/symfony/rate-limiter/tree/v7.4.10" }, "funding": [ { @@ -6431,20 +6572,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-04T13:25:50+00:00" }, { "name": "symfony/routing", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b" + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", - "reference": "9608de9873ec86e754fb6c0a0fa7e5f1a960eb6b", + "url": "https://api.github.com/repos/symfony/routing/zipball/287771d8bc86eacb30678dd10eda6c64a859951f", + "reference": "287771d8bc86eacb30678dd10eda6c64a859951f", "shasum": "" }, "require": { @@ -6496,7 +6637,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.8" + "source": "https://github.com/symfony/routing/tree/v7.4.9" }, "funding": [ { @@ -6516,20 +6657,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-22T15:21:55+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -6547,7 +6688,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6583,7 +6724,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -6603,7 +6744,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "symfony/string", @@ -6697,16 +6838,16 @@ }, { "name": "symfony/type-info", - "version": "v8.0.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "622d81551770029d44d16be68969712eb47892f1" + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/622d81551770029d44d16be68969712eb47892f1", - "reference": "622d81551770029d44d16be68969712eb47892f1", + "url": "https://api.github.com/repos/symfony/type-info/zipball/08723aceb8c3271e8cb3db8b2565728b0c88e866", + "reference": "08723aceb8c3271e8cb3db8b2565728b0c88e866", "shasum": "" }, "require": { @@ -6755,7 +6896,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v8.0.8" + "source": "https://github.com/symfony/type-info/tree/v8.0.9" }, "funding": [ { @@ -6775,20 +6916,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-04-29T15:02:55+00:00" }, { "name": "symfony/uid", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56" + "reference": "2676b524340abcfe4d6151ec698463cebafee439" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/6883ebdf7bf6a12b37519dbc0df62b0222401b56", - "reference": "6883ebdf7bf6a12b37519dbc0df62b0222401b56", + "url": "https://api.github.com/repos/symfony/uid/zipball/2676b524340abcfe4d6151ec698463cebafee439", + "reference": "2676b524340abcfe4d6151ec698463cebafee439", "shasum": "" }, "require": { @@ -6833,7 +6974,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.8" + "source": "https://github.com/symfony/uid/tree/v7.4.9" }, "funding": [ { @@ -6853,30 +6994,29 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-30T15:19:22+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.4.8", + "version": "v8.0.9", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225" + "reference": "24cf67be4dd0926e4413635418682f4fff831412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/398907e89a2a56fe426f7955c6fa943ec0c77225", - "reference": "398907e89a2a56fe426f7955c6fa943ec0c77225", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/24cf67be4dd0926e4413635418682f4fff831412", + "reference": "24cf67be4dd0926e4413635418682f4fff831412", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.4" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -6914,7 +7054,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.4.8" + "source": "https://github.com/symfony/var-exporter/tree/v8.0.9" }, "funding": [ { @@ -6934,20 +7074,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-04-18T13:51:42+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.8", + "version": "v7.4.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c58fdf7b3d6c2995368264c49e4e8b05bcff2883" + "reference": "c660d6538545a3e8e65a5621ee3d7a6d352892c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c58fdf7b3d6c2995368264c49e4e8b05bcff2883", - "reference": "c58fdf7b3d6c2995368264c49e4e8b05bcff2883", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c660d6538545a3e8e65a5621ee3d7a6d352892c7", + "reference": "c660d6538545a3e8e65a5621ee3d7a6d352892c7", "shasum": "" }, "require": { @@ -6990,7 +7130,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.8" + "source": "https://github.com/symfony/yaml/tree/v7.4.10" }, "funding": [ { @@ -7010,7 +7150,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-05T08:01:55+00:00" }, { "name": "typo3/class-alias-loader", @@ -8880,16 +9020,16 @@ }, { "name": "typo3/html-sanitizer", - "version": "v2.2.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/TYPO3/html-sanitizer.git", - "reference": "c672a2e02925de8eed0dcaeb3a3c90d3642049a0" + "reference": "988caa31b5fa0dbe17a8331bfa3245898a650d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/TYPO3/html-sanitizer/zipball/c672a2e02925de8eed0dcaeb3a3c90d3642049a0", - "reference": "c672a2e02925de8eed0dcaeb3a3c90d3642049a0", + "url": "https://api.github.com/repos/TYPO3/html-sanitizer/zipball/988caa31b5fa0dbe17a8331bfa3245898a650d88", + "reference": "988caa31b5fa0dbe17a8331bfa3245898a650d88", "shasum": "" }, "require": { @@ -8925,9 +9065,9 @@ "description": "HTML sanitizer aiming to provide XSS-safe markup based on explicitly allowed tags, attributes and values.", "support": { "issues": "https://github.com/TYPO3/html-sanitizer/issues", - "source": "https://github.com/TYPO3/html-sanitizer/tree/v2.2.0" + "source": "https://github.com/TYPO3/html-sanitizer/tree/v2.3.1" }, - "time": "2024-07-12T15:52:25+00:00" + "time": "2026-04-30T11:50:45+00:00" }, { "name": "typo3fluid/fluid", diff --git a/config/sites/vitec/config.yaml b/config/sites/vitec/config.yaml new file mode 100644 index 0000000..0f8c708 --- /dev/null +++ b/config/sites/vitec/config.yaml @@ -0,0 +1,50 @@ +base: / +dependencies: + - typo3/fluid-styled-content + - typo3/fluid-styled-content-css + - typo3/felogin + - typo3/form + - typo3/seo-sitemap + - friendsoftypo3/headless + - friendsoftypo3/headless-mixed + - itplusx/headless-container + - nb-headless-content-blocks/headless-content-blocks + - evomedien/vitecset + - vitec/content-blocks-bundle + - vitec/cta-banner + - vitec/herosection +frontendBase: '' +headless: 1 +languages: + - + title: English + enabled: true + languageId: 0 + base: / + locale: en_US.UTF-8 + navigationTitle: English + flag: us +rootPageId: 1 +routeEnhancers: + ProductPlugin: + type: Extbase + limitToPages: + - 10 + extension: Vitec + plugin: Productshow + routes: + - + routePath: '/{product_slug}' + _controller: 'Product::show' + _arguments: + product_slug: product + defaultController: 'Product::show' + requirements: + product_slug: '[a-zA-Z0-9\-]+' + aspects: + product_slug: + type: PersistedAliasMapper + tableName: tx_vitec_domain_model_product + routeFieldName: slug + routeValuePrefix: '' +websiteTitle: '' diff --git a/config/sites/vitec/settings.yaml b/config/sites/vitec/settings.yaml new file mode 100644 index 0000000..434c564 --- /dev/null +++ b/config/sites/vitec/settings.yaml @@ -0,0 +1,4 @@ +vitec.debugMode: true +my.example.setting: Test +menu.footer.pageUids: '1,2,3' +menu.meta.pageUids: '5' diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3ec4110 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "live", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/packages/vitec/.editorconfig b/packages/vitec/.editorconfig new file mode 100644 index 0000000..c9705d2 --- /dev/null +++ b/packages/vitec/.editorconfig @@ -0,0 +1,55 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# TS/JS-Files +[*.{ts,js}] +indent_size = 2 + +# JSON files +[*.json] +indent_style = tab + +# package.json +[package.json] +indent_size = 2 + +# ReST files +[*.rst] +indent_size = 3 +max_line_length = 80 + +# SQL files +[*.sql] +indent_style = tab +indent_size = 2 + +# TypoScript files +[*.{typoscript,tsconfig}] +indent_size = 2 + +# YAML files +[{*.yml,*.yaml}] +indent_size = 2 + +# XLF files +[*.xlf] +indent_style = tab + +# .htaccess +[.htaccess] +indent_style = tab + +# Markdown files +[*.md] +max_line_length = 80 diff --git a/packages/vitec/Classes/Controller/ComponentCollection.php b/packages/vitec/Classes/Controller/ComponentCollection.php new file mode 100644 index 0000000..4eb5faf --- /dev/null +++ b/packages/vitec/Classes/Controller/ComponentCollection.php @@ -0,0 +1,19 @@ +setTemplateRootPaths([ + 'EXT:Vitec/Resources/Private/Components/', + ]); + return $templatePaths; + } +} diff --git a/packages/vitec/Classes/Controller/DownloadController.php b/packages/vitec/Classes/Controller/DownloadController.php new file mode 100644 index 0000000..3ea69f7 --- /dev/null +++ b/packages/vitec/Classes/Controller/DownloadController.php @@ -0,0 +1,445 @@ +downloadRepository = $downloadRepository; + $this->productRepository = $productRepository; + $this->mailer = $mailer; + $this->responseFactory = $responseFactory; + } + + /** + * action list + */ + public function listAction(): ResponseInterface + { + $downloads = $this->downloadRepository->findAll(); + $this->view->assign('downloads', $downloads); + return $this->htmlResponse(); + } + + /** + * action show + */ + public function showAction(): ResponseInterface + { + $products = $this->productRepository->findAlphabetically(false, false); + $ps = []; + $used = []; + + foreach ($products as $p) { + if (!in_array($p->getUid(), $used)) { + $scat = $p->getProductsubcategory()->toArray(); + $cat = $p->getProductcategory()->toArray(); + if (!empty($scat)) { + $ps[$scat[0]->getTitle()][] = $p; + } elseif (!empty($cat)) { + $ps[$cat[0]->getTitle()][] = $p; + } + $used[] = $p->getUid(); + } + } + ksort($ps); + + $this->view->assign('settings', $this->settings); + $this->view->assign('products', $ps); + // $dcs = $this->dcRepository->fetchForWebsite(); + // $this->view->assign('dcs', $dcs); + + return $this->htmlResponse(); + } + + /** + * action directdownload + */ + public function directdownloadAction(Download $download): ResponseInterface + { + $json = $download->getForJSON(); + $file = Environment::getProjectPath() . $json['file']; + + if (!file_exists($file)) { + throw new \Exception('File not found', 404); + } + + $response = $this->responseFactory->createResponse() + ->withHeader('Content-Type', 'application/pdf') + ->withHeader('Content-Disposition', 'inline; filename="' . basename($file) . '"') + ->withHeader('Content-Transfer-Encoding', 'binary') + ->withHeader('Accept-Ranges', 'bytes') + ->withHeader('Content-Length', (string)filesize($file)) + ->withHeader('Pragma', 'public') + ->withHeader('Expires', '0') + ->withHeader('Cache-Control', 'must-revalidate'); + + $stream = new Stream(fopen($file, 'rb')); + return $response->withBody($stream); + } + + /** + * action ac + */ + public function acAction(): ResponseInterface + { + $downloads = []; + $request = $this->request; + + if (!empty($request->getQueryParams()['dc'])) { + $downloads = $this->downloadRepository->findByCategory($request->getQueryParams()['dc'], 1); + } elseif (!empty($request->getQueryParams()['k'])) { + $downloads = $this->downloadRepository->search($request->getQueryParams()['k'], 1); + } elseif (!empty($request->getQueryParams()['p'])) { + $downloads = $this->downloadRepository->findByProduct($request->getQueryParams()['p'], 1); + $product = $this->productRepository->findByUid($request->getQueryParams()['p']); + $this->view->assign('product', $product); + } + + $this->downloadInfo($downloads); + return $this->htmlResponse(); + } + + private function downloadInfo($downloads): void + { + if (!empty($downloads)) { + $info = []; + foreach ($downloads as $d) { + $file = $d->getFile(); + if (!empty($file)) { + $fileResourceReference = GeneralUtility::makeInstance(ResourceFactory::class) + ->getFileReferenceObject($file->getUid()); + $file = urldecode($fileResourceReference->getOriginalFile()->getPublicUrl()); + $i = pathinfo($file); + $i['size'] = $this->human_filesize(filesize($file)); + $info[$d->getUid()] = $i; + } + } + $this->view->assign('info', $info); + } + $this->view->assign('downloads', $downloads); + } + + private function human_filesize($bytes, $decimals = 2): string + { + $sz = 'BKMGTP'; + $factor = floor((strlen(strval($bytes)) - 1) / 3); + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor]; + } + + /** + * action download + */ + public function downloadAction(Download $download): ResponseInterface + { + return $this->directdownloadAction($download); + } + + public function combineddlAction(string $files): ResponseInterface + { + $ids = explode(',', $files); + $downloads = $this->downloadRepository->findByUids($ids); + $file = $this->createZip(md5($files), $downloads, null); + + if ($file !== false) { + $response = $this->responseFactory->createResponse() + ->withHeader('Content-disposition', 'attachment; filename=Vitec.zip') + ->withHeader('Content-type', 'application/zip') + ->withHeader('Content-Length', (string)filesize($file)); + + $stream = new Stream(fopen($file, 'rb')); + unlink($file); + return $response->withBody($stream); + } + + return $this->htmlResponse(); + } + + /** + * action datasheets + */ + public function datasheetsAction(): ResponseInterface + { + $productso = $this->productRepository->fetchForDatasheets(); + $sheets = []; + $faved = []; + $faves = []; + $request = $this->request; + + if (!empty($_COOKIE['datasheet-faves'])) { + $faved = json_decode($_COOKIE['datasheet-faves']); + } + + if (!empty($request->getParsedBody()['download'])) { + $downloads = $this->downloadRepository->findByUids($faved); + $file = $this->createZip($_COOKIE['datasheet-faves'], $downloads, null); + if ($file !== false) { + $response = $this->responseFactory->createResponse() + ->withHeader('Content-disposition', 'attachment; filename=Vitec.zip') + ->withHeader('Content-type', 'application/zip') + ->withHeader('Content-Length', (string)filesize($file)); + + $stream = new Stream(fopen($file, 'rb')); + unlink($file); + return $response->withBody($stream); + } + } + + if (!empty($productso)) { + foreach ($productso as $p) { + $downloads = $p->getDownload(); + foreach ($downloads as $d) { + $isSheet = false; + foreach ($d->getType() as $t) { + // filter download type ids + if ($t->getUid() == 1 || $t->getUid() == 3) { + $isSheet = true; + break; + } + } + if ($isSheet) { + $cat = null; + $maincategorysort = 0; + foreach ($p->getProductcategory() as $c) { + $cat = $c->getTitle(); + $maincategorysort = $c->getSort3(); + break; + } + $subcat = null; + $subcatsort = 0; + foreach ($p->getProductsubcategory() as $s) { + $subcat = $s->getTitle(); + $subcatsort = $s->getSort3(); + break; + } + $isFaved = false; + if (in_array($d->getUid(), $faved)) { + $faves[] = [ + 'product' => $p, + 'datasheet' => $d + ]; + $isFaved = true; + } + $sheets[] = [ + 'isfaved' => $isFaved, + 'category' => $cat, + 'sort' => $maincategorysort, + 'subcategory' => $subcat, + 'subcategorysort' => strval($subcatsort), + 'product' => $p, + 'datasheet' => $d + ]; + } + } + } + } + + usort($sheets, function ($a, $b) { + if ($a["sort"] === $b["sort"]) { + if ($a["subcategorysort"] === $b["subcategorysort"]) { + return strcmp($a["product"]->getTitle(), $b["product"]->getTitle()); + } else { + return strcmp($a["subcategorysort"], $b["subcategorysort"]); + } + } + return strcmp(strval($a["sort"]), strval($b["sort"])); + }); + + $this->view->assign('sheets', $sheets); + $this->view->assign('faves', $faves); + return $this->htmlResponse(); + } + + /** + * action index + */ + public function indexAction(): ResponseInterface + { + return $this->htmlResponse(); + } + + private function createZip($req, $downloads, $stories): string|false + { + $zip = new \ZipArchive(); + $tmp_file = '/tmp/' . sha1($req) . '.zip'; + + if ($zip->open($tmp_file, \ZipArchive::CREATE)) { + if (!empty($downloads)) { + foreach ($downloads as $download) { + $f = $download->getFilepfad(); + if ($f !== false) { + $zip->addFile($f, basename($f)); + } + } + } + if (!empty($stories)) { + foreach ($stories as $story) { + $f = $story['pdf']; + if ($f !== false) { + $zip->addFile($f, basename($f)); + } + } + } + $zip->close(); + return $tmp_file; + } + return false; + } + + /** + * action collection + */ + public function collectionAction(): ResponseInterface + { + $downloads = []; + $downloadids = []; + $stories = []; + $storyids = []; + $request = $this->request; + $parsedBody = $request->getParsedBody(); + + if (!empty($parsedBody['downloads'])) { + $ids = explode(',', $parsedBody['downloads']); + foreach ($ids as $id) { + $downloadids[] = intval($id); + } + $downloadids = implode(',', $downloadids); + $downloads = $this->downloadRepository->findByUids($ids); + } + + if (!empty($parsedBody['stories'])) { + $ids = explode(',', $parsedBody['stories']); + foreach ($ids as $id) { + $storyids[] = intval($id); + } + $storyids = implode(',', $storyids); + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + $whereExpressions = [ + $queryBuilder->expr()->eq('colPos', $queryBuilder->createNamedParameter(0)), + $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(18)), + $queryBuilder->expr()->in( + 'uid', + $queryBuilder->createNamedParameter( + explode(',', $storyids), + Connection::PARAM_INT_ARRAY + ) + ), + $queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('mask_use_case')), + $queryBuilder->expr()->eq('tx_mask_app_visible', $queryBuilder->createNamedParameter(1)) + ]; + + $result = $queryBuilder->select('tt_content.*') + ->from('tt_content') + ->where(...$whereExpressions) + ->orderBy('tt_content.sorting') + ->executeQuery(); + $rows = $result->fetchAllAssociative(); + + $fileRepository = GeneralUtility::makeInstance(FileRepository::class); + + foreach ($rows as $content) { + $pdf_url = ''; + $filename = ''; + if (!empty($content['tx_mask_detail_pdf'])) { + try { + $filename = Environment::getProjectPath() . '/' . $content['tx_mask_detail_pdf']; + $pdf_url = $this->uriBuilder->setTargetPageUid(1)->buildFrontendUri() . + substr(str_replace(Environment::getProjectPath(), '', $filename), 1); + } catch (\Exception $e) { + // Handle exception + } + } + $stories[$content['uid']] = [ + 'uid' => $content['uid'], + 'header' => $content['header'], + 'pdf' => $filename, + 'detail_pdf' => $pdf_url + ]; + } + } + + if (!empty($parsedBody['email']) && + filter_var($parsedBody['email'], FILTER_VALIDATE_EMAIL) && + (!empty($parsedBody['downloads']) || !empty($parsedBody['stories']))) { + + $file = $this->createZip($parsedBody['downloads'] . $parsedBody['stories'], $downloads, $stories); + if ($file !== false) { + $email = GeneralUtility::makeInstance(FluidEmail::class); + $email->from('downloads@vitec.com', 'VITEC') + ->to($parsedBody['email']) + ->subject('VITEC Datasheets download') + ->text('Thank you for your interest in VITEC products. Attached, please find your Datasheets ready for download.') + ->attachFromPath($file, 'Vitec.zip'); + + $this->mailer->send($email); + + $redirect = $this->uriBuilder->setTargetPageUid(186)->setCreateAbsoluteUri(true)->buildFrontendUri(); + return $this->redirectToUri($redirect); + } + } elseif (!empty($parsedBody['downloads']) || !empty($parsedBody['stories'])) { + $file = $this->createZip($parsedBody['downloads'] . $parsedBody['stories'], $downloads, $stories); + if ($file !== false) { + $response = $this->responseFactory->createResponse() + ->withHeader('Content-disposition', 'attachment; filename=Vitec.zip') + ->withHeader('Content-type', 'application/zip') + ->withHeader('Content-Length', (string)filesize($file)); + + $stream = new Stream(fopen($file, 'rb')); + unlink($file); + return $response->withBody($stream); + } + } + + $this->view->assign('downloads', $downloads); + $this->view->assign('downloadids', $downloadids); + $this->view->assign('stories', $stories); + $this->view->assign('storyids', $storyids); + + return $this->htmlResponse(); + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/Controller/MarketController.php b/packages/vitec/Classes/Controller/MarketController.php new file mode 100644 index 0000000..e4aca8d --- /dev/null +++ b/packages/vitec/Classes/Controller/MarketController.php @@ -0,0 +1,60 @@ +marketRepository = $marketRepository; + } + + /** + * action show + * + * @return ResponseInterface + */ + public function showAction(): ResponseInterface + { + $selectedMarketId = (int)$this->settings['market']; + $market = null; + if ($selectedMarketId) { + $market = $this->marketRepository->findByUid($selectedMarketId); + } + + $this->view->assign('market', $market); + + return $this->htmlResponse(); + } +} diff --git a/packages/vitec/Classes/Controller/ProductController.php b/packages/vitec/Classes/Controller/ProductController.php new file mode 100644 index 0000000..cae6f74 --- /dev/null +++ b/packages/vitec/Classes/Controller/ProductController.php @@ -0,0 +1,199 @@ +productRepository = $productRepository; + } + + /** + * Inject the TitleProvider + * + * @param \Evomedien\Vitec\PageTitle\ProductPageTitleProvider $titleProvider + */ + public function injectTitleProvider(ProductPageTitleProvider $titleProvider): void + { + $this->titleProvider = $titleProvider; + } + /** + * @var LinkService + */ + protected $linkService; + + /** + * Constructor + * + * @param ProductRepository $productRepository + * @param ProductPageTitleProvider $titleProvider + * @param LinkService $linkService + */ + public function __construct( + ProductRepository $productRepository, + ProductPageTitleProvider $titleProvider, + + ) { + $this->productRepository = $productRepository; + $this->titleProvider = $titleProvider; + + } + /** + * action index + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function indexAction(): \Psr\Http\Message\ResponseInterface + { + return $this->htmlResponse(); + } + + /** + * action list + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function listAction(): \Psr\Http\Message\ResponseInterface + { + // Extract and sanitize category UIDs from FlexForm + $categoryUids = array_filter( + array_map('intval', explode(',', (string)($this->settings['categories'] ?? ''))) + ); + + // Get filtered products based on categories and visibility flags + $products = $this->productRepository->findFilteredProducts($categoryUids); + + $this->view->assign('products', $products); + + // Check if we're in headless mode (JSON request) + $request = $this->request; + $pageType = $request->getAttribute('routing')?->getPageType() ?? 0; + + if ($pageType === 834) { // Headless JSON page type + return $this->jsonResponse(json_encode([ + 'products' => $this->serializeProducts($products) + ])); + } + + return $this->htmlResponse(); + } + + /** + * Serialize products to array for JSON output + */ + protected function serializeProducts($products): array + { + $productsData = []; + + foreach ($products as $product) { + $categories = []; + if ($product->getCategories()) { + foreach ($product->getCategories() as $category) { + $categories[] = [ + 'uid' => $category->getUid(), + 'title' => $category->getTitle(), + ]; + } + } + + $image = null; + if ($product->getProductimage() && $product->getProductimage()->count() > 0) { + $imageObject = $product->getProductimage()->current(); + $image = [ + 'uid' => $imageObject->getUid(), + 'url' => $imageObject->getOriginalResource()->getPublicUrl(), + 'title' => $imageObject->getTitle(), + 'alternative' => $imageObject->getAlternative(), + 'description' => $imageObject->getDescription(), + ]; + } + + $productsData[] = [ + 'uid' => $product->getUid(), + 'title' => $product->getTitle(), + 'subtitle' => $product->getSubtitle(), + 'teaser' => $product->getTeaser(), + 'description' => $product->getDescription(), + 'slug' => $product->getSlug(), + 'categories' => $categories, + 'image' => $image, + ]; + } + + return $productsData; + } + + /** + * action show + * + * @param \Evomedien\Vitec\Domain\Model\Product $product + * @return \Psr\Http\Message\ResponseInterface + */ + public function showAction(\Evomedien\Vitec\Domain\Model\Product $product): \Psr\Http\Message\ResponseInterface + { + + $metaTagManager = GeneralUtility::makeInstance(MetaTagManagerRegistry::class)->getManagerForProperty('og:title'); + $metaTagManager->addProperty('og:title', ($product->getSeotitle())); + $metaTagManager->addProperty('og:description', ($product->getDescription())); + //$metaTagManager->addProperty('og:url', $this->request->getRequestUri()); + $metaTagManager->addProperty('og:type', 'product'); + + if ($product->getOgimage()) { + $ogImageUrl = $product->getOgimage()->getOriginalResource()->getPublicUrl(); + $metaTagManager->addProperty('og:image', $ogImageUrl); + } + + // Access the layout setting from FlexForm + $layout = $this->settings['layout'] ?? 0; + + + // Pass the layout to the view + $this->view->assign('layout', $layout); + + $this->view->assign('product', $product); + $this->titleProvider->setSeoTitle($product->getSeotitle()); + return $this->htmlResponse(); + } +} diff --git a/packages/vitec/Classes/Controller/SimplecardController.php b/packages/vitec/Classes/Controller/SimplecardController.php new file mode 100644 index 0000000..1bf77f9 --- /dev/null +++ b/packages/vitec/Classes/Controller/SimplecardController.php @@ -0,0 +1,12 @@ +htmlResponse(); + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/Controller/SolutionController.php b/packages/vitec/Classes/Controller/SolutionController.php new file mode 100644 index 0000000..59a4305 --- /dev/null +++ b/packages/vitec/Classes/Controller/SolutionController.php @@ -0,0 +1,60 @@ +solutionRepository = $solutionRepository; + } + + /** + * action show + * + * @return ResponseInterface + */ + public function showAction(): ResponseInterface + { + $selectedSolutionId = (int)$this->settings['solution']; + $solution = null; + if ($selectedSolutionId) { + $solution = $this->solutionRepository->findByUid($selectedSolutionId); + } + + $this->view->assign('solution', $solution); + + return $this->htmlResponse(); + } +} diff --git a/packages/vitec/Classes/Controller/UsecaseController.php b/packages/vitec/Classes/Controller/UsecaseController.php new file mode 100644 index 0000000..fb374a7 --- /dev/null +++ b/packages/vitec/Classes/Controller/UsecaseController.php @@ -0,0 +1,75 @@ +usecaseRepository = $usecaseRepository; + } + + /** + * action show + * + * @param Usecase $usecase + * @return ResponseInterface + */ + public function showAction(): ResponseInterface + { + $selectedUsecaseId = (int)$this->settings['usecase']; // Die ID aus dem Flexform + $usecase = null; + if ($selectedUsecaseId) { + $usecase = $this->usecaseRepository->findByUid($selectedUsecaseId); + } + + $this->view->assign('usecase', $usecase); + + return $this->htmlResponse(); + } + + /** + * action list + * + * @param Usecase $usecase + * @return ResponseInterface + */ + public function listAction(): ResponseInterface + { + $usecases = $this->usecaseRepository->findAllWithCategories(); + $this->view->assign('usecases', $usecases); + + return $this->htmlResponse(); + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php b/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php new file mode 100755 index 0000000..7bc36d7 --- /dev/null +++ b/packages/vitec/Classes/DataProcessing/ContainerChildrenProcessor.php @@ -0,0 +1,157 @@ +data['uid'] ?? 0); + if ($parentUid <= 0) { + $processedData[$as] = []; + return $processedData; + } + + $pid = (int)($cObj->data['pid'] ?? 0); + $sysLanguageUid = (int)($cObj->data['sys_language_uid'] ?? 0); + + $qb = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + $rows = $qb + ->select('*') + ->from('tt_content') + ->where( + $qb->expr()->eq('tx_container_parent', $qb->createNamedParameter($parentUid, Connection::PARAM_INT)), + $qb->expr()->eq('pid', $qb->createNamedParameter($pid, Connection::PARAM_INT)), + $qb->expr()->eq('sys_language_uid', $qb->createNamedParameter($sysLanguageUid, Connection::PARAM_INT)) + ) + ->orderBy('colPos') + ->addOrderBy('sorting') + ->executeQuery() + ->fetchAllAssociative(); + + $byColPos = []; + foreach ($rows as $record) { + $byColPos[(int)$record['colPos']][] = $this->normalise($record); + } + ksort($byColPos); + + $items = []; + foreach ($byColPos as $colPos => $contentElements) { + $items[] = [ + 'config' => ['colPos' => $colPos], + 'contentElements' => $contentElements, + ]; + } + + $processedData[$as] = $items; + } catch (\Throwable $e) { + $processedData[$as] = []; + } + + return $processedData; + } + + private function normalise(array $record): array + { + $data = []; + foreach ($record as $field => $value) { + if (in_array($field, self::ENVELOPE, true)) { + continue; + } + if (in_array($field, self::SYSTEM_FIELDS, true)) { + continue; + } + if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) { + continue; + } + $data[$field] = $this->castValue($field, $value); + } + + return [ + 'id' => (int)$record['uid'], + 'type' => (string)$record['CType'], + 'colPos' => (int)$record['colPos'], + 'sorting' => (int)($record['sorting'] ?? 0), + 'appearance' => [ + 'layout' => (string)($record['layout'] ?? ''), + 'frameClass' => (string)($record['frame_class'] ?? 'default'), + 'spaceBefore' => (string)($record['space_before_class'] ?? ''), + 'spaceAfter' => (string)($record['space_after_class'] ?? ''), + ], + 'data' => (object)$data, + ]; + } + + private function isEmpty(mixed $value): bool + { + return $value === null + || $value === '' + || $value === 0 + || $value === '0'; + } + + private function castValue(string $field, mixed $value): mixed + { + if (in_array($field, ['header_layout'], true)) { + return (int)$value; + } + return $value; + } +} diff --git a/packages/vitec/Classes/DataProcessing/ProductListProcessor.php b/packages/vitec/Classes/DataProcessing/ProductListProcessor.php new file mode 100644 index 0000000..6406cde --- /dev/null +++ b/packages/vitec/Classes/DataProcessing/ProductListProcessor.php @@ -0,0 +1,91 @@ +flexFormService->convertFlexFormContentToArray( + $processedData['data']['pi_flexform'] ?? '' + ); + + $settings = $flexFormData['settings'] ?? []; + + // Extract and sanitize category UIDs from FlexForm + $categoryUids = array_filter( + array_map('intval', explode(',', (string)($settings['categories'] ?? ''))) + ); + + // Get filtered products based on categories + $products = $this->productRepository->findFilteredProducts($categoryUids); + + // Serialize products directly + $productsData = []; + foreach ($products as $product) { + $categories = []; + if ($product->getCategories()) { + foreach ($product->getCategories() as $category) { + $categories[] = [ + 'uid' => $category->getUid(), + 'title' => $category->getTitle(), + ]; + } + } + + $image = null; + if ($product->getProductimage() && $product->getProductimage()->count() > 0) { + $imageObject = $product->getProductimage()->current(); + $image = [ + 'uid' => $imageObject->getUid(), + 'url' => $imageObject->getOriginalResource()->getPublicUrl(), + 'title' => $imageObject->getTitle(), + 'alternative' => $imageObject->getAlternative(), + 'description' => $imageObject->getDescription(), + ]; + } + + $productsData[] = [ + 'uid' => $product->getUid(), + 'title' => $product->getTitle(), + 'subtitle' => $product->getSubtitle(), + 'teaser' => $product->getTeaser(), + 'description' => $product->getDescription(), + 'slug' => $product->getSlug(), + 'categories' => $categories, + 'image' => $image, + ]; + } + + $processedData['products'] = $productsData; + + return $processedData; + } +} diff --git a/packages/vitec/Classes/DataProcessing/VitecProductProcessor.php b/packages/vitec/Classes/DataProcessing/VitecProductProcessor.php new file mode 100644 index 0000000..9f7d7db --- /dev/null +++ b/packages/vitec/Classes/DataProcessing/VitecProductProcessor.php @@ -0,0 +1,68 @@ +flexFormService->convertFlexFormContentToArray( + $processedData['data']['pi_flexform'] ?? '' + ); + + $settings = $flexFormData['settings'] ?? []; + + // Extract category UIDs + $categoryUids = array_filter( + array_map('intval', explode(',', (string)($settings['categories'] ?? ''))) + ); + + // Get products + $products = $this->productRepository->findFilteredProducts($categoryUids); + + // Serialize products + $productsData = []; + foreach ($products as $product) { + $productsData[] = [ + 'uid' => $product->getUid(), + 'title' => $product->getTitle(), + 'slug' => $product->getSlug(), + ]; + } + + // Add to processedData at content level + if (!isset($processedData['content'])) { + $processedData['content'] = []; + } + $processedData['content']['products'] = $productsData; + + return $processedData; + } +} diff --git a/packages/vitec/Classes/Domain/Model/Category.php b/packages/vitec/Classes/Domain/Model/Category.php new file mode 100644 index 0000000..7d371b1 --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Category.php @@ -0,0 +1,29 @@ +class; + } + + /** + * @param string $class + */ + public function setClass(string $class): void + { + $this->class = $class; + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Model/Download.php b/packages/vitec/Classes/Domain/Model/Download.php new file mode 100644 index 0000000..552b207 --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Download.php @@ -0,0 +1,55 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSlug(): string + { + return $this->slug; + } + + /** + * @param string $slug + */ + public function setSlug(string $slug): void + { + $this->slug = $slug; + } + +/* ----------------------------------------------------*/ +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Model/Dto/ProductListContentElement.php b/packages/vitec/Classes/Domain/Model/Dto/ProductListContentElement.php new file mode 100644 index 0000000..9cca595 --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Dto/ProductListContentElement.php @@ -0,0 +1,80 @@ +products = $products; + } + + public function setSettings(array $settings): void + { + $this->settings = $settings; + } + + public function jsonSerialize(): array + { + $productsData = []; + + foreach ($this->products as $product) { + $productsData[] = [ + 'uid' => $product->getUid(), + 'title' => $product->getTitle(), + 'subtitle' => $product->getSubtitle(), + 'teaser' => $product->getTeaser(), + 'description' => $product->getDescription(), + 'slug' => $product->getSlug(), + 'categories' => $this->serializeCategories($product->getCategories()), + 'image' => $this->serializeImage($product->getProductimage()), + ]; + } + + return [ + 'products' => $productsData, + 'settings' => $this->settings, + ]; + } + + protected function serializeCategories($categories): array + { + $categoriesData = []; + + if ($categories) { + foreach ($categories as $category) { + $categoriesData[] = [ + 'uid' => $category->getUid(), + 'title' => $category->getTitle(), + ]; + } + } + + return $categoriesData; + } + + protected function serializeImage($images): ?array + { + if (!$images || $images->count() === 0) { + return null; + } + + $imageObject = $images->current(); + + return [ + 'uid' => $imageObject->getUid(), + 'url' => $imageObject->getOriginalResource()->getPublicUrl(), + 'title' => $imageObject->getTitle(), + 'alternative' => $imageObject->getAlternative(), + 'description' => $imageObject->getDescription(), + ]; + } +} diff --git a/packages/vitec/Classes/Domain/Model/Market.php b/packages/vitec/Classes/Domain/Model/Market.php new file mode 100644 index 0000000..3ed79bb --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Market.php @@ -0,0 +1,163 @@ + + * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy + */ + protected $categories; + + public function __construct() + { + $this->categories = new ObjectStorage(); + } + +/* ---------------------------------------------- */ +/* Getter und Setter */ +/* ---------------------------------------------- */ + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSubtitle(): string + { + return $this->subtitle; + } + + /** + * @param string $subtitle + */ + public function setSubtitle(string $subtitle): void + { + $this->subtitle = $subtitle; + } + + /** + * @return string + */ + public function getTeaser(): string + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser(string $teaser): void + { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image): void + { + $this->image = $image; + } + + /** + * @param Category $category + */ + public function addCategory(Category $category): void + { + $this->categories->attach($category); + } + + /** + * @param Category $categoryToRemove + */ + public function removeCategory(Category $categoryToRemove): void + { + $this->categories->detach($categoryToRemove); + } + + /** + * @return ObjectStorage + */ + public function getCategories(): ObjectStorage + { + return $this->categories; + } + + /** + * @param ObjectStorage $categories + */ + public function setCategories(ObjectStorage $categories): void + { + $this->categories = $categories; + } +} diff --git a/packages/vitec/Classes/Domain/Model/Product.php b/packages/vitec/Classes/Domain/Model/Product.php new file mode 100644 index 0000000..a913f06 --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Product.php @@ -0,0 +1,922 @@ + + */ + protected $categories; + + /** + * Product images + * + * @var ObjectStorage + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $productimage; + + /** + * Downloads + * + * @var ObjectStorage<\Evomedien\Vitec\Domain\Model\Download> + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $downloads; + + + /** + * Open Graph Image + * + * @var FileReference + */ + protected $ogimage; + + /** + * Content element link - Key Features + * + * @var string + */ + protected $contentelement; + + /** + * Content element link - CTA Features + * + * @var string + */ + protected $contentelementcta; + + /** + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Evomedien\Vitec\Domain\Model\Product> + * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $relatedprodukt; + + +/* --------------------------------------------------------------------- */ + + /** + * Returns the title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Sets the title + * + * @param string $title + * @return void + */ + public function setTitle(string $title) + { + $this->title = $title; + } + + /** + * Returns the video + * + * @return string + */ + public function getVideo() + { + return $this->video; + } + + /** + * Sets the video + * + * @param string $video + * @return void + */ + public function setVideo(string $video) + { + $this->video = $video; + } + + /** + * Returns the slug + * + * @return string + */ + public function getSlug() + { + return $this->slug; + } + + /** + * Sets the slug + * + * @param string $slug + * @return void + */ + public function setSlug(string $slug) + { + $this->slug = $slug; + } + + /** + * Returns the urltitle + * + * @return string + */ + public function getUrltitle() + { + return $this->urltitle; + } + + /** + * Sets the urltitle + * + * @param string $urltitle + * @return void + */ + public function setUrltitle(string $urltitle) + { + $this->urltitle = $urltitle; + } + + + /** + * Returns the seotitle + * + * @return string + */ + public function getSeotitle() + { + return $this->seotitle; + } + + /** + * Sets the seotitle + * + * @param string $seotitle + * @return void + */ + public function setSeotitle(string $seotitle) + { + $this->seotitle = $seotitle; + } + + + /** + * Returns the seometa + * + * @return string $seometa + */ + public function getSeometa() + { + return $this->seometa; + } + + /** + * Sets the seometa + * + * @param string $seometa + * @return void + */ + public function setSeometa($seometa) + { + $this->seometa = $seometa; + } + + /** + * Returns the keywords + * + * @return string $keywords + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * Sets the keywords + * + * @param string $keywords + * @return void + */ + public function setKeywords($keywords) + { + $this->keywords = $keywords; + } + + /** + * Returns the teaser + * + * @return string $kteasereywords + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * Sets the teaser + * + * @param string $teaser + * @return void + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + + /** + * Returns the subtitle + * + * @return string $subtitle + */ + public function getSubtitle() + { + return $this->subtitle; + } + + /** + * Sets the subtitle + * + * @param string $subtitle + * @return void + */ + public function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + /** + * Returns the hideonapp + * + * @return bool $hideonapp + */ + public function getHideonapp() + { + return $this->hideonapp; + } + + /** + * Sets the hideonapp + * + * @param bool $hideonapp + * @return void + */ + public function setHideonapp($hideonapp) + { + $this->hideonapp = $hideonapp; + } + + /** + * Returns the description + * + * @return bool $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the description + * + * @param bool $description + * @return void + */ + public function setDescriptionp($description) + { + $this->description = $description; + } + + /** + * Returns the hideonwebsite + * + * @return bool $hideonwebsite + */ + public function getHideonwebsite() + { + return $this->hideonwebsite; + } + + /** + * Sets the hideonwebsite + * + * @param bool $hideonwebsite + * @return void + */ + public function setHideonwebsite($hideonwebsite) + { + $this->hideonwebsite = $hideonwebsite; + } + + /** + * Returns the hideondatasheets + * + * @return bool $hideondatasheets + */ + public function getHideondatasheets() + { + return $this->hideondatasheets; + } + + /** + * Sets the hideondatasheets + * + * @param bool $hideondatasheets + * @return void + */ + public function setHideondatasheets($hideondatasheets) + { + $this->hideondatasheets = $hideondatasheets; + } + + /** + * Returns the hideonproducts + * + * @return bool $hideonproducts + */ + public function getHideonproducts() + { + return $this->hideonproducts; + } + + /** + * Sets the hideonproducts + * + * @param bool $hideonproducts + * @return void + */ + public function setHideonproducts($hideonproducts) + { + $this->hideonproducts = $hideonproducts; + } + + /** + * Returns the structureddata + * + * @return string $structureddata + */ + public function getStructureddata() + { + return $this->structureddata; + } + + /** + * Sets the structureddata + * + * @param string $structureddata + * @return void + */ + public function setStructureddata($structureddata) + { + $this->structureddata = $structureddata; + } + + /** + * Returns the applications + * + * @return string $applications + */ + public function getApplications() + { + return $this->applications; + } + + /** + * Sets the applications + * + * @param string $applications + * @return void + */ + public function setApplications($applications) + { + $this->applications = $applications; + } + + /** + * Returns the highlights + * + * @return string $highlights + */ + public function getHighlights() + { + return $this->highlights; + } + + /** + * Sets the highlights + * + * @param string $highlights + * @return void + */ + public function setHighlights($highlights) + { + $this->highlights = $highlights; + } + /** + * Returns the shortcutpid + * + * @return string + */ + public function getShortcutpid() + { + return $this->shortcutpid; + } + + /** + * Sets the shortcutpid + * + * @param string $shortcutpid + * @return void + */ + public function setShortcutpid ($shortcutpid) + { + $this->shortcutpid = $shortcutpid; + } + + /** + * Returns the shortcut + * + * @return bool $shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Sets the shortcut + * + * @param bool $shortcut + * @return void + */ + public function setShortcut($shortcut) + { + $this->shortcut = $shortcut; + } + + /** + * Returns the legacy + * + * @return bool $legacy + */ + public function getLegacy() + { + return $this->legacy; + } + + /** + * Sets the legacy + * + * @param bool $legacy + * @return void + */ + public function setLegacy($legacy) + { + $this->legacy = $legacy; + } + + /** + * Returns the supportproduct + * + * @return bool $supportproduct + */ + public function getSupportproduct() + { + return $this->supportproduct; + } + + /** + * Sets the supportproduct + * + * @param bool $supportproduct + * @return void + */ + public function setSupportproduct($supportproduct) + { + $this->supportproduct = $supportproduct; + } + + /** + * Returns the subproduct + * + * @return bool $subproduct + */ + public function getSubproduct() + { + return $this->subproduct; + } + + /** + * Sets the subproduct + * + * @param bool $subproduct + * @return void + */ + public function setSubproduct($subproduct) + { + $this->subproduct = $subproduct; + } + + /** + * Initializes the ObjectStorage for categories + */ + public function __construct() + { + $this->categories = new ObjectStorage(); + $this->productimage = new ObjectStorage(); + $this->downloads = new ObjectStorage(); + } + + /** + * Adds a category + * + * @param Category $category + * @return void + */ + public function addCategory(Category $category) + { + $this->categories->attach($category); + } + + /** + * Removes a category + * + * @param Category $categoryToRemove + * @return void + */ + public function removeCategory(Category $categoryToRemove) + { + $this->categories->detach($categoryToRemove); + } + + /** + * Returns the categories + * + * @return ObjectStorage + */ + public function getCategories() + { + return $this->categories; + } + + /** + * Sets the categories + * + * @param ObjectStorage $categories + * @return void + */ + public function setCategories(ObjectStorage $categories) + { + $this->categories = $categories; + } + + + /** + * Adds a product image + * + * @param FileReference $productimage + * @return void + */ + public function addProductimage(FileReference $productimage) + { + $this->productimage->attach($productimage); + } + + /** + * Removes a product image + * + * @param FileReference $productimageToRemove + * @return void + */ + public function removeProductimage(FileReference $productimageToRemove) + { + $this->productimage->detach($productimageToRemove); + } + + /** + * Returns the product images + * + * @return ObjectStorage + */ + public function getProductimage() + { + return $this->productimage; + } + + /** + * Sets the product images + * + * @param ObjectStorage $productimage + * @return void + */ + public function setProductimage(ObjectStorage $productimage) + { + $this->productimage = $productimage; + } + + + /** + * Adds a download + * + * @param \Evomedien\Vitec\Domain\Model\Download $download + * @return void + */ + public function addDownload(\Evomedien\Vitec\Domain\Model\Download $download): void + { + $this->downloads->attach($download); + } + + /** + * Removes a download + * + * @param \Evomedien\Vitec\Domain\Model\Download $downloadToRemove + * @return void + */ + public function removeDownload(\Evomedien\Vitec\Domain\Model\Download $downloadToRemove): void + { + $this->downloads->detach($downloadToRemove); + } + + /** + * Returns the downloads + * + * @return ObjectStorage<\Evomedien\Vitec\Domain\Model\Download> + */ + public function getDownloads(): ObjectStorage + { + return $this->downloads; + } + + /** + * Sets the downloads + * + * @param ObjectStorage<\Evomedien\Vitec\Domain\Model\Download> $downloads + * @return void + */ + public function setDownloads(ObjectStorage $downloads): void + { + $this->downloads = $downloads; + } + + + /** + * Returns the Open Graph Image + * + * @return FileReference|null + */ + public function getOgimage(): ?FileReference + { + return $this->ogimage; + } + + /** + * Sets the Open Graph Image + * + * @param FileReference $ogimage + * @return void + */ + public function setOgimage(FileReference $ogimage): void + { + $this->ogimage = $ogimage; + } + + /** + * Returns the content element link + * + * @return string + */ + public function getContentelement(): string + { + return $this->contentelement; + } + + /** + * Sets the content element link + * + * @param string $contentelement + * @return void + */ + public function setContentelement(string $contentelement): void + { + $this->contentelement = $contentelement; + } + + /** + * Returns the content element cta link + * + * @return string + */ + public function getContentelementcta(): string + { + return $this->contentelementcta; + } + + /** + * Sets the content element link cta + * + * @param string $contentelementcta + * @return void + */ + public function setContentelementcta(string $contentelementcta): void + { + $this->contentelementcta = $contentelementcta; + } + + /** + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product> + */ + public function getRelatedprodukt(): \TYPO3\CMS\Extbase\Persistence\ObjectStorage + { + return $this->relatedprodukt; + } + + /** + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Extension\Domain\Model\Product> $relatedprodukt + */ + public function setRelatedprodukt(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $relatedprodukt): void + { + $this->relatedprodukt = $relatedprodukt; + } +/* --------------------------------------------------------------------- */ + +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Model/Solution.php b/packages/vitec/Classes/Domain/Model/Solution.php new file mode 100644 index 0000000..aa6243c --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Solution.php @@ -0,0 +1,163 @@ + + * @TYPO3\CMS\Extbase\Annotation\ORM\Lazy + */ + protected $categories; + + public function __construct() + { + $this->categories = new ObjectStorage(); + } + +/* ---------------------------------------------- */ +/* Getter und Setter */ +/* ---------------------------------------------- */ + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSubtitle(): string + { + return $this->subtitle; + } + + /** + * @param string $subtitle + */ + public function setSubtitle(string $subtitle): void + { + $this->subtitle = $subtitle; + } + + /** + * @return string + */ + public function getTeaser(): string + { + return $this->teaser; + } + + /** + * @param string $teaser + */ + public function setTeaser(string $teaser): void + { + $this->teaser = $teaser; + } + + /** + * @return string + */ + public function getDescription(): string + { + return $this->description; + } + + /** + * @param string $description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } + + /** + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null + */ + public function getImage() + { + return $this->image; + } + + /** + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image + */ + public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image): void + { + $this->image = $image; + } + + /** + * @param Category $category + */ + public function addCategory(Category $category): void + { + $this->categories->attach($category); + } + + /** + * @param Category $categoryToRemove + */ + public function removeCategory(Category $categoryToRemove): void + { + $this->categories->detach($categoryToRemove); + } + + /** + * @return ObjectStorage + */ + public function getCategories(): ObjectStorage + { + return $this->categories; + } + + /** + * @param ObjectStorage $categories + */ + public function setCategories(ObjectStorage $categories): void + { + $this->categories = $categories; + } +} diff --git a/packages/vitec/Classes/Domain/Model/Usecase.php b/packages/vitec/Classes/Domain/Model/Usecase.php new file mode 100644 index 0000000..62d08fa --- /dev/null +++ b/packages/vitec/Classes/Domain/Model/Usecase.php @@ -0,0 +1,335 @@ + + * @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove") + */ + protected $categories; + +/* ---------------------------------------------- */ +/* Getter und Setter */ +/* ---------------------------------------------- */ + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getSlug(): string + { + return $this->slug; + } + + /** + * @param string $slug + */ + public function setSlug(string $slug): void + { + $this->slug = $slug; + } + + /** + * @return string + */ + public function getSinglepid(): string + { + return $this->singlepid; + } + + /** + * @param string $singlepid + */ + public function setSinglepid(string $singlepid): void + { + $this->singlepid = $singlepid; + } +/** + * Returns the teaser + * + * @return string $kteasereywords + */ + public function getTeaser() + { + return $this->teaser; + } + + /** + * Sets the teaser + * + * @param string $teaser + * @return void + */ + public function setTeaser($teaser) + { + $this->teaser = $teaser; + } + /** + * Returns the subtitle + * + * @return string $subtitle + */ + public function getSubtitle() + { + return $this->subtitle; + } + + /** + * Sets the subtitle + * + * @param string $subtitle + * @return void + */ + public function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } + + /** + * Returns the hideonapp + * + * @return bool $hideonapp + */ + public function getHideonapp() + { + return $this->hideonapp; + } + + /** + * Sets the hideonapp + * + * @param bool $hideonapp + * @return void + */ + public function setHideonapp($hideonapp) + { + $this->hideonapp = $hideonapp; + } + + /** + * Returns the hideonwebsite + * + * @return bool $hideonwebsite + */ + public function getHideonwebsite() + { + return $this->hideonwebsite; + } + + /** + * Sets the hideonwebsite + * + * @param bool $hideonwebsite + * @return void + */ + public function setHideonwebsite($hideonwebsite) + { + $this->hideonwebsite = $hideonwebsite; + } + /** + * Returns the description + * + * @return string $description + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the description + * + * @param string $description + * @return void + */ + public function setDescription($description) + { + $this->description = $description; + } + + + /** + * Returns the caseimage + * + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage + */ + public function getCaseimage() + { + return $this->caseimage; + } + + /** + * Sets the caseimage + * + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage + * @return void + */ + public function setCaseimage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $caseimage) + { + $this->caseimage = $caseimage; + } + + /** + * Returns the logoimage + * + * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage + */ + public function getLogoimage() + { + return $this->logoimage; + } + + /** + * Sets the logoimage + * + * @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage + * @return void + */ + public function setLogoimage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $logoimage) + { + $this->logoimage = $logoimage; + } + + + /** + * Initializes the ObjectStorage for categories + */ + public function __construct() + { + $this->categories = new ObjectStorage(); + $this->productimage = new ObjectStorage(); + $this->downloads = new ObjectStorage(); + } + + /** + * Adds a category + * + * @param Category $category + * @return void + */ + public function addCategory(Category $category) + { + $this->categories->attach($category); + } + + /** + * Removes a category + * + * @param Category $categoryToRemove + * @return void + */ + public function removeCategory(Category $categoryToRemove) + { + $this->categories->detach($categoryToRemove); + } + + /** + * Returns the categories + * + * @return ObjectStorage + */ + public function getCategories() + { + return $this->categories; + } + + /** + * Sets the categories + * + * @param ObjectStorage $categories + * @return void + */ + public function setCategories(ObjectStorage $categories) + { + $this->categories = $categories; + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Repository/DownloadRepository.php b/packages/vitec/Classes/Domain/Repository/DownloadRepository.php new file mode 100644 index 0000000..26f5ef4 --- /dev/null +++ b/packages/vitec/Classes/Domain/Repository/DownloadRepository.php @@ -0,0 +1,28 @@ +createQuery(); + return $query->matching( + $query->in('uid', $uids) // Use 'uid' instead of 'uids' + )->execute(); +} +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Repository/MarketRepository.php b/packages/vitec/Classes/Domain/Repository/MarketRepository.php new file mode 100644 index 0000000..ff8fe6b --- /dev/null +++ b/packages/vitec/Classes/Domain/Repository/MarketRepository.php @@ -0,0 +1,11 @@ +createQuery(); + $constraints = []; + + // Add visibility constraints if requested + if ($respectVisibility) { + $constraints[] = $query->equals('legacy', 0); + $constraints[] = $query->equals('supportproduct', 0); + $constraints[] = $query->equals('hideonwebsite', 0); + $constraints[] = $query->equals('hideonproducts', 0); + $constraints[] = $query->equals('subproduct', 0); + } + + // Add category filtering if categories are provided + $categoryUids = array_filter(array_map('intval', $categoryUids)); + if (!empty($categoryUids)) { + // For TYPO3 13.4, use contains() for MM relations to sys_category + $categoryConstraints = []; + foreach ($categoryUids as $categoryUid) { + $categoryConstraints[] = $query->contains('categories', $categoryUid); + } + $constraints[] = $query->logicalOr(...$categoryConstraints); + } + + // Apply constraints if any exist + if (!empty($constraints)) { + $query->matching($query->logicalAnd(...$constraints)); + } + + return $query->execute(); + } + + + + /** + * Find all visible products (convenience method) + */ + public function findAllVisible(): QueryResultInterface + { + return $this->findFilteredProducts([], true); + } + + /** + * Find products by categories only (no visibility filtering) + */ + public function findByCategoriesOnly(array $categoryUids): QueryResultInterface + { + return $this->findFilteredProducts($categoryUids, false); + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/Domain/Repository/SolutionRepository.php b/packages/vitec/Classes/Domain/Repository/SolutionRepository.php new file mode 100644 index 0000000..069b748 --- /dev/null +++ b/packages/vitec/Classes/Domain/Repository/SolutionRepository.php @@ -0,0 +1,11 @@ +createQuery(); + $query->getQuerySettings()->setRespectStoragePage(false); // Fetch from all storage pages + return $query->execute(); + } + } diff --git a/packages/vitec/Classes/EventListener/ShowFrontendLayoutBanner.php b/packages/vitec/Classes/EventListener/ShowFrontendLayoutBanner.php new file mode 100755 index 0000000..b85b1a7 --- /dev/null +++ b/packages/vitec/Classes/EventListener/ShowFrontendLayoutBanner.php @@ -0,0 +1,52 @@ +getRequest(); + $pageId = (int)($request->getQueryParams()['id'] ?? 0); + if ($pageId <= 0) { + return; + } + + $page = BackendUtility::getRecord('pages', $pageId, 'layout'); + if (!is_array($page)) { + return; + } + + $layoutValue = (int)($page['layout'] ?? 0); + $title = BackendLayoutDataProvider::LAYOUT_MAP[$layoutValue]['title'] ?? null; + if ($title === null) { + return; + } + + $banner = sprintf( + '
' + . '
' + . '
Frontend Layout
' + . '
%s ' + . '(value: %d)
' + . '
' + . '
', + htmlspecialchars($title, ENT_QUOTES, 'UTF-8'), + $layoutValue + ); + + $event->addHeaderContent($banner); + } +} diff --git a/packages/vitec/Classes/Hook/SyncBackendLayoutHook.php b/packages/vitec/Classes/Hook/SyncBackendLayoutHook.php new file mode 100755 index 0000000..d2f2e97 --- /dev/null +++ b/packages/vitec/Classes/Hook/SyncBackendLayoutHook.php @@ -0,0 +1,37 @@ +" whenever pages.layout is saved. + * Also syncs backend_layout_next_level so subpages inherit the same layout + * (until they pick their own frontend layout). + * + * Hooked via $TYPO3_CONF_VARS[SC_OPTIONS][t3lib/class.t3lib_tcemain.php][processDatamapClass]. + */ +final class SyncBackendLayoutHook +{ + public function processDatamap_postProcessFieldArray( + string $status, + string $table, + $id, + array &$fieldArray, + DataHandler $pObj + ): void { + if ($table !== 'pages') { + return; + } + if (!array_key_exists('layout', $fieldArray)) { + return; + } + + $layoutValue = (int)$fieldArray['layout']; + $beIdentifier = 'vitec__' . $layoutValue; + + $fieldArray['backend_layout'] = $beIdentifier; + $fieldArray['backend_layout_next_level'] = $beIdentifier; + } +} diff --git a/packages/vitec/Classes/PageTitle/ProductPageTitleProvider.php b/packages/vitec/Classes/PageTitle/ProductPageTitleProvider.php new file mode 100644 index 0000000..a43be6e --- /dev/null +++ b/packages/vitec/Classes/PageTitle/ProductPageTitleProvider.php @@ -0,0 +1,22 @@ +seotitle = $seotitle; + } + + public function getTitle(): string + { + return $this->seotitle; + } +} diff --git a/packages/vitec/Classes/Preview/UsecaseshowPluginPreview.php b/packages/vitec/Classes/Preview/UsecaseshowPluginPreview.php new file mode 100644 index 0000000..59daefb --- /dev/null +++ b/packages/vitec/Classes/Preview/UsecaseshowPluginPreview.php @@ -0,0 +1,86 @@ +getRecord(); + + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:vitec/Resources/Private/Templates/Preview/Usecaseshow.html'); + + // Parse FlexForm data + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($record['pi_flexform'] ?? ''); + + // Get usecase data if selected + $usecaseData = null; + $usecaseUid = (int)($flexFormData['settings']['usecase'] ?? 0); + + if ($usecaseUid > 0) { + $usecaseRepository = GeneralUtility::makeInstance(UsecaseRepository::class); + $usecase = $usecaseRepository->findByUid($usecaseUid); + if ($usecase) { + $usecaseData = [ + 'uid' => $usecase->getUid(), + 'title' => $usecase->getTitle(), + 'subtitle' => $usecase->getSubtitle(), + 'description' => $usecase->getDescription() + ]; + } + } + + // Get layout setting + $layout = $flexFormData['settings']['layout'] ?? 'default'; + + // Assign variables to view + $view->assignMultiple([ + 'record' => $record, + 'flexFormData' => $flexFormData, + 'usecase' => $usecaseData, + 'layout' => $layout, + 'pluginName' => 'Usecaseshow' + ]); + + return $view->render(); + } + + /** + * @param GridColumnItem $item + * @return string + */ + public function renderPageModulePreviewHeader(GridColumnItem $item): string + { + $record = $item->getRecord(); + + // Parse FlexForm to get usecase info for header + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($record['pi_flexform'] ?? ''); + + $usecaseUid = (int)($flexFormData['settings']['usecase'] ?? 0); + + if ($usecaseUid > 0) { + return '🎯 Usecase Show Plugin (Usecase ID: ' . $usecaseUid . ')'; + } + + return '🎯 Usecase Show Plugin (No usecase selected)'; + } +} \ No newline at end of file diff --git a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php new file mode 100644 index 0000000..a65264d --- /dev/null +++ b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php @@ -0,0 +1,499 @@ +id ?? 0); + + // Query tt_content for vitec_productlist on this page + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + + $contentElements = $queryBuilder + ->select('*') + ->from('tt_content') + ->where( + $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productlist', ParameterType::STRING)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAllAssociative(); + + if (empty($contentElements)) { + return json_encode(['debug' => 'No vitec_productlist on page ' . $pageId]); + } + + // Take the first one (there should typically be only one) + $contentElement = $contentElements[0]; + + // Parse FlexForm + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); + $settings = $flexFormData['settings'] ?? []; + + // Extract category UIDs and debug setting + $categoryUids = array_filter( + array_map('intval', explode(',', (string)($settings['categories'] ?? ''))) + ); + $debugMode = (bool)($settings['debug'] ?? false); + $allProducts = (bool)($settings['allproducts'] ?? false); + + // Extbase repositories don't work in UserFunc context + // Use direct database query instead + $productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + + $productQuery = $productQueryBuilder + ->select('p.*') + ->from('tx_vitec_domain_model_product', 'p') + ->where( + $productQueryBuilder->expr()->eq('p.deleted', 0), + $productQueryBuilder->expr()->eq('p.hidden', 0), + $productQueryBuilder->expr()->eq('p.legacy', 0), + $productQueryBuilder->expr()->eq('p.supportproduct', 0), + $productQueryBuilder->expr()->eq('p.hideonwebsite', 0), + $productQueryBuilder->expr()->eq('p.hideonproducts', 0), + $productQueryBuilder->expr()->eq('p.subproduct', 0) + ); + + // Add category filter if specified and allProducts is not enabled + if (!empty($categoryUids) && !$allProducts) { + // Join with sys_category_record_mm to filter by categories + $productQuery + ->join( + 'p', + 'sys_category_record_mm', + 'mm', + 'mm.uid_foreign = p.uid AND mm.tablenames = ' . $productQueryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . ' AND mm.fieldname = ' . $productQueryBuilder->createNamedParameter('categories', ParameterType::STRING) + ) + ->andWhere( + $productQueryBuilder->expr()->in('mm.uid_local', $productQueryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY)) + ) + ->groupBy('p.uid'); + } + + $products = $productQuery->executeQuery()->fetchAllAssociative(); + + // Serialize products (they're already associative arrays from the query) + $productsData = []; + foreach ($products as $product) { + $productsData[] = $this->serializeProduct($product); + } + + // If debug mode is enabled, return object with products and debug info + if ($debugMode) { + return json_encode([ + 'products' => $productsData, + 'debug' => [ + 'pageId' => $pageId, + 'categoryUids' => $categoryUids, + 'productCount' => count($productsData), + 'settings' => $settings + ] + ]); + } + + // Otherwise return products array directly (backward compatible) + return json_encode($productsData); + } + + /** + * Serialize a single product DB row to the full headless JSON structure. + * + * Includes every field declared on the Product domain model + * (Evomedien\Vitec\Domain\Model\Product) plus all fully resolved + * relations (categories, productimage, downloads, ogimage, relatedprodukt). + * + * DB-only columns that are NOT part of the domain model + * (cta, links, sorting1-5, key1-3, apptext1-3, productlayout, image, + * relatedimage, system fields) are intentionally omitted. + * + * @param array $product Associative DB row of tx_vitec_domain_model_product + * @return array + */ + protected function serializeProduct(array $product): array + { + $uid = (int)$product['uid']; + + return [ + // --- identifier --- + 'uid' => $uid, + + // --- scalar string fields (Product domain model) --- + 'title' => (string)($product['title'] ?? ''), + 'slug' => (string)($product['slug'] ?? ''), + 'urltitle' => (string)($product['urltitle'] ?? ''), + 'seotitle' => (string)($product['seotitle'] ?? ''), + 'seometa' => (string)($product['seometa'] ?? ''), + 'keywords' => (string)($product['keywords'] ?? ''), + 'structureddata' => (string)($product['structureddata'] ?? ''), + 'teaser' => (string)($product['teaser'] ?? ''), + 'subtitle' => (string)($product['subtitle'] ?? ''), + 'video' => (string)($product['video'] ?? ''), + 'applications' => (string)($product['applications'] ?? ''), + 'description' => (string)($product['description'] ?? ''), + 'highlights' => (string)($product['highlights'] ?? ''), + 'shortcutpid' => (string)($product['shortcutpid'] ?? ''), + 'contentelement' => (string)($product['contentelement'] ?? ''), + 'contentelementcta' => (string)($product['contentelementcta'] ?? ''), + + // --- boolean flags (Product domain model) --- + 'hideonapp' => (bool)($product['hideonapp'] ?? false), + 'hideonwebsite' => (bool)($product['hideonwebsite'] ?? false), + 'hideondatasheets' => (bool)($product['hideondatasheets'] ?? false), + 'hideonproducts' => (bool)($product['hideonproducts'] ?? false), + 'shortcut' => (bool)($product['shortcut'] ?? false), + 'legacy' => (bool)($product['legacy'] ?? false), + 'supportproduct' => (bool)($product['supportproduct'] ?? false), + 'subproduct' => (bool)($product['subproduct'] ?? false), + + // --- convenience link (kept for backward compatibility) --- + 'link' => '/product/' . (string)($product['slug'] ?? ''), + + // --- fully resolved relations --- + 'categories' => $this->getProductCategories($uid), + 'images' => $this->getProductImages($uid), + 'downloads' => $this->getProductDownloads($uid), + 'ogimage' => $this->getProductOgImage($uid), + 'relatedprodukt' => $this->getRelatedProducts($uid), + ]; + } + + /** + * Get product images from FAL (sys_file_reference) + * Processes images through ImageService and generates srcset for responsive images + */ + protected function getProductImages(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileReferences = $queryBuilder + ->select('sfr.uid', 'sfr.uid_local', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop') + ->from('sys_file_reference', 'sfr') + ->where( + $queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('sfr.deleted', 0), + $queryBuilder->expr()->eq('sfr.hidden', 0) + ) + ->orderBy('sfr.sorting_foreign') + ->executeQuery() + ->fetchAllAssociative(); + + $imageService = GeneralUtility::makeInstance(ImageService::class); + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + + $images = []; + foreach ($fileReferences as $fileRefData) { + try { + // Get FAL FileReference object + $fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']); + + // Define image sizes for srcset + $sizes = [ + 'small' => ['width' => 400, 'height' => null], + 'medium' => ['width' => 800, 'height' => null], + 'large' => ['width' => 1200, 'height' => null], + 'xlarge' => ['width' => 1600, 'height' => null], + ]; + + $srcset = []; + foreach ($sizes as $sizeName => $dimensions) { + $processedImage = $imageService->applyProcessingInstructions( + $fileReference, + [ + 'width' => $dimensions['width'], + 'height' => $dimensions['height'], + 'crop' => $fileRefData['crop'] ?? null + ] + ); + + $imageUri = $imageService->getImageUri($processedImage); + $srcset[] = [ + 'url' => $imageUri, + 'width' => $dimensions['width'], + 'descriptor' => $dimensions['width'] . 'w' + ]; + } + + // Get original/default image + $defaultProcessed = $imageService->applyProcessingInstructions( + $fileReference, + ['width' => 800, 'crop' => $fileRefData['crop'] ?? null] + ); + + $images[] = [ + 'uid' => (int)$fileRefData['uid'], + 'url' => $imageService->getImageUri($defaultProcessed), + 'title' => $fileRefData['title'] ?? '', + 'alternative' => $fileRefData['alternative'] ?? '', + 'description' => $fileRefData['description'] ?? '', + 'srcset' => $srcset, + 'properties' => [ + 'width' => $fileReference->getProperty('width'), + 'height' => $fileReference->getProperty('height'), + 'mimeType' => $fileReference->getProperty('mime_type') + ] + ]; + } catch (\Exception $e) { + // Skip images that can't be processed + continue; + } + } + + return $images; + } + + /** + * Get the single Open Graph image (ogimage) for a product, or null. + * + * @return array|null + */ + protected function getProductOgImage(int $productUid): ?array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileRefData = $queryBuilder + ->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop') + ->from('sys_file_reference', 'sfr') + ->where( + $queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('ogimage', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('sfr.deleted', 0), + $queryBuilder->expr()->eq('sfr.hidden', 0) + ) + ->orderBy('sfr.sorting_foreign') + ->setMaxResults(1) + ->executeQuery() + ->fetchAssociative(); + + if (!$fileRefData) { + return null; + } + + try { + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + $imageService = GeneralUtility::makeInstance(ImageService::class); + $fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']); + + $processed = $imageService->applyProcessingInstructions( + $fileReference, + ['width' => 1200, 'crop' => $fileRefData['crop'] ?? null] + ); + + return [ + 'uid' => (int)$fileRefData['uid'], + 'url' => $imageService->getImageUri($processed), + 'title' => $fileRefData['title'] ?? '', + 'alternative' => $fileRefData['alternative'] ?? '', + 'description' => $fileRefData['description'] ?? '', + 'properties' => [ + 'width' => $fileReference->getProperty('width'), + 'height' => $fileReference->getProperty('height'), + 'mimeType' => $fileReference->getProperty('mime_type'), + ], + ]; + } catch (\Exception $e) { + return null; + } + } + + /** + * Get categories for a product (resolved sys_category records). + */ + protected function getProductCategories(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('sys_category'); + + $categories = $queryBuilder + ->select('c.uid', 'c.title', 'c.description') + ->from('sys_category', 'c') + ->join( + 'c', + 'sys_category_record_mm', + 'mm', + 'mm.uid_local = c.uid AND mm.tablenames = ' . + $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . + ' AND mm.fieldname = ' . + $queryBuilder->createNamedParameter('categories', ParameterType::STRING) + ) + ->where( + $queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('c.deleted', 0), + $queryBuilder->expr()->eq('c.hidden', 0) + ) + ->orderBy('mm.sorting', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + return array_map(static function ($cat) { + return [ + 'uid' => (int)$cat['uid'], + 'title' => $cat['title'] ?? '', + 'description' => $cat['description'] ?? '', + ]; + }, $categories); + } + + /** + * Get all downloads for a product (resolved via tx_vitec_product_download_mm). + */ + protected function getProductDownloads(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_download'); + + $downloads = $queryBuilder + ->select('d.*') + ->from('tx_vitec_domain_model_download', 'd') + ->join( + 'd', + 'tx_vitec_product_download_mm', + 'mm', + 'mm.uid_foreign = d.uid' + ) + ->where( + $queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('d.deleted', 0), + $queryBuilder->expr()->eq('d.hidden', 0), + $queryBuilder->expr()->eq('d.hideonwebsite', 0) + ) + ->orderBy('mm.sorting', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + $result = []; + foreach ($downloads as $download) { + $fileInfo = null; + + // Get file information from FAL if file reference exists + if (!empty($download['file'])) { + $fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileRef = $fileQueryBuilder + ->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type') + ->from('sys_file_reference', 'fr') + ->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid') + ->where( + $fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)), + $fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)), + $fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)), + $fileQueryBuilder->expr()->eq('fr.deleted', 0), + $fileQueryBuilder->expr()->eq('f.missing', 0) + ) + ->orderBy('fr.sorting_foreign', 'ASC') + ->setMaxResults(1) + ->executeQuery() + ->fetchAssociative(); + + if ($fileRef) { + $fileInfo = [ + 'uid' => (int)$fileRef['file_uid'], + 'name' => $fileRef['name'], + 'url' => '/fileadmin' . $fileRef['identifier'], + 'size' => (int)$fileRef['size'], + 'extension' => $fileRef['extension'], + 'mimeType' => $fileRef['mime_type'] ?? '', + ]; + } + } + + $result[] = [ + 'uid' => (int)$download['uid'], + 'title' => $download['title'] ?? '', + 'slug' => $download['slug'] ?? '', + 'teaser' => $download['teaser'] ?? '', + 'description' => $download['description'] ?? '', + 'keywords' => $download['keywords'] ?? '', + 'icon' => $download['icon'] ?? '', + 'file' => $fileInfo, + ]; + } + + return $result; + } + + /** + * Get related products (resolved via tx_vitec_product_related_mm). + * + * Returns a shallow representation (no nested relations) to avoid + * infinite recursion between mutually related products. + */ + protected function getRelatedProducts(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + + $related = $queryBuilder + ->select('p.uid', 'p.title', 'p.slug', 'p.subtitle', 'p.teaser', 'p.description') + ->from('tx_vitec_domain_model_product', 'p') + ->join( + 'p', + 'tx_vitec_product_related_mm', + 'mm', + 'mm.uid_foreign = p.uid' + ) + ->where( + $queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('p.deleted', 0), + $queryBuilder->expr()->eq('p.hidden', 0) + ) + ->orderBy('mm.sorting', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + $result = []; + foreach ($related as $rel) { + $relUid = (int)$rel['uid']; + $images = $this->getProductImages($relUid); + + $result[] = [ + 'uid' => $relUid, + 'title' => (string)($rel['title'] ?? ''), + 'slug' => (string)($rel['slug'] ?? ''), + 'subtitle' => (string)($rel['subtitle'] ?? ''), + 'teaser' => (string)($rel['teaser'] ?? ''), + 'description' => (string)($rel['description'] ?? ''), + 'link' => '/product/' . (string)($rel['slug'] ?? ''), + 'images' => $images, + ]; + } + + return $result; + } +} diff --git a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php.bak.20260518144047 b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php.bak.20260518144047 new file mode 100755 index 0000000..03f7556 --- /dev/null +++ b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php.bak.20260518144047 @@ -0,0 +1,228 @@ +id ?? 0); + + // Query tt_content for vitec_productlist on this page + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + + $contentElements = $queryBuilder + ->select('*') + ->from('tt_content') + ->where( + $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productlist', ParameterType::STRING)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAllAssociative(); + + if (empty($contentElements)) { + return json_encode(['debug' => 'No vitec_productlist on page ' . $pageId]); + } + + // Take the first one (there should typically be only one) + $contentElement = $contentElements[0]; + + // Parse FlexForm + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); + $settings = $flexFormData['settings'] ?? []; + + // Extract category UIDs and debug setting + $categoryUids = array_filter( + array_map('intval', explode(',', (string)($settings['categories'] ?? ''))) + ); + $debugMode = (bool)($settings['debug'] ?? false); + $allProducts = (bool)($settings['allproducts'] ?? false); + + // Extbase repositories don't work in UserFunc context + // Use direct database query instead + $productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + + $productQuery = $productQueryBuilder + ->select('p.*') + ->from('tx_vitec_domain_model_product', 'p') + ->where( + $productQueryBuilder->expr()->eq('p.deleted', 0), + $productQueryBuilder->expr()->eq('p.hidden', 0), + $productQueryBuilder->expr()->eq('p.legacy', 0), + $productQueryBuilder->expr()->eq('p.supportproduct', 0), + $productQueryBuilder->expr()->eq('p.hideonwebsite', 0), + $productQueryBuilder->expr()->eq('p.hideonproducts', 0), + $productQueryBuilder->expr()->eq('p.subproduct', 0) + ); + + // Add category filter if specified and allProducts is not enabled + if (!empty($categoryUids) && !$allProducts) { + // Join with sys_category_record_mm to filter by categories + $productQuery + ->join( + 'p', + 'sys_category_record_mm', + 'mm', + 'mm.uid_foreign = p.uid AND mm.tablenames = ' . $productQueryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . ' AND mm.fieldname = ' . $productQueryBuilder->createNamedParameter('categories', ParameterType::STRING) + ) + ->andWhere( + $productQueryBuilder->expr()->in('mm.uid_local', $productQueryBuilder->createNamedParameter($categoryUids, Connection::PARAM_INT_ARRAY)) + ) + ->groupBy('p.uid'); + } + + $products = $productQuery->executeQuery()->fetchAllAssociative(); + + // Serialize products (they're already associative arrays from the query) + $productsData = []; + foreach ($products as $product) { + // Get product images (FAL references) + $images = $this->getProductImages((int)$product['uid']); + + $productsData[] = [ + 'uid' => (int)$product['uid'], + 'title' => $product['title'] ?? '', + 'subtitle' => $product['subtitle'] ?? '', + 'slug' => $product['slug'] ?? '', + 'teaser' => $product['teaser'] ?? '', + 'description' => $product['description'] ?? '', + 'link' => '/product/' . ($product['slug'] ?? ''), + 'images' => $images, + // Add more fields as needed + ]; + } + + // If debug mode is enabled, return object with products and debug info + if ($debugMode) { + return json_encode([ + 'products' => $productsData, + 'debug' => [ + 'pageId' => $pageId, + 'categoryUids' => $categoryUids, + 'productCount' => count($productsData), + 'settings' => $settings + ] + ]); + } + + // Otherwise return products array directly (backward compatible) + return json_encode($productsData); + } + + /** + * Get product images from FAL (sys_file_reference) + * Processes images through ImageService and generates srcset for responsive images + */ + protected function getProductImages(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileReferences = $queryBuilder + ->select('sfr.uid', 'sfr.uid_local', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop') + ->from('sys_file_reference', 'sfr') + ->where( + $queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)), + $queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('sfr.deleted', 0), + $queryBuilder->expr()->eq('sfr.hidden', 0) + ) + ->orderBy('sfr.sorting_foreign') + ->executeQuery() + ->fetchAllAssociative(); + + $imageService = GeneralUtility::makeInstance(ImageService::class); + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + + $images = []; + foreach ($fileReferences as $fileRefData) { + try { + // Get FAL FileReference object + $fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']); + + // Define image sizes for srcset + $sizes = [ + 'small' => ['width' => 400, 'height' => null], + 'medium' => ['width' => 800, 'height' => null], + 'large' => ['width' => 1200, 'height' => null], + 'xlarge' => ['width' => 1600, 'height' => null], + ]; + + $srcset = []; + foreach ($sizes as $sizeName => $dimensions) { + $processedImage = $imageService->applyProcessingInstructions( + $fileReference, + [ + 'width' => $dimensions['width'], + 'height' => $dimensions['height'], + 'crop' => $fileRefData['crop'] ?? null + ] + ); + + $imageUri = $imageService->getImageUri($processedImage); + $srcset[] = [ + 'url' => $imageUri, + 'width' => $dimensions['width'], + 'descriptor' => $dimensions['width'] . 'w' + ]; + } + + // Get original/default image + $defaultProcessed = $imageService->applyProcessingInstructions( + $fileReference, + ['width' => 800, 'crop' => $fileRefData['crop'] ?? null] + ); + + $images[] = [ + 'uid' => (int)$fileRefData['uid'], + 'url' => $imageService->getImageUri($defaultProcessed), + 'title' => $fileRefData['title'] ?? '', + 'alternative' => $fileRefData['alternative'] ?? '', + 'description' => $fileRefData['description'] ?? '', + 'srcset' => $srcset, + 'properties' => [ + 'width' => $fileReference->getProperty('width'), + 'height' => $fileReference->getProperty('height'), + 'mimeType' => $fileReference->getProperty('mime_type') + ] + ]; + } catch (\Exception $e) { + // Skip images that can't be processed + continue; + } + } + + return $images; + } +} diff --git a/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php new file mode 100644 index 0000000..8bb69b3 --- /dev/null +++ b/packages/vitec/Classes/UserFunc/ProductShowJsonRenderer.php @@ -0,0 +1,366 @@ +id; + + // DEBUG: Log that the UserFunc is being called + $debugInfo = [ + 'userFuncCalled' => true, + 'pageId' => $pageId, + 'requestUri' => $_SERVER['REQUEST_URI'] ?? 'unknown', + ]; + + // Query tt_content for vitec_productshow on this page + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + + $contentElements = $queryBuilder + ->select('*') + ->from('tt_content') + ->where( + $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('vitec_productshow', ParameterType::STRING)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAllAssociative(); + + $debugInfo['contentElementsFound'] = count($contentElements); + + if (empty($contentElements)) { + $debugInfo['error'] = 'No vitec_productshow on page'; + return json_encode(['debug' => $debugInfo]); + } + + // Take the first one + $contentElement = $contentElements[0]; + + // Parse FlexForm + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); + $settings = $flexFormData['settings'] ?? []; + + // Get product UID from FlexForm or route parameter + $productUid = (int)($settings['product'] ?? 0); + $layout = (int)($settings['layout'] ?? 0); + $debugMode = (bool)($settings['debug'] ?? false); + + // If no product selected in FlexForm, try to get from route parameter + if (!$productUid) { + // Get the product parameter from GET request + $routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams(); + $productParam = $routeParams['tx_vitec_productshow']['product'] ?? null; + + if ($productParam) { + // If it's a slug, resolve it to UID + if (!is_numeric($productParam)) { + $slugQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + + $productBySlug = $slugQueryBuilder + ->select('uid') + ->from('tx_vitec_domain_model_product') + ->where( + $slugQueryBuilder->expr()->eq('slug', $slugQueryBuilder->createNamedParameter($productParam)), + $slugQueryBuilder->expr()->eq('deleted', 0), + $slugQueryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAssociative(); + + $productUid = (int)($productBySlug['uid'] ?? 0); + } else { + $productUid = (int)$productParam; + } + } + } + + if (!$productUid) { + return json_encode([ + 'error' => 'No product selected or found', + 'debug' => [ + 'settings' => $settings, + 'routeParams' => $routeParams ?? [], + 'allQueryParams' => $GLOBALS['TYPO3_REQUEST']->getQueryParams() ?? [], + 'requestUri' => $GLOBALS['TYPO3_REQUEST']->getUri()->getPath() ?? '' + ] + ]); + } + + // Query product + $productQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + + $product = $productQueryBuilder + ->select('*') + ->from('tx_vitec_domain_model_product') + ->where( + $productQueryBuilder->expr()->eq('uid', $productQueryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $productQueryBuilder->expr()->eq('deleted', 0), + $productQueryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAssociative(); + + if (!$product) { + return json_encode([ + 'error' => 'Product not found', + 'debug' => $debugMode ? ['productUid' => $productUid] : null + ]); + } + + // Get product images + $images = $this->getProductImages((int)$product['uid']); + + // Get categories + $categories = $this->getProductCategories((int)$product['uid']); + + // Get downloads + $downloads = $this->getProductDownloads((int)$product['uid']); + + // Build response + $response = [ + 'product' => [ + 'uid' => (int)$product['uid'], + 'title' => $product['title'], + 'subtitle' => $product['subtitle'], + 'slug' => $product['slug'], + 'teaser' => $product['teaser'], + 'description' => $product['description'], + 'seotitle' => $product['seotitle'], + 'categories' => $categories, + 'images' => $images, + 'downloads' => $downloads, + ], + 'layout' => $layout, + 'settings' => [ + 'layout' => $layout, + ], + ]; + + if ($debugMode) { + $response['debug'] = [ + 'pageId' => $pageId, + 'productUid' => $productUid, + 'layout' => $layout, + 'settings' => $settings, + ]; + } + + return json_encode($response); + } + + /** + * Get all images for a product with FAL and ImageService processing + */ + protected function getProductImages(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileReferences = $queryBuilder + ->select('*') + ->from('sys_file_reference') + ->where( + $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING)), + $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('productimage', ParameterType::STRING)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0) + ) + ->orderBy('sorting_foreign', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + if (empty($fileReferences)) { + return []; + } + + $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); + $imageService = GeneralUtility::makeInstance(ImageService::class); + $images = []; + + foreach ($fileReferences as $fileRef) { + try { + $fileReference = $resourceFactory->getFileReferenceObject($fileRef['uid']); + $originalFile = $fileReference->getOriginalFile(); + + // Process main image + $processedImage = $imageService->applyProcessingInstructions( + $fileReference, + ['width' => '1874c', 'height' => '625c'] + ); + + // Generate srcset + $srcset = []; + foreach ([400, 800, 1200, 1600] as $width) { + $processedVariant = $imageService->applyProcessingInstructions( + $fileReference, + ['width' => $width . 'c', 'height' => (int)($width / 3) . 'c'] + ); + $srcset[] = [ + 'url' => $imageService->getImageUri($processedVariant), + 'width' => $width, + 'descriptor' => $width . 'w', + ]; + } + + $images[] = [ + 'uid' => $fileRef['uid'], + 'url' => $imageService->getImageUri($processedImage), + 'title' => $fileReference->getTitle() ?: '', + 'alternative' => $fileReference->getAlternative() ?: '', + 'description' => $fileReference->getDescription() ?: '', + 'srcset' => $srcset, + 'properties' => [ + 'width' => $originalFile->getProperty('width'), + 'height' => $originalFile->getProperty('height'), + 'mimeType' => $originalFile->getMimeType(), + ], + ]; + } catch (\Exception $e) { + // Skip invalid file references + continue; + } + } + + return $images; + } + + /** + * Get categories for a product + */ + protected function getProductCategories(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('sys_category'); + + $categories = $queryBuilder + ->select('c.uid', 'c.title', 'c.description') + ->from('sys_category', 'c') + ->join( + 'c', + 'sys_category_record_mm', + 'mm', + 'mm.uid_local = c.uid AND mm.tablenames = ' . + $queryBuilder->createNamedParameter('tx_vitec_domain_model_product', ParameterType::STRING) . + ' AND mm.fieldname = ' . + $queryBuilder->createNamedParameter('categories', ParameterType::STRING) + ) + ->where( + $queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('c.deleted', 0), + $queryBuilder->expr()->eq('c.hidden', 0) + ) + ->orderBy('mm.sorting', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + return array_map(function ($cat) { + return [ + 'uid' => (int)$cat['uid'], + 'title' => $cat['title'], + 'description' => $cat['description'] ?? '', + ]; + }, $categories); + } + + /** + * Get all downloads for a product + */ + protected function getProductDownloads(int $productUid): array + { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_download'); + + $downloads = $queryBuilder + ->select('d.*') + ->from('tx_vitec_domain_model_download', 'd') + ->join( + 'd', + 'tx_vitec_product_download_mm', + 'mm', + 'mm.uid_foreign = d.uid' + ) + ->where( + $queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('d.deleted', 0), + $queryBuilder->expr()->eq('d.hidden', 0), + $queryBuilder->expr()->eq('d.hideonwebsite', 0) + ) + ->orderBy('mm.sorting', 'ASC') + ->executeQuery() + ->fetchAllAssociative(); + + $result = []; + foreach ($downloads as $download) { + $fileInfo = null; + + // Get file information from FAL if file reference exists + if (!empty($download['file'])) { + $fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('sys_file_reference'); + + $fileRef = $fileQueryBuilder + ->select('fr.uid', 'f.uid as file_uid', 'f.identifier', 'f.name', 'f.size', 'f.extension', 'f.mime_type') + ->from('sys_file_reference', 'fr') + ->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid') + ->where( + $fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)), + $fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)), + $fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)), + $fileQueryBuilder->expr()->eq('fr.deleted', 0), + $fileQueryBuilder->expr()->eq('f.missing', 0) + ) + ->orderBy('fr.sorting_foreign', 'ASC') + ->setMaxResults(1) + ->executeQuery() + ->fetchAssociative(); + + if ($fileRef) { + $fileInfo = [ + 'uid' => (int)$fileRef['file_uid'], + 'name' => $fileRef['name'], + 'url' => '/fileadmin' . $fileRef['identifier'], + 'size' => (int)$fileRef['size'], + 'extension' => $fileRef['extension'], + 'mimeType' => $fileRef['mime_type'] ?? '', + ]; + } + } + + $result[] = [ + 'uid' => (int)$download['uid'], + 'title' => $download['title'] ?? '', + 'slug' => $download['slug'] ?? '', + 'teaser' => $download['teaser'] ?? '', + 'description' => $download['description'] ?? '', + 'keywords' => $download['keywords'] ?? '', + 'icon' => $download['icon'] ?? '', + 'file' => $fileInfo, + ]; + } + + return $result; + } +} diff --git a/packages/vitec/Classes/View/BackendLayoutDataProvider.php b/packages/vitec/Classes/View/BackendLayoutDataProvider.php new file mode 100755 index 0000000..635ec4e --- /dev/null +++ b/packages/vitec/Classes/View/BackendLayoutDataProvider.php @@ -0,0 +1,122 @@ +. + * + * The layout's title is shown above the page module and tells the editor + * which frontend layout is currently active. + */ +final class BackendLayoutDataProvider implements DataProviderInterface +{ + /** colPos values across all VITEC backend layouts. + * Must NOT collide with container child colPos (>= 211). */ + private const COL_POS = [ + 'main' => 0, + 'hero' => 1, + 'sidebar' => 2, + 'preFooter' => 3, + ]; + + /** Per-frontend-layout: title + ordered list of zones. */ + public const LAYOUT_MAP = [ + 0 => ['title' => 'Default', 'zones' => ['main']], + 1 => ['title' => 'Index Page', 'zones' => ['hero', 'main', 'preFooter']], + 2 => ['title' => 'Markets Overview', 'zones' => ['hero', 'main']], + 3 => ['title' => 'Market Detail', 'zones' => ['hero', 'main', 'sidebar']], + 4 => ['title' => 'Solutions Overview', 'zones' => ['hero', 'main']], + 5 => ['title' => 'Solution Detail', 'zones' => ['hero', 'main', 'sidebar']], + 6 => ['title' => 'Use Cases Overview', 'zones' => ['hero', 'main']], + 7 => ['title' => 'Use Case Detail', 'zones' => ['hero', 'main', 'sidebar']], + 8 => ['title' => 'Products Overview', 'zones' => ['hero', 'main']], + 9 => ['title' => 'Product Main Category', 'zones' => ['hero', 'main', 'preFooter']], + 10 => ['title' => 'Product Detail', 'zones' => ['hero', 'main', 'sidebar']], + 11 => ['title' => 'Support Overview', 'zones' => ['hero', 'main']], + 12 => ['title' => 'News Overview', 'zones' => ['hero', 'main']], + 13 => ['title' => 'News Detail V1', 'zones' => ['hero', 'main', 'sidebar']], + 14 => ['title' => 'News Detail V2', 'zones' => ['hero', 'main']], + 15 => ['title' => 'News Detail V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']], + 16 => ['title' => 'Content Page V1', 'zones' => ['hero', 'main']], + 17 => ['title' => 'Content Page V2', 'zones' => ['hero', 'main', 'sidebar']], + 18 => ['title' => 'Content Page V3', 'zones' => ['hero', 'main', 'sidebar', 'preFooter']], + 19 => ['title' => 'Events Overview', 'zones' => ['hero', 'main']], + 20 => ['title' => 'Contact Page', 'zones' => ['hero', 'main', 'sidebar']], + ]; + + /** Display name for each zone. Shown as column header in the BE page module. */ + private const ZONE_NAMES = [ + 'main' => 'Main Content', + 'hero' => 'Hero', + 'sidebar' => 'Sidebar', + 'preFooter' => 'Pre-Footer', + ]; + + public function addBackendLayouts(DataProviderContext $dataProviderContext, BackendLayoutCollection $backendLayoutCollection) + { + foreach (self::LAYOUT_MAP as $value => $info) { + $backendLayoutCollection->add($this->buildBackendLayout((int)$value, $info['title'], $info['zones'])); + } + } + + public function getBackendLayout($identifier, $pageId) + { + $value = (int)$identifier; + if (!isset(self::LAYOUT_MAP[$value])) { + return null; + } + $info = self::LAYOUT_MAP[$value]; + return $this->buildBackendLayout($value, $info['title'], $info['zones']); + } + + /** + * @param string[] $zones + */ + private function buildBackendLayout(int $value, string $title, array $zones): BackendLayout + { + $rowsTs = ''; + $rowNum = 1; + foreach ($zones as $zoneKey) { + $name = self::ZONE_NAMES[$zoneKey]; + $colPos = self::COL_POS[$zoneKey]; + $rowsTs .= <<" + return new BackendLayout( + (string)$value, + 'VITEC Layout: ' . $title, + $configuration + ); + } +} diff --git a/packages/vitec/Classes/View/ProductListJsonView.php b/packages/vitec/Classes/View/ProductListJsonView.php new file mode 100644 index 0000000..9891a40 --- /dev/null +++ b/packages/vitec/Classes/View/ProductListJsonView.php @@ -0,0 +1,58 @@ +variables['products'] ?? []; + $productsData = []; + + foreach ($products as $product) { + $categories = []; + if ($product->getCategories()) { + foreach ($product->getCategories() as $category) { + $categories[] = [ + 'uid' => $category->getUid(), + 'title' => $category->getTitle(), + ]; + } + } + + $image = null; + if ($product->getProductimage() && $product->getProductimage()->count() > 0) { + $imageObject = $product->getProductimage()->current(); + $image = [ + 'uid' => $imageObject->getUid(), + 'url' => $imageObject->getOriginalResource()->getPublicUrl(), + 'title' => $imageObject->getTitle(), + 'alternative' => $imageObject->getAlternative(), + 'description' => $imageObject->getDescription(), + ]; + } + + $productsData[] = [ + 'uid' => $product->getUid(), + 'title' => $product->getTitle(), + 'subtitle' => $product->getSubtitle(), + 'teaser' => $product->getTeaser(), + 'description' => $product->getDescription(), + 'slug' => $product->getSlug(), + 'categories' => $categories, + 'image' => $image, + ]; + } + + return json_encode([ + 'products' => $productsData, + ], JSON_THROW_ON_ERROR); + } +} diff --git a/packages/vitec/Classes/View/VitecBackendLayoutView.php b/packages/vitec/Classes/View/VitecBackendLayoutView.php new file mode 100755 index 0000000..afe3479 --- /dev/null +++ b/packages/vitec/Classes/View/VitecBackendLayoutView.php @@ -0,0 +1,29 @@ +__" + return 'vitec__' . (int)$page['layout']; + } +} diff --git a/packages/vitec/Classes/Widgets/VitecWidget.php b/packages/vitec/Classes/Widgets/VitecWidget.php new file mode 100644 index 0000000..01c826a --- /dev/null +++ b/packages/vitec/Classes/Widgets/VitecWidget.php @@ -0,0 +1,38 @@ +configuration = $configuration; + } + + public function render(): string + { + // Render the widget container + return '
' . $this->renderWidgetContent() . '
'; + } + + public function renderWidgetContent(): string + { + // Render the actual widget content + return '
Welcome to the Vitec custom widget!
'; + } + + public function getOptions(): array + { + // Return widget options (if any) + return []; + } + + public function getConfiguration(): WidgetConfiguration + { + return $this->configuration; + } +} \ No newline at end of file diff --git a/packages/vitec/Configuration/ContentBlocks.yaml b/packages/vitec/Configuration/ContentBlocks.yaml new file mode 100644 index 0000000..3317c4d --- /dev/null +++ b/packages/vitec/Configuration/ContentBlocks.yaml @@ -0,0 +1,4 @@ +contentBlocks: + paths: + # Register all content elements under ContentBlocks/ContentElements + - 'EXT:vitec/ContentBlocks/ContentElements/' \ No newline at end of file diff --git a/packages/vitec/Configuration/ExtensionBuilder/settings.yaml b/packages/vitec/Configuration/ExtensionBuilder/settings.yaml new file mode 100644 index 0000000..c81ea72 --- /dev/null +++ b/packages/vitec/Configuration/ExtensionBuilder/settings.yaml @@ -0,0 +1,103 @@ +# +# Extension Builder settings for extension vitec +# generated 2025-01-13T15:54:51Z +# +# See http://www.yaml.org/spec/1.2/spec.html +# + +############# Overwrite settings ########### +# +# These settings only apply, if the roundtrip feature of the extension builder +# is enabled in the extension manager +# +# Usage: +# nesting reflects the file structure +# a setting applies to a file or recursive to all files and subfolders +# +# merge: +# means for classes: All properties, methods and method bodies +# of the existing class will be modified according to the new settings +# but not overwritten +# +# for locallang.xlf files: Existing keys and labels are always +# preserved (renaming a property or DomainObject will result in new keys and new labels) +# +# for other files: You will find a Split token at the end of the file +# see: \EBT\ExtensionBuilder\Service\RoundTrip::SPLIT_TOKEN +# +# After this token you can write whatever you want and it will be appended +# everytime the code is generated +# +# keep: +# files are never overwritten +# These settings may break the functionality of the extension builder! +# Handle with care! + +############# Extension settings ########### + +overwriteSettings: + Classes: + Controller: merge + Domain: + Model: merge + Repository: merge + + Configuration: + # TCA merge not possible - use overrides directory + #TypoScript: keep + + Resources: + Private: + #Language: merge + #Layouts: keep + #Partials: keep + #Templates: keep + Backend: + #Layouts: keep + #Partials: keep + #Templates: keep + + user_extension.svg: keep + #ext_localconf.php: merge + #ext_tables.php: merge + #ext_tables.sql: merge + +## add declare strict types in php files +declareStrictTypes: true + +## use static date attribute in xliff files +#staticDateInXliffFiles: '2025-01-13T15:54:51Z' + +## skip docComment (license header) +#skipDocComment: false + +## list of error codes for warnings that should be ignored +#ignoreWarnings: + #503 + +############# settings for classBuilder ####################### +# +# here you may define default parent classes for your classes +# these settings only apply for new generated classes +# you may also just change the parent class in the generated class file. +# It will be kept on next code generation, if the overwrite settings +# are configured to merge it +# +################################################################# + +classBuilder: + + Controller: + parentClass: \TYPO3\CMS\Extbase\Mvc\Controller\ActionController + + Model: + AbstractEntity: + parentClass: \TYPO3\CMS\Extbase\DomainObject\AbstractEntity + + AbstractValueObject: + parentClass: \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject + + Repository: + parentClass: \TYPO3\CMS\Extbase\Persistence\Repository + + setDefaultValuesForClassProperties: true diff --git a/packages/vitec/Configuration/FlexForms/Datasheets.xml b/packages/vitec/Configuration/FlexForms/Datasheets.xml new file mode 100644 index 0000000..03a4ac2 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Datasheets.xml @@ -0,0 +1,65 @@ + + + + 1 + + + + + + General Settings + + array + + + + + + check + 1 + + + + + + + + + check + 1 + + + + + + + + + number + 20 + + + + + + + + + select + selectSingle + tx_vitec_domain_model_downloadcategory + ORDER BY title + + + + 0 + + + + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Downloadcard.xml b/packages/vitec/Configuration/FlexForms/Downloadcard.xml new file mode 100644 index 0000000..f16f44c --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Downloadcard.xml @@ -0,0 +1,119 @@ + + + + + + + General Settings + + array + + + + + select + selectSingle + + + + 0 + + + tx_vitec_domain_model_product + AND (tx_vitec_domain_model_product.hidden = 0 AND tx_vitec_domain_model_product.deleted = 0) ORDER BY tx_vitec_domain_model_product.title + 1 + 0 + 99 + + + + + + select + selectSingle + + + + 0 + + + tx_vitec_domain_model_download + AND (tx_vitec_domain_model_download.hidden = 0 AND tx_vitec_domain_model_download.deleted = 0) ORDER BY tx_vitec_domain_model_download.title + 1 + 0 + 99 + + + + + + select + selectSingle + + + + 0 + + + + 1 + + + + 2 + + + + 3 + + + + + + + + check + 0 + + + + + + check + 0 + + + + + + input + 30 + + + + + + input + 30 + + + + + + input + 30 + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Downloadcardcollection.xml b/packages/vitec/Configuration/FlexForms/Downloadcardcollection.xml new file mode 100644 index 0000000..e813433 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Downloadcardcollection.xml @@ -0,0 +1,172 @@ + + + + + + + General Settings + + array + + + + + inline + 1 + sys_file_reference + tablenames + uid_local + sorting_foreign + uid_foreign + uid_local + + + + file + gif,jpg,jpeg,png,svg + + + + + + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette + + + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette + + + + image + + + 1 + + uid_local + 64 + 64 + + + 1 + 0 + 0 + 1 + 0 + 1 + 1 + + LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference + + + select + 1 + + + + + + + file + jpg,png,svg,jpeg,gif + + + + + + + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,--palette--;;filePalette + + + + + + + + + select + selectMultipleSideBySide + + + + 0 + + + tx_vitec_domain_model_download + AND (tx_vitec_domain_model_download.hidden = 0 AND tx_vitec_domain_model_download.deleted = 0) ORDER BY tx_vitec_domain_model_download.title + 4 + 0 + 99 + + + + + + select + selectSingle + + + + 0 + + + + 1 + + + + 2 + + + + 3 + + + + + + + + check + 0 + + + + + + check + 0 + + + + + + input + 30 + + + + + + input + 30 + + + + + + input + 30 + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Market.xml b/packages/vitec/Configuration/FlexForms/Market.xml new file mode 100644 index 0000000..6812290 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Market.xml @@ -0,0 +1,69 @@ + + + + + + + Select Market + + array + + + + + check + + + + + + + + + + + select + selectSingle + tx_vitec_domain_model_market + AND tx_vitec_domain_model_market.hidden = 0 AND tx_vitec_domain_model_market.deleted = 0 ORDER BY tx_vitec_domain_model_market.title + 1 + 0 + 1 + + + + + + select + selectSingle + + + + default + + + + card + + + + hero + + + + compact + + + default + 1 + + + + + + + diff --git a/packages/vitec/Configuration/FlexForms/Productlist.xml b/packages/vitec/Configuration/FlexForms/Productlist.xml new file mode 100644 index 0000000..4daf0f8 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Productlist.xml @@ -0,0 +1,99 @@ + + + + + + + Select Categories + + array + + + + + check + + + + + + + + + + + check + + + + + + + + + + + Select one or more categories to filter the products in the product list. + + + + select + tree + selectTree + + + parent + + 99 + TRUE + TRUE + + + sys_category + AND (sys_category.sys_language_uid = 0 OR sys_category.l10n_parent = 0) ORDER BY sys_category.sorting + 15 + 0 + 99 + + + + + + Select which layout variation should be used for the product list in the frontend. + + + select + selectSingle + + + + 0 + + + + 1 + + + + 2 + + + + 3 + + + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Productshow.xml b/packages/vitec/Configuration/FlexForms/Productshow.xml new file mode 100644 index 0000000..cd52c63 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Productshow.xml @@ -0,0 +1,82 @@ + + + + + + + Select Product + + array + + + + + check + + + + + + + + + + + select + selectSingle + + + + 0 + + + tx_vitec_domain_model_product + AND tx_vitec_domain_model_product.hidden = 0 AND tx_vitec_domain_model_product.deleted = 0 ORDER BY tx_vitec_domain_model_product.title + 1 + 0 + 1 + + + + + + Select which layout variation should be used for the product in the frontend. + + + select + selectSingle + + + + 0 + + + + 1 + + + + 2 + + + + 3 + + + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Simplecard.xml b/packages/vitec/Configuration/FlexForms/Simplecard.xml new file mode 100644 index 0000000..aea3f7a --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Simplecard.xml @@ -0,0 +1,66 @@ + + + + + + Simple Card Settings + array + + + + + input + 48 + + + + + + text + 1 + default + 48 + 5 + + + + + + file + + Add Image + + 1 + 0 + jpg,jpeg,png,gif,svg + + + 0 + + + 0 + + + + + + + + input + 30 + + + + + + input + inputLink + 30 + trim + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Solution.xml b/packages/vitec/Configuration/FlexForms/Solution.xml new file mode 100644 index 0000000..9168a4f --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Solution.xml @@ -0,0 +1,69 @@ + + + + + + + Select Solution + + array + + + + + check + + + + + + + + + + + select + selectSingle + tx_vitec_domain_model_solution + AND tx_vitec_domain_model_solution.hidden = 0 AND tx_vitec_domain_model_solution.deleted = 0 ORDER BY tx_vitec_domain_model_solution.title + 1 + 0 + 1 + + + + + + select + selectSingle + + + + default + + + + card + + + + hero + + + + compact + + + default + 1 + + + + + + + diff --git a/packages/vitec/Configuration/FlexForms/Usecase.xml b/packages/vitec/Configuration/FlexForms/Usecase.xml new file mode 100644 index 0000000..f026803 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Usecase.xml @@ -0,0 +1,69 @@ + + + + + + + Select Usecase + + array + + + + + check + + + + + + + + + + + select + selectSingle + tx_vitec_domain_model_usecase + AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title + 1 + 0 + 1 + + + + + + select + selectSingle + + + + default + + + + card + + + + hero + + + + compact + + + default + 1 + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/FlexForms/Usecaselist.xml b/packages/vitec/Configuration/FlexForms/Usecaselist.xml new file mode 100644 index 0000000..f7a1cb6 --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Usecaselist.xml @@ -0,0 +1,29 @@ + + + + + + + Select Usecase + + array + + + + + select + selectSingle + tx_vitec_domain_model_usecase + AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title + 1 + 0 + 1 + + + + + + + \ No newline at end of file diff --git a/packages/vitec/Configuration/Icons.php b/packages/vitec/Configuration/Icons.php new file mode 100755 index 0000000..b6f157e --- /dev/null +++ b/packages/vitec/Configuration/Icons.php @@ -0,0 +1,27 @@ + [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-cols-50-50.svg', + ], + 'vitec-cols-33-33-33' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-cols-33-33-33.svg', + ], + 'vitec-cols-25-25-25-25' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-cols-25-25-25-25.svg', + ], + 'vitec-cols-66-33' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-cols-66-33.svg', + ], + 'vitec-cols-33-66' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-cols-33-66.svg', + ], +]; diff --git a/packages/vitec/Configuration/RTE/Vitec.yaml b/packages/vitec/Configuration/RTE/Vitec.yaml new file mode 100644 index 0000000..5fc5456 --- /dev/null +++ b/packages/vitec/Configuration/RTE/Vitec.yaml @@ -0,0 +1,199 @@ +# ============================================================ +# VITEC — Full CKEditor5 Preset +# All available toolbar items, styles, fonts, and options +# ============================================================ + +imports: + - { resource: 'EXT:rte_ckeditor/Configuration/RTE/Processing.yaml' } + - { resource: 'EXT:rte_ckeditor/Configuration/RTE/Editor/Base.yaml' } + - { resource: 'EXT:rte_ckeditor/Configuration/RTE/Editor/Plugins.yaml' } + +editor: + config: + # Load frontend-like classes into RTE iframe for live style preview + contentsCss: + - 'EXT:rte_ckeditor/Resources/Public/Css/contents.css' + - 'EXT:vitec/Resources/Public/Css/rte-content.css' + + # ---- Toolbar (all available items) ---------------------- + toolbar: + shouldNotGroupWhenFull: true + items: + # Undo / Redo / Cleanup + - undo + - redo + - removeFormat + - selectAll + - '|' + # Find & Replace / Source + - findAndReplace + - sourceEditing + - showBlocks + - fullscreen + - '|' + # Headings & Styles + - heading + - style + - '-' + # Basic formatting + - bold + - italic + - underline + - strikethrough + - subscript + - superscript + - softhyphen + - '|' + # Font + - fontFamily + - fontSize + - fontColor + - fontBackgroundColor + - highlight + - highlight:greenMarker + - '-' + # Lists & Indent + - bulletedList + - numberedList + - blockQuote + - indent + - outdent + - alignment + - horizontalLine + - '|' + # Links & Media + - link + - '|' + # Tables + - insertTable + - tableColumn + - tableRow + - mergeTableCells + - TableProperties + - TableCellProperties + - '-' + # Special + - specialCharacters + - textPartLanguage + + # ---- Heading options ------------------------------------ + heading: + options: + - { model: 'paragraph', title: 'Paragraph' } + - { model: 'heading1', view: 'h1', title: 'Heading 1' } + - { model: 'heading2', view: 'h2', title: 'Heading 2' } + - { model: 'heading3', view: 'h3', title: 'Heading 3' } + - { model: 'heading4', view: 'h4', title: 'Heading 4' } + - { model: 'heading5', view: 'h5', title: 'Heading 5' } + - { model: 'heading6', view: 'h6', title: 'Heading 6' } + - { model: 'formatted', view: 'pre', title: 'Pre-Formatted Text' } + + # ---- Style definitions ---------------------------------- + style: + definitions: + # Block level + - { name: 'Lead / Intro', element: 'p', classes: ['lead'] } + - { name: 'Quote / Citation', element: 'blockquote', classes: ['blockquote'] } + - { name: 'Code block', element: 'code' } + # Buttons (VITEC color system) + - { name: 'Button: Orange', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--orange'] } + - { name: 'Button: Blue', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--blue'] } + - { name: 'Button: Graphite', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--graphite'] } + - { name: 'Button: Midnight', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--midnight'] } + - { name: 'Button: Light', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--light'] } + - { name: 'Button: Outline Orange', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--outline-orange'] } + - { name: 'Button: Outline Blue', element: 'a', classes: ['btn', 'btn-vitec', 'btn-vitec--outline-blue'] } + # Inline + - { name: 'Highlight yellow', element: 'mark', classes: ['highlight-yellow'] } + - { name: 'Highlight green', element: 'mark', classes: ['highlight-green'] } + - { name: 'Small text', element: 'small' } + - { name: 'Keyboard input', element: 'kbd' } + - { name: 'Delete / Strike', element: 'del' } + - { name: 'Insert / Underline', element: 'ins' } + + # ---- Alignment ----------------------------------------- + alignment: + options: + - { name: 'left', className: 'text-start' } + - { name: 'center', className: 'text-center' } + - { name: 'right', className: 'text-end' } + - { name: 'justify', className: 'text-justify' } + + # ---- Table defaults ------------------------------------ + table: + defaultHeadings: { rows: 1 } + contentToolbar: + - tableColumn + - tableRow + - mergeTableCells + - tableProperties + - tableCellProperties + - toggleTableCaption + + # ---- Font family ---------------------------------------- + fontFamily: + supportAllValues: false + options: + - 'default' + - 'Arial, sans-serif' + - 'Georgia, serif' + - 'Courier New, monospace' + + # ---- Font size ------------------------------------------ + fontSize: + options: + - 'default' + - 12 + - 14 + - 16 + - 18 + - 20 + - 24 + - 28 + - 32 + - 36 + - 48 + + # ---- Font color ----------------------------------------- + fontColor: + columns: 6 + colors: + - { label: 'VITEC Orange', color: '#F47937' } + - { label: 'VITEC Blue', color: '#26358C' } + - { label: 'VITEC Graphite', color: '#313131' } + - { label: 'VITEC Midnight', color: '#0D0D0D' } + - { label: 'VITEC Light', color: '#CCCCCC' } + - { label: 'Black', color: '#000000' } + - { label: 'White', color: '#FFFFFF' } + + # ---- Font background color ------------------------------ + fontBackgroundColor: + columns: 6 + colors: + - { label: 'VITEC Orange', color: '#F47937' } + - { label: 'VITEC Orange Light', color: '#FBD2BF' } + - { label: 'VITEC Blue', color: '#26358C' } + - { label: 'VITEC Blue Light', color: '#C9D0EE' } + - { label: 'VITEC Graphite', color: '#313131' } + - { label: 'VITEC Midnight', color: '#0D0D0D' } + - { label: 'VITEC Light', color: '#CCCCCC' } + - { label: 'White', color: '#FFFFFF' } + + # ---- Highlight ----------------------------------------- + highlight: + options: + - { model: 'yellowMarker', class: 'marker-yellow', title: 'Yellow marker', color: '#fdfd77', type: 'marker' } + - { model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#63f963', type: 'marker' } + - { model: 'pinkMarker', class: 'marker-pink', title: 'Pink marker', color: '#fc7999', type: 'marker' } + - { model: 'blueMarker', class: 'marker-blue', title: 'Blue marker', color: '#72cdfd', type: 'marker' } + - { model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' } + - { model: 'greenPen', class: 'pen-green', title: 'Green pen', color: '#118800', type: 'pen' } + + # ---- Special characters -------------------------------- + specialCharacters: + order: 'alphabet' + + # ---- Word count (optional — remove if not needed) ------- + wordCount: + displayWords: true + displayCharacters: true diff --git a/packages/vitec/Configuration/Services.yaml b/packages/vitec/Configuration/Services.yaml new file mode 100644 index 0000000..3a1c59b --- /dev/null +++ b/packages/vitec/Configuration/Services.yaml @@ -0,0 +1,18 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Evomedien\Vitec\: + resource: '../Classes/*' + exclude: '../Classes/Domain/Model/*' + + # Make our VitecBackendLayoutView the canonical BackendLayoutView. + # Any service / controller requesting BackendLayoutView gets our override. + Evomedien\Vitec\View\VitecBackendLayoutView: + public: true + + TYPO3\CMS\Backend\View\BackendLayoutView: + alias: Evomedien\Vitec\View\VitecBackendLayoutView + public: true diff --git a/packages/vitec/Configuration/Sets/Vitecset/config.yaml b/packages/vitec/Configuration/Sets/Vitecset/config.yaml new file mode 100644 index 0000000..0e6a3fe --- /dev/null +++ b/packages/vitec/Configuration/Sets/Vitecset/config.yaml @@ -0,0 +1,11 @@ +name: evomedien/vitecset +label: VITEC Set +settings: + website: + background: + color: '#386492' +dependencies: + - typo3/fluid-styled-content + - friendsoftypo3/headless + - itplusx/headless-container + - nb-headless-content-blocks/headless-content-blocks diff --git a/packages/vitec/Configuration/Sets/Vitecset/constants.typoscript b/packages/vitec/Configuration/Sets/Vitecset/constants.typoscript new file mode 100644 index 0000000..e69de29 diff --git a/packages/vitec/Configuration/Sets/Vitecset/page.tsconfig b/packages/vitec/Configuration/Sets/Vitecset/page.tsconfig new file mode 100644 index 0000000..444d69a --- /dev/null +++ b/packages/vitec/Configuration/Sets/Vitecset/page.tsconfig @@ -0,0 +1,2 @@ +# Use the custom VITEC CKEditor preset for all RTE fields +RTE.default.preset = vitec diff --git a/packages/vitec/Configuration/Sets/Vitecset/settings.definitions.yaml b/packages/vitec/Configuration/Sets/Vitecset/settings.definitions.yaml new file mode 100644 index 0000000..6b2776f --- /dev/null +++ b/packages/vitec/Configuration/Sets/Vitecset/settings.definitions.yaml @@ -0,0 +1,44 @@ +categories: + VITEC: + label: 'VITEC Settings' + description: 'Settings for VITEC Stuff' + seo: + label: 'SEO Settings' + description: 'SEO related settings' + menu: + label: 'Menu / Navigation' + description: 'Footer & meta menu page selection' + +settings: + my.vitec.setting: + label: 'My example setting' + category: VITEC + type: string + default: '' + + my.seoRelevantSetting: + label: 'My SEO relevant setting' + category: seo + type: int + default: 5 + + vitec.debugMode: + label: 'Enable Debug Mode' + description: 'Enable debug mode for VITEC extensions' + category: VITEC + type: bool + default: false + + menu.footer.pageUids: + label: 'Footer menu — page UIDs' + description: 'Comma-separated list of page UIDs to display in the footer menu (in order).' + category: menu + type: string + default: '' + + menu.meta.pageUids: + label: 'Meta menu — page UIDs' + description: 'Comma-separated list of page UIDs to display in the meta menu (e.g. Imprint, Privacy).' + category: menu + type: string + default: '' diff --git a/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript new file mode 100644 index 0000000..17b9f1a --- /dev/null +++ b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript @@ -0,0 +1,44 @@ +config { + pageTitleProviders { + vitec { + provider = Evomedien\Vitec\PageTitle\ProductPageTitleProvider + before = record + before = seo + } + } +} + +# Headless: Add products field to vitec_productlist +tt_content.list.20.vitec_productlist = USER +tt_content.list.20.vitec_productlist { + userFunc = Evomedien\Vitec\UserFunc\ProductListJsonRenderer->render +} + +# Override the JSON structure for vitec_productlist +tt_content.list.fields.content.fields.products = USER +tt_content.list.fields.content.fields.products { + userFunc = Evomedien\Vitec\UserFunc\ProductListJsonRenderer->render +} + +# Headless: Add product field to vitec_productshow +tt_content.list.20.vitec_productshow = USER +tt_content.list.20.vitec_productshow { + userFunc = Evomedien\Vitec\UserFunc\ProductShowJsonRenderer->render +} + +# Override the JSON structure for vitec_productshow +tt_content.list.fields.content.fields.product = USER +tt_content.list.fields.content.fields.product { + userFunc = Evomedien\Vitec\UserFunc\ProductShowJsonRenderer->render +} + +# Include container (layout) rendering definitions +@import 'EXT:vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript' + +# Ensure headless's content element JSON definitions take precedence over +# fluid_styled_content's HTML definitions. This is loaded at the end of the +# Vitecset to guarantee winning load order. +@import 'EXT:headless/Configuration/TypoScript/ContentElement/*.typoscript' + +# Include menu (navigation) JSON definitions +@import 'EXT:vitec/Configuration/TypoScript/Headless/vitec_menus.typoscript' diff --git a/packages/vitec/Configuration/Sets/Viterappset/config.yaml b/packages/vitec/Configuration/Sets/Viterappset/config.yaml new file mode 100644 index 0000000..e9d0fbe --- /dev/null +++ b/packages/vitec/Configuration/Sets/Viterappset/config.yaml @@ -0,0 +1,6 @@ +name: evomedien/vitecappset +label: VITEC App Set +settings: + website: + background: + color: '#386492' diff --git a/packages/vitec/Configuration/Sets/Viterappset/settings.definitions.yaml b/packages/vitec/Configuration/Sets/Viterappset/settings.definitions.yaml new file mode 100644 index 0000000..3f1e674 --- /dev/null +++ b/packages/vitec/Configuration/Sets/Viterappset/settings.definitions.yaml @@ -0,0 +1,16 @@ +categories: + myCategory: + label: 'My Category' + +settings: + my.example.setting: + label: 'My example setting' + category: myCategory + type: string + default: '' + + my.seoRelevantSetting: + label: 'My SEO relevant setting' + category: seo + type: int + default: 5 \ No newline at end of file diff --git a/packages/vitec/Configuration/TCA/Overrides/pages.php b/packages/vitec/Configuration/TCA/Overrides/pages.php new file mode 100755 index 0000000..a9a5b04 --- /dev/null +++ b/packages/vitec/Configuration/TCA/Overrides/pages.php @@ -0,0 +1,41 @@ + $l . 'layout.0', 'value' => '0'], + ['label' => $l . 'layout.1', 'value' => '1'], + ['label' => $l . 'layout.2', 'value' => '2'], + ['label' => $l . 'layout.3', 'value' => '3'], + ['label' => $l . 'layout.4', 'value' => '4'], + ['label' => $l . 'layout.5', 'value' => '5'], + ['label' => $l . 'layout.6', 'value' => '6'], + ['label' => $l . 'layout.7', 'value' => '7'], + ['label' => $l . 'layout.8', 'value' => '8'], + ['label' => $l . 'layout.9', 'value' => '9'], + ['label' => $l . 'layout.10', 'value' => '10'], + ['label' => $l . 'layout.11', 'value' => '11'], + ['label' => $l . 'layout.12', 'value' => '12'], + ['label' => $l . 'layout.13', 'value' => '13'], + ['label' => $l . 'layout.14', 'value' => '14'], + ['label' => $l . 'layout.15', 'value' => '15'], + ['label' => $l . 'layout.16', 'value' => '16'], + ['label' => $l . 'layout.17', 'value' => '17'], + ['label' => $l . 'layout.18', 'value' => '18'], + ['label' => $l . 'layout.19', 'value' => '19'], + ['label' => $l . 'layout.20', 'value' => '20'], + ]; + $GLOBALS['TCA']['pages']['columns']['layout']['config']['default'] = '0'; + + // Hide the backend_layout / backend_layout_next_level fields from the editor — + // they are auto-synced from the frontend layout via SyncBackendLayoutHook. + $GLOBALS['TCA']['pages']['columns']['backend_layout']['displayCond'] = 'HIDE_FOR_NON_ADMINS'; + $GLOBALS['TCA']['pages']['columns']['backend_layout_next_level']['displayCond'] = 'HIDE_FOR_NON_ADMINS'; +})(); diff --git a/packages/vitec/Configuration/TCA/Overrides/sys_category.php b/packages/vitec/Configuration/TCA/Overrides/sys_category.php new file mode 100644 index 0000000..801a2ae --- /dev/null +++ b/packages/vitec/Configuration/TCA/Overrides/sys_category.php @@ -0,0 +1,46 @@ + [ + 'exclude' => true, + 'label' => 'CSS Class', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'filetype' => [ + 'exclude' => true, + 'label' => 'Filetype (2nd part of filename)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], + 'type' => [ + 'exclude' => true, + 'label' => 'Type - actual Type, can be different to filename', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + ], + ], +]; + +// Add the fields to the TCA +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('sys_category', $customSysCategoryColumns); + +// Add the fields to the "General" tab of sys_category +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes( + 'sys_category', + 'class, filetype, type', + '', + 'after:description' +); \ No newline at end of file diff --git a/packages/vitec/Configuration/TCA/Overrides/sys_template.php b/packages/vitec/Configuration/TCA/Overrides/sys_template.php new file mode 100644 index 0000000..16eb15f --- /dev/null +++ b/packages/vitec/Configuration/TCA/Overrides/sys_template.php @@ -0,0 +1,4 @@ + 'Vitec: Full Width', 'value' => 'vitec-full-width'], + ['label' => 'Vitec: Centered Container', 'value' => 'vitec-centered'], + ['label' => 'Vitec: Card Style', 'value' => 'vitec-card'], + ['label' => 'Vitec: Dark Background', 'value' => 'vitec-dark'], + ['label' => 'Vitec: Highlight Box', 'value' => 'vitec-highlight'], + ] +); diff --git a/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_cols_25_25_25_25.php b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_cols_25_25_25_25.php new file mode 100755 index 0000000..fcb7702 --- /dev/null +++ b/packages/vitec/Configuration/TCA/Overrides/tt_content_vitec_cols_25_25_25_25.php @@ -0,0 +1,60 @@ +configureContainer( + (new ContainerConfiguration( + 'vitec_cols_25_25_25_25', + $l . 'cols_25_25_25_25.title', + $l . 'cols_25_25_25_25.description', + [ + [ + ['name' => $l . 'column.1', 'colPos' => 231], + ['name' => $l . 'column.2', 'colPos' => 232], + ['name' => $l . 'column.3', 'colPos' => 233], + ['name' => $l . 'column.4', 'colPos' => 234], + ], + ] + )) + ->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cols-25-25-25-25.svg') + ->setGroup('vitec') + ->setSaveAndCloseInNewContentElementWizard(true) + ); + + $GLOBALS['TCA']['tt_content']['types']['vitec_cols_25_25_25_25']['showitem'] = + '--palette--;;general, + header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, + subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline, + tx_vitec_bg_variant, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access'; + + ExtensionManagementUtility::addPageTSConfig(<<configureContainer( + (new ContainerConfiguration( + 'vitec_cols_33_33_33', + $l . 'cols_33_33_33.title', + $l . 'cols_33_33_33.description', + [ + [ + ['name' => $l . 'column.1', 'colPos' => 221], + ['name' => $l . 'column.2', 'colPos' => 222], + ['name' => $l . 'column.3', 'colPos' => 223], + ], + ] + )) + ->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cols-33-33-33.svg') + ->setGroup('vitec') + ->setSaveAndCloseInNewContentElementWizard(true) + ); + + $GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_33_33']['showitem'] = + '--palette--;;general, + header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, + subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline, + tx_vitec_bg_variant, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access'; + + ExtensionManagementUtility::addPageTSConfig(<<configureContainer( + (new ContainerConfiguration( + 'vitec_cols_33_66', + $l . 'cols_33_66.title', + $l . 'cols_33_66.description', + [ + [ + ['name' => $l . 'column.sidebar_33', 'colPos' => 251], + ['name' => $l . 'column.main_66', 'colPos' => 252], + ], + ] + )) + ->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cols-33-66.svg') + ->setGroup('vitec') + ->setSaveAndCloseInNewContentElementWizard(true) + ); + + $GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_66']['showitem'] = + '--palette--;;general, + header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, + subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline, + tx_vitec_bg_variant, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access'; + + ExtensionManagementUtility::addPageTSConfig(<<configureContainer( + (new ContainerConfiguration( + 'vitec_cols_50_50', + $l . 'cols_50_50.title', + $l . 'cols_50_50.description', + [ + [ + ['name' => $l . 'column.1', 'colPos' => 211], + ['name' => $l . 'column.2', 'colPos' => 212], + ], + ] + )) + ->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cols-50-50.svg') + ->setGroup('vitec') + ->setSaveAndCloseInNewContentElementWizard(true) + ); + + $GLOBALS['TCA']['tt_content']['types']['vitec_cols_50_50']['showitem'] = + '--palette--;;general, + header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, + subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline, + tx_vitec_bg_variant, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access'; + + ExtensionManagementUtility::addPageTSConfig(<<configureContainer( + (new ContainerConfiguration( + 'vitec_cols_66_33', + $l . 'cols_66_33.title', + $l . 'cols_66_33.description', + [ + [ + ['name' => $l . 'column.main_66', 'colPos' => 241], + ['name' => $l . 'column.sidebar_33', 'colPos' => 242], + ], + ] + )) + ->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cols-66-33.svg') + ->setGroup('vitec') + ->setSaveAndCloseInNewContentElementWizard(true) + ); + + $GLOBALS['TCA']['tt_content']['types']['vitec_cols_66_33']['showitem'] = + '--palette--;;general, + header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, + subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline, + tx_vitec_bg_variant, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, + --palette--;;frames, + --palette--;;appearanceLinks, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, + --palette--;;language, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, + --palette--;;hidden, + --palette--;;access'; + + ExtensionManagementUtility::addPageTSConfig(<< [ + 'label' => $l . 'bg_variant.label', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + ['label' => $l . 'bg_variant.option.none', 'value' => 'none'], + ['label' => $l . 'bg_variant.option.orange', 'value' => 'orange'], + ['label' => $l . 'bg_variant.option.blue', 'value' => 'blue'], + ['label' => $l . 'bg_variant.option.graphite', 'value' => 'graphite'], + ['label' => $l . 'bg_variant.option.midnight', 'value' => 'midnight'], + ], + 'default' => 'none', + ], + ], + ]); + + // Register VITEC wizard group header + ExtensionManagementUtility::addPageTSConfig(<< [ + 'title' => 'VITEC Download', + 'label' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,slug,teaser,description,sort1,sort2,sort3,icon,filepath,fileprefix', + 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_download.gif' + ], + 'types' => [ + '1' => ['showitem' => 'hidden, title, slug, keywords, teaser, description, + --div--;File, useolddl, file, fileprefix, categories, + --div--;Visibility, hideonapp, hideonwebsite, hideondatasheets, hideonproducts, + --div--;Currently not in use,icon, filepath, private_download, onlyondatasheets, sort1, sort2, sort3, + --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.access, starttime, endtime'], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'special' => 'languages', + 'items' => [ + [ + 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', + -1, + 'flags-multiple' + ] + ], + 'default' => 0, + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_vitec_domain_model_download', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_download}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_download}.{#sys_language_uid} IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => true + ] + ], + ], + ], + 'starttime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true + ] + ], + ], + 'endtime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'range' => [ + 'upper' => mktime(0, 0, 0, 1, 1, 2038) + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true + ] + ], + ], + 'categories' => [ + 'config' => [ + 'type' => 'category' + ] + ], + 'title' => [ + 'exclude' => true, + 'label' => 'Title', + 'description' => 'Title of the Download - this will be shown in search results', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'teaser' => [ + 'exclude' => true, + 'label' => 'Teaser Text for Download Cards', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'slug' => [ + 'exclude' => true, + 'label' => 'Slug', + 'config' => [ + 'type' => 'slug', + 'generatorOptions' => [ + 'fields' => ['title'], + 'fieldSeparator' => '/', + 'prefixParentPageSlug' => true, + 'replacements' => [ + '/' => '', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'unique', + ], + ], + 'keywords' => [ + 'exclude' => true, + 'label' => 'Keywords for internal search engine', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'description' => [ + 'exclude' => true, + 'label' => 'Description', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => true, + 'richtextConfiguration' => 'default', + 'fieldControl' => [ + 'fullScreenRichtext' => [ + 'disabled' => false, + ], + ], + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim', + ], + + ], + 'file' => [ + 'exclude' => true, + 'label' => 'Manual File Upload - if "Use manual file upload" is checked', + 'config' => [ + 'type' => 'file', + 'appearance' => [ + 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.addFileReference' + ], + 'foreign_types' => [ + '0' => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + \TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + \TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + \TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + \TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ], + \TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [ + 'showitem' => ' + --palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette, + --palette--;;filePalette' + ] + ], + 'foreign_match_fields' => [ + 'fieldname' => 'file', + 'tablenames' => 'tx_vitec_domain_model_download', + ], + 'maxitems' => 1 + ], + ], + 'sort1' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.sort1', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'sort2' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.sort2', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'sort3' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.sort3', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'private_download' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.private_download', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + 'hideonapp' => [ + 'exclude' => true, + 'label' => 'Do not show this Download in the App', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + 'hideonwebsite' => [ + 'exclude' => true, + 'label' => 'Do not show this Download on the Website', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + 'hideondatasheets' => [ + 'exclude' => true, + 'label' => 'Do not show this Download on the Datasheets-Page', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + 'hideonproducts' => [ + 'exclude' => true, + 'label' => 'Do not show this Download on a Products-Page', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + 'icon' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.icon', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'filepath' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.filepath', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'fileprefix' => [ + 'exclude' => true, + 'label' => 'Fileprefix (1st part of filename)', + 'description' => 'This is the first part of the filename.', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'useolddl' => [ + 'exclude' => true, + 'label' => 'Use manual file upload', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + ] + ], + 'default' => 0, + ] + ], + ], +]; diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_market.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_market.php new file mode 100644 index 0000000..12420ce --- /dev/null +++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_market.php @@ -0,0 +1,204 @@ + [ + 'title' => 'VITEC Market', + 'label' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,subtitle,teaser', + 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_market.gif', + 'security' => [ + 'ignorePageTypeRestriction' => true, + ], + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, subtitle, teaser, description, image, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime', + ], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'language', + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_vitec_domain_model_market', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_market}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_market}.{#sys_language_uid} IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => true, + ], + ], + ], + ], + 'starttime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + 'endtime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'range' => [ + 'upper' => mktime(0, 0, 0, 1, 1, 2038), + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + 'title' => [ + 'exclude' => false, + 'label' => 'Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'required' => true, + 'default' => '', + ], + ], + 'subtitle' => [ + 'exclude' => true, + 'label' => 'Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'teaser' => [ + 'exclude' => true, + 'label' => 'Short Teaser (optional)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'description' => [ + 'exclude' => true, + 'label' => 'Description', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => true, + 'eval' => 'trim', + 'default' => '', + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]', + ], + 'categories' => [ + 'config' => [ + 'type' => 'category', + ], + ], + 'image' => [ + 'exclude' => true, + 'label' => 'Image', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'image', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'overrideChildTca' => [ + 'columns' => [ + 'uid_local' => [ + 'config' => [ + 'appearance' => [ + 'elementBrowserType' => 'file', + 'elementBrowserAllowed' => 'jpg,jpeg,png,gif,webp,svg', + ], + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => ' + --palette--;;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'appearance' => [ + 'headerThumbnail' => [ + 'field' => 'uid_local', + 'width' => '45', + 'height' => '45', + ], + 'enabledControls' => [ + 'info' => true, + 'new' => false, + 'dragdrop' => true, + 'sort' => false, + 'hide' => true, + 'delete' => true, + ], + 'fileUploadAllowed' => true, + ], + ], + ], + ], +]; diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_product.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_product.php new file mode 100644 index 0000000..90850fe --- /dev/null +++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_product.php @@ -0,0 +1,541 @@ + [ + 'title' => 'VITEC Product', + 'label' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,slug', + 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif', + 'security' => [ + 'ignorePageTypeRestriction' => true, + ], + ], + 'types' => [ + '1' => ['showitem' => 'title, subtitle, slug, teaser, description, applications, highlights, contentelement, contentelementcta, downloads, + --div--;SEO, seotitle, urltitle, seometa, keywords, structureddata, + --div--;Images and Videos, productimage, ogimage, video, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories, + --div--;Visibility, hideonapp, hideonwebsite, hideondatasheets, hideonproducts, shortcut, shortcutpid, + --div--;Misc, legacy, supportproduct, subproduct, relatedprodukt, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime'], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'language', + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_vitec_domain_model_product', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_product}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_product}.{#sys_language_uid} IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => true + ] + ], + ], + ], + 'starttime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true + ] + ], + ], + 'endtime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'range' => [ + 'upper' => mktime(0, 0, 0, 1, 1, 2038) + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true + ] + ], + ], + 'categories' => [ + 'config' => [ + 'type' => 'category' + ] + ], + 'title' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.title.description', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'required' => true, + 'default' => '' + ], + ], + 'slug' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.slug.description', + 'config' => [ + 'type' => 'slug', + 'size' => 50, + 'generatorOptions' => [ + 'fields' => ['title'], // TODO: adjust this field to the one you want to use + 'fieldSeparator' => '-', + 'replacements' => [ + '/' => '', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'uniqueInPid', + ], + + ], + 'seotitle' => [ + 'exclude' => false, + 'label' => 'SEO-Title', + 'description' => 'SEO specific title, used for sharing options', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'urltitle' => [ + 'exclude' => false, + 'label' => 'URL-Title', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_product.urltitle.description', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'video' => [ + 'exclude' => false, + 'label' => 'Product Video - just the Youtube Clip ID', + 'description' => 'Product Video - just the Youtube Clip ID', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'downloads' => [ + 'exclude' => true, + 'label' => 'Downloads', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_vitec_domain_model_download', + 'MM' => 'tx_vitec_product_download_mm', // Define the MM table for the relation + 'size' => 10, + 'maxitems' => 9999, + 'minitems' => 0, + 'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering + ], + ], + 'structureddata' => [ + 'exclude' => true, + 'label' => 'Structured Data in JSON Format', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'keywords' => [ + 'exclude' => true, + 'label' => 'Keywords', + 'description' => 'Keywords for SEO, separated by commas', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'seometa' => [ + 'exclude' => true, + 'label' => 'SEO Meta Description', + 'description' => 'SEO specific meta description, used for sharing options', + 'config' => [ + 'type' => 'text', + 'cols' => 40, + 'rows' => 15, + 'eval' => 'trim' + ] + ], + 'teaser' => [ + 'exclude' => true, + 'label' => 'Short Teaser (optional)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + + 'subtitle' => [ + 'exclude' => true, + 'label' => 'Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + 'hideonapp' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on App', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + 'hideonwebsite' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on Website', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + + 'hideonproducts' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on Product Section', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + + 'hideondatasheets' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on Datasheets Page', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + + 'applications' => [ + 'exclude' => true, + 'label' => 'Applications', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => 'true', + 'eval' => 'trim', + 'default' => '' + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]' + ], + 'description' => [ + 'exclude' => true, + 'label' => 'Description', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => 'true', + 'eval' => 'trim', + 'default' => '' + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]' + ], + + 'highlights' => [ + 'exclude' => true, + 'label' => 'Highlights', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => 'true', + 'eval' => 'trim', + 'default' => '' + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]' + ], + + 'shortcut' => [ + 'exclude' => true, + 'label' => 'Does this Product have a custom Page', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + 'shortcutpid' => [ + 'exclude' => false, + 'label' => 'Page ID', + 'description' => 'PID of the custom page', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '' + ], + ], + + 'legacy' => [ + 'exclude' => true, + 'label' => 'Is this a legacy produc?', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + + 'supportproduct' => [ + 'exclude' => true, + 'label' => 'Is this a support product?', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + + 'subproduct' => [ + 'exclude' => true, + 'label' => 'Product is a sub-products', + 'description' => 'This Product will not be shown as a main product', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false + ] + ], + ], + ], + 'relatedprodukt' => [ + 'exclude' => true, + 'label' => 'Related Products', + 'description' => 'Select related products for this product', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectMultipleSideBySide', + 'foreign_table' => 'tx_vitec_domain_model_product', // Reference the same table + 'MM' => 'tx_vitec_product_related_mm', // Define the MM table for the relation + 'size' => 10, + 'maxitems' => 9999, + 'minitems' => 0, + 'enableMultiSelectFilterTextfield' => true, // Optional: Adds a search box for filtering + ], + ], + 'productimage' => [ + 'exclude' => true, + 'label' => 'Product Image(s)', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'productimage', + ], + 'appearance' => [ + 'collapseAll' => true, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => true, + 'showPossibleLocalizationRecords' => true, + 'showAllLocalizationLink' => true, + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'filter' => [ + [ + 'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren', + 'parameters' => [ + 'allowedFileExtensions' => 'jpg,jpeg,png,gif', + ], + ], + ], + 'maxitems' => 10, + 'minitems' => 0, + ], + ], + + 'ogimage' => [ + 'exclude' => true, + 'label' => 'Open Graph Image', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'ogimage', + ], + 'appearance' => [ + 'collapseAll' => true, + 'levelLinksPosition' => 'top', + 'showSynchronizationLink' => true, + 'showPossibleLocalizationRecords' => true, + 'showAllLocalizationLink' => true, + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'filter' => [ + [ + 'userFunc' => \TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter::class . '->filterInlineChildren', + 'parameters' => [ + 'allowedFileExtensions' => 'jpg,jpeg,png,gif,webp', + ], + ], + ], + 'maxitems' => 1, // Allow only one image + 'minitems' => 0, + ], + ], + + 'contentelement' => [ + 'exclude' => true, + 'label' => 'Select Content Element for Key Features Section', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputLink', + 'softref' => 'typolink', + 'eval' => 'trim', + 'fieldControl' => [ + 'linkPopup' => [ + 'options' => [ + 'title' => 'Select Content Element', + 'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types + 'blindLinkFields' => 'class,params', // Disable unnecessary fields + ], + ], + ], + ], + ], + + 'contentelementcta' => [ + 'exclude' => true, + 'label' => 'Select Content Element for CTA Section', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputLink', + 'softref' => 'typolink', + 'eval' => 'trim', + 'fieldControl' => [ + 'linkPopup' => [ + 'options' => [ + 'title' => 'Select Content Element', + 'blindLinkOptions' => 'mail,folder,url', // Disable unnecessary link types + 'blindLinkFields' => 'class,params', // Disable unnecessary fields + ], + ], + ], + ], + ], + +/* ----------------------------------------------------- */ + ], +]; \ No newline at end of file diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_solution.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_solution.php new file mode 100644 index 0000000..566f9b7 --- /dev/null +++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_solution.php @@ -0,0 +1,204 @@ + [ + 'title' => 'VITEC Solution', + 'label' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,subtitle,teaser', + 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_solution.gif', + 'security' => [ + 'ignorePageTypeRestriction' => true, + ], + ], + 'types' => [ + '1' => [ + 'showitem' => 'title, subtitle, teaser, description, image, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime', + ], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'language', + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_vitec_domain_model_solution', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_solution}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_solution}.{#sys_language_uid} IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => true, + ], + ], + ], + ], + 'starttime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + 'endtime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'range' => [ + 'upper' => mktime(0, 0, 0, 1, 1, 2038), + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + 'title' => [ + 'exclude' => false, + 'label' => 'Title', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'required' => true, + 'default' => '', + ], + ], + 'subtitle' => [ + 'exclude' => true, + 'label' => 'Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'teaser' => [ + 'exclude' => true, + 'label' => 'Short Teaser (optional)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'description' => [ + 'exclude' => true, + 'label' => 'Description', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => true, + 'eval' => 'trim', + 'default' => '', + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]', + ], + 'categories' => [ + 'config' => [ + 'type' => 'category', + ], + ], + 'image' => [ + 'exclude' => true, + 'label' => 'Image', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'image', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'overrideChildTca' => [ + 'columns' => [ + 'uid_local' => [ + 'config' => [ + 'appearance' => [ + 'elementBrowserType' => 'file', + 'elementBrowserAllowed' => 'jpg,jpeg,png,gif,webp,svg', + ], + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => ' + --palette--;;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'appearance' => [ + 'headerThumbnail' => [ + 'field' => 'uid_local', + 'width' => '45', + 'height' => '45', + ], + 'enabledControls' => [ + 'info' => true, + 'new' => false, + 'dragdrop' => true, + 'sort' => false, + 'hide' => true, + 'delete' => true, + ], + 'fileUploadAllowed' => true, + ], + ], + ], + ], +]; diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php new file mode 100644 index 0000000..8ac1fd6 --- /dev/null +++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php @@ -0,0 +1,318 @@ + [ + 'title' => 'VITEC Success Story', + 'label' => 'title', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'versioningWS' => true, + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l10n_parent', + 'transOrigDiffSourceField' => 'l10n_diffsource', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'starttime', + 'endtime' => 'endtime', + ], + 'searchFields' => 'title,slug', + 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif', + 'security' => [ + 'ignorePageTypeRestriction' => true, + ], + ], + 'types' => [ + '1' => ['showitem' => 'title, slug, subtitle, teaser, description, singlepid, caseimage, logoimage, + --div--;Visibility, hideonapp, hideonwebsite, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories, + --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime', ], + ], + 'columns' => [ + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'type' => 'language', + ], + ], + 'l10n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'default' => 0, + 'items' => [ + ['', 0], + ], + 'foreign_table' => 'tx_vitec_domain_model_download', + 'foreign_table_where' => 'AND {#tx_vitec_domain_model_download}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_download}.{#sys_language_uid} IN (-1,0)', + ], + ], + 'l10n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'hidden' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => true, + ], + ], + ], + ], + 'starttime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + 'endtime' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', + 'config' => [ + 'type' => 'input', + 'renderType' => 'inputDateTime', + 'eval' => 'datetime,int', + 'default' => 0, + 'range' => [ + 'upper' => mktime(0, 0, 0, 1, 1, 2038), + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + ], + ], + + 'title' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title.description', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'required' => true, + 'default' => '', + ], + ], + 'slug' => [ + 'exclude' => false, + 'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.slug', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.slug.description', + 'config' => [ + 'type' => 'slug', + 'size' => 50, + 'generatorOptions' => [ + 'fields' => ['title'], // TODO: adjust this field to the one you want to use + 'fieldSeparator' => '-', + 'replacements' => [ + '/' => '', + ], + ], + 'fallbackCharacter' => '-', + 'eval' => 'uniqueInPid', + ], + + ], + 'singlepid' => [ + 'exclude' => false, + 'label' => 'Page ID of Success Story', + 'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title.description', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'required' => false, + 'default' => '', + ], + ], + 'teaser' => [ + 'exclude' => true, + 'label' => 'Short Teaser (optional)', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'subtitle' => [ + 'exclude' => true, + 'label' => 'Subtitle', + 'config' => [ + 'type' => 'input', + 'size' => 30, + 'eval' => 'trim', + 'default' => '', + ], + ], + 'hideonapp' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on App', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false, + ], + ], + ], + ], + 'hideonwebsite' => [ + 'exclude' => true, + 'label' => 'If checked - Product will not be shown on Website', + 'config' => [ + 'type' => 'check', + 'renderType' => 'checkboxToggle', + 'items' => [ + [ + 0 => '', + 1 => '', + 'invertStateDisplay' => false, + ], + ], + ], + ], + 'description' => [ + 'exclude' => true, + 'label' => 'Description', + 'config' => [ + 'type' => 'text', + 'enableRichtext' => 'true', + 'eval' => 'trim', + 'default' => '', + ], + 'defaultExtras' => 'richtext:rte_transform[mode=ts_css]', + ], + 'caseimage' => [ + 'exclude' => true, + 'label' => 'Case Image', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'caseimage', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'overrideChildTca' => [ + 'columns' => [ + 'uid_local' => [ + 'config' => [ + 'appearance' => [ + 'elementBrowserType' => 'file', + 'elementBrowserAllowed' => 'jpg,jpeg,png,gif', + ], + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => ' + --palette--;;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'appearance' => [ + 'headerThumbnail' => [ + 'field' => 'uid_local', + 'width' => '45', + 'height' => '45', + ], + 'enabledControls' => [ + 'info' => true, + 'new' => false, + 'dragdrop' => true, + 'sort' => false, + 'hide' => true, + 'delete' => true, + ], + 'fileUploadAllowed' => true, + ], + ], + ], + 'logoimage' => [ + 'exclude' => true, + 'label' => 'Customer Logo', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'sys_file_reference', + 'foreign_field' => 'uid_foreign', + 'foreign_sortby' => 'sorting_foreign', + 'foreign_table_field' => 'tablenames', + 'foreign_match_fields' => [ + 'fieldname' => 'logoimage', + ], + 'foreign_label' => 'uid_local', + 'foreign_selector' => 'uid_local', + 'overrideChildTca' => [ + 'columns' => [ + 'uid_local' => [ + 'config' => [ + 'appearance' => [ + 'elementBrowserType' => 'file', + 'elementBrowserAllowed' => 'jpg,jpeg,png,gif', + ], + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => ' + --palette--;;imageoverlayPalette, + --palette--;;filePalette', + ], + ], + ], + 'maxitems' => 1, + 'appearance' => [ + 'headerThumbnail' => [ + 'field' => 'uid_local', + 'width' => '45', + 'height' => '45', + ], + 'enabledControls' => [ + 'info' => true, + 'new' => false, + 'dragdrop' => true, + 'sort' => false, + 'hide' => true, + 'delete' => true, + ], + 'fileUploadAllowed' => true, + ], + ], + ], + 'categories' => [ + 'config' => [ + 'type' => 'category' + ] + ], + ], +]; \ No newline at end of file diff --git a/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript b/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript new file mode 100755 index 0000000..7effd3a --- /dev/null +++ b/packages/vitec/Configuration/TypoScript/Headless/vitec_containers.typoscript @@ -0,0 +1,89 @@ +# ============================================================================= +# VITEC container content elements — self-contained JSON rendering +# Children collected via Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor +# (exception-safe; returns [] on any error, never crashes the outer CE). +# ============================================================================= + +tt_content.vitec_cols_50_50 = JSON +tt_content.vitec_cols_50_50 { + fields { + id = INT + id.field = uid + type = TEXT + type.field = CType + colPos = INT + colPos.field = colPos + + categories = COA + categories { + 10 = CONTENT + 10 { + table = sys_category + select { + pidInList = root + selectFields = sys_category.title + join = sys_category_record_mm on sys_category_record_mm.uid_local = sys_category.uid + where { + field = uid + wrap = AND sys_category_record_mm.tablenames = 'tt_content' AND sys_category_record_mm.uid_foreign=| + } + } + renderObj = TEXT + renderObj { + field = title + wrap = |###BREAK### + } + } + stdWrap.split { + token = ###BREAK### + cObjNum = 1 |*|2|*| 3 + 1 { + current = 1 + stdWrap.wrap = | + } + 2 { + current = 1 + stdWrap.wrap = ,| + } + 3 { + current = 1 + stdWrap.wrap = | + } + } + } + + appearance = JSON + appearance { + fields { + layout = TEXT + layout.field = layout + frameClass = TEXT + frameClass.field = frame_class + spaceBefore = TEXT + spaceBefore.field = space_before_class + spaceAfter = TEXT + spaceAfter.field = space_after_class + } + } + + header = TEXT + header.field = header + subheader = TEXT + subheader.field = subheader + tx_vitec_bg_variant = TEXT + tx_vitec_bg_variant.field = tx_vitec_bg_variant + + items = JSON + items { + dataProcessing { + 10 = Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor + 10.as = items + } + } + } +} + +tt_content.vitec_cols_33_33_33 =< tt_content.vitec_cols_50_50 +tt_content.vitec_cols_25_25_25_25 =< tt_content.vitec_cols_50_50 +tt_content.vitec_cols_66_33 =< tt_content.vitec_cols_50_50 +tt_content.vitec_cols_33_66 =< tt_content.vitec_cols_50_50 diff --git a/packages/vitec/Configuration/TypoScript/Headless/vitec_menus.typoscript b/packages/vitec/Configuration/TypoScript/Headless/vitec_menus.typoscript new file mode 100755 index 0000000..4cebe2b --- /dev/null +++ b/packages/vitec/Configuration/TypoScript/Headless/vitec_menus.typoscript @@ -0,0 +1,74 @@ +# ============================================================================= +# VITEC menus — Headless / JSON output +# +# Three menus are exposed at the page root of the JSON response: +# - mainNavigation : full hierarchy from root, all visible pages +# - footerMenu : curated, configured via site setting menu.footer.pageUids +# - metaMenu : curated, configured via site setting menu.meta.pageUids +# +# Each menu uses friendsoftypo3/headless MenuProcessor which: +# * resolves Shortcut pages to their target +# * skips pages with "Show in menu = no" (nav_hide=1) by default +# * marks active / current items +# * exposes `children` for sub-levels +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Main navigation — full hierarchy +# ----------------------------------------------------------------------------- +lib.mainNavigation = JSON +lib.mainNavigation { + dataProcessing { + 10 = FriendsOfTYPO3\Headless\DataProcessing\MenuProcessor + 10 { + levels = 10 + expandAll = 1 + includeSpacer = 0 + titleField = nav_title // title + as = mainNavigation + } + } +} + +# ----------------------------------------------------------------------------- +# Footer menu — curated by site setting (UIDs) +# ----------------------------------------------------------------------------- +lib.footerMenu = JSON +lib.footerMenu { + dataProcessing { + 10 = FriendsOfTYPO3\Headless\DataProcessing\MenuProcessor + 10 { + special = list + special.value = {$menu.footer.pageUids} + levels = 1 + includeSpacer = 0 + titleField = nav_title // title + as = footerMenu + } + } +} + +# ----------------------------------------------------------------------------- +# Meta menu — curated by site setting (UIDs) +# ----------------------------------------------------------------------------- +lib.metaMenu = JSON +lib.metaMenu { + dataProcessing { + 10 = FriendsOfTYPO3\Headless\DataProcessing\MenuProcessor + 10 { + special = list + special.value = {$menu.meta.pageUids} + levels = 1 + includeSpacer = 0 + titleField = nav_title // title + as = metaMenu + } + } +} + +# ----------------------------------------------------------------------------- +# Wire them into the page-level JSON output +# ----------------------------------------------------------------------------- +page.10.fields.mainNavigation =< lib.mainNavigation +page.10.fields.footerMenu =< lib.footerMenu +page.10.fields.metaMenu =< lib.metaMenu diff --git a/packages/vitec/Configuration/TypoScript/Headless/vitec_productlist.typoscript b/packages/vitec/Configuration/TypoScript/Headless/vitec_productlist.typoscript new file mode 100644 index 0000000..c42c6f2 --- /dev/null +++ b/packages/vitec/Configuration/TypoScript/Headless/vitec_productlist.typoscript @@ -0,0 +1,3 @@ +# Test if we can add any field at all +tt_content.list.fields.content.fields.testField = TEXT +tt_content.list.fields.content.fields.testField.value = TEST VALUE FROM VITEC diff --git a/packages/vitec/Configuration/TypoScript/constants.typoscript b/packages/vitec/Configuration/TypoScript/constants.typoscript new file mode 100644 index 0000000..e5d124b --- /dev/null +++ b/packages/vitec/Configuration/TypoScript/constants.typoscript @@ -0,0 +1 @@ +plugin.tx_vitec.settings.layout = 0 \ No newline at end of file diff --git a/packages/vitec/Configuration/TypoScript/setup.typoscript b/packages/vitec/Configuration/TypoScript/setup.typoscript new file mode 100644 index 0000000..b4c86a1 --- /dev/null +++ b/packages/vitec/Configuration/TypoScript/setup.typoscript @@ -0,0 +1,74 @@ +plugin.tx_vitec_usecaseshow { + view { + templateRootPaths.0 = EXT:vitec/Resources/Private/Templates/ + partialRootPaths.0 = EXT:vitec/Resources/Private/Partials/ + layoutRootPaths.0 = EXT:vitec/Resources/Private/Layouts/ + } + persistence { + storagePid = 50 + } +} + +plugin.tx_vitec_marketshow { + view { + templateRootPaths.0 = EXT:vitec/Resources/Private/Templates/ + partialRootPaths.0 = EXT:vitec/Resources/Private/Partials/ + layoutRootPaths.0 = EXT:vitec/Resources/Private/Layouts/ + } + persistence { + storagePid = 50 + } +} + +plugin.tx_vitec_solutionshow { + view { + templateRootPaths.0 = EXT:vitec/Resources/Private/Templates/ + partialRootPaths.0 = EXT:vitec/Resources/Private/Partials/ + layoutRootPaths.0 = EXT:vitec/Resources/Private/Layouts/ + } + persistence { + storagePid = 50 + } +} + +plugin.tx_vitec_usecaselist { + view { + templateRootPaths.0 = EXT:vitec/Resources/Private/Templates/ + partialRootPaths.0 = EXT:vitec/Resources/Private/Partials/ + layoutRootPaths.0 = EXT:vitec/Resources/Private/Layouts/ + } + persistence { + storagePid = 50 + } +} +plugin.tx_vitec_Prodcutlist { + settings { + # Map FlexForm settings to the controller + layout = {$plugin.tx_vitec.settings.layout} + } +} +plugin.tx_vitec_Prodcutlist { + settings { + categories = 1,2,3 + } +} +config.tx_extbase { + persistence { + classes { + TYPO3\CMS\Extbase\Domain\Model\Category { + subclass = Evomedien\Vitec\Domain\Model\Category + } + Evomedien\Vitec\Domain\Model\Category { + mapping { + tableName = sys_category + recordType = 0 + } + } + } + } +} + +# Include Headless configurations +@import 'EXT:vitec/Configuration/TypoScript/Headless/*.typoscript' + + diff --git a/packages/vitec/Configuration/page.tsconfig b/packages/vitec/Configuration/page.tsconfig new file mode 100644 index 0000000..312c5d7 --- /dev/null +++ b/packages/vitec/Configuration/page.tsconfig @@ -0,0 +1,118 @@ +# ============================================================= +# VITEC Extension — page.tsconfig +# ============================================================= + +mod { + wizards.newContentElement.wizardItems.vitec { + header = VITEC + show = * + elements { + # -------- Extbase Plugins -------- + productlist { + iconIdentifier = vitec-plugin-productlist + title = List of VITEC Products + description = LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_productlist.description + tt_content_defValues { + CType = list + list_type = vitec_productlist + } + } + productshow { + iconIdentifier = vitec-plugin-productshow + title = Show single VITEC Product + description = Either show a single product coming from a list, OR select a specific product in flexform + tt_content_defValues { + CType = list + list_type = vitec_productshow + } + } + usecaseshow { + iconIdentifier = vitec-plugin-usecaseshow + title = Show single VITEC Success Story + description = Shows a selected Success Story in a Card with different layouts + tt_content_defValues { + CType = list + list_type = vitec_usecaseshow + } + } + marketshow { + iconIdentifier = vitec-plugin-marketshow + title = Show single VITEC Market + description = Shows a selected Market in a Card with different layouts + tt_content_defValues { + CType = list + list_type = vitec_marketshow + } + } + solutionshow { + iconIdentifier = vitec-plugin-solutionshow + title = Show single VITEC Solution + description = Shows a selected Solution in a Card with different layouts + tt_content_defValues { + CType = list + list_type = vitec_solutionshow + } + } + usecaselist { + iconIdentifier = vitec-plugin-usecaselist + title = Show all VITEC Success Stories + description = LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_usecaselist.description + tt_content_defValues { + CType = list + list_type = vitec_usecaselist + } + } + downloadcard { + iconIdentifier = vitec-plugin-downloadcard + title = Downloadcard for single Download + description = LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_usecaselist.description + tt_content_defValues { + CType = list + list_type = vitec_downloadcard + } + } + downloadcardcollection { + iconIdentifier = vitec-plugin-downloadcardcollection + title = Downloadcard Collection + description = Multiple Downloads in one Card + tt_content_defValues { + CType = list + list_type = vitec_downloadcardcollection + } + } + datasheets { + iconIdentifier = vitec-plugin-datasheets + title = VITEC Datasheets + description = Interactive datasheets with filtering and download functionality + tt_content_defValues { + CType = list + list_type = vitec_datasheets + } + } + simplecard { + iconIdentifier = vitec-plugin-simplecard + title = Simple Card + description = Simple Card (Image, Title, Text, Link) + tt_content_defValues { + CType = list + list_type = vitec_simplecard + } + } + + } + } +} + +############################################################ +# Backend Previews für klassische Plugins (list_type) +############################################################ + +mod.web_layout.tt_content.preview { + simplecard = EXT:vitec/Resources/Private/Templates/Simplecard/Backendpreview.html + # vitec_usecaseshow = EXT:vitec/Resources/Private/Templates/Usecaseshow/Backendpreview.html + # ^ Pfad anpassen wenn das Template existiert, dann Kommentar entfernen +} + +# Hinweis: Content-Blocks-Previews werden automatisch aus +# packages/vitec/ContentBlocks/ContentElements//templates/backend-preview.html geladen. +# Keine separate Registrierung nötig. \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/cta-banner/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/cta-banner/assets/icon.svg new file mode 100644 index 0000000..eadcc5f --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/cta-banner/assets/icon.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/cta-banner/config.yaml b/packages/vitec/ContentBlocks/ContentElements/cta-banner/config.yaml new file mode 100644 index 0000000..87a3f46 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/cta-banner/config.yaml @@ -0,0 +1,74 @@ +name: vitec/cta-banner +group: vitec +prefixFields: true +prefixType: vendor + +fields: + - identifier: header + useExistingField: true + required: true + + - identifier: bodytext + useExistingField: true + enableRichtext: true + + - identifier: primary_cta + type: Link + required: true + allowedTypes: + - page + - url + - file + - email + + - identifier: primary_cta_label + type: Text + required: true + default: 'Get in touch' + max: 30 + + - identifier: secondary_cta + type: Link + allowedTypes: + - page + - url + - file + - email + + - identifier: secondary_cta_label + type: Text + max: 30 + + - identifier: layout_variant + type: Select + renderType: selectSingle + default: centered + items: + - label: Centered (text over background) + value: centered + - label: Split (text left, CTAs right) + value: split + - label: Stacked (text top, CTAs below) + value: stacked + + - identifier: background_variant + type: Select + renderType: selectSingle + default: orange + items: + - label: Orange + value: orange + - label: Blue + value: blue + - label: Graphite + value: graphite + - label: Midnight + value: midnight + - label: Light (neutral) + value: light + + - identifier: background_image + type: File + minitems: 0 + maxitems: 1 + allowed: common-image-types \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/cta-banner/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/cta-banner/language/labels.xlf new file mode 100644 index 0000000..e49f186 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/cta-banner/language/labels.xlf @@ -0,0 +1,42 @@ + + + + + + VITEC · CTA Banner + + + Call-to-Action Banner mit optionalem zweiten Button und Background-Varianten + + + + Primary CTA Link + + + Primary CTA Button Text + + + + Secondary CTA Link (optional) + + + Secondary CTA Button Text (optional) + + + + Layout Variant + + + + Background Variant + + + + Background Image (optional) + + + Optionales Hintergrundbild — überlagert die Background-Variant-Farbe + + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/backend-preview.html new file mode 100644 index 0000000..d5b2c74 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/backend-preview.html @@ -0,0 +1,97 @@ + + + + + + +
+ + Thumbnail (Background Image oder Farb-Placeholder) + + + + + +
+ + 🟠 + 🔵 + + + + No BG + +
+
+
+ + Body +
+
VITEC · CTA Banner
+ +

+ + {data.header} + + ⚠ Headline fehlt + + +

+ + +
+ {data.bodytext} +
+
+ +
+ + {data.primary_cta_label} → + + + + {data.secondary_cta_label} + + +
+
+ + Settings Sidebar +
+ Background Badge + + + + VITEC Orange + VITEC Blue + VITEC Graphite + VITEC Midnight + VITEC Light + {data.background_variant} + + + + Layout Variant Badge mit Icon + + + ◎ Centered + ⇄ Split + ≡ Stacked + {data.layout_variant} + + + + Warnung wenn Primary-CTA-Link fehlt + + ⚠ Kein Primary-Link + +
+ +
+ +
+ \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/frontend.html new file mode 100644 index 0000000..27873dc --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/cta-banner/templates/frontend.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg new file mode 100644 index 0000000..0bb2746 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/hero-section/assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml b/packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml new file mode 100644 index 0000000..450eb92 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/hero-section/config.yaml @@ -0,0 +1,60 @@ +name: vitec/herosection +group: vitec +prefixFields: true +prefixType: vendor + +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 \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf new file mode 100644 index 0000000..60d8738 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/hero-section/language/labels.xlf @@ -0,0 +1,42 @@ + + + + + + + VITEC · Hero Section + + + Haupt-Hero mit Headline, CTA, Background-Variante + + + + + Eyebrow Text + + + Small Label-Text above the Headline + + + + CTA Link + + + + CTA Button Text + + + + Hero Image + + + + Background Variant + + + + Show Logo Wall below + + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/backend-preview.html new file mode 100644 index 0000000..1c1c794 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/backend-preview.html @@ -0,0 +1,75 @@ + + + + + + +
+ + Thumbnail + + + + + +
No Image
+
+
+ +
+
VITEC · Hero Section
+ + +
{data.eyebrow}
+
+ +

+ + {data.header} + + ⚠ Headline fehlt + + +

+ + +
{data.subheader}
+
+ + +
+ {data.bodytext} +
+
+ + +
+ {data.cta_label} → +
+
+
+ + Settings Sidebar (ohne Partial) +
+ + + Background: {data.background_variant} + + + + 🏢 Logo Wall + + + + ⚠ Kein Bild + +
+ +
+ +
+ \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/frontend.html new file mode 100644 index 0000000..a9bfdf0 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/hero-section/templates/frontend.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/vitec/ContentBlocks/Shared/BackgroundBadge.html b/packages/vitec/ContentBlocks/Shared/BackgroundBadge.html new file mode 100644 index 0000000..765c19a --- /dev/null +++ b/packages/vitec/ContentBlocks/Shared/BackgroundBadge.html @@ -0,0 +1,15 @@ + + + + + + VITEC-Blue + VITEC-Orange + VITEC-Graphite + VITEC-Midnight + VITEC-Light + {variant} + + + + \ No newline at end of file diff --git a/packages/vitec/Documentation/HeadlessIntegration.md b/packages/vitec/Documentation/HeadlessIntegration.md new file mode 100644 index 0000000..655a0a0 --- /dev/null +++ b/packages/vitec/Documentation/HeadlessIntegration.md @@ -0,0 +1,307 @@ +# Headless Integration Guide for TYPO3 13 Custom Plugins + +This guide explains how to integrate a custom TYPO3 Extbase plugin with the TYPO3 Headless extension to provide JSON API output. + +## The Challenge + +When integrating custom plugins with TYPO3 Headless, you face two main obstacles: + +1. **Lost Context**: Headless creates new ContentObjectRenderer contexts when rendering JSON fields, causing the content element context to be lost. Standard methods like `$conf['parentObj']` or `$GLOBALS['TSFE']->cObj->data` will only contain page data, not the content element. + +2. **Extbase Limitations**: Extbase repositories don't work in UserFunc context because the full Extbase framework isn't bootstrapped during headless JSON rendering. + +## The Solution + +### Step 1: Override Headless TypoScript in Site Set + +Create or modify your site set's `setup.typoscript` file to add custom fields to the headless JSON output: + +**File**: `packages/yourext/Configuration/Sets/YourSetName/setup.typoscript` + +```typoscript +# Override headless rendering for list plugins +tt_content.list.fields.content.fields.yourCustomField = USER +tt_content.list.fields.content.fields.yourCustomField { + userFunc = Vendor\YourExt\UserFunc\YourJsonRenderer->render +} +``` + +**Why in site set?** Site sets load AFTER headless TypoScript, allowing you to override the default configuration. + +### Step 2: Create a UserFunc Renderer + +Create a UserFunc class that will render your plugin data as JSON: + +**File**: `packages/yourext/Classes/UserFunc/YourJsonRenderer.php` + +```php +id ?? 0); + + $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tt_content'); + + $contentElements = $queryBuilder + ->select('*') + ->from('tt_content') + ->where( + $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('list_type', $queryBuilder->createNamedParameter('yourext_pluginname', ParameterType::STRING)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0) + ) + ->executeQuery() + ->fetchAllAssociative(); + + if (empty($contentElements)) { + return json_encode([]); + } + + $contentElement = $contentElements[0]; + + // Step 2: Parse FlexForm settings + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); + $settings = $flexFormData['settings'] ?? []; + + // Step 3: Use direct database queries (NOT Extbase repositories) + $dataQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_yourext_domain_model_yourmodel'); + + $records = $dataQueryBuilder + ->select('*') + ->from('tx_yourext_domain_model_yourmodel') + ->where( + $dataQueryBuilder->expr()->eq('deleted', 0), + $dataQueryBuilder->expr()->eq('hidden', 0) + // Add your filters based on $settings + ) + ->executeQuery() + ->fetchAllAssociative(); + + // Step 4: Serialize to JSON + $data = []; + foreach ($records as $record) { + $data[] = [ + 'uid' => (int)$record['uid'], + 'title' => $record['title'] ?? '', + // Add more fields as needed + ]; + } + + return json_encode($data); + } +} +``` + +### Step 3: Using FlexForm Settings + +You can access any FlexForm field from your plugin configuration: + +```php +// Parse FlexForm +$flexFormService = GeneralUtility::makeInstance(FlexFormService::class); +$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); +$settings = $flexFormData['settings'] ?? []; + +// Access specific settings +$categoryUids = array_filter( + array_map('intval', explode(',', (string)($settings['categories'] ?? ''))) +); +$debugMode = (bool)($settings['debug'] ?? false); + +// Use settings in your query +if (!empty($categoryUids)) { + // Add filtering based on categories +} +``` + +#### Conditional Debug Output + +You can add debug information based on a FlexForm checkbox: + +```php +// At the end of your render method: +if ($debugMode) { + return json_encode([ + 'products' => $data, + 'debug' => [ + 'pageId' => $pageId, + 'categoryUids' => $categoryUids, + 'recordCount' => count($data), + 'settings' => $settings // Show all settings for troubleshooting + ] + ]); +} + +// Return just the data array when debug is off +return json_encode($data); +``` + +This allows you to toggle debug information on/off directly in the backend without code changes. + +### Step 4: Important Notes + +#### Use Doctrine ParameterType (TYPO3 13) + +In TYPO3 13, always use `Doctrine\DBAL\ParameterType` instead of `\PDO::PARAM_*`: + +```php +use Doctrine\DBAL\ParameterType; + +// Correct for TYPO3 13: +$queryBuilder->createNamedParameter($value, ParameterType::INTEGER) +$queryBuilder->createNamedParameter($value, ParameterType::STRING) + +// Wrong (deprecated): +$queryBuilder->createNamedParameter($value, \PDO::PARAM_INT) +``` + +#### Why Direct Database Queries? + +**Don't use**: +```php +// This won't work in UserFunc context +$repository = GeneralUtility::makeInstance(YourRepository::class); +$records = $repository->findAll(); // Returns empty! +``` + +**Use instead**: +```php +// Direct database query +$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) + ->getQueryBuilderForTable('tx_yourext_domain_model_yourmodel'); +$records = $queryBuilder->select('*')->from('tx_yourext_domain_model_yourmodel')->... +``` + +### Step 5: Clear Cache + +After making changes, always clear the TYPO3 cache: + +```bash +./vendor/bin/typo3 cache:flush +``` + +## Testing + +Test your JSON output using curl and jq: + +```bash +# Check if your field appears +curl -s "https://yourdomain.com/your-page" | jq '.content.colPos0[] | select(.type=="your_plugin") | .content' + +# Check specific field +curl -s "https://yourdomain.com/your-page" | jq '.content.colPos0[] | select(.type=="your_plugin") | .content.yourCustomField' + +# With debug mode enabled in backend, you'll see: +curl -s "https://yourdomain.com/your-page" | jq '.content.colPos0[] | select(.type=="your_plugin") | .content.yourCustomField.debug' +``` + +### Example Output + +**Without debug mode**: +```json +[ + { + "uid": 1, + "title": "Product Name", + "slug": "product-name" + } +] +``` + +**With debug mode enabled** (checkbox in backend): +```json +{ + "products": [ + { + "uid": 1, + "title": "Product Name", + "slug": "product-name" + } + ], + "debug": { + "pageId": 5, + "categoryUids": [8], + "productCount": 1, + "settings": { + "debug": "1", + "categories": "8", + "layout": "0" + } + } +} +``` + +## Example: Real-World Implementation + +See `Classes/UserFunc/ProductListJsonRenderer.php` for a complete working example that: +- Queries tt_content for plugin configuration +- Parses FlexForm settings (including debug mode, categories) +- Queries products with visibility filters using direct database queries +- Provides conditional debug output based on FlexForm settings +- Serializes product data to JSON with backward compatibility + +## Common Issues + +### Empty Results +- **Problem**: UserFunc returns empty array +- **Solution**: Check that you're using direct database queries, not Extbase repositories + +### Parameter Type Errors +- **Problem**: `createNamedParameter(): Argument #2 ($type) must be of type Doctrine\DBAL\ParameterType` +- **Solution**: Use `ParameterType::INTEGER` instead of `\PDO::PARAM_INT` + +### Content Element Not Found +- **Problem**: tt_content query returns empty +- **Solution**: Verify the `list_type` value matches your plugin signature exactly + +### Wrong Context Data +- **Problem**: Trying to access content element via `$GLOBALS['TSFE']->cObj->data` +- **Solution**: This only contains page data. Query tt_content directly instead + +## Architecture Summary + +``` +1. Browser requests JSON page + ↓ +2. Headless extension renders page as JSON + ↓ +3. Encounters tt_content.list (your plugin) + ↓ +4. Executes your TypoScript override + ↓ +5. Calls your UserFunc + ↓ +6. UserFunc queries tt_content for plugin config + ↓ +7. UserFunc queries database directly for records + ↓ +8. Returns JSON array + ↓ +9. Headless includes it in final JSON output +``` + +## Conclusion + +The key insight is that headless rendering requires a different approach than normal Extbase plugins: +- Use site sets for TypoScript overrides +- Use UserFuncs for custom rendering +- Use direct database queries instead of repositories +- Query tt_content to recover plugin configuration + +This approach provides full control over JSON output while working within the constraints of the headless rendering context. diff --git a/packages/vitec/ExtensionBuilder.json b/packages/vitec/ExtensionBuilder.json new file mode 100644 index 0000000..48a5c80 --- /dev/null +++ b/packages/vitec/ExtensionBuilder.json @@ -0,0 +1,380 @@ +{ + "modules": [ + { + "config": { + "position": [ + 529, + 328 + ] + }, + "name": "", + "value": { + "actionGroup": { + "_default0_index": false, + "_default1_list": false, + "_default2_show": false, + "_default3_new_create": false, + "_default4_edit_update": false, + "_default5_delete": false, + "customActions": [] + }, + "name": "", + "objectsettings": { + "addDeletedField": true, + "addHiddenField": true, + "addStarttimeEndtimeFields": true, + "aggregateRoot": false, + "controllerScope": "Frontend", + "categorizable": false, + "description": "", + "mapToTable": "", + "parentClass": "", + "sorting": false, + "type": "Entity", + "uid": "c5830401-5be8-41c8-be95-191f3c7ef1d5" + }, + "propertyGroup": { + "properties": [] + }, + "relationGroup": { + "relations": [] + } + } + }, + { + "config": { + "position": [ + 437, + 331 + ] + }, + "name": "Product", + "value": { + "actionGroup": { + "_default0_index": true, + "_default1_list": true, + "_default2_show": true, + "_default3_new_create": false, + "_default4_edit_update": false, + "_default5_delete": false, + "customActions": [] + }, + "name": "Product", + "objectsettings": { + "addDeletedField": true, + "addHiddenField": true, + "addStarttimeEndtimeFields": true, + "aggregateRoot": true, + "controllerScope": "Frontend", + "categorizable": false, + "description": "", + "mapToTable": "", + "parentClass": "", + "sorting": false, + "type": "Entity", + "uid": "79b6c941-ee45-4b49-93d7-0bde2e30593a" + }, + "propertyGroup": { + "properties": [ + { + "allowedFileTypes": "", + "propertyDescription": "", + "propertyIsL10nModeExclude": false, + "propertyIsNullable": false, + "propertyIsRequired": true, + "propertyName": "title", + "propertyType": "String", + "typeSelect": { + "selectboxValues": "", + "renderType": "selectSingle", + "foreignTable": "", + "whereClause": "" + }, + "typeText": { + "enableRichtext": false + }, + "typeNumber": { + "enableSlider": false, + "steps": 1, + "setRange": false, + "upperRange": 255, + "lowerRange": 0 + }, + "typeColor": { + "setValuesColorPicker": false, + "colorPickerValues": "" + }, + "typeBoolean": { + "renderType": "default", + "booleanValues": "" + }, + "typePassword": { + "renderPasswordGenerator": false + }, + "typeDateTime": { + "dbTypeDateTime": "", + "formatDateTime": "" + }, + "typeFile": { + "allowedFileTypes": "" + }, + "size": "30", + "rows": "10", + "minItems": "", + "maxItems": "", + "uid": "b5773ee7-bb5f-474f-9464-629f42c9b5a2" + }, + { + "allowedFileTypes": "", + "propertyDescription": "", + "propertyIsL10nModeExclude": false, + "propertyIsNullable": false, + "propertyIsRequired": false, + "propertyName": "slug", + "propertyType": "Slug", + "typeSelect": { + "selectboxValues": "", + "renderType": "selectSingle", + "foreignTable": "", + "whereClause": "" + }, + "typeText": { + "enableRichtext": false + }, + "typeNumber": { + "enableSlider": false, + "steps": 1, + "setRange": false, + "upperRange": 255, + "lowerRange": 0 + }, + "typeColor": { + "setValuesColorPicker": false, + "colorPickerValues": "" + }, + "typeBoolean": { + "renderType": "default", + "booleanValues": "" + }, + "typePassword": { + "renderPasswordGenerator": false + }, + "typeDateTime": { + "dbTypeDateTime": "", + "formatDateTime": "" + }, + "typeFile": { + "allowedFileTypes": "" + }, + "size": "30", + "rows": "10", + "minItems": "", + "maxItems": "", + "uid": "95426fa5-210a-424a-9618-a1b6c8aa6621" + } + ] + }, + "relationGroup": { + "relations": [] + } + } + } + ], + "properties": { + "backendModules": [], + "description": "Manage all VITEC Products and Downloads", + "emConf": { + "category": "plugin", + "custom_category": "", + "dependsOn": "typo3 => 12.4.0-12.4.99", + "disableLocalization": false, + "disableVersioning": false, + "generateDocumentationTemplate": false, + "generateEditorConfig": true, + "generateEmptyGitRepository": true, + "sourceLanguage": "en", + "state": "alpha", + "targetVersion": "12.4", + "version": "0.0.1" + }, + "extensionKey": "vitec", + "name": "VITEC", + "originalExtensionKey": "", + "originalVendorName": "", + "persons": [], + "plugins": [], + "vendorName": "Evomedien" + }, + "wires": [], + "nodes": [ + { + "type": "customModel", + "position": { + "x": 529, + "y": 328 + }, + "data": { + "label": "", + "objectType": "", + "isAggregateRoot": false, + "controllerScope": "Frontend", + "enableSorting": false, + "addDeletedField": true, + "addHiddenField": true, + "addStarttimeEndtimeFields": true, + "enableCategorization": false, + "description": "", + "mapToExistingTable": "", + "extendExistingModelClass": "", + "actions": { + "actionIndex": false, + "actionList": false, + "actionShow": false, + "actionNewCreate": false, + "actionEditUpdate": false, + "actionDelete": false + }, + "customActions": [], + "properties": [], + "relations": [] + }, + "dragHandle": ".drag-handle", + "draggable": true + }, + { + "id": "dndnode_1", + "type": "customModel", + "position": { + "x": 437, + "y": 331 + }, + "data": { + "label": "Product", + "objectType": "", + "isAggregateRoot": true, + "controllerScope": "Frontend", + "enableSorting": false, + "addDeletedField": true, + "addHiddenField": true, + "addStarttimeEndtimeFields": true, + "enableCategorization": false, + "description": "", + "mapToExistingTable": "", + "extendExistingModelClass": "", + "actions": { + "actionIndex": true, + "actionList": true, + "actionShow": true, + "actionNewCreate": false, + "actionEditUpdate": false, + "actionDelete": false + }, + "customActions": [], + "properties": [ + { + "name": "title", + "type": "String", + "description": "", + "isRequired": true, + "isNullable": false, + "isExcludeField": false, + "isl10nModeExlude": false, + "typeSelect": { + "selectboxValues": "", + "renderType": "selectSingle", + "foreignTable": "" + }, + "typeText": { + "enableRichtext": false + }, + "typeNumber": { + "enableSlider": false, + "steps": 1, + "setRange": false, + "upperRange": 255, + "lowerRange": 0 + }, + "typeColor": { + "setValuesColorPicker": false, + "colorPickerValues": "" + }, + "typeBoolean": { + "renderType": "default", + "booleanValues": "" + }, + "typePassword": { + "renderPasswordGenerator": false + }, + "typeDateTime": { + "dbTypeDateTime": "", + "formatDateTime": "" + }, + "typeFile": { + "allowedFileTypes": "" + }, + "size": "", + "minItems": "", + "maxItems": "" + }, + { + "name": "slug", + "type": "Slug", + "description": "", + "isRequired": false, + "isNullable": false, + "isExcludeField": false, + "isl10nModeExlude": false, + "typeSelect": { + "selectboxValues": "", + "renderType": "selectSingle", + "foreignTable": "" + }, + "typeText": { + "enableRichtext": false + }, + "typeNumber": { + "enableSlider": false, + "steps": 1, + "setRange": false, + "upperRange": 255, + "lowerRange": 0 + }, + "typeColor": { + "setValuesColorPicker": false, + "colorPickerValues": "" + }, + "typeBoolean": { + "renderType": "default", + "booleanValues": "" + }, + "typePassword": { + "renderPasswordGenerator": false + }, + "typeDateTime": { + "dbTypeDateTime": "", + "formatDateTime": "" + }, + "typeFile": { + "allowedFileTypes": "" + }, + "size": "", + "minItems": "", + "maxItems": "" + } + ], + "relations": [] + }, + "dragHandle": ".drag-handle", + "draggable": true, + "width": 300, + "height": 1170 + } + ], + "edges": [], + "storagePath": "\/usr\/www\/users\/vitecxxx\/live\/extensions\/", + "log": { + "last_modified": "2025-01-13 03:54", + "extension_builder_version": "v12.0.0-beta.2", + "be_user": " (1)" + } +} \ No newline at end of file diff --git a/packages/vitec/Readme.MD b/packages/vitec/Readme.MD new file mode 100644 index 0000000..e69de29 diff --git a/packages/vitec/Resources/Private/.htaccess b/packages/vitec/Resources/Private/.htaccess new file mode 100644 index 0000000..96d0729 --- /dev/null +++ b/packages/vitec/Resources/Private/.htaccess @@ -0,0 +1,11 @@ +# Apache < 2.3 + + Order allow,deny + Deny from all + Satisfy All + + +# Apache >= 2.3 + + Require all denied + diff --git a/packages/vitec/Resources/Private/Components/Atom/Button/Button.html b/packages/vitec/Resources/Private/Components/Atom/Button/Button.html new file mode 100644 index 0000000..025406d --- /dev/null +++ b/packages/vitec/Resources/Private/Components/Atom/Button/Button.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/vitec/Resources/Private/Language/locallang.xlf b/packages/vitec/Resources/Private/Language/locallang.xlf new file mode 100644 index 0000000..7bfde2d --- /dev/null +++ b/packages/vitec/Resources/Private/Language/locallang.xlf @@ -0,0 +1,32 @@ + + + +
+ + + + + + + + + Product + + + Product + + + Title + + + Title + + + Slug + + + Slug + + + + diff --git a/packages/vitec/Resources/Private/Language/locallang_containers.xlf b/packages/vitec/Resources/Private/Language/locallang_containers.xlf new file mode 100755 index 0000000..68279b8 --- /dev/null +++ b/packages/vitec/Resources/Private/Language/locallang_containers.xlf @@ -0,0 +1,91 @@ + + + +
+ + + VITEC + + + + Section Heading + + + Section Subline + + + + Background Variant + + + None + + + Orange + + + Blue + + + Graphite + + + Midnight + + + + VITEC · Two Columns (50 / 50) + + + Two equal columns + + + + VITEC · Three Columns (33 / 33 / 33) + + + Three equal columns + + + + VITEC · Four Columns (25 / 25 / 25 / 25) + + + Four equal columns + + + + VITEC · Two Columns (66 / 33) + + + Wide main column + narrow sidebar + + + + VITEC · Two Columns (33 / 66) + + + Narrow sidebar + wide main column + + + + Column 1 + + + Column 2 + + + Column 3 + + + Column 4 + + + Main (66%) + + + Sidebar (33%) + + + + diff --git a/packages/vitec/Resources/Private/Language/locallang_csh_tx_vitec_domain_model_product.xlf b/packages/vitec/Resources/Private/Language/locallang_csh_tx_vitec_domain_model_product.xlf new file mode 100644 index 0000000..639a784 --- /dev/null +++ b/packages/vitec/Resources/Private/Language/locallang_csh_tx_vitec_domain_model_product.xlf @@ -0,0 +1,14 @@ + + + +
+ + + title + + + slug + + + + diff --git a/packages/vitec/Resources/Private/Language/locallang_db.xlf b/packages/vitec/Resources/Private/Language/locallang_db.xlf new file mode 100644 index 0000000..09be3f9 --- /dev/null +++ b/packages/vitec/Resources/Private/Language/locallang_db.xlf @@ -0,0 +1,32 @@ + + + +
+ + + + + + + + + Product + + + Product + + + Title + + + Title + + + Slug + + + Slug + + + + diff --git a/packages/vitec/Resources/Private/Language/locallang_pages.xlf b/packages/vitec/Resources/Private/Language/locallang_pages.xlf new file mode 100755 index 0000000..a433fdb --- /dev/null +++ b/packages/vitec/Resources/Private/Language/locallang_pages.xlf @@ -0,0 +1,71 @@ + + + +
+ + + Default + + + Index Page + + + Markets Overview + + + Market Detail + + + Solutions Overview + + + Solution Detail + + + Use Cases Overview + + + Use Case Detail + + + Products Overview + + + Product Main Category + + + Product Detail + + + Support Overview + + + News Overview + + + News Detail V1 + + + News Detail V2 + + + News Detail V3 + + + Content Page V1 + + + Content Page V2 + + + Content Page V3 + + + Events Overview + + + Contact Page + + + + diff --git a/packages/vitec/Resources/Private/Layouts/Backend/Default.html b/packages/vitec/Resources/Private/Layouts/Backend/Default.html new file mode 100644 index 0000000..a6d2195 --- /dev/null +++ b/packages/vitec/Resources/Private/Layouts/Backend/Default.html @@ -0,0 +1,8 @@ + +
+ +

Hi

+ +
+ \ No newline at end of file diff --git a/packages/vitec/Resources/Private/Layouts/Default.html b/packages/vitec/Resources/Private/Layouts/Default.html new file mode 100644 index 0000000..6ffaaa7 --- /dev/null +++ b/packages/vitec/Resources/Private/Layouts/Default.html @@ -0,0 +1,4 @@ + +
+ +
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Downloads.html b/packages/vitec/Resources/Private/Partials/Product/Downloads.html new file mode 100644 index 0000000..9c6d486 --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Downloads.html @@ -0,0 +1,12 @@ +
+

Downloads

+ +
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Productlist0.html b/packages/vitec/Resources/Private/Partials/Product/Productlist0.html new file mode 100644 index 0000000..a90bacc --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Productlist0.html @@ -0,0 +1,16 @@ + + +
+

Productlist0

+
+
+

Product LIST by Category

+
+ +

{p.title}

+ + {c.title}, {c.uid} + +
+
+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Productlist1.html b/packages/vitec/Resources/Private/Partials/Product/Productlist1.html new file mode 100644 index 0000000..6d0f67c --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Productlist1.html @@ -0,0 +1,12 @@ + +
+

Productlist1

+
+
+

Product LIST by Category

+
+ +

{p.title}

+
+
+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Productlist2.html b/packages/vitec/Resources/Private/Partials/Product/Productlist2.html new file mode 100644 index 0000000..bfad06c --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Productlist2.html @@ -0,0 +1,4 @@ + +
+

Productlist2

+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Productlist3.html b/packages/vitec/Resources/Private/Partials/Product/Productlist3.html new file mode 100644 index 0000000..4ba1f22 --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Productlist3.html @@ -0,0 +1,4 @@ + +
+

Productlist3

+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Productlistfour.html b/packages/vitec/Resources/Private/Partials/Product/Productlistfour.html new file mode 100644 index 0000000..4ba1f22 --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Productlistfour.html @@ -0,0 +1,4 @@ + +
+

Productlist3

+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Partials/Product/Structureddata.html b/packages/vitec/Resources/Private/Partials/Product/Structureddata.html new file mode 100644 index 0000000..dc265db --- /dev/null +++ b/packages/vitec/Resources/Private/Partials/Product/Structureddata.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Download/Downloadcard.html b/packages/vitec/Resources/Private/Templates/Download/Downloadcard.html new file mode 100644 index 0000000..fae90fe --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Download/Downloadcard.html @@ -0,0 +1,42 @@ + + + + + + + +

Download List

+ + + {_all} + DownloadCard Plugin + vitec/Resources/Private/Templates/Download/Downloadcard.html + + + + + + +

Selected Product

+

Title: {product.title}

+

Description: {product.description}

+
+ + +

No product selected.

+
+
+
+ + + +

Selected download

+

Title: {download.title}

+

Description: {download.description}

+
+ +

No download selected.

+
+
+
+ diff --git a/packages/vitec/Resources/Private/Templates/Download/Downloadcardcollection.html b/packages/vitec/Resources/Private/Templates/Download/Downloadcardcollection.html new file mode 100644 index 0000000..ff57cbb --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Download/Downloadcardcollection.html @@ -0,0 +1,23 @@ + + + + + + {_all} + DownloadCard Plugin + vitec/Resources/Private/Templates/Download/DownloadCardCollection.html + + +

Download Card Collection

+ +

Product: {product.name}

+
    + +
  • {download.title}
  • +
    +
+
+ +

No product or downloads found.

+
+
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Download/List.html b/packages/vitec/Resources/Private/Templates/Download/List.html new file mode 100644 index 0000000..13f1b20 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Download/List.html @@ -0,0 +1,42 @@ + + + + + + + +

Download List

+ + + {_all} + DownloadCard Plugin + vitec/Resources/Private/Templates/Download/List.html + + + + + + +

Selected Product

+

Title: {product.title}

+

Description: {product.description}

+
+ + +

No product selected.

+
+
+
+ + + +

Selected download

+

Title: {download.title}

+

Description: {download.description}

+
+ +

No download selected.

+
+
+
+ diff --git a/packages/vitec/Resources/Private/Templates/Market/Show.html b/packages/vitec/Resources/Private/Templates/Market/Show.html new file mode 100644 index 0000000..3bc176b --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Market/Show.html @@ -0,0 +1,165 @@ + + ### This template renders a single market with different layouts + {_all} + File path: EXT:vitec/Resources/Private/Templates/Market/Show.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Market/Show.html + + +
+
+
+
+ + + + + + No image available + + +
+

+

{market.title}
+ {market.subtitle}
+ {market.description} +

+
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Market/Show.html + + +
+
+
+
+
+
+ + + +
+
+
+
+

{market.title}

+ {market.subtitle} +
+

+ {market.description} +

+ +
+ + + {category.title} + + +
+
+
+
+
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Market/Show.html + + +
+ +
+
+
+
+
+
+

{market.title}

+

{market.subtitle}

+
+
+ {market.description} +
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Market/Show.html + + +
+
+
+
+ + + +
+
{market.title}
+

{market.subtitle}

+ + {market.description} + +
+
+
+
+
+
diff --git a/packages/vitec/Resources/Private/Templates/Preview/Usecaseshow.html b/packages/vitec/Resources/Private/Templates/Preview/Usecaseshow.html new file mode 100644 index 0000000..76e0c8c --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Preview/Usecaseshow.html @@ -0,0 +1,135 @@ +
+
+ 🎯 Usecase Show Plugin + + Layout: {layout} + +
+ +
+ + + +
+
+ Selected Usecase: +
+

{usecase.title}

+ +

{usecase.subtitle}

+
+ +

+ {usecase.description} +

+
+ UID: {usecase.uid} +
+
+
+
+ + +
+ ⚠️ No usecase selected +

Please configure the plugin to select a usecase.

+
+
+
+ + + +
+
+ Plugin Settings + + + + + + + + + +
{key}{value}
+
+
+
+
+
+ + \ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Product/List.html b/packages/vitec/Resources/Private/Templates/Product/List.html new file mode 100644 index 0000000..1c125fa --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Product/List.html @@ -0,0 +1,20 @@ + + + + + + {_all} + + +

-- {settings.layout} --

+ + +

Product LIST by Category

+

{settings.categories}

+ + + + + + +
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Product/Show.html b/packages/vitec/Resources/Private/Templates/Product/Show.html new file mode 100644 index 0000000..bf1a1f2 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Product/Show.html @@ -0,0 +1,191 @@ + + + + + + {_all} + + +
+
+
+

{product.title}

+

{product.subtitle}

+
+
+
+ + + {product.structureddata} + + + +
+
+
+
+

{product.title}

+

{product.teaser}

+
+

Categories

+ + + {c.title} - {c.uid}
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+
+ + +
+

{product.video}

+
+
+ {product.highlights} +
+
+ +
+ {product.highlights} +
+
+
+
+
+
+ + + + +
+
+
+
+
+

The Key Features of {product.title}

+

Bunch of Icons

+
+
+
+
+
+
+
+
+
+ {product.description} +
+
+ {product.applications} +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+

WHY VITEC

+ + + +
+
+
+
+ +
+
+
+
+

CTA

+ + + +
+
+
+
+
+
+
+
+

Blog

+
+
+
+


+

Ab hier nix wichtiges mehr

+


+
+

{product.title}

+

{product.subtitle}

+ +

{product.applications}

+

{product.highlights}

+
+ + + +
+ + + + +
+ + +
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Simplecard/Backendpreview.html b/packages/vitec/Resources/Private/Templates/Simplecard/Backendpreview.html new file mode 100644 index 0000000..6470959 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Simplecard/Backendpreview.html @@ -0,0 +1,2 @@ +uhgbzughbz + diff --git a/packages/vitec/Resources/Private/Templates/Simplecard/List.html b/packages/vitec/Resources/Private/Templates/Simplecard/List.html new file mode 100644 index 0000000..f07ada7 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Simplecard/List.html @@ -0,0 +1,31 @@ + + +
+
+
+ +
+
+

{settings.header}

+
+
+
+

{settings.bodytext}

+
+ +
+ + +

{settings.header}

+ {settings.bodytext} + + + + + + + {settings.buttontext} + +
\ No newline at end of file diff --git a/packages/vitec/Resources/Private/Templates/Solution/Show.html b/packages/vitec/Resources/Private/Templates/Solution/Show.html new file mode 100644 index 0000000..f58e5a5 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Solution/Show.html @@ -0,0 +1,165 @@ + + ### This template renders a single solution with different layouts + {_all} + File path: EXT:vitec/Resources/Private/Templates/Solution/Show.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Solution/Show.html + + +
+
+
+
+ + + + + + No image available + + +
+

+

{solution.title}
+ {solution.subtitle}
+ {solution.description} +

+
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Solution/Show.html + + +
+
+
+
+
+
+ + + +
+
+
+
+

{solution.title}

+ {solution.subtitle} +
+

+ {solution.description} +

+ +
+ + + {category.title} + + +
+
+
+
+
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Solution/Show.html + + +
+ +
+
+
+
+
+
+

{solution.title}

+

{solution.subtitle}

+
+
+ {solution.description} +
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Solution/Show.html + + +
+
+
+
+ + + +
+
{solution.title}
+

{solution.subtitle}

+ + {solution.description} + +
+
+
+
+
+
diff --git a/packages/vitec/Resources/Private/Templates/Usecase/List.html b/packages/vitec/Resources/Private/Templates/Usecase/List.html new file mode 100644 index 0000000..c94266e --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Usecase/List.html @@ -0,0 +1,79 @@ + +{_all} + + + + + + + + + +

{u.title}

- +
+ + {category.class} + + + {u.title} + +
+ +



+
USECASE Selector here
+ + + +
+
Categories:
+ + {category.title} + +
+
+
+
+
{u.title}
+
+
+ +
+
+ +
+
+ +
+
+ {u.teaser} +
+
+

VITEC’s IPTV and digital signage solution transformed the CO'Met Arena in Orléans, enhancing visitor engagement with dynamic content across 200 screens. The innovative system ensures efficient content management, live event streaming, and an exceptional visual experience in multiple event spaces.

+
+ + Learn More + +
+
+ +
+
+
+
+
Atlanta Hawks

“VITEC has enabled us to create a dynamic and customised visual experience for visitors to the various event spaces while also creating an efficient and simple content management and delivery operation."

Fabrice Leguay
Manganelli Technology

+
+ + Learn More + +
+
+
+
+
diff --git a/packages/vitec/Resources/Private/Templates/Usecase/Show.html b/packages/vitec/Resources/Private/Templates/Usecase/Show.html new file mode 100644 index 0000000..5bfbc57 --- /dev/null +++ b/packages/vitec/Resources/Private/Templates/Usecase/Show.html @@ -0,0 +1,203 @@ + + ### This template renders a single usecase with different layouts + {_all} + File path: EXT:vitec/Resources/Private/Templates/Usecase/Show.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Usecase/Show.html + + +
+
+
+
+ + + + + + No case image available + + +
+
+ + + + + + No logo image available + + +
+

+

{usecase.title}
+ {usecase.subtitle}
+ {usecase.description} +

+ + View Success Story + +
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Usecase/Show.html + + +
+
+
+
+
+
+ + + +
+
+
+
+ + + +
+

{usecase.title}

+ {usecase.subtitle} +
+
+

+ {usecase.description} +

+ +
+ + + {category.title} + + +
+
+ + Read More → + +
+
+
+
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Usecase/Show.html + + +
+ +
+
+
+
+
+
+ + + +
+

{usecase.title}

+

{usecase.subtitle}

+
+
+
+ {usecase.description} +
+ + Discover the Success Story + +
+
+
+
+
+ + + + + {_all} + + File path: vscode://file/vitec/Resources/Private/Templates/Usecase/Show.html + + +
+
+
+
+ + + +
+
{usecase.title}
+

{usecase.subtitle}

+ + {usecase.description} + +
+ + View + +
+
+
+
+
+ + diff --git a/packages/vitec/Resources/Public/Css/backend-preview.css b/packages/vitec/Resources/Public/Css/backend-preview.css new file mode 100644 index 0000000..89cb68c --- /dev/null +++ b/packages/vitec/Resources/Public/Css/backend-preview.css @@ -0,0 +1,205 @@ +/* ============================================================= + VITEC Backend Preview Styles + Wird über in den Content Blocks eingebunden + ============================================================= */ + +.vitec-preview { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 1rem; + padding: 0.75rem; + border-left: 4px solid var(--vitec-accent, #F47937); + background: var(--typo3-state-default-bg, #f8f9fa); + border-radius: 4px; + align-items: start; + font-size: 0.875rem; + line-height: 1.4; +} + +.vitec-preview--orange { border-left-color: #F47937; } +.vitec-preview--blue { border-left-color: #26358C; } +.vitec-preview--graphite { border-left-color: #313131; } +.vitec-preview--midnight { border-left-color: #0D0D0D; } +.vitec-preview--light { border-left-color: #cccccc; } + +/* Thumbnail-Slot */ +.vitec-preview__thumb { + width: 120px; + height: 80px; + object-fit: cover; + border-radius: 3px; + background: #e5e5e5; + flex-shrink: 0; +} + +.vitec-preview__thumb-placeholder { + width: 120px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + background: repeating-linear-gradient( + 45deg, + #e5e5e5, + #e5e5e5 8px, + #f0f0f0 8px, + #f0f0f0 16px + ); + color: #999; + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.05em; + border-radius: 3px; + flex-shrink: 0; +} + +/* Content-Slot */ +.vitec-preview__body { + min-width: 0; +} + +.vitec-preview__label { + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #666; + font-weight: 600; + margin-bottom: 0.35rem; +} + +.vitec-preview__eyebrow { + font-size: 0.7rem; + color: #F47937; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 0.15rem; +} + +.vitec-preview__headline { + font-size: 1rem; + font-weight: 600; + margin: 0 0 0.2rem 0; + color: var(--typo3-text-color, #333); +} + +.vitec-preview__subline { + font-size: 0.85rem; + color: #666; + margin-bottom: 0.35rem; +} + +.vitec-preview__body-text { + font-size: 0.8rem; + color: #555; + margin-bottom: 0.5rem; + max-height: 2.6em; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +/* Settings-Slot (Badges) */ +.vitec-preview__settings { + display: flex; + flex-direction: column; + gap: 0.25rem; + align-items: flex-end; + min-width: 120px; +} + +.vitec-badge { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.15rem 0.5rem; + font-size: 0.7rem; + background: rgba(0,0,0,0.06); + border-radius: 3px; + color: #444; + white-space: nowrap; +} + +.vitec-badge--primary { + background: #F47937; + color: white; +} + +.vitec-badge--secondary { + background: transparent; + border: 1px solid #F47937; + color: #F47937; +} + +.vitec-badge--warning { + background: #fef3c7; + color: #92400e; +} + +.vitec-badge__dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; +} + +.vitec-badge__dot--orange { background: #F47937; } +.vitec-badge__dot--blue { background: #26358C; } +.vitec-badge__dot--graphite { background: #313131; } +.vitec-badge__dot--midnight { background: #0D0D0D; } +.vitec-badge__dot--light { background: #cccccc; border: 1px solid #999; } + +/* CTA-Leiste */ +.vitec-preview__ctas { + display: flex; + gap: 0.4rem; + margin-top: 0.4rem; + flex-wrap: wrap; +} + +.vitec-preview__cta-btn { + display: inline-block; + padding: 0.15rem 0.6rem; + background: #F47937; + color: white !important; + font-size: 0.75rem; + border-radius: 3px; + text-decoration: none; +} + +.vitec-preview__cta-btn--secondary { + background: transparent; + color: #F47937 !important; + border: 1px solid #F47937; +} + +/* Dark Mode Support */ +@media (prefers-color-scheme: dark) { + .vitec-preview { + background: rgba(255,255,255,0.04); + } + .vitec-preview__headline { + color: #eee; + } + .vitec-preview__subline, + .vitec-preview__body-text { + color: #bbb; + } + .vitec-badge { + background: rgba(255,255,255,0.08); + color: #ddd; + } +} + +/* Responsive Fallback */ +@media (max-width: 680px) { + .vitec-preview { + grid-template-columns: 1fr; + } + .vitec-preview__settings { + flex-direction: row; + align-items: flex-start; + flex-wrap: wrap; + } +} \ No newline at end of file diff --git a/packages/vitec/Resources/Public/Css/rte-content.css b/packages/vitec/Resources/Public/Css/rte-content.css new file mode 100644 index 0000000..6dc7f54 --- /dev/null +++ b/packages/vitec/Resources/Public/Css/rte-content.css @@ -0,0 +1,90 @@ +/* RTE backend content styles for VITEC preset preview */ + +.lead { + font-size: 1.125rem; + line-height: 1.6; + font-weight: 500; +} + +.blockquote { + border-left: 4px solid #f47937; + margin: 1rem 0; + padding: 0.5rem 1rem; + color: #313131; +} + +.highlight-yellow { + background-color: #fff9c4; +} + +.highlight-green { + background-color: #c8e6c9; +} + +.btn-vitec { + display: inline-block; + padding: 0.6rem 1rem; + border-radius: 0.25rem; + border: 1px solid transparent; + font-weight: 600; + line-height: 1.2; + text-decoration: none; +} + +.btn-vitec--orange { + background-color: #f47937; + border-color: #f47937; + color: #ffffff; +} + +.btn-vitec--blue { + background-color: #26358c; + border-color: #26358c; + color: #ffffff; +} + +.btn-vitec--graphite { + background-color: #313131; + border-color: #313131; + color: #ffffff; +} + +.btn-vitec--midnight { + background-color: #0d0d0d; + border-color: #0d0d0d; + color: #ffffff; +} + +.btn-vitec--light { + background-color: #cccccc; + border-color: #cccccc; + color: #0d0d0d; +} + +.btn-vitec--outline-orange { + background-color: transparent; + border-color: #f47937; + color: #f47937; +} + +.btn-vitec--outline-blue { + background-color: transparent; + border-color: #26358c; + color: #26358c; +} + +.text-start { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-end { + text-align: right; +} + +.text-justify { + text-align: justify; +} diff --git a/packages/vitec/Resources/Public/Icons/Extension.svg b/packages/vitec/Resources/Public/Icons/Extension.svg new file mode 100644 index 0000000..0cfa220 --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/Extension.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/vitec/Resources/Public/Icons/relation.gif b/packages/vitec/Resources/Public/Icons/relation.gif new file mode 100644 index 0000000..db61d7e Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/relation.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_.gif new file mode 100644 index 0000000..37ba37b Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_download.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_download.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_download.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_market.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_market.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_market.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_product.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_solution.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_solution.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_solution.gif differ diff --git a/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif new file mode 100644 index 0000000..6cc5f16 Binary files /dev/null and b/packages/vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif differ diff --git a/packages/vitec/Resources/Public/Icons/vitec-cols-25-25-25-25.svg b/packages/vitec/Resources/Public/Icons/vitec-cols-25-25-25-25.svg new file mode 100755 index 0000000..5961a65 --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-cols-25-25-25-25.svg @@ -0,0 +1 @@ + diff --git a/packages/vitec/Resources/Public/Icons/vitec-cols-33-33-33.svg b/packages/vitec/Resources/Public/Icons/vitec-cols-33-33-33.svg new file mode 100755 index 0000000..d5e613a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-cols-33-33-33.svg @@ -0,0 +1 @@ + diff --git a/packages/vitec/Resources/Public/Icons/vitec-cols-33-66.svg b/packages/vitec/Resources/Public/Icons/vitec-cols-33-66.svg new file mode 100755 index 0000000..f5e7589 --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-cols-33-66.svg @@ -0,0 +1 @@ + diff --git a/packages/vitec/Resources/Public/Icons/vitec-cols-50-50.svg b/packages/vitec/Resources/Public/Icons/vitec-cols-50-50.svg new file mode 100755 index 0000000..0d1538f --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-cols-50-50.svg @@ -0,0 +1 @@ + diff --git a/packages/vitec/Resources/Public/Icons/vitec-cols-66-33.svg b/packages/vitec/Resources/Public/Icons/vitec-cols-66-33.svg new file mode 100755 index 0000000..b19b7d1 --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-cols-66-33.svg @@ -0,0 +1 @@ + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcardcollection.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcardcollection.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-downloadcardcollection.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-marketshow.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-marketshow.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-marketshow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-productlist.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-productlist.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-productlist.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-productshow.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-productshow.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-productshow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-simplecard.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-simplecard.svg new file mode 100644 index 0000000..50e6041 --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-simplecard.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-solutionshow.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-solutionshow.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-solutionshow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaseshow.svg b/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaseshow.svg new file mode 100644 index 0000000..5785f5a --- /dev/null +++ b/packages/vitec/Resources/Public/Icons/vitec-plugin-usecaseshow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/Resources/Public/Images/VITEC_LOGO.svg b/packages/vitec/Resources/Public/Images/VITEC_LOGO.svg new file mode 100644 index 0000000..8a110d1 --- /dev/null +++ b/packages/vitec/Resources/Public/Images/VITEC_LOGO.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/vitec/Resources/Public/Images/login_back.png b/packages/vitec/Resources/Public/Images/login_back.png new file mode 100644 index 0000000..0cb9e75 Binary files /dev/null and b/packages/vitec/Resources/Public/Images/login_back.png differ diff --git a/packages/vitec/Resources/Public/Images/og-template.jpg b/packages/vitec/Resources/Public/Images/og-template.jpg new file mode 100644 index 0000000..37aa702 Binary files /dev/null and b/packages/vitec/Resources/Public/Images/og-template.jpg differ diff --git a/packages/vitec/Resources/Public/Javascript/gsap/3.12.2/gsap.min.js b/packages/vitec/Resources/Public/Javascript/gsap/3.12.2/gsap.min.js new file mode 100644 index 0000000..63cb55c --- /dev/null +++ b/packages/vitec/Resources/Public/Javascript/gsap/3.12.2/gsap.min.js @@ -0,0 +1,10 @@ +/*! + * GSAP 3.12.2 + * https://greensock.com + * + * @license Copyright 2023, GreenSock. All rights reserved. + * Subject to the terms at https://greensock.com/standard-license or for Club GreenSock members, the agreement issued with that membership. + * @author: Jack Doyle, jack@greensock.com + */ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function P(t){return(i=yt(t,ot))&&Ee}function Q(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function R(t,e){return!e&&console.warn(t)}function S(t,e){return t&&(ot[t]=e)&&i&&(i[t]=e)||ot}function T(){return 0}function ea(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=gt.length;r--&&!gt[r].targetTest(i););e=gt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Vt(t[r],e)))||t.splice(r,1);return t}function fa(t){return t._gsap||ea(Ot(t))[0]._gsap}function ga(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ha(t,e){return(t=t.split(",")).forEach(e)||t}function ia(t){return Math.round(1e5*t)/1e5||0}function ja(t){return Math.round(1e7*t)/1e7||0}function ka(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function la(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++ia;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function ya(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function za(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Aa(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Ca(t,e,r,i){return t._startAt&&(L?t._startAt.revert(ht):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Ea(t){return t._repeat?Tt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ga(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ha(t){return t._end=ja(t._start+(t._tDur/Math.abs(t._ts||t._rts||X)||0))}function Ia(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=ja(r._time-(0X)&&e.render(r,!0)),Aa(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur(n=Math.abs(n))&&(a=i,o=n);return a}function tb(t){return za(t),t.scrollTrigger&&t.scrollTrigger.kill(!!L),t.progress()<1&&At(t,"onInterrupt"),t}function wb(t){if(x()&&t){var e=(t=!t.name&&t.default||t).name,r=s(t),i=e&&!r&&t.init?function(){this._props=[]}:t,n={init:T,render:he,add:Qt,kill:ce,modifier:fe,rawVars:0},a={targetTest:0,get:0,getSetter:ne,aliases:{},register:0};if(Ft(),t!==i){if(pt[e])return;qa(i,qa(ua(t,n),a)),yt(i.prototype,yt(n,ua(t,a))),pt[i.prop=e]=i,t.targetTest&&(gt.push(i),ft[e]=1),e=("css"===e?"CSS":e.charAt(0).toUpperCase()+e.substr(1))+"Plugin"}S(e,i),t.register&&t.register(Ee,i,_e)}else t&&Ct.push(t)}function zb(t,e,r){return(6*(t+=t<0?1:1>16,e>>8&St,e&St]:0:Et.black;if(!p){if(","===e.substr(-1)&&(e=e.substr(0,e.length-1)),Et[e])p=Et[e];else if("#"===e.charAt(0)){if(e.length<6&&(e="#"+(n=e.charAt(1))+n+(a=e.charAt(2))+a+(s=e.charAt(3))+s+(5===e.length?e.charAt(4)+e.charAt(4):"")),9===e.length)return[(p=parseInt(e.substr(1,6),16))>>16,p>>8&St,p&St,parseInt(e.substr(7),16)/255];p=[(e=parseInt(e.substr(1),16))>>16,e>>8&St,e&St]}else if("hsl"===e.substr(0,3))if(p=d=e.match(tt),r){if(~e.indexOf("="))return p=e.match(et),i&&p.length<4&&(p[3]=1),p}else o=+p[0]%360/360,u=p[1]/100,n=2*(h=p[2]/100)-(a=h<=.5?h*(u+1):h+u-h*u),3=U?u.endTime(!1):t._dur;return r(e)&&(isNaN(e)||e in o)?(a=e.charAt(0),s="%"===e.substr(-1),n=e.indexOf("="),"<"===a||">"===a?(0<=n&&(e=e.replace(/=/,"")),("<"===a?u._start:u.endTime(0<=u._repeat))+(parseFloat(e.substr(1))||0)*(s?(n<0?u:i).totalDuration()/100:1)):n<0?(e in o||(o[e]=h),o[e]):(a=parseFloat(e.charAt(n-1)+e.substr(n+1)),s&&i&&(a=a/100*($(i)?i[0]:i).totalDuration()),1=r&&te)return i;i=i._next}else for(i=t._last;i&&i._start>=r;){if("isPause"===i.data&&i._start=n._start)&&n._ts&&h!==n){if(n.parent!==this)return this.render(t,e,r);if(n.render(0=this.totalDuration()||!v&&_)&&(f!==this._start&&Math.abs(l)===Math.abs(this._ts)||this._lock||(!t&&g||!(v===m&&0=i&&(a instanceof Zt?e&&n.push(a):(r&&n.push(a),t&&n.push.apply(n,a.getChildren(!0,e,r)))),a=a._next;return n},e.getById=function getById(t){for(var e=this.getChildren(1,1,1),r=e.length;r--;)if(e[r].vars.id===t)return e[r]},e.remove=function remove(t){return r(t)?this.removeLabel(t):s(t)?this.killTweensOf(t):(ya(this,t),t===this._recent&&(this._recent=this._last),Aa(this))},e.totalTime=function totalTime(t,e){return arguments.length?(this._forcing=1,!this._dp&&this._ts&&(this._start=ja(Rt.time-(0r:!r||s.isActive())&&n.push(s):(i=s.getTweensOf(a,r)).length&&n.push.apply(n,i),s=s._next;return n},e.tweenTo=function tweenTo(t,e){e=e||{};var r,i=this,n=xt(i,t),a=e.startAt,s=e.onStart,o=e.onStartParams,u=e.immediateRender,h=Zt.to(i,qa({ease:e.ease||"none",lazy:!1,immediateRender:!1,time:n,overwrite:"auto",duration:e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale())||X,onStart:function onStart(){if(i.pause(),!r){var t=e.duration||Math.abs((n-(a&&"time"in a?a.time:i._time))/i.timeScale());h._dur!==t&&Ra(h,t,0,1).render(h._time,!0,!0),r=1}s&&s.apply(h,o||[])}},e));return u?h.render(0):h},e.tweenFromTo=function tweenFromTo(t,e,r){return this.tweenTo(e,qa({startAt:{time:xt(this,t)}},r))},e.recent=function recent(){return this._recent},e.nextLabel=function nextLabel(t){return void 0===t&&(t=this._time),rb(this,xt(this,t))},e.previousLabel=function previousLabel(t){return void 0===t&&(t=this._time),rb(this,xt(this,t),1)},e.currentLabel=function currentLabel(t){return arguments.length?this.seek(t,!0):this.previousLabel(this._time+X)},e.shiftChildren=function shiftChildren(t,e,r){void 0===r&&(r=0);for(var i,n=this._first,a=this.labels;n;)n._start>=r&&(n._start+=t,n._end+=t),n=n._next;if(e)for(i in a)a[i]>=r&&(a[i]+=t);return Aa(this)},e.invalidate=function invalidate(t){var e=this._first;for(this._lock=0;e;)e.invalidate(t),e=e._next;return i.prototype.invalidate.call(this,t)},e.clear=function clear(t){void 0===t&&(t=!0);for(var e,r=this._first;r;)e=r._next,this.remove(r),r=e;return this._dp&&(this._time=this._tTime=this._pTime=0),t&&(this.labels={}),Aa(this)},e.totalDuration=function totalDuration(t){var e,r,i,n=0,a=this,s=a._last,o=U;if(arguments.length)return a.timeScale((a._repeat<0?a.duration():a.totalDuration())/(a.reversed()?-t:t));if(a._dirty){for(i=a.parent;s;)e=s._prev,s._dirty&&s.totalDuration(),o<(r=s._start)&&a._sort&&s._ts&&!a._lock?(a._lock=1,Ka(a,s,r-s._delay,1)._lock=0):o=r,r<0&&s._ts&&(n-=r,(!i&&!a._dp||i&&i.smoothChildTiming)&&(a._start+=r/a._ts,a._time-=r,a._tTime-=r),a.shiftChildren(-r,!1,-Infinity),o=0),s._end>n&&s._ts&&(n=s._end),s=e;Ra(a,a===I&&a._time>n?a._time:n,1,1),a._dirty=0}return a._tDur},Timeline.updateRoot=function updateRoot(t){if(I._ts&&(na(I,Ga(t,I)),f=Rt.frame),Rt.frame>=mt){mt+=q.autoSleep||120;var e=I._first;if((!e||!e._ts)&&q.autoSleep&&Rt._listeners.length<2){for(;e&&!e._ts;)e=e._next;e||Rt.sleep()}}},Timeline}(Ut);qa(Xt.prototype,{_lock:0,_hasPause:0,_forcing:0});function ac(t,e,i,n,a,o){var u,h,l,f;if(pt[t]&&!1!==(u=new pt[t]).init(a,u.rawVars?e[t]:function _processVars(t,e,i,n,a){if(s(t)&&(t=Kt(t,a,e,i,n)),!v(t)||t.style&&t.nodeType||$(t)||Z(t))return r(t)?Kt(t,a,e,i,n):t;var o,u={};for(o in t)u[o]=Kt(t[o],a,e,i,n);return u}(e[t],n,a,o,i),i,n,o)&&(i._pt=h=new _e(i._pt,a,t,0,1,u.render,u,0,u.priority),i!==c))for(l=i._ptLookup[i._targets.indexOf(a)],f=u._props.length;f--;)l[u._props[f]]=h;return u}function gc(t,r,e,i){var n,a,s=r.ease||i||"power1.inOut";if($(r))a=e[t]||(e[t]=[]),r.forEach(function(t,e){return a.push({t:e/(r.length-1)*100,v:t,e:s})});else for(n in r)a=e[n]||(e[n]=[]),"ease"===n||a.push({t:parseFloat(t),v:r[n],e:s})}var Nt,Wt,Qt=function _addPropTween(t,e,i,n,a,o,u,h,l,f){s(n)&&(n=n(a||0,t,o));var c,d=t[e],p="get"!==i?i:s(d)?l?t[e.indexOf("set")||!s(t["get"+e.substr(3)])?e:"get"+e.substr(3)](l):t[e]():d,_=s(d)?l?re:te:$t;if(r(n)&&(~n.indexOf("random(")&&(n=ob(n)),"="===n.charAt(1)&&(!(c=ka(p,n)+(Ya(p)||0))&&0!==c||(n=c))),!f||p!==n||Wt)return isNaN(p*n)||""===n?(d||e in t||Q(e,n),function _addComplexStringPropTween(t,e,r,i,n,a,s){var o,u,h,l,f,c,d,p,_=new _e(this._pt,t,e,0,1,ue,null,n),m=0,g=0;for(_.b=r,_.e=i,r+="",(d=~(i+="").indexOf("random("))&&(i=ob(i)),a&&(a(p=[r,i],t,e),r=p[0],i=p[1]),u=r.match(it)||[];o=it.exec(i);)l=o[0],f=i.substring(m,o.index),h?h=(h+1)%5:"rgba("===f.substr(-5)&&(h=1),l!==u[g++]&&(c=parseFloat(u[g-1])||0,_._pt={_next:_._pt,p:f||1===g?f:",",s:c,c:"="===l.charAt(1)?ka(c,l)-c:parseFloat(l)-c,m:h&&h<4?Math.round:0},m=it.lastIndex);return _.c=m")}),s.duration();else{for(l in u={},x)"ease"===l||"easeEach"===l||gc(l,x[l],u,x.easeEach);for(l in u)for(C=u[l].sort(function(t,e){return t.t-e.t}),o=D=0;o=t._tDur||e<0)&&t.ratio===u&&(u&&za(t,1),r||L||(At(t,u?"onComplete":"onReverseComplete",!0),t._prom&&t._prom()))}else t._zTime||(t._zTime=e)}(this,t,e,r);return this},e.targets=function targets(){return this._targets},e.invalidate=function invalidate(t){return t&&this.vars.runBackwards||(this._startAt=0),this._pt=this._op=this._onUpdate=this._lazy=this.ratio=0,this._ptLookup=[],this.timeline&&this.timeline.invalidate(t),z.prototype.invalidate.call(this,t)},e.resetTo=function resetTo(t,e,r,i){d||Rt.wake(),this._ts||this.play();var n,a=Math.min(this._dur,(this._dp._time-this._start)*this._ts);return this._initted||Gt(this,a),n=this._ease(a/this._dur),function _updatePropTweens(t,e,r,i,n,a,s){var o,u,h,l,f=(t._pt&&t._ptCache||(t._ptCache={}))[e];if(!f)for(f=t._ptCache[e]=[],h=t._ptLookup,l=t._targets.length;l--;){if((o=h[l][e])&&o.d&&o.d._pt)for(o=o.d._pt;o&&o.p!==e&&o.fp!==e;)o=o._next;if(!o)return Wt=1,t.vars[e]="+=0",Gt(t,s),Wt=0,1;f.push(o)}for(l=f.length;l--;)(o=(u=f[l])._pt||u).s=!i&&0!==i||n?o.s+(i||0)+a*o.c:i,o.c=r-o.s,u.e&&(u.e=ia(r)+Ya(u.e)),u.b&&(u.b=o.s+Ya(u.b))}(this,t,e,r,i,n,a)?this.resetTo(t,e,r,i):(Ia(this,0),this.parent||xa(this._dp,this,"_first","_last",this._dp._sort?"_start":0),this.render(0))},e.kill=function kill(t,e){if(void 0===e&&(e="all"),!(t||e&&"all"!==e))return this._lazy=this._pt=0,this.parent?tb(this):this;if(this.timeline){var i=this.timeline.totalDuration();return this.timeline.killTweensOf(t,e,Nt&&!0!==Nt.vars.overwrite)._first||tb(this),this.parent&&i!==this.timeline.totalDuration()&&Ra(this,this._dur*this.timeline._tDur/i,0,1),this}var n,a,s,o,u,h,l,f=this._targets,c=t?Ot(t):f,d=this._ptLookup,p=this._pt;if((!e||"all"===e)&&function _arraysMatch(t,e){for(var r=t.length,i=r===e.length;i&&r--&&t[r]===e[r];);return r<0}(f,c))return"all"===e&&(this._pt=0),tb(this);for(n=this._op=this._op||[],"all"!==e&&(r(e)&&(u={},ha(e,function(t){return u[t]=1}),e=u),e=function _addAliasesToVars(t,e){var r,i,n,a,s=t[0]?fa(t[0]).harness:0,o=s&&s.aliases;if(!o)return e;for(i in r=yt({},e),o)if(i in r)for(n=(a=o[i].split(",")).length;n--;)r[a[n]]=r[i];return r}(f,e)),l=f.length;l--;)if(~c.indexOf(f[l]))for(u in a=d[l],"all"===e?(n[l]=e,o=a,s={}):(s=n[l]=n[l]||{},o=e),o)(h=a&&a[u])&&("kill"in h.d&&!0!==h.d.kill(u)||ya(this,h,"_pt"),delete a[u]),"all"!==s&&(s[u]=1);return this._initted&&!this._pt&&p&&tb(this),this},Tween.to=function to(t,e,r){return new Tween(t,e,r)},Tween.from=function from(t,e){return Va(1,arguments)},Tween.delayedCall=function delayedCall(t,e,r,i){return new Tween(e,0,{immediateRender:!1,lazy:!1,overwrite:!1,delay:t,onComplete:e,onReverseComplete:e,onCompleteParams:r,onReverseCompleteParams:r,callbackScope:i})},Tween.fromTo=function fromTo(t,e,r){return Va(2,arguments)},Tween.set=function set(t,e){return e.duration=0,e.repeatDelay||(e.repeat=0),new Tween(t,e)},Tween.killTweensOf=function killTweensOf(t,e,r){return I.killTweensOf(t,e,r)},Tween}(Ut);qa(Zt.prototype,{_targets:[],_lazy:0,_startAt:0,_op:0,_onInit:0}),ha("staggerTo,staggerFrom,staggerFromTo",function(r){Zt[r]=function(){var t=new Xt,e=Mt.call(arguments,0);return e.splice("staggerFromTo"===r?5:4,0,0),t[r].apply(t,e)}});function oc(t,e,r){return t.setAttribute(e,r)}function wc(t,e,r,i){i.mSet(t,e,i.m.call(i.tween,r,i.mt),i)}var $t=function _setterPlain(t,e,r){return t[e]=r},te=function _setterFunc(t,e,r){return t[e](r)},re=function _setterFuncWithParam(t,e,r,i){return t[e](i.fp,r)},ne=function _getSetter(t,e){return s(t[e])?te:u(t[e])&&t.setAttribute?oc:$t},ae=function _renderPlain(t,e){return e.set(e.t,e.p,Math.round(1e6*(e.s+e.c*t))/1e6,e)},se=function _renderBoolean(t,e){return e.set(e.t,e.p,!!(e.s+e.c*t),e)},ue=function _renderComplexString(t,e){var r=e._pt,i="";if(!t&&e.b)i=e.b;else if(1===t&&e.e)i=e.e;else{for(;r;)i=r.p+(r.m?r.m(r.s+r.c*t):Math.round(1e4*(r.s+r.c*t))/1e4)+i,r=r._next;i+=e.c}e.set(e.t,e.p,i,e)},he=function _renderPropTweens(t,e){for(var r=e._pt;r;)r.r(t,r.d),r=r._next},fe=function _addPluginModifier(t,e,r,i){for(var n,a=this._pt;a;)n=a._next,a.p===i&&a.modifier(t,e,r),a=n},ce=function _killPropTweensOf(t){for(var e,r,i=this._pt;i;)r=i._next,i.p===t&&!i.op||i.op===t?ya(this,i,"_pt"):i.dep||(e=1),i=r;return!e},pe=function _sortPropTweensByPriority(t){for(var e,r,i,n,a=t._pt;a;){for(e=a._next,r=i;r&&r.pr>a.pr;)r=r._next;(a._prev=r?r._prev:n)?a._prev._next=a:i=a,(a._next=r)?r._prev=a:n=a,a=e}t._pt=i},_e=(PropTween.prototype.modifier=function modifier(t,e,r){this.mSet=this.mSet||this.set,this.set=wc,this.m=t,this.mt=r,this.tween=e},PropTween);function PropTween(t,e,r,i,n,a,s,o,u){this.t=e,this.s=i,this.c=n,this.p=r,this.r=a||ae,this.d=s||this,this.set=o||$t,this.pr=u||0,(this._next=t)&&(t._prev=this)}ha(vt+"parent,duration,ease,delay,overwrite,runBackwards,startAt,yoyo,immediateRender,repeat,repeatDelay,data,paused,reversed,lazy,callbackScope,stringFilter,id,yoyoEase,stagger,inherit,repeatRefresh,keyframes,autoRevert,scrollTrigger",function(t){return ft[t]=1}),ot.TweenMax=ot.TweenLite=Zt,ot.TimelineLite=ot.TimelineMax=Xt,I=new Xt({sortChildren:!1,defaults:V,autoRemoveChildren:!0,id:"root",smoothChildTiming:!0}),q.stringFilter=Fb;function Ec(t){return(ye[t]||Te).map(function(t){return t()})}function Fc(){var t=Date.now(),o=[];2 'VITEC', + 'description' => 'Manage all VITEC Products and Downloads', + 'category' => 'plugin', + 'author' => '', + 'author_email' => '', + 'state' => 'alpha', + 'clearCacheOnLoad' => 0, + 'version' => '1.0.1', + 'constraints' => [ + 'depends' => [ + 'typo3' => '13.4.0-13.4.99', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/packages/vitec/ext_localconf.php b/packages/vitec/ext_localconf.php new file mode 100755 index 0000000..9be5ac9 --- /dev/null +++ b/packages/vitec/ext_localconf.php @@ -0,0 +1,189 @@ + \Evomedien\Vitec\View\VitecBackendLayoutView::class, +]; + + +// Register custom VITEC CKEditor RTE preset +$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['vitec'] = 'EXT:vitec/Configuration/RTE/Vitec.yaml'; + +// TSConfig für Seiten laden +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig( + "@import 'EXT:aifood/Configuration/page.tsconfig'" +); + +(static function () { + // Register plugins + ExtensionUtility::configurePlugin( + 'Vitec', + 'Productlist', + [ + \Evomedien\Vitec\Controller\ProductController::class => 'list' + ], + [ + \Evomedien\Vitec\Controller\ProductController::class => 'list' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Simplecard', + [ + \Evomedien\Vitec\Controller\SimplecardController::class => 'list' + ], + [ + \Evomedien\Vitec\Controller\SimplecardController::class => 'list' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Productshow', + [ + \Evomedien\Vitec\Controller\ProductController::class => 'show' + ], + [ + \Evomedien\Vitec\Controller\ProductController::class => 'show' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Usecaseshow', + [ + \Evomedien\Vitec\Controller\UsecaseController::class => 'show' + ], + [ + \Evomedien\Vitec\Controller\UsecaseController::class => 'show' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Marketshow', + [ + \Evomedien\Vitec\Controller\MarketController::class => 'show' + ], + [ + \Evomedien\Vitec\Controller\MarketController::class => 'show' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Solutionshow', + [ + \Evomedien\Vitec\Controller\SolutionController::class => 'show' + ], + [ + \Evomedien\Vitec\Controller\SolutionController::class => 'show' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Usecaselist', + [ + \Evomedien\Vitec\Controller\UsecaseController::class => 'list' + ], + [ + \Evomedien\Vitec\Controller\UsecaseController::class => 'list' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Downloadcard', + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'list, show' + ], + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'list, show' + ] + ); + + ExtensionUtility::configurePlugin( + 'Vitec', + 'Downloadcardcollection', + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'downloadcardcollection' + ], + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'downloadcardcollection' + ] + ); + ExtensionUtility::configurePlugin( + 'Vitec', + 'Datasheets', + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'datasheets' + ], + [ + \Evomedien\Vitec\Controller\DownloadController::class => 'datasheets' + ] + ); + + // Register icons + $iconRegistry = GeneralUtility::makeInstance(IconRegistry::class); + $iconRegistry->registerIcon( + 'vitec-plugin-productlist', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-productlist.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-productshow', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-productshow.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-usecaseshow', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-usecaseshow.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-marketshow', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-marketshow.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-solutionshow', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-solutionshow.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-usecaselist', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-downloadcard', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-downloadcardcollection', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-downloadcardcollection.svg'] + ); + $iconRegistry->registerIcon( + 'vitec-plugin-datasheets', + \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + ['source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-datasheets.svg'] + ); + +})(); diff --git a/packages/vitec/ext_tables.php b/packages/vitec/ext_tables.php new file mode 100644 index 0000000..563e7c0 --- /dev/null +++ b/packages/vitec/ext_tables.php @@ -0,0 +1,58 @@ +' +); + +// Register the Downloadcard plugin +\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( + 'Vitec', + 'Downloadcard', + 'Download Card' +); + +// Register the Simplecard plugin +\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( + 'Vitec', + 'Simplecard', + 'Simple Card' +); + +// Register the Downloadcard plugin +\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( + 'Vitec', + 'Downloadcardcollection', + 'Download Card Collection' +); + +$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['vitec_productlist'] = 'pi_flexform'; +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue( + 'vitec_productlist', + 'FILE:EXT:vitec/Configuration/FlexForms/Productlist.xml' +); + +// Add FlexForm for Downloadcard plugin +$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['vitec_downloadcard'] = 'pi_flexform'; +ExtensionManagementUtility::addPiFlexFormValue( + 'vitec_downloadcard', + 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcard.xml' +); + +// Add FlexForm for DownloadcardCollection plugin +$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['vitec_downloadcardcollection'] = 'pi_flexform'; +ExtensionManagementUtility::addPiFlexFormValue( + 'vitec_downloadcardcollection', + 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcardcollection.xml' +); \ No newline at end of file diff --git a/packages/vitec/ext_tables.sql b/packages/vitec/ext_tables.sql new file mode 100644 index 0000000..8e6ed68 --- /dev/null +++ b/packages/vitec/ext_tables.sql @@ -0,0 +1,171 @@ +CREATE TABLE tx_vitec_domain_model_product ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) DEFAULT '0' NOT NULL, + hidden tinyint(4) DEFAULT '0' NOT NULL, + starttime int(11) DEFAULT '0' NOT NULL, + endtime int(11) DEFAULT '0' NOT NULL, + title varchar(255) DEFAULT '' NOT NULL, + slug varchar(255) DEFAULT '' NOT NULL, + PRIMARY KEY (uid), + urltitle varchar(255) DEFAULT '' NOT NULL, + seotitle varchar(255) DEFAULT '' NOT NULL, + seometa text, + keywords text, + productimage int(11) DEFAULT '0' NOT NULL, + structureddata text, + teaser varchar(255) DEFAULT '' NOT NULL, + subtitle varchar(255) DEFAULT '' NOT NULL, + applications text, + highlights text, + description text, + image INTEGER, + relatedimage INTEGER, + ogimage INTEGER, + legacy tinyint(4) DEFAULT '0' NOT NULL, + supportproduct tinyint(4) DEFAULT '0' NOT NULL, + subproduct tinyint(4) DEFAULT '0' NOT NULL, + cta varchar(255) DEFAULT '' NOT NULL, + sorting1 smallint(5) NOT NULL, + sorting2 smallint(5) NOT NULL, + sorting3 smallint(5) NOT NULL, + sorting4 smallint(5) NOT NULL, + sorting5 smallint(5) NOT NULL, + links text, + hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL, + hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL, + hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL, + hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL, + shortcut tinyint(4) DEFAULT '0' NOT NULL, + shortcutpid smallint(5) NOT NULL, + video varchar(255) DEFAULT '' NOT NULL, + key1 varchar(255) DEFAULT '' NOT NULL, + key2 varchar(255) DEFAULT '' NOT NULL, + key3 varchar(255) DEFAULT '' NOT NULL, + apptext1 varchar(255) DEFAULT '' NOT NULL, + apptext2 varchar(255) DEFAULT '' NOT NULL, + apptext3 varchar(255) DEFAULT '' NOT NULL, + productlayout int(11) DEFAULT '0' NOT NULL, + contentelement varchar(255) DEFAULT '' NOT NULL, + contentelementcta varchar(255) DEFAULT '' NOT NULL, + KEY parent (pid) +); +CREATE TABLE tx_vitec_domain_model_download ( + title varchar(255) DEFAULT '' NOT NULL, + slug varchar(255) DEFAULT '' NOT NULL, + teaser varchar(255) NOT NULL DEFAULT '', + keywords varchar(255) NOT NULL DEFAULT '', + description text, + file int(11) unsigned NOT NULL DEFAULT '0', + sort1 varchar(255) NOT NULL DEFAULT '', + sort2 varchar(255) NOT NULL DEFAULT '', + sort3 varchar(255) NOT NULL DEFAULT '', + private_download smallint(1) unsigned NOT NULL DEFAULT '0', + hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL, + hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL, + hideondatasheets smallint(5) unsigned DEFAULT '0' NOT NULL, + hideonproducts smallint(5) unsigned DEFAULT '0' NOT NULL, + icon varchar(255) NOT NULL DEFAULT '', + filepath varchar(255) NOT NULL DEFAULT '', + fileprefix varchar(255) NOT NULL DEFAULT '', + useolddl smallint(1) unsigned NOT NULL DEFAULT '0' +); + +CREATE TABLE tx_vitec_product_download_mm ( + uid_local int(11) DEFAULT '0' NOT NULL, + uid_foreign int(11) DEFAULT '0' NOT NULL, + sorting int(11) DEFAULT '0' NOT NULL, + sorting_foreign int(11) DEFAULT '0' NOT NULL, + KEY uid_local (uid_local), + KEY uid_foreign (uid_foreign) +); + +CREATE TABLE tx_vitec_product_related_mm ( + uid_local INT(11) NOT NULL, + uid_foreign INT(11) NOT NULL, + sorting INT(11) DEFAULT '0' NOT NULL, + PRIMARY KEY (uid_local, uid_foreign) +); + +CREATE TABLE tx_vitec_domain_model_usecase ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) DEFAULT '0' NOT NULL, + hidden tinyint(4) DEFAULT '0' NOT NULL, + starttime int(11) DEFAULT '0' NOT NULL, + endtime int(11) DEFAULT '0' NOT NULL, + title varchar(255) DEFAULT '' NOT NULL, + slug varchar(255) DEFAULT '' NOT NULL, + teaser varchar(255) DEFAULT '' NOT NULL, + subtitle varchar(255) DEFAULT '' NOT NULL, + description text, + caseimage INTEGER, + logoimage INTEGER, + singlepid varchar(255) DEFAULT '' NOT NULL, + hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL, + hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL, + PRIMARY KEY (uid), + KEY parent (pid) +); + +CREATE TABLE tx_vitec_domain_model_solution ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) DEFAULT '0' NOT NULL, + hidden tinyint(4) DEFAULT '0' NOT NULL, + starttime int(11) DEFAULT '0' NOT NULL, + endtime int(11) DEFAULT '0' NOT NULL, + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + title varchar(255) DEFAULT '' NOT NULL, + subtitle varchar(255) DEFAULT '' NOT NULL, + teaser varchar(255) DEFAULT '' NOT NULL, + description text, + image int(11) DEFAULT '0' NOT NULL, + PRIMARY KEY (uid), + KEY parent (pid), + KEY language (l10n_parent, sys_language_uid) +); + +CREATE TABLE tx_vitec_domain_model_market ( + uid int(11) NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + tstamp int(11) DEFAULT '0' NOT NULL, + crdate int(11) DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) DEFAULT '0' NOT NULL, + hidden tinyint(4) DEFAULT '0' NOT NULL, + starttime int(11) DEFAULT '0' NOT NULL, + endtime int(11) DEFAULT '0' NOT NULL, + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l10n_parent int(11) DEFAULT '0' NOT NULL, + l10n_diffsource mediumblob, + title varchar(255) DEFAULT '' NOT NULL, + subtitle varchar(255) DEFAULT '' NOT NULL, + teaser varchar(255) DEFAULT '' NOT NULL, + description text, + image int(11) DEFAULT '0' NOT NULL, + PRIMARY KEY (uid), + KEY parent (pid), + KEY language (l10n_parent, sys_language_uid) +); + +ALTER TABLE sys_category +ADD class VARCHAR(255) DEFAULT '' NOT NULL, +ADD filetype VARCHAR(255) DEFAULT '' NOT NULL, +ADD type VARCHAR(255) DEFAULT '' NOT NULL; + + +CREATE TABLE tt_content ( + tx_vitec_bg_variant VARCHAR(20) DEFAULT 'none' NOT NULL +); \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..100555f --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,393 @@ +##### +# +# Example .htaccess file for TYPO3 CMS - for use with Apache Webserver +# +# This file includes settings for the following configuration options: +# +# - Compression +# - Caching +# - MIME types +# - Cross Origin requests +# - Rewriting and Access +# - Miscellaneous +# - PHP optimisation +# +# If you want to use it, you have to copy it to the root folder of your TYPO3 installation (if its +# not there already) and rename it to '.htaccess'. To make .htaccess files work, you might need to +# adjust the 'AllowOverride' directive in your Apache configuration file. +# +# IMPORTANT: You may need to change this file depending on your TYPO3 installation! +# Consider adding this file's content to your webserver's configuration directly for speed improvement +# +# Lots of the options are taken from https://github.com/h5bp/html5-boilerplate/blob/master/dist/.htaccess +# +#### + + +### Begin: Compression ### + +# Compressing resource files will save bandwidth and so improve loading speed especially for users +# with slower internet connections. TYPO3 can compress the .js and .css files for you. +# *) Uncomment the following lines and +# *) Set $GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel'] = 9 for the Backend +# *) Set $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] = 9 together with the TypoScript properties +# config.compressJs and config.compressCss for GZIP compression of Frontend JS and CSS files. + +# +# AddType "text/javascript" .gz +# +# +# AddType "text/css" .gz +# +#AddEncoding x-gzip .gz + + + # Force compression for mangled `Accept-Encoding` request headers + + + SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding + RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding + + + + # Compress all output labeled with one of the following media types. + # + # (!) For Apache versions below version 2.3.7 you don't need to + # enable `mod_filter` and can remove the `` + # and `` lines as `AddOutputFilterByType` is still in + # the core directives. + # + # https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype + + + AddOutputFilterByType DEFLATE application/atom+xml \ + application/javascript \ + application/json \ + application/ld+json \ + application/manifest+json \ + application/rdf+xml \ + application/rss+xml \ + application/schema+json \ + application/vnd.geo+json \ + application/geo+json \ + application/vnd.ms-fontobject \ + application/x-font-ttf \ + application/x-javascript \ + application/x-web-app-manifest+json \ + application/xhtml+xml \ + application/xml \ + font/eot \ + font/opentype \ + font/otf \ + font/ttf \ + image/bmp \ + image/svg+xml \ + image/vnd.microsoft.icon \ + image/x-icon \ + text/cache-manifest \ + text/css \ + text/html \ + text/javascript \ + text/plain \ + text/vcard \ + text/vnd.rim.location.xloc \ + text/vtt \ + text/x-component \ + text/x-cross-domain-policy \ + text/xml + + + + AddEncoding gzip svgz + + + +### End: Compression ### + + + +### Begin: Browser caching of resource files ### + +# This affects Frontend and Backend and increases performance. + + + ExpiresActive On + ExpiresDefault "access plus 1 month" + + ExpiresByType text/css "access plus 1 year" + + ExpiresByType application/json "access plus 0 seconds" + ExpiresByType application/ld+json "access plus 0 seconds" + ExpiresByType application/schema+json "access plus 0 seconds" + ExpiresByType application/vnd.geo+json "access plus 0 seconds" + ExpiresByType application/geo+json "access plus 0 seconds" + ExpiresByType application/xml "access plus 0 seconds" + ExpiresByType text/xml "access plus 0 seconds" + + ExpiresByType image/vnd.microsoft.icon "access plus 1 week" + ExpiresByType image/x-icon "access plus 1 week" + + ExpiresByType text/x-component "access plus 1 month" + + ExpiresByType text/html "access plus 0 seconds" + + ExpiresByType application/javascript "access plus 1 year" + ExpiresByType application/x-javascript "access plus 1 year" + ExpiresByType text/javascript "access plus 1 year" + + ExpiresByType application/manifest+json "access plus 1 week" + ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" + ExpiresByType text/cache-manifest "access plus 0 seconds" + + ExpiresByType audio/ogg "access plus 1 month" + ExpiresByType image/apng "access plus 1 month" + ExpiresByType image/avif "access plus 1 month" + ExpiresByType image/avif-sequence "access plus 1 month" + ExpiresByType image/bmp "access plus 1 month" + ExpiresByType image/gif "access plus 1 month" + ExpiresByType image/jpeg "access plus 1 month" + ExpiresByType image/jxl "access plus 1 month" + ExpiresByType image/png "access plus 1 month" + ExpiresByType image/svg+xml "access plus 1 month" + ExpiresByType image/webp "access plus 1 month" + ExpiresByType video/mp4 "access plus 1 month" + ExpiresByType video/ogg "access plus 1 month" + ExpiresByType video/webm "access plus 1 month" + + ExpiresByType application/atom+xml "access plus 1 hour" + ExpiresByType application/rdf+xml "access plus 1 hour" + ExpiresByType application/rss+xml "access plus 1 hour" + + ExpiresByType font/collection "access plus 1 month" + ExpiresByType application/vnd.ms-fontobject "access plus 1 month" + ExpiresByType font/eot "access plus 1 month" + ExpiresByType font/opentype "access plus 1 month" + ExpiresByType font/otf "access plus 1 month" + ExpiresByType application/x-font-ttf "access plus 1 month" + ExpiresByType font/ttf "access plus 1 month" + ExpiresByType application/font-woff "access plus 1 month" + ExpiresByType application/x-font-woff "access plus 1 month" + ExpiresByType font/woff "access plus 1 month" + ExpiresByType application/font-woff2 "access plus 1 month" + ExpiresByType font/woff2 "access plus 1 month" + + ExpiresByType text/x-cross-domain-policy "access plus 1 week" + + + +### End: Browser caching of resource files ### + + +### Begin: MIME types ### + +# Proper MIME types for all files + + # Security configuration + RemoveType .html .htm + + AddType text/html .html .htm + + + RemoveType .svg .svgz + + AddType image/svg+xml .svg .svgz + + + # Data interchange + AddType application/atom+xml atom + AddType application/json json map topojson + AddType application/ld+json jsonld + AddType application/rss+xml rss + AddType application/vnd.geo+json geojson + AddType application/xml rdf xml + + # JavaScript + AddType application/javascript js mjs + + # Manifest files + AddType application/manifest+json webmanifest + AddType application/x-web-app-manifest+json webapp + AddType text/cache-manifest appcache + + # Media files + + AddType audio/mp4 f4a f4b m4a + AddType audio/ogg oga ogg opus + AddType image/avif avif + AddType image/avif-sequence avifs + AddType image/bmp bmp + AddType image/jxl jxl + AddType image/webp webp + AddType video/mp4 f4v f4p m4v mp4 + AddType video/ogg ogv + AddType video/webm webm + AddType video/x-flv flv + AddType image/x-icon cur ico + + # Web fonts + AddType font/woff woff + AddType font/woff2 woff2 + AddType application/vnd.ms-fontobject eot + AddType font/ttf ttc ttf + AddType font/otf otf + + # Other + AddType application/octet-stream safariextz + AddType application/x-bb-appworld bbaw + AddType application/x-chrome-extension crx + AddType application/x-opera-extension oex + AddType application/x-xpinstall xpi + AddType text/vcard vcard vcf + AddType text/vnd.rim.location.xloc xloc + AddType text/vtt vtt + AddType text/x-component htc + + + +# UTF-8 encoding +AddDefaultCharset utf-8 + + AddCharset utf-8 .atom .css .js .json .manifest .rdf .rss .vtt .webapp .webmanifest .xml + + +### End: MIME types ### + + + +### Begin: Cross Origin ### + +# Send the CORS header for images when browsers request it. + + + + SetEnvIf Origin ":" IS_CORS + Header set Access-Control-Allow-Origin "*" env=IS_CORS + + + + +# Allow cross-origin access to web fonts. + + + Header set Access-Control-Allow-Origin "*" + + + +### End: Cross Origin ### + + + +### Begin: Rewriting and Access ### + + + + # Enable URL rewriting + RewriteEngine On + + # Store the current location in an environment variable CWD to use + # mod_rewrite in .htaccess files without knowing the RewriteBase + RewriteCond $0#%{REQUEST_URI} ([^#]*)#(.*)\1$ + RewriteRule ^.*$ - [E=CWD:%2] + + # Rules to set ApplicationContext based on hostname + #RewriteCond %{HTTP_HOST} ^dev\.example\.com$ + #RewriteRule .? - [E=TYPO3_CONTEXT:Development] + #RewriteCond %{HTTP_HOST} ^staging\.example\.com$ + #RewriteRule .? - [E=TYPO3_CONTEXT:Production/Staging] + #RewriteCond %{HTTP_HOST} ^www\.example\.com$ + #RewriteRule .? - [E=TYPO3_CONTEXT:Production] + + # Rule for versioned static files, configured through: + # - $GLOBALS['TYPO3_CONF_VARS']['BE']['versionNumberInFilename'] + # - $GLOBALS['TYPO3_CONF_VARS']['FE']['versionNumberInFilename'] + # IMPORTANT: This rule has to be the very first RewriteCond in order to work! + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^(.+)\.(\d+)\.(php|js|mjs|css|png|jpg|gif|svg|avif|webp|gz)$ %{ENV:CWD}$1.$3 [L] + + # Access block for folders + RewriteRule _(?:recycler|temp)_/ - [F] + RewriteRule fileadmin/templates/.*\.(?:txt|ts)$ - [F] + RewriteRule ^(?:vendor|typo3_src|typo3temp/var) - [F] + RewriteRule (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/ - [F] + + # Block access to all hidden files and directories with the exception of + # the visible content from within the `/.well-known/` hidden directory (RFC 5785). + RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC] + RewriteCond %{SCRIPT_FILENAME} -d [OR] + RewriteCond %{SCRIPT_FILENAME} -f + RewriteRule (?:^|/)\. - [F] + + # Stop rewrite processing, if we are in any known directory + # NOTE: Add your additional local storages here + RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|uploads/) - [L] + + # If the file/symlink/directory does not exist but is below /typo3/, redirect to the main TYPO3 entry point. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^typo3/(.*)$ %{ENV:CWD}index.php [QSA,L] + + # If the file/symlink/directory does not exist => Redirect to index.php. + # For httpd.conf, you need to prefix each '%{REQUEST_FILENAME}' with '%{DOCUMENT_ROOT}'. + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-l + RewriteRule ^.*$ %{ENV:CWD}index.php [QSA,L] + + + +# Access block for files +# Apache < 2.3 + + + Order allow,deny + Deny from all + Satisfy All + + +# Apache ≥ 2.3 + + + Require all denied + + + +# Block access to vcs directories + + RedirectMatch 404 /\.(?:git|svn|hg)/ + + +### End: Rewriting and Access ### + + + +### Begin: Miscellaneous ### + +# 404 error prevention for non-existing redirected folders +Options -MultiViews + +# Make sure that directory listings are disabled. + + Options -Indexes + + + + # Force IE to render pages in the highest available mode + Header set X-UA-Compatible "IE=edge" + + Header unset X-UA-Compatible + + + # Reducing MIME type security risks + Header set X-Content-Type-Options "nosniff" + + +# ETag removal + + Header unset ETag + +FileETag None + +### End: Miscellaneous ### + + +# Add your own rules here. diff --git a/public/_frontend/.htaccess b/public/_frontend/.htaccess new file mode 100644 index 0000000..fec2ba7 --- /dev/null +++ b/public/_frontend/.htaccess @@ -0,0 +1,10 @@ +RewriteEngine On +RewriteBase /_frontend/ + +RewriteRule ^index\.html$ - [L] + +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +RewriteRule ^ index.html [L] \ No newline at end of file diff --git a/public/_frontend/assets/index-B-ARIO24.css b/public/_frontend/assets/index-B-ARIO24.css new file mode 100644 index 0000000..cd1c351 --- /dev/null +++ b/public/_frontend/assets/index-B-ARIO24.css @@ -0,0 +1 @@ +@import "https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600&display=swap";:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;color:#ffffffde;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#242424;font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}a{color:#646cff;-webkit-text-decoration:inherit;text-decoration:inherit;font-weight:500}a:hover{color:#535bf2}body{place-items:center;min-width:320px;min-height:100vh;margin:0;display:flex}h1{font-size:3.2em;line-height:1.1}button{cursor:pointer;background-color:#1a1a1a;border:1px solid #0000;border-radius:8px;padding:.6em 1.2em;font-family:inherit;font-size:1em;font-weight:500;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme:light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}*,:before,:after{box-sizing:border-box}body,html{color:#1f2937;background:#f4f7fb;width:100%;min-height:100vh;margin:0;font-family:Space Grotesk,Segoe UI,system-ui,-apple-system,sans-serif}#page{flex-direction:column;width:100%;min-height:100vh;display:flex}#header{color:#f8fafc;background:linear-gradient(120deg,#26358cbf 20%,#f4793680 100%);flex-direction:column;justify-content:center;gap:1rem;width:100%;min-height:300px;padding:1rem 1.75rem;display:flex;box-shadow:0 12px 30px #0f274947}.header-chrome{z-index:2;flex-direction:column;gap:1rem;display:flex;position:relative}#header.header--with-hero{min-height:0;box-shadow:none;background:0 0;justify-content:flex-start;gap:0;padding:0;position:relative;overflow:hidden}#header.header--with-hero .header-chrome{padding:1.65rem 1.75rem 0;position:absolute;inset:0 0 auto}.header-hero{color:#f8fafc;background:#0f172a;width:100%;min-height:620px;margin-left:0;position:relative;top:0;overflow:hidden}.header-hero-media,.header-hero-overlay{position:absolute;inset:0}.header-hero-media{opacity:.45;background-position:50%;background-repeat:no-repeat;background-size:cover}.header-hero-overlay{background:linear-gradient(120deg,#26358cc7 20%,#f4793694 100%)}.header-hero-content{z-index:1;align-items:center;min-height:620px;padding-top:9.5rem;padding-bottom:4rem;display:flex;position:relative}.header-hero-copy{line-height:1.6}.header-hero-copy p{margin:0 0 1rem}.header-hero-copy p:last-of-type{margin-bottom:0}.header-hero-copy a{color:inherit}.header-hero-copy mark{color:inherit;background:#facc15d9;padding:0 .2rem}.header-hero-image-sr{clip:rect(0,0,0,0);border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.header-top-row{justify-content:space-between;align-items:center;gap:1rem;width:100%;display:flex}.header-logo-link{align-items:center;display:inline-flex}.header-logo{object-fit:contain;width:auto;height:34px}#meta-menu ul,#main-navigation ul{flex-wrap:wrap;align-items:center;gap:.75rem;margin:0;padding:0;list-style:none;display:flex}#meta-menu ul{justify-content:flex-end;gap:.5rem}#main-navigation ul{justify-content:center}#meta-menu a,#main-navigation a{color:#f8fafc;border-radius:999px;align-items:center;padding:.35rem .7rem;font-weight:500;text-decoration:none;transition:background-color .2s,color .2s;display:inline-flex}#meta-menu li.active a,#main-navigation li.active a,#meta-menu a:hover,#main-navigation a:hover{color:#fff;background-color:#ffffff2e}.main-content{flex:1;padding:4rem 0}.vitecMainContainer{background:#fff;border-radius:16px;margin-top:-2.5rem;box-shadow:0 16px 36px #0f27491f}.vitecMainContainer--withHero{margin-top:0}@media (max-width:768px){#header{min-height:130px;padding:.85rem}.header-top-row{flex-direction:column;align-items:center}#meta-menu ul,#main-navigation ul{justify-content:center;gap:.5rem}.header-logo{height:28px}#header.header--with-hero{padding:0}#header.header--with-hero .header-chrome{padding:1.15rem .85rem 0}.header-hero{width:100%;min-height:560px;margin-left:0}.header-hero-content{min-height:560px;padding-top:10rem;padding-bottom:2.5rem}.vitecMainContainer{border-radius:12px;margin-top:-2rem}.vitecMainContainer--withHero{margin-top:0}}#footer{color:#f8fafc;background:#020617} diff --git a/public/_frontend/assets/index-QAXHHeve.js b/public/_frontend/assets/index-QAXHHeve.js new file mode 100644 index 0000000..09d78b3 --- /dev/null +++ b/public/_frontend/assets/index-QAXHHeve.js @@ -0,0 +1,119 @@ +var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},ee=Object.prototype.hasOwnProperty;function te(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ne(e,t){return te(e.type,t,e.props)}function T(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function re(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ie=/\/+/g;function ae(e,t){return typeof e==`object`&&e&&e.key!=null?re(``+e.key):t.toString(36)}function oe(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function se(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,se(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ae(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(ie,`$&/`)+`/`),se(o,r,i,``,function(e){return e})):o!=null&&(T(o)&&(o=ne(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ie,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,T());else{var t=n(l);t!==null&&ae(x,t.startTime-e)}}var S=!1,C=-1,w=5,ee=-1;function te(){return g?!0:!(e.unstable_now()-eet&&te());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ae(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?T():S=!1}}}var T;if(typeof y==`function`)T=function(){y(ne)};else if(typeof MessageChannel<`u`){var re=new MessageChannel,ie=re.port2;re.port1.onmessage=ne,T=function(){ie.postMessage(null)}}else T=function(){_(ne,0)};function ae(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,ae(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,T()))),r},e.unstable_shouldYield=te,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1fe||(e.current=de[fe],de[fe]=null,fe--)}function O(e,t){fe++,de[fe]=e.current,e.current=t}var he=pe(null),ge=pe(null),_e=pe(null),ve=pe(null);function ye(e,t){switch(O(_e,t),O(ge,e),O(he,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Ud(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Ud(t),e=Wd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}me(he),O(he,e)}function be(){me(he),me(ge),me(_e)}function xe(e){e.memoizedState!==null&&O(ve,e);var t=he.current,n=Wd(t,e.type);t!==n&&(O(ge,e),O(he,n))}function Se(e){ge.current===e&&(me(he),me(ge)),ve.current===e&&(me(ve),$f._currentValue=ue)}var Ce,we;function Te(e){if(Ce===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);Ce=t&&t[1]||``,we=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ee=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Te(n):``}function Oe(e,t){switch(e.tag){case 26:case 27:case 5:return Te(e.type);case 16:return Te(`Lazy`);case 13:return e.child!==t&&t!==null?Te(`Suspense Fallback`):Te(`Suspense`);case 19:return Te(`SuspenseList`);case 0:case 15:return De(e.type,!1);case 11:return De(e.type.render,!1);case 1:return De(e.type,!0);case 31:return Te(`Activity`);default:return``}}function ke(e){try{var t=``,n=null;do t+=Oe(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var Ae=Object.prototype.hasOwnProperty,je=t.unstable_scheduleCallback,Me=t.unstable_cancelCallback,Ne=t.unstable_shouldYield,Pe=t.unstable_requestPaint,Fe=t.unstable_now,Ie=t.unstable_getCurrentPriorityLevel,Le=t.unstable_ImmediatePriority,Re=t.unstable_UserBlockingPriority,ze=t.unstable_NormalPriority,Be=t.unstable_LowPriority,Ve=t.unstable_IdlePriority,He=t.log,Ue=t.unstable_setDisableYieldValue,We=null,Ge=null;function Ke(e){if(typeof He==`function`&&Ue(e),Ge&&typeof Ge.setStrictMode==`function`)try{Ge.setStrictMode(We,e)}catch{}}var qe=Math.clz32?Math.clz32:Xe,Je=Math.log,Ye=Math.LN2;function Xe(e){return e>>>=0,e===0?32:31-(Je(e)/Ye|0)|0}var Ze=256,Qe=262144,$e=4194304;function et(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function tt(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=et(n))):i=et(o):i=et(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=et(n))):i=et(o)):i=et(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function nt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function rt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function it(){var e=$e;return $e<<=1,!($e&62914560)&&($e=4194304),e}function at(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function ot(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function st(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),bn=!1;if(yn)try{var xn={};Object.defineProperty(xn,`passive`,{get:function(){bn=!0}}),window.addEventListener(`test`,xn,xn),window.removeEventListener(`test`,xn,xn)}catch{bn=!1}var Sn=null,Cn=null,wn=null;function Tn(){if(wn)return wn;var e,t=Cn,n=t.length,r,i=`value`in Sn?Sn.value:Sn.textContent,a=i.length;for(e=0;e=rr),or=` `,sr=!1;function cr(e,t){switch(e){case`keyup`:return tr.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function lr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var ur=!1;function dr(e,t){switch(e){case`compositionend`:return lr(t);case`keypress`:return t.which===32?(sr=!0,or):null;case`textInput`:return e=t.data,e===or&&sr?null:e;default:return null}}function fr(e,t){if(ur)return e===`compositionend`||!nr&&cr(e,t)?(e=Tn(),wn=Cn=Sn=null,ur=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Pr(n)}}function Ir(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ir(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Lr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Kt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Kt(e.document)}return t}function Rr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var zr=yn&&`documentMode`in document&&11>=document.documentMode,Br=null,Vr=null,Hr=null,Ur=!1;function Wr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ur||Br==null||Br!==Kt(r)||(r=Br,`selectionStart`in r&&Rr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Hr&&Nr(Hr,r)||(Hr=r,r=Od(Vr,`onSelect`),0>=o,i-=o,Ni=1<<32-qe(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),N&&Fi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),N&&Fi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return N&&Fi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),N&&Fi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===T&&Pa(l)===r.type){n(e,r.sibling),c=a(r,o.props),Va(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=xi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=bi(o.type,o.key,o.props,null,e.mode,c),Va(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=wi(o,e.mode,c),c.return=e,e=c}return s(e);case T:return o=Pa(o),b(e,r,o,c)}if(le(o))return h(e,r,o,c);if(oe(o)){if(l=oe(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ba(o),c);if(o.$$typeof===C)return b(e,r,ca(e,o),c);Ha(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=Si(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{za=0;var i=b(e,t,n,r);return Ra=null,i}catch(t){if(t===Oa||t===Aa)throw t;var a=k(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Wa=Ua(!0),Ga=Ua(!1),Ka=!1;function qa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ja(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ya(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Xa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,B&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=hi(e),mi(e,null,n),t}return di(e,r,t,n),hi(e)}function Za(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,lt(e,n)}}function Qa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var $a=!1;function eo(){if($a){var e=ya;if(e!==null)throw e}}function to(e,t,n,r){$a=!1;var i=e.updateQueue;Ka=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(U&f)===f:(r&f)===f){f!==0&&f===va&&($a=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Ka=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Zl|=o,e.lanes=o,e.memoizedState=d}}function no(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function ro(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=E.T,s={};E.T=s,Hs(e,!1,t,n);try{var c=i(),l=E.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Vs(e,t,Sa(c,r),vu(e)):Vs(e,t,r,vu(e))}catch(n){Vs(e,t,{then:function(){},status:`rejected`,reason:n},vu())}finally{D.p=a,o!==null&&s.types!==null&&(o.types=s.types),E.T=o}}function js(){}function Ms(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Ns(e).queue;As(e,a,t,ue,n===null?js:function(){return Ps(e),n(r)})}function Ns(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Uo,lastRenderedState:ue},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Uo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ps(e){var t=Ns(e);t.next===null&&(t=e.alternate.memoizedState),Vs(e,t.next.queue,{},vu())}function Fs(){return sa($f)}function Is(){return Ro().memoizedState}function Ls(){return Ro().memoizedState}function Rs(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=vu();e=Ya(n);var r=Xa(t,e,n);r!==null&&(bu(r,t,n),Za(r,t,n)),t={cache:ma()},e.payload=t;return}t=t.return}}function zs(e,t,n){var r=vu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Us(e)?Ws(t,n):(n=fi(e,t,n,r),n!==null&&(bu(n,e,r),Gs(n,t,r)))}function Bs(e,t,n){Vs(e,t,n,vu())}function Vs(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Us(e))Ws(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Mr(s,o))return di(e,t,i,0),V===null&&ui(),!1}catch{}if(n=fi(e,t,i,r),n!==null)return bu(n,e,r),Gs(n,t,r),!0}return!1}function Hs(e,t,n,r){if(r={lane:2,revertLane:pd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Us(e)){if(t)throw Error(i(479))}else t=fi(e,n,r,2),t!==null&&bu(t,e,2)}function Us(e){var t=e.alternate;return e===P||t!==null&&t===P}function Ws(e,t){So=xo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Gs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,lt(e,n)}}var Ks={readContext:sa,use:Vo,useCallback:Oo,useContext:Oo,useEffect:Oo,useImperativeHandle:Oo,useLayoutEffect:Oo,useInsertionEffect:Oo,useMemo:Oo,useReducer:Oo,useRef:Oo,useState:Oo,useDebugValue:Oo,useDeferredValue:Oo,useTransition:Oo,useSyncExternalStore:Oo,useId:Oo,useHostTransitionStatus:Oo,useFormState:Oo,useActionState:Oo,useOptimistic:Oo,useMemoCache:Oo,useCacheRefresh:Oo};Ks.useEffectEvent=Oo;var qs={readContext:sa,use:Vo,useCallback:function(e,t){return Lo().memoizedState=[e,t===void 0?null:t],e},useContext:sa,useEffect:_s,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),hs(4194308,4,Cs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return hs(4194308,4,e,t)},useInsertionEffect:function(e,t){hs(4,2,e,t)},useMemo:function(e,t){var n=Lo();t=t===void 0?null:t;var r=e();if(Co){Ke(!0);try{e()}finally{Ke(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=Lo();if(n!==void 0){var i=n(t);if(Co){Ke(!0);try{n(t)}finally{Ke(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=zs.bind(null,P,e),[r.memoizedState,e]},useRef:function(e){var t=Lo();return e={current:e},t.memoizedState=e},useState:function(e){e=$o(e);var t=e.queue,n=Bs.bind(null,P,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:Ts,useDeferredValue:function(e,t){return Os(Lo(),e,t)},useTransition:function(){var e=$o(!1);return e=As.bind(null,P,e.queue,!0,!1),Lo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=P,a=Lo();if(N){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),V===null)throw Error(i(349));U&127||Jo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,_s(Xo.bind(null,r,o,e),[e]),r.flags|=2048,ps(9,{destroy:void 0},Yo.bind(null,r,o,n,t),null),n},useId:function(){var e=Lo(),t=V.identifierPrefix;if(N){var n=Pi,r=Ni;n=(r&~(1<<32-qe(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=wo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[gt]=t,o[_t]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Id(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&zc(t)}}return R(t),Bc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&zc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=_e.current,qi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Bi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[gt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Pd(e.nodeValue,n)),e||Wi(t,!0)}else e=Hd(e).createTextNode(r),e[gt]=t,t.stateNode=e}return R(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=qi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[gt]=t}else Ji(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;R(t),e=!1}else n=Yi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(go(t),t):(go(t),null);if(t.flags&128)throw Error(i(558))}return R(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=qi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[gt]=t}else Ji(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;R(t),a=!1}else a=Yi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(go(t),t):(go(t),null)}return go(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Hc(t,t.updateQueue),R(t),null);case 4:return be(),e===null&&wd(t.stateNode.containerInfo),R(t),null;case 10:return ta(t.type),R(t),null;case 19:if(me(_o),r=t.memoizedState,r===null)return R(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Uc(r,!1);else{if(Xl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=vo(e),o!==null){for(t.flags|=128,Uc(r,!1),e=o.updateQueue,t.updateQueue=e,Hc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)yi(n,e),n=n.sibling;return O(_o,_o.current&1|2),N&&Fi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Fe()>su&&(t.flags|=128,a=!0,Uc(r,!1),t.lanes=4194304)}else{if(!a)if(e=vo(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Hc(t,e),Uc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!N)return R(t),null}else 2*Fe()-r.renderingStartTime>su&&n!==536870912&&(t.flags|=128,a=!0,Uc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(R(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Fe(),e.sibling=null,n=_o.current,O(_o,a?n&1|2:n&1),N&&Fi(t,r.treeForkCount),e);case 22:case 23:return go(t),co(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(R(t),t.subtreeFlags&6&&(t.flags|=8192)):R(t),n=t.updateQueue,n!==null&&Hc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&me(wa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ta(pa),R(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function z(e,t){switch(Ri(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ta(pa),be(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Se(t),null;case 31:if(t.memoizedState!==null){if(go(t),t.alternate===null)throw Error(i(340));Ji()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(go(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Ji()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return me(_o),null;case 4:return be(),null;case 10:return ta(t.type),null;case 22:case 23:return go(t),co(),e!==null&&me(wa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ta(pa),null;case 25:return null;default:return null}}function Gc(e,t){switch(Ri(t),t.tag){case 3:ta(pa),be();break;case 26:case 27:case 5:Se(t);break;case 4:be();break;case 31:t.memoizedState!==null&&go(t);break;case 13:go(t);break;case 19:me(_o);break;case 10:ta(t.type);break;case 22:case 23:go(t),co(),e!==null&&me(wa);break;case 24:ta(pa)}}function Kc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Y(t,t.return,e)}}function qc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Y(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Y(t,t.return,e)}}function Jc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{ro(t,n)}catch(t){Y(e,e.return,t)}}}function Yc(e,t,n){n.props=ec(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Y(e,t,n)}}function Xc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Y(e,t,n)}}function Zc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Y(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Y(e,t,n)}else n.current=null}function Qc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Y(e,e.return,t)}}function $c(e,t,n){try{var r=e.stateNode;Ld(r,e.type,n,t),r[_t]=t}catch(t){Y(e,e.return,t)}}function el(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&$d(e.type)||e.tag===4}function tl(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||el(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&$d(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function nl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=un));else if(r!==4&&(r===27&&$d(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(nl(e,t,n),e=e.sibling;e!==null;)nl(e,t,n),e=e.sibling}function rl(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&$d(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(rl(e,t,n),e=e.sibling;e!==null;)rl(e,t,n),e=e.sibling}function il(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Id(t,r,n),t[gt]=e,t[_t]=n}catch(t){Y(e,e.return,t)}}var al=!1,ol=!1,sl=!1,cl=typeof WeakSet==`function`?WeakSet:Set,ll=null;function ul(e,t){if(e=e.containerInfo,Bd=sp,e=Lr(e),Rr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Vd={focusedElem:e,selectionRange:n},sp=!1,ll=t;ll!==null;)if(t=ll,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,ll=e;else for(;ll!==null;){switch(t=ll,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Id(o,r,n),o[gt]=e,kt(o),r=o;break a;case`link`:var s=Uf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Fr(s,h),v=Fr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,E.T=null,n=mu,mu=null;var o=uu,s=fu;if(lu=0,du=uu=null,fu=0,B&6)throw Error(i(331));var c=B;if(B|=4,Bl(o.current),Ml(o,o.current,s,n),B=c,od(0,!1),Ge&&typeof Ge.onPostCommitFiberRoot==`function`)try{Ge.onPostCommitFiberRoot(We,o)}catch{}return!0}finally{D.p=a,E.T=r,Gu(e,t)}}function Ku(e,t,n){t=Ei(n,t),t=oc(e.stateNode,t,2),e=Xa(e,t,2),e!==null&&(ot(e,2),ad(e))}function Y(e,t,n){if(e.tag===3)Ku(e,e,n);else for(;t!==null;){if(t.tag===3){Ku(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(G===null||!G.has(r))){e=Ei(n,e),n=sc(2),r=Xa(t,n,2),r!==null&&(cc(n,r,t,e),ot(r,2),ad(r));break}}t=t.return}}function qu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Wl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Jl=!0,i.add(n),e=Ju.bind(null,e,t,n),t.then(e,e))}function Ju(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,V===e&&(U&n)===n&&(Xl===4||Xl===3&&(U&62914560)===U&&300>Fe()-au?!(B&2)&&Du(e,0):$l|=n,tu===U&&(tu=0)),ad(e)}function Yu(e,t){t===0&&(t=it()),e=pi(e,t),e!==null&&(ot(e,t),ad(e))}function Xu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Yu(e,n)}function Zu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Yu(e,n)}function Qu(e,t){return je(e,t)}var $u=null,ed=null,td=!1,nd=!1,rd=!1,id=0;function ad(e){e!==ed&&e.next===null&&(ed===null?$u=ed=e:ed=ed.next=e),nd=!0,td||(td=!0,fd())}function od(e,t){if(!rd&&nd){rd=!0;do for(var n=!1,r=$u;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-qe(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,dd(r,a))}else a=U,a=tt(r,r===V?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||nt(r,a)||(n=!0,dd(r,a));r=r.next}while(n);rd=!1}}function sd(){cd()}function cd(){nd=td=!1;var e=0;id!==0&&qd()&&(e=id);for(var t=Fe(),n=null,r=$u;r!==null;){var i=r.next,a=ld(r,t);a===0?(r.next=null,n===null?$u=i:n.next=i,i===null&&(ed=n)):(n=r,(e!==0||a&3)&&(nd=!0)),r=i}lu!==0&&lu!==5||od(e,!1),id!==0&&(id=0)}function ld(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Rd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Cf(e,t,n){var r=Sf;if(r&&typeof t==`string`&&t){var i=Jt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),_f.has(i)||(_f.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Id(t,`link`,e),kt(t),r.head.appendChild(t)))}}function wf(e){yf.D(e),Cf(`dns-prefetch`,e,null)}function Tf(e,t){yf.C(e,t),Cf(`preconnect`,e,t)}function Ef(e,t,n){yf.L(e,t,n);var r=Sf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Jt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Jt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Jt(n.imageSizes)+`"]`)):i+=`[href="`+Jt(e)+`"]`;var a=i;switch(t){case`style`:a=Mf(e);break;case`script`:a=If(e)}gf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),gf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Nf(a))||t===`script`&&r.querySelector(Lf(a))||(t=r.createElement(`link`),Id(t,`link`,e),kt(t),r.head.appendChild(t)))}}function Df(e,t){yf.m(e,t);var n=Sf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Jt(r)+`"][href="`+Jt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=If(e)}if(!gf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),gf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Lf(a)))return}r=n.createElement(`link`),Id(r,`link`,e),kt(r),n.head.appendChild(r)}}}function Of(e,t,n){yf.S(e,t,n);var r=Sf;if(r&&e){var i=Ot(r).hoistableStyles,a=Mf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Nf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=gf.get(a))&&Bf(e,n);var c=o=r.createElement(`link`);kt(c),Id(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,zf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function kf(e,t){yf.X(e,t);var n=Sf;if(n&&e){var r=Ot(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=h({src:e,async:!0},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),kt(a),Id(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Af(e,t){yf.M(e,t);var n=Sf;if(n&&e){var r=Ot(n).hoistableScripts,i=If(e),a=r.get(i);a||(a=n.querySelector(Lf(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=gf.get(i))&&Vf(e,t),a=n.createElement(`script`),kt(a),Id(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function jf(e,t,n,r){var a=(a=_e.current)?vf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Mf(n.href),n=Ot(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Mf(n.href);var o=Ot(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Nf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),gf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},gf.set(e,n),o||Ff(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=If(n),n=Ot(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Mf(e){return`href="`+Jt(e)+`"`}function Nf(e){return`link[rel="stylesheet"][`+e+`]`}function Pf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Ff(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Id(t,`link`,n),kt(t),e.head.appendChild(t))}function If(e){return`[src="`+Jt(e)+`"]`}function Lf(e){return`script[async]`+e}function Rf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Jt(n.href)+`"]`);if(r)return t.instance=r,kt(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),kt(r),Id(r,`style`,a),zf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Mf(n.href);var o=e.querySelector(Nf(a));if(o)return t.state.loading|=4,t.instance=o,kt(o),o;r=Pf(n),(a=gf.get(a))&&Bf(r,a),o=(e.ownerDocument||e).createElement(`link`),kt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Id(o,`link`,r),t.state.loading|=4,zf(o,n.precedence,e),t.instance=o;case`script`:return o=If(n.src),(a=e.querySelector(Lf(o)))?(t.instance=a,kt(a),a):(r=n,(a=gf.get(o))&&(r=h({},n),Vf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),kt(a),Id(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,zf(r,n.precedence,e));return t.instance}function zf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Gf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Kf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function qf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Mf(r.href),a=t.querySelector(Nf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Q.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,kt(a);return}a=t.ownerDocument||t,r=Pf(r),(i=gf.get(i))&&Bf(r,i),a=a.createElement(`link`),kt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Id(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Q.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Jf=0;function Yf(e,t){return e.stylesheets&&e.count===0&&Zf(e,e.stylesheets),0Jf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Q(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Zf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Xf=null;function Zf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Xf=new Map,t.forEach(Qf,e),Xf=null,Q.call(e))}function Qf(e,t){if(!(t.state.loading&4)){var n=Xf.get(e);if(n)var r=n.get(null);else{n=new Map,Xf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=o(((e,t)=>{var n=typeof Element<`u`,r=typeof Map==`function`,i=typeof Set==`function`,a=typeof ArrayBuffer==`function`&&!!ArrayBuffer.isView;function o(e,t){if(e===t)return!0;if(e&&t&&typeof e==`object`&&typeof t==`object`){if(e.constructor!==t.constructor)return!1;var s,c,l;if(Array.isArray(e)){if(s=e.length,s!=t.length)return!1;for(c=s;c--!==0;)if(!o(e[c],t[c]))return!1;return!0}var u;if(r&&e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(u=e.entries();!(c=u.next()).done;)if(!t.has(c.value[0]))return!1;for(u=e.entries();!(c=u.next()).done;)if(!o(c.value[1],t.get(c.value[0])))return!1;return!0}if(i&&e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(u=e.entries();!(c=u.next()).done;)if(!t.has(c.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(e)&&ArrayBuffer.isView(t)){if(s=e.length,s!=t.length)return!1;for(c=s;c--!==0;)if(e[c]!==t[c])return!1;return!0}if(e.constructor===RegExp)return e.source===t.source&&e.flags===t.flags;if(e.valueOf!==Object.prototype.valueOf&&typeof e.valueOf==`function`&&typeof t.valueOf==`function`)return e.valueOf()===t.valueOf();if(e.toString!==Object.prototype.toString&&typeof e.toString==`function`&&typeof t.toString==`function`)return e.toString()===t.toString();if(l=Object.keys(e),s=l.length,s!==Object.keys(t).length)return!1;for(c=s;c--!==0;)if(!Object.prototype.hasOwnProperty.call(t,l[c]))return!1;if(n&&e instanceof Element)return!1;for(c=s;c--!==0;)if(!((l[c]===`_owner`||l[c]===`__v`||l[c]===`__o`)&&e.$$typeof)&&!o(e[l[c]],t[l[c]]))return!1;return!0}return e!==e&&t!==t}t.exports=function(e,t){try{return o(e,t)}catch(e){if((e.message||``).match(/stack|recursion/i))return console.warn(`react-fast-compare cannot handle circular refs`),!1;throw e}}})),v=o(((e,t)=>{t.exports=function(e,t,n,r,i,a,o,s){if(!e){var c;if(t===void 0)c=Error(`Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.`);else{var l=[n,r,i,a,o,s],u=0;c=Error(t.replace(/%s/g,function(){return l[u++]})),c.name=`Invariant Violation`}throw c.framesToPop=1,c}}})),y=o(((e,t)=>{t.exports=function(e,t,n,r){var i=n?n.call(r,e,t):void 0;if(i!==void 0)return!!i;if(e===t)return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;var a=Object.keys(e),o=Object.keys(t);if(a.length!==o.length)return!1;for(var s=Object.prototype.hasOwnProperty.bind(t),c=0;c(e.BASE=`base`,e.BODY=`body`,e.HEAD=`head`,e.HTML=`html`,e.LINK=`link`,e.META=`meta`,e.NOSCRIPT=`noscript`,e.SCRIPT=`script`,e.STYLE=`style`,e.TITLE=`title`,e.FRAGMENT=`Symbol(react.fragment)`,e))(ee||{}),te={link:{rel:[`amphtml`,`canonical`,`alternate`]},script:{type:[`application/ld+json`]},meta:{charset:``,name:[`generator`,`robots`,`description`],property:[`og:type`,`og:title`,`og:url`,`og:image`,`og:image:alt`,`og:description`,`twitter:url`,`twitter:title`,`twitter:description`,`twitter:image`,`twitter:image:alt`,`twitter:card`,`twitter:site`]}},ne=Object.values(ee),T={accesskey:`accessKey`,charset:`charSet`,class:`className`,contenteditable:`contentEditable`,contextmenu:`contextMenu`,"http-equiv":`httpEquiv`,itemprop:`itemProp`,tabindex:`tabIndex`},re=Object.entries(T).reduce((e,[t,n])=>(e[n]=t,e),{}),ie=`data-rh`,ae={DEFAULT_TITLE:`defaultTitle`,DEFER:`defer`,ENCODE_SPECIAL_CHARACTERS:`encodeSpecialCharacters`,ON_CHANGE_CLIENT_STATE:`onChangeClientState`,TITLE_TEMPLATE:`titleTemplate`,PRIORITIZE_SEO_TAGS:`prioritizeSeoTags`},oe=(e,t)=>{for(let n=e.length-1;n>=0;--n){let r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},se=e=>{let t=oe(e,`title`),n=oe(e,ae.TITLE_TEMPLATE);if(Array.isArray(t)&&(t=t.join(``)),n&&t)return n.replace(/%s/g,()=>t);let r=oe(e,ae.DEFAULT_TITLE);return t||r||void 0},ce=e=>oe(e,ae.ON_CHANGE_CLIENT_STATE)||(()=>{}),le=(e,t)=>t.filter(t=>t[e]!==void 0).map(t=>t[e]).reduce((e,t)=>({...e,...t}),{}),E=(e,t)=>t.filter(e=>e.base!==void 0).map(e=>e.base).reverse().reduce((t,n)=>{if(!t.length){let r=Object.keys(n);for(let i=0;iconsole&&typeof console.warn==`function`&&console.warn(e),ue=(e,t,n)=>{let r={};return n.filter(t=>Array.isArray(t[e])?!0:(t[e]!==void 0&&D(`Helmet: ${e} should be of type "Array". Instead found type "${typeof t[e]}"`),!1)).map(t=>t[e]).reverse().reduce((e,n)=>{let i={};n.filter(e=>{let n,a=Object.keys(e);for(let r=0;re.push(t));let a=Object.keys(i);for(let e=0;e{if(Array.isArray(e)&&e.length){for(let n=0;n({baseTag:E([`href`],e),bodyAttributes:le(`bodyAttributes`,e),defer:oe(e,ae.DEFER),encode:oe(e,ae.ENCODE_SPECIAL_CHARACTERS),htmlAttributes:le(`htmlAttributes`,e),linkTags:ue(`link`,[`rel`,`href`],e),metaTags:ue(`meta`,[`name`,`charset`,`http-equiv`,`property`,`itemprop`],e),noscriptTags:ue(`noscript`,[`innerHTML`],e),onChangeClientState:ce(e),scriptTags:ue(`script`,[`src`,`innerHTML`],e),styleTags:ue(`style`,[`cssText`],e),title:se(e),titleAttributes:le(`titleAttributes`,e),prioritizeSeoTags:de(e,ae.PRIORITIZE_SEO_TAGS)}),pe=e=>Array.isArray(e)?e.join(``):e,me=(e,t)=>{let n=Object.keys(e);for(let r=0;rArray.isArray(e)?e.reduce((e,n)=>(me(n,t)?e.priority.push(n):e.default.push(n),e),{priority:[],default:[]}):{default:e,priority:[]},he=(e,t)=>({...e,[t]:void 0}),ge=[`noscript`,`script`,`style`],_e=(e,t=!0)=>t===!1?String(e):String(e).replace(/&/g,`&`).replace(//g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),ve=e=>Object.keys(e).reduce((t,n)=>{let r=e[n]===void 0?`${n}`:`${n}="${e[n]}"`;return t?`${t} ${r}`:r},``),ye=(e,t,n,r)=>{let i=ve(n),a=pe(t);return i?`<${e} ${ie}="true" ${i}>${_e(a,r)}`:`<${e} ${ie}="true">${_e(a,r)}`},be=(e,t,n=!0)=>t.reduce((t,r)=>{let i=r,a=Object.keys(i).filter(e=>!(e===`innerHTML`||e===`cssText`)).reduce((e,t)=>{let r=i[t]===void 0?t:`${t}="${_e(i[t],n)}"`;return e?`${e} ${r}`:r},``),o=i.innerHTML||i.cssText||``;return`${t}<${e} ${ie}="true" ${a}${ge.indexOf(e)===-1?`/>`:`>${o}`}`},``),xe=(e,t={})=>Object.keys(e).reduce((t,n)=>{let r=T[n];return t[r||n]=e[n],t},t),Se=(e,t,n)=>{let r=xe(n,{key:t,[ie]:!0});return[x.createElement(`title`,r,t)]},Ce=(e,t)=>t.map((t,n)=>{let r={key:n,[ie]:!0};return Object.keys(t).forEach(e=>{let n=T[e]||e;n===`innerHTML`||n===`cssText`?r.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:r[n]=t[e]}),x.createElement(e,r)}),we=(e,t,n=!0)=>{switch(e){case`title`:return{toComponent:()=>Se(e,t.title,t.titleAttributes),toString:()=>ye(e,t.title,t.titleAttributes,n)};case`bodyAttributes`:case`htmlAttributes`:return{toComponent:()=>xe(t),toString:()=>ve(t)};default:return{toComponent:()=>Ce(e,t),toString:()=>be(e,t,n)}}},Te=({metaTags:e,linkTags:t,scriptTags:n,encode:r})=>{let i=O(e,te.meta),a=O(t,te.link),o=O(n,te.script);return{priorityMethods:{toComponent:()=>[...Ce(`meta`,i.priority),...Ce(`link`,a.priority),...Ce(`script`,o.priority)],toString:()=>`${we(`meta`,i.priority,r)} ${we(`link`,a.priority,r)} ${we(`script`,o.priority,r)}`},metaTags:i.default,linkTags:a.default,scriptTags:o.default}},Ee=e=>{let{baseTag:t,bodyAttributes:n,encode:r=!0,htmlAttributes:i,noscriptTags:a,styleTags:o,title:s=``,titleAttributes:c,prioritizeSeoTags:l}=e,{linkTags:u,metaTags:d,scriptTags:f}=e,p={toComponent:()=>[],toString:()=>``};return l&&({priorityMethods:p,linkTags:u,metaTags:d,scriptTags:f}=Te(e)),{priority:p,base:we(`base`,t,r),bodyAttributes:we(`bodyAttributes`,n,r),htmlAttributes:we(`htmlAttributes`,i,r),link:we(`link`,u,r),meta:we(`meta`,d,r),noscript:we(`noscript`,a,r),script:we(`script`,f,r),style:we(`style`,o,r),title:we(`title`,{title:s,titleAttributes:c},r)}},De=[],Oe=!!(typeof window<`u`&&window.document&&window.document.createElement),ke=class{instances=[];canUseDOM=Oe;context;value={setHelmet:e=>{this.context.helmet=e},helmetInstances:{get:()=>this.canUseDOM?De:this.instances,add:e=>{(this.canUseDOM?De:this.instances).push(e)},remove:e=>{let t=(this.canUseDOM?De:this.instances).indexOf(e);(this.canUseDOM?De:this.instances).splice(t,1)}}};constructor(e,t){this.context=e,this.canUseDOM=t||!1,t||(e.helmet=Ee({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:``,titleAttributes:{}}))}},Ae=parseInt(`19.2.0`.split(`.`)[0],10)>=19,je=x.createContext({}),Me=class e extends x.Component{static canUseDOM=Oe;helmetData;constructor(t){super(t),Ae?this.helmetData=null:this.helmetData=new ke(this.props.context||{},e.canUseDOM)}render(){return Ae?x.createElement(x.Fragment,null,this.props.children):x.createElement(je.Provider,{value:this.helmetData.value},this.props.children)}},Ne=(e,t)=>{let n=document.head||document.querySelector(`head`),r=n.querySelectorAll(`${e}[${ie}]`),i=[].slice.call(r),a=[],o;return t&&t.length&&t.forEach(t=>{let n=document.createElement(e);for(let e in t)if(Object.prototype.hasOwnProperty.call(t,e))if(e===`innerHTML`)n.innerHTML=t.innerHTML;else if(e===`cssText`){let e=t.cssText;n.appendChild(document.createTextNode(e))}else{let r=e,i=t[r]===void 0?``:t[r];n.setAttribute(e,i)}n.setAttribute(ie,`true`),i.some((e,t)=>(o=t,n.isEqualNode(e)))?i.splice(o,1):a.push(n)}),i.forEach(e=>e.parentNode?.removeChild(e)),a.forEach(e=>n.appendChild(e)),{oldTags:i,newTags:a}},Pe=(e,t)=>{let n=document.getElementsByTagName(e)[0];if(!n)return;let r=n.getAttribute(ie),i=r?r.split(`,`):[],a=[...i],o=Object.keys(t);for(let e of o){let r=t[e]||``;n.getAttribute(e)!==r&&n.setAttribute(e,r),i.indexOf(e)===-1&&i.push(e);let o=a.indexOf(e);o!==-1&&a.splice(o,1)}for(let e=a.length-1;e>=0;--e)n.removeAttribute(a[e]);i.length===a.length?n.removeAttribute(ie):n.getAttribute(ie)!==o.join(`,`)&&n.setAttribute(ie,o.join(`,`))},Fe=(e,t)=>{e!==void 0&&document.title!==e&&(document.title=pe(e)),Pe(`title`,t)},Ie=(e,t)=>{let{baseTag:n,bodyAttributes:r,htmlAttributes:i,linkTags:a,metaTags:o,noscriptTags:s,onChangeClientState:c,scriptTags:l,styleTags:u,title:d,titleAttributes:f}=e;Pe(`body`,r),Pe(`html`,i),Fe(d,f);let p={baseTag:Ne(`base`,n),linkTags:Ne(`link`,a),metaTags:Ne(`meta`,o),noscriptTags:Ne(`noscript`,s),scriptTags:Ne(`script`,l),styleTags:Ne(`style`,u)},m={},h={};Object.keys(p).forEach(e=>{let{newTags:t,oldTags:n}=p[e];t.length&&(m[e]=t),n.length&&(h[e]=p[e].oldTags)}),t&&t(),c(e,m,h)},Le=null,Re=e=>{Le&&cancelAnimationFrame(Le),e.defer?Le=requestAnimationFrame(()=>{Ie(e,()=>{Le=null})}):(Ie(e),Le=null)},ze=class extends x.Component{rendered=!1;shouldComponentUpdate(e){return!(0,w.default)(e,this.props)}componentDidUpdate(){this.emitChange()}componentWillUnmount(){let{helmetInstances:e}=this.props.context;e.remove(this),this.emitChange()}emitChange(){let{helmetInstances:e,setHelmet:t}=this.props.context,n=null,r=fe(e.get().map(e=>{let{context:t,...n}=e.props;return n}));Me.canUseDOM?Re(r):Ee&&(n=Ee(r)),t(n)}init(){if(this.rendered)return;this.rendered=!0;let{helmetInstances:e}=this.props.context;e.add(this),this.emitChange()}render(){return this.init(),null}},Be=[],Ve=e=>{let t={};for(let n of Object.keys(e))t[re[n]||n]=e[n];return t},He=e=>{let t={};for(let n of Object.keys(e)){let r=T[n];t[r||n]=e[n]}return t},Ue=(e,t)=>{if(!Oe)return;let n=document.getElementsByTagName(e)[0];if(!n)return;let r=`data-rh-managed`,i=n.getAttribute(r),a=i?i.split(`,`):[],o=Object.keys(t);for(let e of a)o.includes(e)||n.removeAttribute(e);for(let e of o){let r=t[e];r==null||r===!1?n.removeAttribute(e):r===!0?n.setAttribute(e,``):n.setAttribute(e,String(r))}o.length>0?n.setAttribute(r,o.join(`,`)):n.removeAttribute(r)},We=()=>{let e={},t={};for(let n of Be){let{htmlAttributes:r,bodyAttributes:i}=n.props;r&&Object.assign(e,Ve(r)),i&&Object.assign(t,Ve(i))}Ue(`html`,e),Ue(`body`,t)},Ge=class extends x.Component{componentDidMount(){Be.push(this),We()}componentDidUpdate(){We()}componentWillUnmount(){let e=Be.indexOf(this);e!==-1&&Be.splice(e,1),We()}resolveTitle(){let{title:e,titleTemplate:t,defaultTitle:n}=this.props;return e&&t?t.replace(/%s/g,()=>Array.isArray(e)?e.join(``):e):e||n||void 0}renderTitle(){let e=this.resolveTitle();if(e===void 0)return null;let t=this.props.titleAttributes||{};return x.createElement(`title`,He(t),e)}renderBase(){let{base:e}=this.props;return e?x.createElement(`base`,He(e)):null}renderMeta(){let{meta:e}=this.props;return!e||!Array.isArray(e)?null:e.map((e,t)=>x.createElement(`meta`,{key:t,...He(e)}))}renderLink(){let{link:e}=this.props;return!e||!Array.isArray(e)?null:e.map((e,t)=>x.createElement(`link`,{key:t,...He(e)}))}renderScript(){let{script:e}=this.props;return!e||!Array.isArray(e)?null:e.map((e,t)=>{let{innerHTML:n,...r}=e,i=He(r);return n&&(i.dangerouslySetInnerHTML={__html:n}),x.createElement(`script`,{key:t,...i})})}renderStyle(){let{style:e}=this.props;return!e||!Array.isArray(e)?null:e.map((e,t)=>{let{cssText:n,...r}=e,i=He(r);return n&&(i.dangerouslySetInnerHTML={__html:n}),x.createElement(`style`,{key:t,...i})})}renderNoscript(){let{noscript:e}=this.props;return!e||!Array.isArray(e)?null:e.map((e,t)=>{let{innerHTML:n,...r}=e,i=He(r);return n&&(i.dangerouslySetInnerHTML={__html:n}),x.createElement(`noscript`,{key:t,...i})})}render(){return x.createElement(x.Fragment,null,this.renderTitle(),this.renderBase(),this.renderMeta(),this.renderLink(),this.renderScript(),this.renderStyle(),this.renderNoscript())}},Ke=class extends x.Component{static defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1};shouldComponentUpdate(e){return!(0,S.default)(he(this.props,`helmetData`),he(e,`helmetData`))}mapNestedChildrenToProps(e,t){if(!t)return null;switch(e.type){case`script`:case`noscript`:return{innerHTML:t};case`style`:return{cssText:t};default:throw Error(`<${e.type} /> elements are self-closing and can not contain children. Refer to our API for more information.`)}}flattenArrayTypeChildren(e,t,n,r){return{...t,[e.type]:[...t[e.type]||[],{...n,...this.mapNestedChildrenToProps(e,r)}]}}mapObjectTypeChildren(e,t,n,r){switch(e.type){case`title`:return{...t,[e.type]:r,titleAttributes:{...n}};case`body`:return{...t,bodyAttributes:{...n}};case`html`:return{...t,htmlAttributes:{...n}};default:return{...t,[e.type]:{...n}}}}mapArrayTypeChildrenToProps(e,t){let n={...t};return Object.keys(e).forEach(t=>{n={...n,[t]:e[t]}}),n}warnOnInvalidChildren(e,t){return(0,C.default)(ne.some(t=>e.type===t),typeof e.type==`function`?`You may be attempting to nest components within each other, which is not allowed. Refer to our API for more information.`:`Only elements types ${ne.join(`, `)} are allowed. Helmet does not support rendering <${e.type}> elements. Refer to our API for more information.`),(0,C.default)(!t||typeof t==`string`||Array.isArray(t)&&!t.some(e=>typeof e!=`string`),`Helmet expects a string as a child of <${e.type}>. Did you forget to wrap your children in braces? ( <${e.type}>{\`\`} ) Refer to our API for more information.`),!0}mapChildrenToProps(e,t){let n={};return x.Children.forEach(e,e=>{if(!e||!e.props)return;let{children:r,...i}=e.props,a=Object.keys(i).reduce((e,t)=>(e[re[t]||t]=i[t],e),{}),{type:o}=e;switch(typeof o==`symbol`?o=o.toString():this.warnOnInvalidChildren(e,r),o){case`Symbol(react.fragment)`:t=this.mapChildrenToProps(r,t);break;case`link`:case`meta`:case`noscript`:case`script`:case`style`:n=this.flattenArrayTypeChildren(e,n,a,r);break;default:t=this.mapObjectTypeChildren(e,t,a,r);break}}),this.mapArrayTypeChildrenToProps(n,t)}render(){let{children:e,...t}=this.props,n={...t},{helmetData:r}=t;return e&&(n=this.mapChildrenToProps(e,n)),r&&!(r instanceof ke)&&(r=new ke(r.context,!0),delete n.helmetData),Ae?x.createElement(Ge,{...n}):r?x.createElement(ze,{...n,context:r.value}):x.createElement(je.Consumer,null,e=>x.createElement(ze,{...n,context:e}))}},qe=`modulepreload`,Je=function(e){return`/_frontend/`+e},Ye={};const Xe=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=Je(t,n),t in Ye)return;Ye[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:qe,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};var Ze=`popstate`;function Qe(e){return typeof e==`object`&&!!e&&`pathname`in e&&`search`in e&&`hash`in e&&`state`in e&&`key`in e}function $e(e={}){function t(e,t){let n=t.state?.masked,{pathname:r,search:i,hash:a}=n||e.location;return at(``,{pathname:r,search:i,hash:a},t.state&&t.state.usr||null,t.state&&t.state.key||`default`,n?{pathname:e.location.pathname,search:e.location.search,hash:e.location.hash}:void 0)}function n(e,t){return typeof t==`string`?t:ot(t)}return ct(t,n,null,e)}function et(e={}){function t(e,t){let{pathname:n=`/`,search:r=``,hash:i=``}=st(e.location.hash.substring(1));return!n.startsWith(`/`)&&!n.startsWith(`.`)&&(n=`/`+n),at(``,{pathname:n,search:r,hash:i},t.state&&t.state.usr||null,t.state&&t.state.key||`default`)}function n(e,t){let n=e.document.querySelector(`base`),r=``;if(n&&n.getAttribute(`href`)){let t=e.location.href,n=t.indexOf(`#`);r=n===-1?t:t.slice(0,n)}return r+`#`+(typeof t==`string`?t:ot(t))}function r(e,t){nt(e.pathname.charAt(0)===`/`,`relative pathnames are not supported in hash history.push(${JSON.stringify(t)})`)}return ct(t,n,r,e)}function tt(e,t){if(e===!1||e==null)throw Error(t)}function nt(e,t){if(!e){typeof console<`u`&&console.warn(t);try{throw Error(t)}catch{}}}function rt(){return Math.random().toString(36).substring(2,10)}function it(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function at(e,t,n=null,r,i){return{pathname:typeof e==`string`?e:e.pathname,search:``,hash:``,...typeof t==`string`?st(t):t,state:n,key:t&&t.key||r||rt(),mask:i}}function ot({pathname:e=`/`,search:t=``,hash:n=``}){return t&&t!==`?`&&(e+=t.charAt(0)===`?`?t:`?`+t),n&&n!==`#`&&(e+=n.charAt(0)===`#`?n:`#`+n),e}function st(e){let t={};if(e){let n=e.indexOf(`#`);n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function ct(e,t,n,r={}){let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=`POP`,c=null,l=u();l??(l=0,o.replaceState({...o.state,idx:l},``));function u(){return(o.state||{idx:null}).idx}function d(){s=`POP`;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=`PUSH`;let r=Qe(e)?e:at(h.location,e,t);n&&n(r,e),l=u()+1;let d=it(r,l),f=h.createHref(r.mask||r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=`REPLACE`;let r=Qe(e)?e:at(h.location,e,t);n&&n(r,e),l=u();let i=it(r,l),d=h.createHref(r.mask||r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){return lt(e)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(Ze,d),c=e,()=>{i.removeEventListener(Ze,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}function lt(e,t=!1){let n=`http://localhost`;typeof window<`u`&&(n=window.location.origin===`null`?window.location.href:window.location.origin),tt(n,`No window.location.(origin|href) available to create URL`);let r=typeof e==`string`?e:ot(e);return r=r.replace(/ $/,`%20`),!t&&r.startsWith(`//`)&&(r=n+r),new URL(r,n)}function ut(e,t,n=`/`){return dt(e,t,n,!1)}function dt(e,t,n,r,i){let a=At((typeof t==`string`?st(t):t).pathname||`/`,n);if(a==null)return null;let o=i??pt(e),s=null,c=kt(a);for(let e=0;s==null&&e{let c={relativePath:s===void 0?e.path||``:s,caseSensitive:e.caseSensitive===!0,childrenIndex:a,route:e};if(c.relativePath.startsWith(`/`)){if(!c.relativePath.startsWith(r)&&o)return;tt(c.relativePath.startsWith(r),`Absolute route path "${c.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),c.relativePath=c.relativePath.slice(r.length)}let l=zt([r,c.relativePath]),u=n.concat(c);e.children&&e.children.length>0&&(tt(e.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${l}".`),mt(e.children,t,u,l,o)),!(e.path==null&&!e.index)&&t.push({path:l,score:wt(l,e.index),routesMeta:u})};return e.forEach((e,t)=>{if(e.path===``||!e.path?.includes(`?`))a(e,t);else for(let n of ht(e.path))a(e,t,!0,n)}),t}function ht(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=ht(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function gt(e){e.sort((e,t)=>e.score===t.score?Tt(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var _t=/^:[\w-]+$/,vt=3,yt=2,bt=1,xt=10,St=-2,Ct=e=>e===`*`;function wt(e,t){let n=e.split(`/`),r=n.length;return n.some(Ct)&&(r+=St),t&&(r+=yt),n.filter(e=>!Ct(e)).reduce((e,t)=>e+(_t.test(t)?vt:t===``?bt:xt),r)}function Tt(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function Et(e,t,n=!1){let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{if(t===`*`){let e=s[r]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let i=s[r];return n&&!i?e[t]=void 0:e[t]=(i||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function Ot(e,t=!1,n=!0){nt(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,`/*`)}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,`/*`)}".`);let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n,i,a)=>{if(r.push({paramName:t,isOptional:n!=null}),n){let t=a.charAt(i+e.length);return t&&t!==`/`?`/([^\\/]*)`:`(?:/([^\\/]*))?`}return`/([^\\/]+)`}).replace(/\/([\w-]+)\?(\/|$)/g,`(/$1)?$2`);return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function kt(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return nt(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function At(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}var jt=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Mt(e,t=`/`){let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?st(e):e,a;return n?(n=Rt(n),a=n.startsWith(`/`)?Nt(n.substring(1),`/`):Nt(n,t)):a=t,{pathname:a,search:Ht(r),hash:Ut(i)}}function Nt(e,t){let n=Bt(t).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function Pt(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Ft(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function It(e){let t=Ft(e);return t.map((e,n)=>n===t.length-1?e.pathname:e.pathnameBase)}function Lt(e,t,n,r=!1){let i;typeof e==`string`?i=st(e):(i={...e},tt(!i.pathname||!i.pathname.includes(`?`),Pt(`?`,`pathname`,`search`,i)),tt(!i.pathname||!i.pathname.includes(`#`),Pt(`#`,`pathname`,`hash`,i)),tt(!i.search||!i.search.includes(`#`),Pt(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=Mt(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var Rt=e=>e.replace(/\/\/+/g,`/`),zt=e=>Rt(e.join(`/`)),Bt=e=>e.replace(/\/+$/,``),Vt=e=>Bt(e).replace(/^\/*/,`/`),Ht=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Ut=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e,Wt=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||``,this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function Gt(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}function Kt(e){return zt(e.map(e=>e.route.path).filter(Boolean))||`/`}var qt=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function Jt(e,t){let n=e;if(typeof n!=`string`||!jt.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,i=!1;if(qt)try{let e=new URL(window.location.href),r=n.startsWith(`//`)?new URL(e.protocol+n):new URL(n),a=At(r.pathname,t);r.origin===e.origin&&a!=null?n=a+r.search+r.hash:i=!0}catch{nt(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:i,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join(`\0`);var Yt=x.createContext(null);Yt.displayName=`DataRouter`;var Xt=x.createContext(null);Xt.displayName=`DataRouterState`;var Zt=x.createContext(!1);function Qt(){return x.useContext(Zt)}var $t=x.createContext({isTransitioning:!1});$t.displayName=`ViewTransition`;var en=x.createContext(new Map);en.displayName=`Fetchers`;var tn=x.createContext(null);tn.displayName=`Await`;var nn=x.createContext(null);nn.displayName=`Navigation`;var rn=x.createContext(null);rn.displayName=`Location`;var an=x.createContext({outlet:null,matches:[],isDataRoute:!1});an.displayName=`Route`;var on=x.createContext(null);on.displayName=`RouteError`;var sn=`REACT_ROUTER_ERROR`,cn=`REDIRECT`,ln=`ROUTE_ERROR_RESPONSE`;function un(e){if(e.startsWith(`${sn}:${cn}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`&&typeof t.location==`string`&&typeof t.reloadDocument==`boolean`&&typeof t.replace==`boolean`)return t}catch{}}function dn(e){if(e.startsWith(`${sn}:${ln}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`)return new Wt(t.status,t.statusText,t.data)}catch{}}function fn(e,{relative:t}={}){tt(pn(),`useHref() may be used only in the context of a component.`);let{basename:n,navigator:r}=x.useContext(nn),{hash:i,pathname:a,search:o}=yn(e,{relative:t}),s=a;return n!==`/`&&(s=a===`/`?n:zt([n,a])),r.createHref({pathname:s,search:o,hash:i})}function pn(){return x.useContext(rn)!=null}function mn(){return tt(pn(),`useLocation() may be used only in the context of a component.`),x.useContext(rn).location}var hn=`You should call navigate() in a React.useEffect(), not when your component is first rendered.`;function gn(e){x.useContext(nn).static||x.useLayoutEffect(e)}function _n(){let{isDataRoute:e}=x.useContext(an);return e?Ln():vn()}function vn(){tt(pn(),`useNavigate() may be used only in the context of a component.`);let e=x.useContext(Yt),{basename:t,navigator:n}=x.useContext(nn),{matches:r}=x.useContext(an),{pathname:i}=mn(),a=JSON.stringify(It(r)),o=x.useRef(!1);return gn(()=>{o.current=!0}),x.useCallback((r,s={})=>{if(nt(o.current,hn),!o.current)return;if(typeof r==`number`){n.go(r);return}let c=Lt(r,JSON.parse(a),i,s.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:zt([t,c.pathname])),(s.replace?n.replace:n.push)(c,s.state,s)},[t,n,a,i,e])}x.createContext(null);function yn(e,{relative:t}={}){let{matches:n}=x.useContext(an),{pathname:r}=mn(),i=JSON.stringify(It(n));return x.useMemo(()=>Lt(e,JSON.parse(i),r,t===`path`),[e,i,r,t])}function bn(e,t,n){tt(pn(),`useRoutes() may be used only in the context of a component.`);let{navigator:r}=x.useContext(nn),{matches:i}=x.useContext(an),a=i[i.length-1],o=a?a.params:{},s=a?a.pathname:`/`,c=a?a.pathnameBase:`/`,l=a&&a.route;{let e=l&&l.path||``;zn(s,!l||e.endsWith(`*`)||e.endsWith(`*?`),`You rendered descendant (or called \`useRoutes()\`) at "${s}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let u=mn(),d;if(t){let e=typeof t==`string`?st(t):t;tt(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):ut(e,{pathname:p});nt(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),nt(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let h=Dn(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:zt([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:zt([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?x.createElement(rn.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},h):h}function xn(){let e=In(),t=Gt(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=x.createElement(x.Fragment,null,x.createElement(`p`,null,`💿 Hey developer 👋`),x.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,x.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,x.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),x.createElement(x.Fragment,null,x.createElement(`h2`,null,`Unexpected Application Error!`),x.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?x.createElement(`pre`,{style:i},n):null,o)}var Sn=x.createElement(xn,null),Cn=class extends x.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=dn(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:x.createElement(an.Provider,{value:this.props.routeContext},x.createElement(on.Provider,{value:e,children:this.props.component}));return this.context?x.createElement(Tn,{error:e},t):t}};Cn.contextType=Zt;var wn=new WeakMap;function Tn({children:e,error:t}){let{basename:n}=x.useContext(nn);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=un(t.digest);if(e){let r=wn.get(t);if(r)throw r;let i=Jt(e.location,n);if(qt&&!wn.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw wn.set(t,n),n}return x.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function En({routeContext:e,match:t,children:n}){let r=x.useContext(Yt);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),x.createElement(an.Provider,{value:e},n)}function Dn(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);tt(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e=0?i.slice(0,s+1):[i[0]];break}}}}let c=n?.onError,l=r&&c?(e,t)=>{c(e,{location:r.location,params:r.matches?.[0]?.params??{},pattern:Kt(r.matches),errorInfo:t})}:void 0;return i.reduceRight((e,n,c)=>{let u,d=!1,f=null,p=null;r&&(u=a&&n.route.id?a[n.route.id]:void 0,f=n.route.errorElement||Sn,o&&(s<0&&c===0?(zn(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),d=!0,p=null):s===c&&(d=!0,p=n.route.hydrateFallbackElement||null)));let m=t.concat(i.slice(0,c+1)),h=()=>{let t;return t=u?f:d?p:n.route.Component?x.createElement(n.route.Component,null):n.route.element?n.route.element:e,x.createElement(En,{match:n,routeContext:{outlet:e,matches:m,isDataRoute:r!=null},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||c===0)?x.createElement(Cn,{location:r.location,revalidation:r.revalidation,component:f,error:u,children:h(),routeContext:{outlet:null,matches:m,isDataRoute:!0},onError:l}):h()},null)}function On(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function kn(e){let t=x.useContext(Yt);return tt(t,On(e)),t}function An(e){let t=x.useContext(Xt);return tt(t,On(e)),t}function jn(e){let t=x.useContext(an);return tt(t,On(e)),t}function Mn(e){let t=jn(e),n=t.matches[t.matches.length-1];return tt(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function Nn(){return Mn(`useRouteId`)}function Pn(){return An(`useNavigation`).navigation}function Fn(){let{matches:e,loaderData:t}=An(`useMatches`);return x.useMemo(()=>e.map(e=>ft(e,t)),[e,t])}function In(){let e=x.useContext(on),t=An(`useRouteError`),n=Mn(`useRouteError`);return e===void 0?t.errors?.[n]:e}function Ln(){let{router:e}=kn(`useNavigate`),t=Mn(`useNavigate`),n=x.useRef(!1);return gn(()=>{n.current=!0}),x.useCallback(async(r,i={})=>{nt(n.current,hn),n.current&&(typeof r==`number`?await e.navigate(r):await e.navigate(r,{fromRouteId:t,...i}))},[e,t])}var Rn={};function zn(e,t,n){!t&&!Rn[e]&&(Rn[e]=!0,nt(!1,n))}x.useOptimistic,x.memo(Bn);function Bn({routes:e,manifest:t,future:n,state:r,isStatic:i,onError:a}){return bn(e,void 0,{manifest:t,state:r,isStatic:i,onError:a,future:n})}function Vn({basename:e=`/`,children:t=null,location:n,navigationType:r=`POP`,navigator:i,static:a=!1,useTransitions:o}){tt(!pn(),`You cannot render a inside another . You should never have more than one in your app.`);let s=e.replace(/^\/*/,`/`),c=x.useMemo(()=>({basename:s,navigator:i,static:a,useTransitions:o,future:{}}),[s,i,a,o]);typeof n==`string`&&(n=st(n));let{pathname:l=`/`,search:u=``,hash:d=``,state:f=null,key:p=`default`,mask:m}=n,h=x.useMemo(()=>{let e=At(l,s);return e==null?null:{location:{pathname:e,search:u,hash:d,state:f,key:p,mask:m},navigationType:r}},[s,l,u,d,f,p,r,m]);return nt(h!=null,` is not able to match the URL "${l}${u}${d}" because it does not start with the basename, so the won't render anything.`),h==null?null:x.createElement(nn.Provider,{value:c},x.createElement(rn.Provider,{children:t,value:h}))}var Hn=`get`,Un=`application/x-www-form-urlencoded`;function Wn(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function Gn(e){return Wn(e)&&e.tagName.toLowerCase()===`button`}function Kn(e){return Wn(e)&&e.tagName.toLowerCase()===`form`}function qn(e){return Wn(e)&&e.tagName.toLowerCase()===`input`}function Jn(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Yn(e,t){return e.button===0&&(!t||t===`_self`)&&!Jn(e)}var Xn=null;function Zn(){if(Xn===null)try{new FormData(document.createElement(`form`),0),Xn=!1}catch{Xn=!0}return Xn}var Qn=new Set([`application/x-www-form-urlencoded`,`multipart/form-data`,`text/plain`]);function $n(e){return e!=null&&!Qn.has(e)?(nt(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Un}"`),null):e}function er(e,t){let n,r,i,a,o;if(Kn(e)){let o=e.getAttribute(`action`);r=o?At(o,t):null,n=e.getAttribute(`method`)||Hn,i=$n(e.getAttribute(`enctype`))||Un,a=new FormData(e)}else if(Gn(e)||qn(e)&&(e.type===`submit`||e.type===`image`)){let o=e.form;if(o==null)throw Error(`Cannot submit a