selected story titles (+ "no quote" flag) * vitec_productfeature -> the featured product's title * vitec_productlist -> hand-picked product titles / category info * * One class for all three - they only differ in how the FlexForm selection * is resolved to titles. */ class SubmarketPluginPreview extends StandardContentPreviewRenderer { public function renderPageModulePreviewContent(GridColumnItem $item): string { $record = $this->recordArray($item); $flexFormData = GeneralUtility::makeInstance(FlexFormService::class) ->convertFlexFormContentToArray((string)($record['pi_flexform'] ?? '')); $settings = $flexFormData['settings'] ?? []; $label = ''; $hint = ''; $items = []; switch ((string)$record['CType']) { case 'vitec_usecaselist': $layout = (string)($settings['layout'] ?? 'grid'); $label = $layout === 'testimonials' ? 'Testimonials' : 'Success Stories'; $uids = GeneralUtility::intExplode(',', (string)($settings['usecases'] ?? ''), true); foreach ($this->rows('tx_vitec_domain_model_usecase', $uids, ['quote']) as $row) { $items[] = $row['title'] . (trim((string)$row['quote']) === '' ? ' — ⚠ kein Zitat' : ''); } $hint = $uids === [] ? 'alle Stories (' . $layout . ')' : count($items) . ' ausgewählt · ' . $layout; break; case 'vitec_productfeature': $label = 'Featured Product'; $uid = (int)($settings['product'] ?? 0); foreach ($this->rows('tx_vitec_domain_model_product', $uid > 0 ? [$uid] : []) as $row) { $items[] = $row['title']; } $hint = $items[0] ?? '⚠ kein Produkt gewählt'; break; case 'vitec_productlist': $label = 'Products'; $uids = GeneralUtility::intExplode(',', (string)($settings['selectedproducts'] ?? ''), true); if ($uids !== []) { foreach ($this->rows('tx_vitec_domain_model_product', $uids) as $row) { $items[] = $row['title']; } $hint = count($items) . ' handverlesen'; } else { $hint = !empty($settings['allproducts']) ? 'alle Produkte' : 'nach Kategorie'; } break; } // v14: StandaloneView was removed - views come from the ViewFactory. $view = GeneralUtility::makeInstance(\TYPO3\CMS\Core\View\ViewFactoryInterface::class) ->create(new \TYPO3\CMS\Core\View\ViewFactoryData( templatePathAndFilename: 'EXT:vitec/Resources/Private/Templates/Preview/SubmarketSection.html', request: $GLOBALS['TYPO3_REQUEST'] ?? null, )); $view->assignMultiple([ 'label' => $label, 'hint' => $hint, 'items' => $items, 'header' => (string)($record['header'] ?? ''), ]); return $view->render(); } /** * Title rows for the given uids, in the given order. * * @param int[] $uids * @param string[] $extraFields * @return array> */ private function rows(string $table, array $uids, array $extraFields = []): array { if ($uids === []) { return []; } $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); $rows = $queryBuilder->select('uid', 'title', ...$extraFields)->from($table) ->where( $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)), $queryBuilder->expr()->eq('deleted', 0) ) ->executeQuery()->fetchAllAssociative(); $byUid = array_column($rows, null, 'uid'); $ordered = []; foreach ($uids as $uid) { if (isset($byUid[$uid])) { $ordered[] = $byUid[$uid]; } } return $ordered; } /** * v14: GridColumnItem::getRecord() returns a Domain\Record object, no * longer an array (found 2026-10-02 - crashed the page module preview). * Normalizes both shapes to the raw database array. * * @return array */ private function recordArray(GridColumnItem $item): array { $record = $item->getRecord(); if ($record instanceof \TYPO3\CMS\Core\Domain\Record) { $raw = $record->getRawRecord(); return $raw !== null ? $raw->toArray() : []; } return (array)$record; } }