From 90ae6803a4e24af7e26f48a8127b57f16d0dd25e Mon Sep 17 00:00:00 2001 From: Oliver Rasche Date: Fri, 2 Oct 2026 10:18:36 +0200 Subject: [PATCH] Backend of the sub-market template (spec agreed section by section with the customer workflow in mind: the customer fills one XLSX per sub-market, an importer builds the page). - Frontend layout 21 'Sub-Market Page' in the existing BackendLayoutDataProvider: six zones, one per Figma section, emitted as colPos110..colPos160 - the JSON groups plus appearance.layout are the front-end contract (spec clause 7.17). A first attempt via page-TSconfig BackendLayouts was the wrong mechanism next to the provider and was removed again - New Content Blocks vitec_markethero, vitec_marketintro, vitec_marketinsights (field bags, serialized unprefixed under data); the workflow section reuses intro-paragraph + vitec_card flip cards - Stories gained quote/quote_person/quote_role (Testimonial tab), maintained once per story, emitted on every story card; usecaselist gained the layout value 'testimonials' - New plugin vitec_productfeature: one referenced product as a full product card, serialisation shared with the product list (cObj made protected for the subclass) - vitec_productlist gained hand-picked product selection: exactly the chosen products in the editor's order, category filter ignored, visibility filters kept - closes the long-standing single-select gap - New CLI vitec:generate-submarket-workbooks: one protected customer XLSX per sub-market (9 main markets, 25 sub-markets from the Market category tree) - dropdowns filled live from the database, named ranges as the importer contract, hidden Meta sheet pinning submarket/group/ record uid, soft character-limit warnings, asset URL fields Verified end to end on Broadcast & Media: layout-21 payload with colPos110 carrying the hero element and its subline. Spec raised to v1.20. --- .../Classes/Service/UsecaseSerializer.php | 6 ++ .../UserFunc/ProductFeatureJsonRenderer.php | 58 +++++++++++++++++++ .../UserFunc/ProductListJsonRenderer.php | 25 +++++++- .../View/BackendLayoutDataProvider.php | 16 +++++ .../FlexForms/Productfeature.xml | 27 +++++++++ .../Configuration/FlexForms/Productlist.xml | 10 ++++ .../Configuration/FlexForms/Usecaselist.xml | 4 ++ .../Sets/Vitecset/setup.typoscript | 15 +++++ .../Configuration/TCA/Overrides/pages.php | 1 + .../TCA/Overrides/tt_content.php | 1 + .../TCA/tx_vitec_domain_model_usecase.php | 22 +++++++ packages/vitec/Configuration/page.tsconfig | 8 +++ .../market-hero/assets/icon.svg | 7 +++ .../ContentElements/market-hero/config.yaml | 45 ++++++++++++++ .../market-hero/language/labels.xlf | 21 +++++++ .../templates/backend-preview.html | 12 ++++ .../market-hero/templates/frontend.html | 3 + .../market-insights/assets/icon.svg | 7 +++ .../market-insights/config.yaml | 50 ++++++++++++++++ .../market-insights/language/labels.xlf | 23 ++++++++ .../templates/backend-preview.html | 12 ++++ .../market-insights/templates/frontend.html | 3 + .../market-intro/assets/icon.svg | 7 +++ .../ContentElements/market-intro/config.yaml | 33 +++++++++++ .../market-intro/language/labels.xlf | 18 ++++++ .../templates/backend-preview.html | 11 ++++ .../market-intro/templates/frontend.html | 3 + .../Headless-JSON-Architecture.md | 42 +++++++++++++- .../Private/Language/locallang_pages.xlf | 3 + packages/vitec/ext_tables.sql | 3 + public/_frontend/index.html | 4 +- 31 files changed, 493 insertions(+), 7 deletions(-) create mode 100644 packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php create mode 100644 packages/vitec/Configuration/FlexForms/Productfeature.xml create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html create mode 100644 packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html diff --git a/packages/vitec/Classes/Service/UsecaseSerializer.php b/packages/vitec/Classes/Service/UsecaseSerializer.php index 1b669cf..63bb09c 100755 --- a/packages/vitec/Classes/Service/UsecaseSerializer.php +++ b/packages/vitec/Classes/Service/UsecaseSerializer.php @@ -51,6 +51,12 @@ final class UsecaseSerializer 'teaser' => (string)($u['teaser'] ?? ''), 'cardImage' => $this->image($uid, 'card_image', null, true), 'customerLogo' => $this->image($uid, 'customer_logo'), + // Testimonial data (2026-10-01): one quote per story, maintained + // on the record, consumed by testimonial cards (sub-market + // template). Empty strings when not yet maintained. + 'quote' => (string)($u['quote'] ?? ''), + 'quotePerson' => (string)($u['quote_person'] ?? ''), + 'quoteRole' => (string)($u['quote_role'] ?? ''), 'markets' => $this->relationMulti('tx_vitec_usecase_market_mm', 'tx_vitec_domain_model_market', $uid), 'categories' => $this->categories($uid), 'featured' => (bool)($u['featured'] ?? false), diff --git a/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php new file mode 100644 index 0000000..73fe9b2 --- /dev/null +++ b/packages/vitec/Classes/UserFunc/ProductFeatureJsonRenderer.php @@ -0,0 +1,58 @@ +cObj?->data ?? []; + $flexFormData = GeneralUtility::makeInstance(FlexFormService::class) + ->convertFlexFormContentToArray((string)($row['pi_flexform'] ?? '')); + $productUid = (int)($flexFormData['settings']['product'] ?? 0); + if ($productUid <= 0) { + return (string)json_encode(null); + } + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('tx_vitec_domain_model_product'); + $product = $queryBuilder->select('*')->from('tx_vitec_domain_model_product') + ->where( + $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($productUid, ParameterType::INTEGER)), + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0), + $queryBuilder->expr()->eq('hideonwebsite', 0) + ) + ->executeQuery()->fetchAssociative(); + if (!$product) { + return (string)json_encode(null); + } + + return (string)json_encode($this->serializeProduct($product), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } catch (\Throwable) { + return (string)json_encode(null); + } + } +} diff --git a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php index bb0aacf..bb8714a 100755 --- a/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php +++ b/packages/vitec/Classes/UserFunc/ProductListJsonRenderer.php @@ -31,7 +31,9 @@ use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; */ class ProductListJsonRenderer { - private ?ContentObjectRenderer $cObj = null; + // protected (not private) since 2026-10-01: ProductFeatureJsonRenderer + // extends this class to reuse serializeProduct() and needs the cObj. + protected ?ContentObjectRenderer $cObj = null; /** * TYPO3 v14 hands the ContentObjectRenderer over through this setter only - @@ -119,6 +121,14 @@ class ProductListJsonRenderer } $debugMode = (bool)($settings['debug'] ?? false); $allProducts = (bool)($settings['allproducts'] ?? false); + // Explicit picks (2026-10-01, built for the sub-market template's + // Products section): when set, EXACTLY these products in the + // editor's order - the category filter is ignored. The visibility + // filters below stay in force on purpose, so a hidden/legacy + // record never surfaces through a stale selection. + $selectedUids = array_filter( + array_map('intval', explode(',', (string)($settings['selectedproducts'] ?? ''))) + ); // Extbase repositories don't work in UserFunc context — direct query. $productQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) @@ -137,7 +147,11 @@ class ProductListJsonRenderer $productQueryBuilder->expr()->eq('p.subproduct', 0) ); - if (!empty($categoryUids) && !$allProducts) { + if ($selectedUids !== [] && !$allProducts) { + $productQuery->andWhere( + $productQueryBuilder->expr()->in('p.uid', $productQueryBuilder->createNamedParameter($selectedUids, Connection::PARAM_INT_ARRAY)) + ); + } elseif (!empty($categoryUids) && !$allProducts) { $productQuery ->join( 'p', @@ -159,7 +173,12 @@ class ProductListJsonRenderer // sitemap. $categoryUids comes from expandWithDescendants in // depth-first tree order; within one category the uid order is // kept - the records were created in sitemap-V2 row order. - if (!empty($categoryUids) && !$allProducts && $products !== []) { + if ($selectedUids !== [] && !$allProducts && $products !== []) { + // Editor's order, not storage order. + $rankBySelection = array_flip($selectedUids); + usort($products, static fn(array $a, array $b): int => + ($rankBySelection[(int)$a['uid']] ?? PHP_INT_MAX) <=> ($rankBySelection[(int)$b['uid']] ?? PHP_INT_MAX)); + } elseif (!empty($categoryUids) && !$allProducts && $products !== []) { $rankByCategory = array_flip($categoryUids); $mmQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) ->getQueryBuilderForTable('sys_category_record_mm'); diff --git a/packages/vitec/Classes/View/BackendLayoutDataProvider.php b/packages/vitec/Classes/View/BackendLayoutDataProvider.php index c21887d..812b6eb 100755 --- a/packages/vitec/Classes/View/BackendLayoutDataProvider.php +++ b/packages/vitec/Classes/View/BackendLayoutDataProvider.php @@ -27,6 +27,15 @@ final class BackendLayoutDataProvider implements DataProviderInterface 'hero' => 1, 'sidebar' => 2, 'preFooter' => 3, + // Sub-market template sections (spec 2026-10-01): one slot per Figma + // section. The headless JSON groups these as colPos110..colPos160 - + // that grouping IS the front-end contract for layout 21. + 'smHero' => 110, + 'smIntro' => 120, + 'smWorkflow' => 130, + 'smTestimonials' => 140, + 'smProducts' => 150, + 'smInsights' => 160, ]; /** Per-frontend-layout: title + ordered list of zones. */ @@ -52,6 +61,7 @@ final class BackendLayoutDataProvider implements DataProviderInterface 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']], + 21 => ['title' => 'Sub-Market Page', 'zones' => ['smHero', 'smIntro', 'smWorkflow', 'smTestimonials', 'smProducts', 'smInsights']], ]; /** Display name for each zone. Shown as column header in the BE page module. */ @@ -60,6 +70,12 @@ final class BackendLayoutDataProvider implements DataProviderInterface 'hero' => 'Hero', 'sidebar' => 'Sidebar', 'preFooter' => 'Pre-Footer', + 'smHero' => '1 · Hero', + 'smIntro' => '2 · Intro', + 'smWorkflow' => '3 · Workflow (header + flip cards)', + 'smTestimonials' => '4 · Testimonials', + 'smProducts' => '5 · Products (featured + list)', + 'smInsights' => '6 · Industry Insights', ]; public function getIdentifier(): string diff --git a/packages/vitec/Configuration/FlexForms/Productfeature.xml b/packages/vitec/Configuration/FlexForms/Productfeature.xml new file mode 100644 index 0000000..6637bad --- /dev/null +++ b/packages/vitec/Configuration/FlexForms/Productfeature.xml @@ -0,0 +1,27 @@ + + + + + + + Featured Product + array + + + + The product shown as the big featured card. Image, title and teaser come from the record. + + group + tx_vitec_domain_model_product + 1 + 1 + + + + + + + diff --git a/packages/vitec/Configuration/FlexForms/Productlist.xml b/packages/vitec/Configuration/FlexForms/Productlist.xml index af4a7c0..205b152 100644 --- a/packages/vitec/Configuration/FlexForms/Productlist.xml +++ b/packages/vitec/Configuration/FlexForms/Productlist.xml @@ -30,6 +30,16 @@ + + + When set, the list shows EXACTLY these products in this order - the category selection below is ignored. Hidden/legacy products stay suppressed. Used e.g. by the sub-market template's Products section. + + group + tx_vitec_domain_model_product + 6 + 50 + + diff --git a/packages/vitec/Configuration/FlexForms/Usecaselist.xml b/packages/vitec/Configuration/FlexForms/Usecaselist.xml index cf4dc0b..63a17b4 100644 --- a/packages/vitec/Configuration/FlexForms/Usecaselist.xml +++ b/packages/vitec/Configuration/FlexForms/Usecaselist.xml @@ -29,6 +29,10 @@ 50-50 + + + testimonials + grid diff --git a/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript index fa086c3..0a88f38 100755 --- a/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript +++ b/packages/vitec/Configuration/Sets/Vitecset/setup.typoscript @@ -67,6 +67,21 @@ tt_content { } } + # Featured product card (sub-market template section 5): one referenced + # product as a full product card under `productfeature` - same shape as + # the list items, serialization shared with ProductListJsonRenderer. + vitec_productfeature < lib.contentElementWithHeader + vitec_productfeature { + fields { + content { + fields { + productfeature = USER + productfeature.userFunc = Evomedien\Vitec\UserFunc\ProductFeatureJsonRenderer->render + } + } + } + } + # Product Finder - only the category hierarchy for the filter selects. # Placed once above the product lists; the front end filters the products # those lists already delivered, client side, via ?cat= and ?subcat=. diff --git a/packages/vitec/Configuration/TCA/Overrides/pages.php b/packages/vitec/Configuration/TCA/Overrides/pages.php index a9a5b04..94c2726 100755 --- a/packages/vitec/Configuration/TCA/Overrides/pages.php +++ b/packages/vitec/Configuration/TCA/Overrides/pages.php @@ -31,6 +31,7 @@ defined('TYPO3') or die(); ['label' => $l . 'layout.18', 'value' => '18'], ['label' => $l . 'layout.19', 'value' => '19'], ['label' => $l . 'layout.20', 'value' => '20'], + ['label' => $l . 'layout.21', 'value' => '21'], ]; $GLOBALS['TCA']['pages']['columns']['layout']['config']['default'] = '0'; diff --git a/packages/vitec/Configuration/TCA/Overrides/tt_content.php b/packages/vitec/Configuration/TCA/Overrides/tt_content.php index 6cf163a..f121e24 100644 --- a/packages/vitec/Configuration/TCA/Overrides/tt_content.php +++ b/packages/vitec/Configuration/TCA/Overrides/tt_content.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility; $registerPluginWithFlexForm('Productlist', 'Show Products by Category', 'FILE:EXT:vitec/Configuration/FlexForms/Productlist.xml', 'vitec-plugin-productlist'); $registerPluginWithFlexForm('Productfinder', 'Product Finder (filter data)', 'FILE:EXT:vitec/Configuration/FlexForms/Productfinder.xml', 'vitec-plugin-productlist'); + $registerPluginWithFlexForm('Productfeature', 'Featured Product Card', 'FILE:EXT:vitec/Configuration/FlexForms/Productfeature.xml', 'vitec-plugin-productlist'); $registerPluginWithFlexForm('Simplecard', 'Simple Card', 'FILE:EXT:vitec/Configuration/FlexForms/Simplecard.xml', 'vitec-plugin-simplecard'); $registerPluginWithFlexForm('Productshow', 'Show Single Product', 'FILE:EXT:vitec/Configuration/FlexForms/Productshow.xml', 'vitec-plugin-productshow'); $registerPluginWithFlexForm('Usecaseshow', 'Single Success Story', 'FILE:EXT:vitec/Configuration/FlexForms/Usecase.xml', 'vitec-plugin-usecaseshow'); diff --git a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php index 72c6724..92e1083 100644 --- a/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php +++ b/packages/vitec/Configuration/TCA/tx_vitec_domain_model_usecase.php @@ -29,6 +29,8 @@ return [ title, slug, detail_page, subtitle, --div--;List view, card_image, customer_logo, teaser, + --div--;Testimonial, + quote, quote_person, quote_role, --div--;Detail · Hero, hero_bgimage, hero_small_image, hero_video, hero_overlay_color, hero_overlay_opacity, --div--;Detail · Content, @@ -153,6 +155,26 @@ return [ 'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'], ], + // ------------------------------------------------------- Testimonial + // One quote per story, maintained editorially and reused wherever the + // story appears as a testimonial card (first consumer: the sub-market + // template, decision Olli 2026-10-01 - the customer's XLS only picks + // the story, never carries quote text). + 'quote' => [ + 'label' => 'Quote', + 'description' => 'Shown on testimonial cards wherever this story is referenced. Plain text, no quotation marks - the front end adds them.', + 'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'], + ], + 'quote_person' => [ + 'label' => 'Quote: person', + 'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'], + ], + 'quote_role' => [ + 'label' => 'Quote: role / title', + 'description' => 'E.g. "Vice President of Technology, KCR" - shown below the name.', + 'config' => ['type' => 'input', 'size' => 30, 'max' => 255, 'eval' => 'trim'], + ], + // ------------------------------------------------------ Detail · Hero 'hero_bgimage' => [ 'label' => 'Background Image', diff --git a/packages/vitec/Configuration/page.tsconfig b/packages/vitec/Configuration/page.tsconfig index 1894d47..2cfc528 100755 --- a/packages/vitec/Configuration/page.tsconfig +++ b/packages/vitec/Configuration/page.tsconfig @@ -16,6 +16,14 @@ mod { CType = vitec_productlist } } + productfeature { + iconIdentifier = vitec-plugin-productlist + title = Featured Product Card + description = One product as the big featured card (sub-market template). All copy comes from the record. + tt_content_defValues { + CType = vitec_productfeature + } + } productfinder { iconIdentifier = vitec-plugin-productlist title = Product Finder (filter data) diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg new file mode 100644 index 0000000..ffd00da --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml new file mode 100644 index 0000000..794b039 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/config.yaml @@ -0,0 +1,45 @@ +# Sub-market template section 1 (Figma "Sub-Market Page", spec 2026-10-01). +# The H1 is NOT a field - it is the page title (dev names are canonical). +name: vitec/market-hero +group: vitec_custom_components +prefixFields: true +prefixType: vendor + +fields: + - identifier: subline + type: Text + max: 120 + + - identifier: cta_section + type: Palette + label: CTA button + fields: + - identifier: cta_label + type: Text + max: 40 + - identifier: cta_link + type: Link + + - identifier: bgimage + type: File + minitems: 0 + maxitems: 1 + allowed: common-image-types + + - identifier: video_section + type: Palette + label: Video teaser (optional - empty url hides the card) + fields: + - identifier: video_title + type: Text + max: 80 + - type: Linebreak + - identifier: video_url + type: Text + max: 500 + - type: Linebreak + - identifier: video_cover + type: File + minitems: 0 + maxitems: 1 + allowed: common-image-types diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf new file mode 100644 index 0000000..c8ff6b9 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/language/labels.xlf @@ -0,0 +1,21 @@ + + + + + VITEC · Market Hero + Sub-market template, section 1: subline, CTA, background image, optional video teaser. The H1 is the page title. + Subline + Max ~90 characters visible (two lines in the design). + CTA label + Empty = front-end default "Get in touch". + CTA target + Empty = /contact. + Background image + 1920x600, dark enough for white text. Empty = dark gradient fallback. + Video title + Video URL + YouTube/Vimeo or file URL. Empty = no video card, hero runs full width. + Video cover image + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html new file mode 100644 index 0000000..2475685 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/backend-preview.html @@ -0,0 +1,12 @@ + + + + +
+
+ Market Hero — {data.vitec_subline} + · 🎬 {data.vitec_video_title} +
+
+
+ diff --git a/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html new file mode 100644 index 0000000..da9c5f0 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-hero/templates/frontend.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg new file mode 100644 index 0000000..ffd00da --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml new file mode 100644 index 0000000..383df9c --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/config.yaml @@ -0,0 +1,50 @@ +# Sub-market template section 6 (spec 2026-10-01). +name: vitec/market-insights +group: vitec_custom_components +prefixFields: true +prefixType: vendor + +fields: + - identifier: text1 + type: Textarea + rows: 4 + + - identifier: text2 + type: Textarea + rows: 4 + + - identifier: cta_section + type: Palette + label: CTA (optional) + fields: + - identifier: cta_label + type: Text + max: 40 + - identifier: cta_target + type: Text + max: 500 + + - identifier: video_section + type: Palette + label: Video card (optional - empty url hides the card) + fields: + - identifier: video_person + type: Text + max: 80 + - identifier: video_role + type: Text + max: 120 + - type: Linebreak + - identifier: video_title + type: Text + max: 120 + - type: Linebreak + - identifier: video_url + type: Text + max: 500 + - type: Linebreak + - identifier: video_cover + type: File + minitems: 0 + maxitems: 1 + allowed: common-image-types diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf new file mode 100644 index 0000000..9cec40d --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/language/labels.xlf @@ -0,0 +1,23 @@ + + + + + VITEC · Market Insights + Sub-market template, section 6: two paragraphs, optional CTA, optional video card with person. + Paragraph 1 + Plain text, max ~250 characters. + Paragraph 2 + Plain text, max ~250 characters. + CTA label + Empty = no button. + CTA target + Section anchor OR URL. Empty with a label set = /success-stories. + Person + Role / title + Video title + Video URL + Empty = no video card, text runs wider. + Cover image + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html new file mode 100644 index 0000000..ac6a499 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/backend-preview.html @@ -0,0 +1,12 @@ + + + + +
+
+ Market Insights — {data.vitec_text1} + · 🎬 {data.vitec_video_person} +
+
+
+ diff --git a/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html new file mode 100644 index 0000000..da9c5f0 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-insights/templates/frontend.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg b/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg new file mode 100644 index 0000000..ffd00da --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml b/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml new file mode 100644 index 0000000..0fa2d33 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/config.yaml @@ -0,0 +1,33 @@ +# Sub-market template section 2 (spec 2026-10-01). +name: vitec/market-intro +group: vitec_custom_components +prefixFields: true +prefixType: vendor + +fields: + - identifier: intro_text + type: Textarea + rows: 6 + + - identifier: image + type: File + minitems: 0 + maxitems: 1 + allowed: common-image-types + + - identifier: learnmore_section + type: Palette + label: Learn-more button (optional) + fields: + - identifier: learnmore_label + type: Text + max: 40 + default: Learn more + - identifier: learnmore_target + type: Text + max: 500 + + - identifier: show_portfolio + type: Checkbox + renderType: checkboxToggle + default: 1 diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf b/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf new file mode 100644 index 0000000..16cd284 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/language/labels.xlf @@ -0,0 +1,18 @@ + + + + + VITEC · Market Intro + Sub-market template, section 2: intro text, image right, optional learn-more button, portfolio button toggle. + Intro text + Plain text, max ~350 characters. No HTML. + Image (right) + 636x469 (about 4:3). + Label + Target + Section anchor (workflow, testimonials, products, insights) OR a URL. Empty = no button. + Show "Product Portfolio" button + Links to /products (fixed for now). + + + diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html new file mode 100644 index 0000000..0087d15 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/backend-preview.html @@ -0,0 +1,11 @@ + + + + +
+
+ Market Intro — {data.vitec_intro_text} +
+
+
+ diff --git a/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html new file mode 100644 index 0000000..da9c5f0 --- /dev/null +++ b/packages/vitec/ContentBlocks/ContentElements/market-intro/templates/frontend.html @@ -0,0 +1,3 @@ + + + diff --git a/packages/vitec/Documentation/Headless-JSON-Architecture.md b/packages/vitec/Documentation/Headless-JSON-Architecture.md index 8265b94..1c6dc72 100755 --- a/packages/vitec/Documentation/Headless-JSON-Architecture.md +++ b/packages/vitec/Documentation/Headless-JSON-Architecture.md @@ -3,9 +3,9 @@ | | | |---|---| | **Document identifier** | EVO‑VITEC‑HL‑001 | -| **Version** | 1.19 | +| **Version** | 1.20 | | **Status** | Released | -| **Date** | 2026‑09‑25 | +| **Date** | 2026‑10‑01 | | **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) | | **Owner** | evomedien — VITEC relaunch | @@ -32,6 +32,7 @@ | 1.17 | 2026‑09‑11 | **Interface change (additive).** The product list payload gained `successStories` (7.15.2.1): stories an editor attaches to a `sys_category` (tab *Options*, with its own layout), emitted in the list envelope beside `products`. Each card carries `forCategories` — the product categories it came from — so the front end can hide it with the same `?cat=`/`?subcat=` filter it applies to the products. Story links reuse the 7.5 rule, with the detail page taken from the element or the new site setting `vitec.storyDetailPid`. | | 1.18 | 2026‑09‑24 | **Interface change (additive).** Products gained `solutions` — an MM relation to solutions, emitted as solution cards in both product payloads — and a **Resources tab**: child table `tx_vitec_product_resource` with documentation / support cards (editable title, text and icon around one link, resolved per 9.12; cards without a resolvable link are not emitted) plus a `datasheets` MM selection of download records rendered into the same envelope as `type: datasheet` cards, delivered detail‑only as the `resources` list envelope whose `layout` switches to `carousel` above three cards in total (7.15). The `solutions` relation accepts group‑level records: the eight groups exist as `is_group = 1` solutions (created by `vitec:create-solution-groups`, excluded from lists and from `vitec:align-solutions`) because the agency workbooks reference the group level. Editorial: the footer had been stuck at v1.15 since 1.16/1.17 (2026‑09‑11). | | 1.19 | 2026‑09‑25 | **Interface change (additive).** The `vitec_card` Content Block gained the `layout_variant` value `flipcard` and a *Card Back* palette: `back_headline` (string, max 100) and `back_text` (richtext → HTML). The two fields are `displayCond`‑gated on the flipcard layout in the backend but serialised unconditionally (7.7) — the front end **shall** key the back face on `layout_variant`, never on field presence, because stored back values persist when an editor switches the layout away. Backend repair: `vitec_layout_variant` / `vitec_background_variant` are shared with `vitec_ctabanner`, and the index‑wise Content‑Blocks merge had dropped four card‑only select values (“Missing label”) — per‑type item lists now come from `columnsOverrides` (7.7); the Card‑Back palette lives in the Layout tab below the variant select. Editorial: the head‑matter version had been stuck at v1.17 since 1.18. | +| 1.20 | 2026‑10‑01 | **Interface change (additive).** Sub-market page template: frontend layout 21 with one colPos group per design section (`colPos110`..`colPos160`, clause 7.17); new Content Blocks `vitec_markethero`, `vitec_marketintro`, `vitec_marketinsights`; new plugin `vitec_productfeature` (key `productfeature`, one product in the 7.15.2 item shape); story cards gained `quote`/`quotePerson`/`quoteRole`; `vitec_usecaselist` gained the layout value `testimonials`; `vitec_productlist` gained hand-picked product selection (editor order, category filter ignored). Field semantics: Template_Sub-Market_Spezifikation_2026-10-01.md. | This document is drafted in the style of, and adopts the terminology conventions of, ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for @@ -1131,6 +1132,43 @@ content is extracted from the page’s `tt_content` rows via `index.queue.pages.fields.content` because the headless JSON output carries no `TYPO3SEARCH` markers. +--- + +### 7.17 Sub-market page template (frontend layout 21) + +A sub-market page is identified by `appearance.layout == "layout-21"`. Its +content is NOT read from element order: the backend layout assigns one column +per design section, and the JSON groups them as + +| Group | Section | Elements | +|---|---|---| +| `colPos110` | Hero | `vitec_markethero` | +| `colPos120` | Intro | `vitec_marketintro` | +| `colPos130` | Workflow | `vitec_introparagraph` + container with `vitec_card` flip cards | +| `colPos140` | Testimonials | `vitec_usecaselist`, `layout: "testimonials"` | +| `colPos150` | Products | `vitec_productfeature` + `vitec_productlist` (hand-picked) | +| `colPos160` | Industry Insights | `vitec_marketinsights` | + +The three `vitec_market*` elements are Content Blocks: their fields arrive +unprefixed under the element's `data` (7.7). Field reference and empty-value +semantics ("empty url = card hidden" etc.) are specified in +`Template_Sub-Market_Spezifikation_2026-10-01.md`; the per-field limits live +in the backend field descriptions. + +Related interface additions (all additive): + +- **Story cards** (7.5 shape, everywhere they appear) carry `quote`, + `quotePerson`, `quoteRole` - maintained once per story, empty strings until + maintained. The testimonial card renders them; the `vitec_usecaselist` + FlexForm gained the `layout` value `testimonials` for this. +- **`vitec_productfeature`** (payload key `productfeature`): exactly one + referenced product as a full product card in the 7.15.2 item shape, `null` + when unset or the product is not visible. Serialisation is shared with the + product list (one source of truth). +- **`vitec_productlist`** gained the FlexForm field *Hand-picked products*: + when set, the list contains exactly those products in the editor's order; + the category selection is ignored, the visibility filters stay in force. + ## 8 Content‑type catalogue | CType | TS pattern | Renderer / Processor | Payload key | Kind | diff --git a/packages/vitec/Resources/Private/Language/locallang_pages.xlf b/packages/vitec/Resources/Private/Language/locallang_pages.xlf index a433fdb..e03315b 100755 --- a/packages/vitec/Resources/Private/Language/locallang_pages.xlf +++ b/packages/vitec/Resources/Private/Language/locallang_pages.xlf @@ -66,6 +66,9 @@ Contact Page + + Sub-Market Page + diff --git a/packages/vitec/ext_tables.sql b/packages/vitec/ext_tables.sql index f7d4f47..0beff96 100755 --- a/packages/vitec/ext_tables.sql +++ b/packages/vitec/ext_tables.sql @@ -139,6 +139,9 @@ CREATE TABLE tx_vitec_domain_model_usecase ( slug varchar(255) DEFAULT '' NOT NULL, detail_page int(11) DEFAULT '0' NOT NULL, subtitle varchar(255) DEFAULT '' NOT NULL, + quote text, + quote_person varchar(255) DEFAULT '' NOT NULL, + quote_role varchar(255) DEFAULT '' NOT NULL, card_image int(11) unsigned DEFAULT '0' NOT NULL, customer_logo int(11) unsigned DEFAULT '0' NOT NULL, diff --git a/public/_frontend/index.html b/public/_frontend/index.html index a5f93a3..05b296d 100644 --- a/public/_frontend/index.html +++ b/public/_frontend/index.html @@ -23,8 +23,8 @@ VITEC - - + +