data or page discovery). * `renderForRecord()` = one specific tt_content row; reused by * ContainerChildrenProcessor / ContentElementResolver. Exception-safe. */ class EventlistJsonRenderer { #[AsAllowedCallable] public function render(string $content, array $conf): string { // 1) cObj data path $row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null; if ($row && (string)($row['CType'] ?? '') === 'vitec_eventlist') { return $this->renderForRecord($row); } // 2) Page discovery via v14 request attribute $pageId = 0; $request = $GLOBALS['TYPO3_REQUEST'] ?? null; if ($request !== null) { $pageInfo = $request->getAttribute('frontend.page.information'); if ($pageInfo !== null) { $pageId = (int)$pageInfo->getId(); } } if ($pageId <= 0) { $pageId = (int)($GLOBALS['TSFE']->id ?? 0); } if ($pageId <= 0) { return ''; } $qb = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tt_content'); $contentElements = $qb ->select('*') ->from('tt_content') ->where( $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_eventlist', ParameterType::STRING)), $qb->expr()->eq('deleted', 0), $qb->expr()->eq('hidden', 0) ) ->executeQuery() ->fetchAllAssociative(); if (empty($contentElements)) { return ''; } return $this->renderForRecord($contentElements[0]); } /** * @param array $contentElement */ public function renderForRecord(array $contentElement): string { try { $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); $settings = $flexFormData['settings'] ?? []; $showPast = (bool)($settings['showpast'] ?? false); $daysInAdvance = (int)($settings['daysinadvance'] ?? 0); $limit = (int)($settings['limit'] ?? 0); $debugMode = (bool)($settings['debug'] ?? false); $layout = (string)($settings['layout'] ?? 'list'); $upcoming = $this->fetchEvents(true, $limit, $daysInAdvance); $response = [ 'events' => array_map(fn($e) => $this->serializeEvent($e), $upcoming), 'settings' => [ 'layout' => $layout, 'showpast' => $showPast, 'daysinadvance' => $daysInAdvance, 'limit' => $limit, ], ]; if ($showPast) { $past = $this->fetchEvents(false, $limit); $response['pastEvents'] = array_map(fn($e) => $this->serializeEvent($e), $past); } if ($debugMode) { $response['debug'] = [ 'upcomingCount' => count($upcoming), 'settings' => $settings, ]; } return json_encode($response); } catch (\Throwable $e) { return ''; } } /** * @return array> */ private function fetchEvents(bool $upcoming, int $limit, int $daysInAdvance = 0): array { $todayMidnight = strtotime('today'); $qb = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_vitec_domain_model_event'); $expr = $qb->expr(); $qb ->select('*') ->from('tx_vitec_domain_model_event') ->where( $expr->eq('deleted', 0), $expr->eq('hidden', 0), $expr->eq('hideonwebsite', 0) ); if ($upcoming) { // running or future: end >= today, or (no end and start >= today) $qb->andWhere( $expr->or( $expr->gte('eventend', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER)), $expr->and( $expr->eq('eventend', 0), $expr->gte('eventstart', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER)) ) ) ); $qb->orderBy('eventstart', 'ASC'); if ($daysInAdvance > 0) { $qb->andWhere( $expr->lt('eventstart', $qb->createNamedParameter($todayMidnight + ($daysInAdvance * 86400), ParameterType::INTEGER)) ); } } else { // past: (end > 0 and end < today) or (no end and 0 < start < today) $qb->andWhere( $expr->or( $expr->and( $expr->gt('eventend', 0), $expr->lt('eventend', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER)) ), $expr->and( $expr->eq('eventend', 0), $expr->gt('eventstart', 0), $expr->lt('eventstart', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER)) ) ) ); $qb->orderBy('eventstart', 'DESC'); } if ($limit > 0) { $qb->setMaxResults($limit); } return $qb->executeQuery()->fetchAllAssociative(); } /** * @param array $event * @return array */ protected function serializeEvent(array $event): array { $uid = (int)$event['uid']; $start = (int)($event['eventstart'] ?? 0); $end = (int)($event['eventend'] ?? 0); return [ 'uid' => $uid, 'title' => (string)($event['title'] ?? ''), 'slug' => (string)($event['slug'] ?? ''), 'teaser' => (string)($event['teaser'] ?? ''), 'description' => (string)($event['description'] ?? ''), 'eventstart' => $start > 0 ? date('Y-m-d', $start) : null, 'eventend' => $end > 0 ? date('Y-m-d', $end) : null, 'venue' => (string)($event['venue'] ?? ''), 'booth' => (string)($event['booth'] ?? ''), 'city' => (string)($event['city'] ?? ''), 'country' => (string)($event['country'] ?? ''), 'attendancemode' => (string)($event['attendancemode'] ?? 'offline'), 'eventstatus' => (string)($event['eventstatus'] ?? 'scheduled'), 'eventurl' => (string)($event['eventurl'] ?? ''), 'meetinglink' => (string)($event['meetinglink'] ?? ''), 'image' => $this->getEventImage($uid), 'categories' => $this->getEventCategories($uid), ]; } /** * @return array|null */ protected function getEventImage(int $eventUid): ?array { $qb = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('sys_file_reference'); $fileRefData = $qb ->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop') ->from('sys_file_reference', 'sfr') ->where( $qb->expr()->eq('sfr.tablenames', $qb->createNamedParameter('tx_vitec_domain_model_event', ParameterType::STRING)), $qb->expr()->eq('sfr.fieldname', $qb->createNamedParameter('image', ParameterType::STRING)), $qb->expr()->eq('sfr.uid_foreign', $qb->createNamedParameter($eventUid, ParameterType::INTEGER)), $qb->expr()->eq('sfr.deleted', 0), $qb->expr()->eq('sfr.hidden', 0) ) ->orderBy('sfr.sorting_foreign') ->setMaxResults(1) ->executeQuery() ->fetchAssociative(); if (!$fileRefData) { return null; } try { $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $imageService = GeneralUtility::makeInstance(ImageService::class); $fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']); $srcset = []; foreach ([400, 800] as $width) { $variant = $imageService->applyProcessingInstructions( $fileReference, ['width' => $width, 'crop' => $fileRefData['crop'] ?? null] ); $srcset[] = [ 'url' => $imageService->getImageUri($variant), 'width' => $width, 'descriptor' => $width . 'w', ]; } $default = $imageService->applyProcessingInstructions( $fileReference, ['width' => 400, 'crop' => $fileRefData['crop'] ?? null] ); return [ 'uid' => (int)$fileRefData['uid'], 'url' => $imageService->getImageUri($default), 'title' => $fileRefData['title'] ?? '', 'alternative' => $fileRefData['alternative'] ?? '', 'description' => $fileRefData['description'] ?? '', 'srcset' => $srcset, 'properties' => [ 'width' => $fileReference->getProperty('width'), 'height' => $fileReference->getProperty('height'), 'mimeType' => $fileReference->getProperty('mime_type'), ], ]; } catch (\Exception $e) { return null; } } protected function getEventCategories(int $eventUid): array { $qb = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('sys_category'); $categories = $qb ->select('c.uid', 'c.title', 'c.description') ->from('sys_category', 'c') ->join( 'c', 'sys_category_record_mm', 'mm', 'mm.uid_local = c.uid AND mm.tablenames = ' . $qb->createNamedParameter('tx_vitec_domain_model_event', ParameterType::STRING) . ' AND mm.fieldname = ' . $qb->createNamedParameter('categories', ParameterType::STRING) ) ->where( $qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($eventUid, ParameterType::INTEGER)), $qb->expr()->eq('c.deleted', 0), $qb->expr()->eq('c.hidden', 0) ) ->orderBy('mm.sorting', 'ASC') ->executeQuery() ->fetchAllAssociative(); return array_map(static function ($cat) { return [ 'uid' => (int)$cat['uid'], 'title' => (string)($cat['title'] ?? ''), 'description' => (string)($cat['description'] ?? ''), ]; }, $categories); } }