cObj stays null, the cObj branch of render() never fires and the * call falls through to page discovery, which picks the FIRST element of * this CType on the page rather than the one actually being rendered. */ public function setContentObjectRenderer(ContentObjectRenderer $cObj): void { $this->cObj = $cObj; } private const NEWS_CTYPES = [ 'news_pi1', 'news_newsliststicky', 'news_newsselectedlist', 'news_newsdetail', 'news_newsdatemenu', 'news_categorylist', 'news_newssearchform', 'news_newssearchresult', 'news_taglist', ]; private const TEMPLATE_LAYOUTS = [ '1' => 'Compact List', '2' => 'Featured Story', '3' => 'Editorial Grid', '4' => 'Detail Spotlight', ]; private const TEMPLATE_LAYOUT_DEFAULTS = [ 'news_pi1' => '1', 'news_newsliststicky' => '1', 'news_newsselectedlist' => '1', 'news_newsdetail' => '2', 'news_newsdatemenu' => '3', 'news_categorylist' => '3', 'news_newssearchform' => '4', 'news_newssearchresult' => '4', 'news_taglist' => '3', ]; #[AsAllowedCallable] public function render(string $content, array $conf): string { try { $row = is_array($this->cObj?->data ?? null) ? $this->cObj->data : null; if ($row !== null && in_array((string)($row['CType'] ?? ''), self::NEWS_CTYPES, true)) { return $this->renderForRecord($row); } $pageId = $this->resolveCurrentPageId(); if ($pageId <= 0) { return ''; } $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tt_content'); $ctypeConstraints = []; foreach (self::NEWS_CTYPES as $ctype) { $ctypeConstraints[] = $queryBuilder->expr()->eq( 'CType', $queryBuilder->createNamedParameter($ctype, ParameterType::STRING) ); } $contentElements = $queryBuilder ->select('*') ->from('tt_content') ->where( $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), $queryBuilder->expr()->or(...$ctypeConstraints), $queryBuilder->expr()->eq('deleted', 0), $queryBuilder->expr()->eq('hidden', 0) ) ->orderBy('sorting', 'ASC') ->executeQuery() ->fetchAllAssociative(); if ($contentElements === []) { return ''; } return $this->renderForRecord($contentElements[0]); } catch (\Throwable $e) { return ''; } } /** * Render exactly the given news tt_content row. * * @param array $contentElement */ public function renderForRecord(array $contentElement): string { try { $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); $settings = $flexFormData['settings'] ?? []; $type = (string)($contentElement['CType'] ?? ''); $mode = $type === 'news_newsdetail' ? 'detail' : 'list'; $debug = (bool)($settings['debug'] ?? false); if ($mode === 'detail') { $news = $this->resolveDetailNews($contentElement, $settings); $response = [ 'mode' => 'detail', 'news' => $news !== null ? $this->serializeNews($news, $settings) : null, 'settings' => $this->buildSettingsPayload($contentElement, $settings), ]; if ($debug) { $response['debug'] = [ 'contentElementUid' => (int)($contentElement['uid'] ?? 0), 'settings' => $settings, ]; } return json_encode($response) ?: ''; } // Server-side pagination for the big overview list (contract // 0070-FE/0075-BE): only a plain/sticky list WITHOUT a limit and // WITHOUT a hand-picked selection paginates. That is exactly the // /insights/news element (#130); every teaser list carries a // limit (1-3) and keeps today's full behaviour, as do selected/ // search lists - the frontend falls back to client-side paging // wherever the `pagination` key is absent. $paginated = in_array($type, ['news_pi1', 'news_newsliststicky'], true) && (int)($settings['limit'] ?? 0) === 0 && trim((string)($settings['selectedList'] ?? '')) === ''; if ($paginated && $this->cObj !== null && $this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER ) { // `page`/`category` sit in cacheHash.excludedParameters, so the // TYPO3 page cache does NOT distinguish the query variants - // a cacheable render would bake page 1 in for every URL. As // USER_INT the element renders per request; EXT:headless wraps // the INT marker itself (JsonContentObject detects // '