Success-story migration + new plugins + JSON/UX polish

- Success Stories: full migration from old site (48/48, audited),
  markets n:n, quotation content block, columns content block,
  pretty SEO detail URLs via SuccessStoryPathRewrite middleware,
  list/detail split (list page 4 / story page), detailUrl/backUrl
- New plugins: VITEC Locations (grid/list/map + RTE map text),
  VITEC Customer Logos (color/bw logic, only-show-selected),
  VITEC Card (one plugin for product/story/market/solution
  with reloading FlexForm + custom backend preview renderer)
- Eventlist: layout dropdown (list/grid/teaserbar) in settings
- Hero section CB: Images/Video tabs, background video + overlay
- Product JSON: full category rootline (parents), fixed missing
  ConnectionPool import (all-products crash), category tree map
- Backend preview CSS: container-query responsive (narrow columns)
- Docs: ISO architecture spec, root + extension READMEs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
khaccountandClaude Fable 5 committed 2026-07-24 09:53:33 +02:00
1 parent aafcb5dcb6
commit eae40f4286
110 files changed
+8502 -279

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Customer Logos Settings</sheetTitle>
<type>array</type>
<el>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>List</label>
<value>list</value>
</numIndex>
<numIndex index="1">
<label>Grid</label>
<value>grid</value>
</numIndex>
<numIndex index="2">
<label>Carousel</label>
<value>carousel</value>
</numIndex>
<numIndex index="3">
<label>Marquee (scrolling ticker)</label>
<value>marquee</value>
</numIndex>
</items>
<default>grid</default>
</config>
</settings.layout>
<settings.customers>
<label>Highlighted Customers</label>
<description>Empty = all logos black &amp; white. Selected customers are shown in color, in front of the remaining (b/w) logos.</description>
<config>
<type>select</type>
<renderType>selectMultipleSideBySide</renderType>
<foreign_table>tx_vitec_domain_model_customer</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_customer.hidden = 0 AND tx_vitec_domain_model_customer.deleted = 0 ORDER BY tx_vitec_domain_model_customer.title</foreign_table_where>
<size>8</size>
<minitems>0</minitems>
<maxitems>999</maxitems>
</config>
</settings.customers>
<settings.onlySelected>
<label>Only show selected</label>
<description>Show only the highlighted customers — the remaining (b/w) logos are omitted entirely.</description>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.onlySelected>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
@@ -9,6 +9,29 @@
<sheetTitle>Event List Settings</sheetTitle>
<type>array</type>
<el>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>List View</label>
<value>list</value>
</numIndex>
<numIndex index="1">
<label>Grid View</label>
<value>grid</value>
</numIndex>
<numIndex index="2">
<label>Teaser Bar</label>
<value>teaserbar</value>
</numIndex>
</items>
<default>list</default>
</config>
</settings.layout>
<settings.daysinadvance>
<label>Days in advance (0 = no limit)</label>
<description>Only show upcoming events starting within the next N days, e.g. 60.</description>
+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Locations Settings</sheetTitle>
<type>array</type>
<el>
<settings.variant>
<label>Display Variant</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Grid</label>
<value>grid</value>
</numIndex>
<numIndex index="1">
<label>List</label>
<value>list</value>
</numIndex>
<numIndex index="2">
<label>Map</label>
<value>map</value>
</numIndex>
</items>
<default>grid</default>
</config>
</settings.variant>
<settings.maptext>
<label>Map Text</label>
<description>Rich text shown alongside the map (Map variant only).</description>
<displayCond>FIELD:settings.variant:=:map</displayCond>
<config>
<type>text</type>
<rows>8</rows>
<enableRichtext>1</enableRichtext>
</config>
</settings.maptext>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Card Settings</sheetTitle>
<type>array</type>
<el>
<settings.modelType>
<label>Card Type</label>
<onChange>reload</onChange>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Product</label>
<value>product</value>
</numIndex>
<numIndex index="1">
<label>Success Story</label>
<value>story</value>
</numIndex>
<numIndex index="2">
<label>Market</label>
<value>market</value>
</numIndex>
<numIndex index="3">
<label>Solution</label>
<value>solution</value>
</numIndex>
</items>
<default>product</default>
</config>
</settings.modelType>
<settings.product>
<label>Product</label>
<displayCond>FIELD:settings.modelType:=:product</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_product</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_product.hidden = 0 AND tx_vitec_domain_model_product.deleted = 0 ORDER BY tx_vitec_domain_model_product.title</foreign_table_where>
</config>
</settings.product>
<settings.story>
<label>Success Story</label>
<displayCond>FIELD:settings.modelType:=:story</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_usecase</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title</foreign_table_where>
</config>
</settings.story>
<settings.market>
<label>Market</label>
<displayCond>FIELD:settings.modelType:=:market</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_market</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_market.hidden = 0 AND tx_vitec_domain_model_market.deleted = 0 ORDER BY tx_vitec_domain_model_market.title</foreign_table_where>
</config>
</settings.market>
<settings.solution>
<label>Solution</label>
<displayCond>FIELD:settings.modelType:=:solution</displayCond>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>— please choose —</label>
<value>0</value>
</numIndex>
</items>
<foreign_table>tx_vitec_domain_model_solution</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_solution.hidden = 0 AND tx_vitec_domain_model_solution.deleted = 0 ORDER BY tx_vitec_domain_model_solution.title</foreign_table_where>
</config>
</settings.solution>
<settings.layout>
<label>Layout</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>Vertical (image top)</label>
<value>vertical</value>
</numIndex>
<numIndex index="1">
<label>Horizontal (image left)</label>
<value>horizontal</value>
</numIndex>
<numIndex index="2">
<label>Image Overlay</label>
<value>overlay</value>
</numIndex>
<numIndex index="3">
<label>Compact</label>
<value>compact</value>
</numIndex>
</items>
<default>vertical</default>
</config>
</settings.layout>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
@@ -8,6 +8,17 @@
</sheetTitle>
<type>array</type>
<el>
<settings.backPid>
<label>Back to List View (page)</label>
<description>Page that holds the Success Story list plugin. Used to build the back link in the JSON output.</description>
<config>
<type>group</type>
<allowed>pages</allowed>
<size>1</size>
<maxitems>1</maxitems>
</config>
</settings.backPid>
<settings.debug>
<label>Allow Debug Output.</label>
<config>
@@ -19,20 +30,6 @@
</items>
</config>
</settings.debug>
<settings.usecase>
<label>
Usecase
</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<foreign_table>tx_vitec_domain_model_usecase</foreign_table>
<foreign_table_where>AND tx_vitec_domain_model_usecase.hidden = 0 AND tx_vitec_domain_model_usecase.deleted = 0 ORDER BY tx_vitec_domain_model_usecase.title</foreign_table_where>
<size>1</size>
<minitems>0</minitems>
<maxitems>1</maxitems>
</config>
</settings.usecase>
<settings.layout>
<label>
Layout
@@ -6,6 +6,17 @@
<sheetTitle>Usecase List</sheetTitle>
<type>array</type>
<el>
<settings.singlePid>
<label>Single PID (detail page)</label>
<description>Page that holds the "Show single VITEC Success Story" plugin. Used to build the detail links in the JSON output.</description>
<config>
<type>group</type>
<allowed>pages</allowed>
<size>1</size>
<maxitems>1</maxitems>
</config>
</settings.singlePid>
<settings.debug>
<label>Allow Debug Output.</label>
<config>
+12
View File
@@ -76,4 +76,16 @@ return [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-ogimage.svg',
],
'vitec-plugin-locationlist' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-locationlist.svg',
],
'vitec-plugin-customerlogos' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-customerlogos.svg',
],
'vitec-plugin-modelcard' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-modelcard.svg',
],
];
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* Frontend middleware registration for EXT:vitec.
*/
return [
'frontend' => [
'vitec/success-story-path-rewrite' => [
'target' => \Evomedien\Vitec\Middleware\SuccessStoryPathRewrite::class,
'after' => [
'typo3/cms-core/normalized-params-attribute',
],
'before' => [
'typo3/cms-frontend/site',
],
],
],
];
@@ -160,6 +160,42 @@ tt_content {
}
}
}
vitec_modelcard < lib.contentElementWithHeader
vitec_modelcard {
fields {
content {
fields {
card = USER
card.userFunc = Evomedien\Vitec\UserFunc\ModelcardJsonRenderer->render
}
}
}
}
vitec_customerlogos < lib.contentElementWithHeader
vitec_customerlogos {
fields {
content {
fields {
customerlogos = USER
customerlogos.userFunc = Evomedien\Vitec\UserFunc\CustomerlogosJsonRenderer->render
}
}
}
}
vitec_locationlist < lib.contentElementWithHeader
vitec_locationlist {
fields {
content {
fields {
locations = USER
locations.userFunc = Evomedien\Vitec\UserFunc\LocationsJsonRenderer->render
}
}
}
}
}
# Include container (layout) rendering definitions
@@ -35,6 +35,41 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
$registerPluginWithFlexForm('Downloadcardcollection', 'Download Card Collection', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcardcollection.xml', 'vitec-plugin-downloadcardcollection');
$registerPluginWithFlexForm('Datasheets', 'Datasheets', 'FILE:EXT:vitec/Configuration/FlexForms/Datasheets.xml');
$registerPluginWithFlexForm('Eventlist', 'Event List', 'FILE:EXT:vitec/Configuration/FlexForms/Eventlist.xml', 'vitec-plugin-eventlist');
// Locations: lives in the "VITEC" wizard group, titled "VITEC Locations".
$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['itemGroups']['vitec']
= 'LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:group.header';
ExtensionUtility::registerPlugin(
'Vitec',
'Modelcard',
'VITEC Card',
'vitec-plugin-modelcard',
'vitec',
'One card for a Product, Success Story, Market or Solution — all data from the model.',
'FILE:EXT:vitec/Configuration/FlexForms/Modelcard.xml'
);
$GLOBALS['TCA']['tt_content']['types']['vitec_modelcard']['previewRenderer']
= \Evomedien\Vitec\Preview\ModelcardPreviewRenderer::class;
ExtensionUtility::registerPlugin(
'Vitec',
'Customerlogos',
'VITEC Customer Logos',
'vitec-plugin-customerlogos',
'vitec',
'Customer logos in list, grid, carousel or marquee layout.',
'FILE:EXT:vitec/Configuration/FlexForms/Customerlogos.xml'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Locationlist',
'VITEC Locations',
'vitec-plugin-locationlist',
'vitec',
'Locations as grid, list or interactive map.',
'FILE:EXT:vitec/Configuration/FlexForms/Locationlist.xml'
);
// Change frame_class to allow multiple selections.
$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['renderType'] = 'selectCheckBox';
@@ -0,0 +1,63 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Customer',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'sortby' => 'sorting',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-customerlogos.svg',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => [
'showitem' =>
'title, logo, emphasize_logo,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime',
],
],
'columns' => [
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [['value' => '', 'label' => '', 'invertStateDisplay' => true]],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => ['type' => 'datetime', 'default' => 0],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => ['type' => 'datetime', 'default' => 0, 'range' => ['upper' => mktime(0, 0, 0, 1, 1, 2038)]],
],
'title' => [
'label' => 'Customer Name',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'required' => true, 'default' => ''],
],
'logo' => [
'label' => 'Logo',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'emphasize_logo' => [
'label' => 'Emphasize Logo',
'description' => 'Highlight this logo (e.g. larger) in the frontend.',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
],
];
@@ -0,0 +1,168 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Location',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'sortby' => 'sorting',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,slug,city,country,country_code',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-locationlist.svg',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => [
'showitem' =>
'--div--;General,
title, slug, country_code,
--palette--;Coordinates;coords,
--div--;Address,
address_company, street, address_additional,
--palette--;;cityline,
region, country,
--div--;Contact & Links,
phone, fax, email, contact_link, legal_links,
--div--;Marker,
marker_label, marker_color, marker_size,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime',
],
],
'palettes' => [
'coords' => ['showitem' => 'latitude, longitude'],
'cityline' => ['showitem' => 'postal_code, city'],
],
'columns' => [
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [['value' => '', 'label' => '', 'invertStateDisplay' => true]],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => ['type' => 'datetime', 'default' => 0],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => ['type' => 'datetime', 'default' => 0, 'range' => ['upper' => mktime(0, 0, 0, 1, 1, 2038)]],
],
// ----------------------------------------------------------- General
'title' => [
'label' => 'Name',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'required' => true, 'default' => ''],
],
'slug' => [
'label' => 'Slug',
'config' => [
'type' => 'slug',
'size' => 50,
'generatorOptions' => ['fields' => ['title'], 'fieldSeparator' => '-', 'replacements' => ['/' => '', ',' => '']],
'fallbackCharacter' => '-',
'eval' => 'uniqueInPid',
],
],
'country_code' => [
'label' => 'Country Code (ISO 3166-1 alpha-2)',
'description' => 'e.g. US, DE, GB',
'config' => ['type' => 'input', 'size' => 6, 'max' => 5, 'eval' => 'trim,upper', 'default' => ''],
],
'latitude' => [
'label' => 'Latitude',
'config' => ['type' => 'number', 'format' => 'decimal', 'default' => 0, 'range' => ['lower' => -90, 'upper' => 90]],
],
'longitude' => [
'label' => 'Longitude',
'config' => ['type' => 'number', 'format' => 'decimal', 'default' => 0, 'range' => ['lower' => -180, 'upper' => 180]],
],
// ----------------------------------------------------------- Address
'address_company' => [
'label' => 'Company (optional)',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'street' => [
'label' => 'Street',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'address_additional' => [
'label' => 'Additional (optional)',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'postal_code' => [
'label' => 'Postal Code',
'config' => ['type' => 'input', 'size' => 10, 'max' => 20, 'eval' => 'trim', 'default' => ''],
],
'city' => [
'label' => 'City',
'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim', 'default' => ''],
],
'region' => [
'label' => 'Region / State (optional)',
'description' => 'e.g. GA',
'config' => ['type' => 'input', 'size' => 20, 'eval' => 'trim', 'default' => ''],
],
'country' => [
'label' => 'Country (display name)',
'config' => ['type' => 'input', 'size' => 30, 'eval' => 'trim', 'default' => ''],
],
// --------------------------------------------------- Contact & Links
'phone' => [
'label' => 'Phone',
'config' => ['type' => 'input', 'size' => 30, 'max' => 100, 'eval' => 'trim', 'default' => ''],
],
'fax' => [
'label' => 'Fax',
'config' => ['type' => 'input', 'size' => 30, 'max' => 100, 'eval' => 'trim', 'default' => ''],
],
'email' => [
'label' => 'E-Mail',
'config' => ['type' => 'email', 'size' => 40, 'default' => ''],
],
'contact_link' => [
'label' => 'Contact Link',
'config' => ['type' => 'link', 'allowedTypes' => ['page', 'url', 'email']],
],
'legal_links' => [
'label' => 'Legal Links',
'description' => 'One link per line (page link or URL).',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 3, 'eval' => 'trim'],
],
// ------------------------------------------------------------ Marker
'marker_label' => [
'label' => 'Marker Label (optional)',
'description' => 'Falls back to the location name.',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
'marker_color' => [
'label' => 'Marker Color',
'config' => ['type' => 'color', 'default' => '#ff6633'],
],
'marker_size' => [
'label' => 'Marker Size (scale, e.g. 0.5)',
'config' => [
'type' => 'number',
'format' => 'decimal',
'default' => 0.5,
'range' => ['lower' => 0, 'upper' => 5],
'slider' => ['step' => 0.05, 'width' => 200],
],
],
],
];
@@ -34,7 +34,7 @@ return [
--div--;Detail · Content,
content_elements,
--div--;Detail · Related,
market, solutions, products, categories,
markets, solutions, products, categories,
--div--;SEO,
seo_title, seo_description, no_index, no_follow, canonical_link,
--palette--;Open Graph;ogPalette,
@@ -182,15 +182,16 @@ return [
],
// --------------------------------------------------- Detail · Related
'market' => [
'label' => 'Market',
'markets' => [
'label' => 'Markets',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_market',
'items' => [['label' => '', 'value' => 0]],
'default' => 0,
'maxitems' => 1,
'MM' => 'tx_vitec_usecase_market_mm',
'size' => 6,
'autoSizeMax' => 20,
'maxitems' => 9999,
],
],
'solutions' => [