Files
VITEC-website/packages/vitec/Configuration/TCA/Overrides/tt_content.php
T
orascheandClaude Fable 5.1 4bc38b1760 Product content check as page content (vitec_productcheck)
The product audit of public/product-check/ becomes data for the React frontend: new content element vitec_productcheck (plugin registration, wizard entry, TypoScript USER func) delivers under content.productcheck either the overview (every product with one {ok,count} check per field - hero, photos, og, video, teaser, subtitle, description, description2, highlights, capabilities, applications, features incl. icon count, resources, datasheets, solutions - plus per-field summary) or, with ?check=<uid>, the detail of one product (texts as stored HTML, images with original URL and 480px thumbnail, videos, feature cards with icons, resources, datasheets, solutions). ProductCheckService ports the PDO queries of the standalone page to Doctrine; the renderer is always USER_INT so the audit is never served from the page cache; check is excluded from the cHash. Migration adds the element to page 607 (/products/product-check). The standalone page stays untouched. Verified on DEV: overview 85 products in 0.13 s warm, detail 0.2-0.9 s, thumbnails resolve.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-12 11:34:59 +01:00

287 lines
15 KiB
PHP

<?php
declare(strict_types=1);
defined('TYPO3') or die();
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
(static function (): void {
$registerPluginWithFlexForm = static function (
string $pluginName,
string $pluginTitle,
string $flexFormFile,
?string $iconIdentifier = null
): void {
ExtensionUtility::registerPlugin(
'Vitec',
$pluginName,
$pluginTitle,
$iconIdentifier ?? 'content-plugin',
'plugins',
'',
$flexFormFile
);
};
$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');
// Product content audit as page content (2026-11-12): no FlexForm, the
// element has nothing to configure - data comes from ProductCheckService.
ExtensionUtility::registerPlugin(
'Vitec',
'Productcheck',
'Product Content Check (audit table)',
'vitec-plugin-productlist',
'plugins',
'Live audit of every product record: one row per product, one check per content field, details per row. Place once on /products/product-check.'
);
$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');
$registerPluginWithFlexForm('Marketshow', 'Show Single Market', 'FILE:EXT:vitec/Configuration/FlexForms/Market.xml', 'vitec-plugin-marketshow');
$registerPluginWithFlexForm('Solutionshow', 'Show Single Solution', 'FILE:EXT:vitec/Configuration/FlexForms/Solution.xml', 'vitec-plugin-solutionshow');
$registerPluginWithFlexForm('Usecaselist', 'Shows List of all Success Stories', 'FILE:EXT:vitec/Configuration/FlexForms/Usecaselist.xml', 'vitec-plugin-usecaselist');
$registerPluginWithFlexForm('Downloadcard', 'Download Card', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcard.xml', 'vitec-plugin-downloadcard');
$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';
// VITEC Forms — own wizard tab for the three form plugins.
$GLOBALS['TCA']['tt_content']['columns']['CType']['config']['itemGroups']['vitec_forms']
= 'VITEC Forms';
ExtensionUtility::registerPlugin(
'Vitec',
'Contactform',
'VITEC Contact Form',
'vitec-plugin-contactform',
'vitec_forms',
'Contact form — email or Salesforce delivery.',
'FILE:EXT:vitec/Configuration/FlexForms/Contactform.xml'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Demoform',
'VITEC Request Demo',
'vitec-plugin-demoform',
'vitec_forms',
'Demo request form — email or Salesforce delivery.',
'FILE:EXT:vitec/Configuration/FlexForms/Demoform.xml'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Helpdeskform',
'VITEC HelpDesk Access',
'vitec-plugin-helpdeskform',
'vitec_forms',
'Request access to the online helpdesk — email or Salesforce delivery.',
'FILE:EXT:vitec/Configuration/FlexForms/Helpdeskform.xml'
);
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'
);
// Collapsible sub-market section previews (2026-10-02)
$GLOBALS['TCA']['tt_content']['types']['vitec_usecaselist']['previewRenderer']
= \Evomedien\Vitec\Preview\SubmarketPluginPreview::class;
$GLOBALS['TCA']['tt_content']['types']['vitec_productfeature']['previewRenderer']
= \Evomedien\Vitec\Preview\SubmarketPluginPreview::class;
$GLOBALS['TCA']['tt_content']['types']['vitec_productlist']['previewRenderer']
= \Evomedien\Vitec\Preview\SubmarketPluginPreview::class;
$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'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Marketlist',
'VITEC Market List',
'vitec-plugin-marketlist',
'vitec',
'All markets as grid, list or carousel.',
'FILE:EXT:vitec/Configuration/FlexForms/Marketlist.xml'
);
ExtensionUtility::registerPlugin(
'Vitec',
'Solutionlist',
'VITEC Solution List',
'vitec-plugin-solutionlist',
'vitec',
'All solutions as grid, list or carousel.',
'FILE:EXT:vitec/Configuration/FlexForms/Solutionlist.xml'
);
// 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'],
// Background variants, ordered light to dark. Mutually exclusive by
// intent, but frame_class is a multi-select here (selectCheckBox,
// see above) and stays that way on purpose -- a restriction would
// have to be built for every other class too. If an editor ticks two
// backgrounds, both end up comma-separated in the JSON `frameClass`
// and the frontend decides which one wins.
//
// `vitec-bg-graphite`, `-blue` and `-midnight` were already styled in
// the frontend bundle but had no backend option; `-white` and
// `-light-grey` still need their CSS.
//
// The older 'Vitec: Dark Background' (vitec-dark) above stays as it
// is: existing content depends on it, and it is the most heavily
// styled class of them all.
['label' => 'Vitec: White Background', 'value' => 'vitec-bg-white'],
['label' => 'Vitec: Light-Grey Background', 'value' => 'vitec-bg-light-grey'],
['label' => 'Vitec: VITEC-Graphite Background', 'value' => 'vitec-bg-graphite'],
['label' => 'Vitec: VITEC-Blue Background', 'value' => 'vitec-bg-blue'],
['label' => 'Vitec: VITEC-Midnight Background', 'value' => 'vitec-bg-midnight'],
]
);
// Inline parent pointer: content elements owned by a Success Story record.
$GLOBALS['TCA']['tt_content']['columns']['tx_vitec_usecase_content'] = [
'config' => ['type' => 'passthrough'],
];
// The standard TYPO3 "Appearance" tab for the Content Blocks elements.
//
// Content Blocks builds each element's showitem itself
// (TcaGenerator::getContentElementStandardShowItem) and appends only the
// "Extended" tab -- Appearance is never part of it. The Extbase plugins are
// unaffected: ExtensionManagementUtility::addPlugin() seeds their type from
// tt_content types['header'], which carries the tab already.
//
// Until now that meant an editor could not reach layout, frame_class
// (including the Vitec classes registered above), spaceBefore/spaceAfter,
// sectionIndex or linkToTop on a Content Block at all.
//
// Deliberately NOT declared as a palette in every config.yaml: palettes live
// in TCA under a global identifier, so a second definition of `frames` would
// collide with the Core one -- the same trap that took the page module down
// on 2026-08-10. Pointing at the Core palettes keeps a single definition.
//
// This file runs after Content Blocks, which generates its TCA on
// BeforeTcaOverridesEvent.
$appearanceTab = '--div--;core.form.tabs:appearance,'
. '--palette--;;frames,'
. '--palette--;;appearanceLinks';
$extendedTab = '--div--;core.form.tabs:extended';
foreach ($GLOBALS['TCA']['tt_content']['types'] as $cType => &$typeConfig) {
// Own elements only, and only those still missing the tab -- that skips
// the plugins and makes a repeated run a no-op.
if (!str_starts_with((string)$cType, 'vitec_')
|| !isset($typeConfig['showitem'])
|| str_contains($typeConfig['showitem'], '--palette--;;frames')
) {
continue;
}
// Content Blocks puts "Extended" last, so Appearance goes in front of it
// and the tab order matches the Core content elements. Should a future
// version drop that marker, append rather than silently do nothing.
if (str_contains($typeConfig['showitem'], $extendedTab)) {
$typeConfig['showitem'] = str_replace(
$extendedTab,
$appearanceTab . ',' . $extendedTab,
$typeConfig['showitem']
);
} else {
$typeConfig['showitem'] = rtrim($typeConfig['showitem'], ", \t\n\r") . ',' . $appearanceTab;
}
}
unset($typeConfig);
// Shared Content-Block select columns (fix 2026-09-25).
//
// `layout_variant` and `background_variant` are defined by BOTH vitec/card
// and vitec/cta-banner - same identifier under the vendor prefix, so both
// blocks write the same tt_content columns. Content Blocks merges such
// diverging item lists INDEX-wise into the base column and generates no
// per-type items override: the card-only values `vertical`, `horizontal`,
// `horizontal_reverse` and background `none` did not exist as items at
// all, and FormEngine showed "[ Missing label ]" for exactly those stored
// values (hero-section / intro-paragraph dodged the trap long ago with
// `hero_` / `intro_` prefixes).
//
// Fixed here WITHOUT renaming columns (existing data and the JSON keys
// `layout_variant` / `background_variant` stay): the base column carries
// the short cta-banner lists, vitec_card gets its full lists through
// columnsOverrides. FormEngine and the DataHandler both respect
// columnsOverrides; the per-type merge is array_replace_recursive, so an
// items override can only EXTEND the base list, never shorten it - which
// holds (9/7 items over 3/5). New blocks must not reuse these identifiers
// with diverging items; prefix them instead.
$GLOBALS['TCA']['tt_content']['columns']['vitec_layout_variant']['config']['items'] = [
['label' => 'Centered (text over background)', 'value' => 'centered'],
['label' => 'Split (text left, CTAs right)', 'value' => 'split'],
['label' => 'Stacked (text top, CTAs below)', 'value' => 'stacked'],
];
$GLOBALS['TCA']['tt_content']['columns']['vitec_background_variant']['config']['items'] = [
['label' => 'Orange', 'value' => 'orange'],
['label' => 'Blue', 'value' => 'blue'],
['label' => 'Graphite', 'value' => 'graphite'],
['label' => 'Midnight', 'value' => 'midnight'],
['label' => 'Light (neutral)', 'value' => 'light'],
];
$GLOBALS['TCA']['tt_content']['types']['vitec_card']['columnsOverrides']['vitec_layout_variant']['config']['items'] = [
['label' => 'Vertical (image top, content below)', 'value' => 'vertical'],
['label' => 'Horizontal (image left, content right)', 'value' => 'horizontal'],
['label' => 'Horizontal Reverse (content left, image right)', 'value' => 'horizontal_reverse'],
['label' => 'Image Overlay (text over image)', 'value' => 'image_overlay'],
['label' => 'Text Only (no image)', 'value' => 'text_only'],
['label' => 'Icon Card (icon focused, text below)', 'value' => 'icon_card'],
['label' => 'Feature (large highlighted card)', 'value' => 'feature'],
['label' => 'Compact (small card, tight spacing)', 'value' => 'compact'],
['label' => 'Flip Card (front / back)', 'value' => 'flipcard'],
];
$GLOBALS['TCA']['tt_content']['types']['vitec_card']['columnsOverrides']['vitec_background_variant']['config']['items'] = [
['label' => 'None', 'value' => 'none'],
['label' => 'Orange', 'value' => 'orange'],
['label' => 'Blue', 'value' => 'blue'],
['label' => 'Graphite', 'value' => 'graphite'],
['label' => 'Midnight', 'value' => 'midnight'],
['label' => 'Light (neutral)', 'value' => 'light'],
['label' => 'Dark (neutral)', 'value' => 'dark'],
];
})();