"|null, "locations": [ … ] } * * Each location follows the frontend contract (id, slug, name, countryCode, * coordinates, address, contact, links, marker, sorting, active). * * `render()` = top-level plugin / page discovery. `renderForRecord()` = one * specific tt_content row (reused by ContainerChildrenProcessor for nested * plugins). Exception-safe. */ class LocationsJsonRenderer { private const TABLE = 'tx_vitec_domain_model_location'; #[AsAllowedCallable] public function render(string $content, array $conf): string { $row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null; if ($row && (string)($row['CType'] ?? '') === 'vitec_locationlist') { return $this->renderForRecord($row); } $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'); $ces = $qb ->select('*') ->from('tt_content') ->where( $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_locationlist', ParameterType::STRING)), $qb->expr()->eq('deleted', 0), $qb->expr()->eq('hidden', 0) ) ->executeQuery() ->fetchAllAssociative(); if (empty($ces)) { return ''; } return $this->renderForRecord($ces[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'] ?? []; $variant = (string)($settings['variant'] ?? 'grid'); $mapText = trim((string)($settings['maptext'] ?? '')); $debugMode = (bool)($settings['debug'] ?? false); $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE); $rows = $qb ->select('*') ->from(self::TABLE) ->where( $qb->expr()->eq('deleted', 0), $qb->expr()->eq('hidden', 0) ) ->orderBy('sorting', 'ASC') ->executeQuery() ->fetchAllAssociative(); $locations = array_map(fn(array $r): array => $this->serializeLocation($r), $rows); $response = [ 'variant' => $variant, 'mapText' => $variant === 'map' && $mapText !== '' ? $mapText : null, 'locations' => $locations, ]; if ($debugMode) { $response['debug'] = ['count' => count($locations), 'settings' => $settings]; } return (string)json_encode($response); } catch (\Throwable $e) { return ''; } } /** * @param array $r * @return array */ private function serializeLocation(array $r): array { $name = (string)($r['title'] ?? ''); return [ 'id' => (int)$r['uid'], 'slug' => (string)($r['slug'] ?? ''), 'name' => $name, 'countryCode' => (string)($r['country_code'] ?? ''), 'coordinates' => [ 'latitude' => (float)($r['latitude'] ?? 0), 'longitude' => (float)($r['longitude'] ?? 0), ], 'address' => [ 'company' => $this->nullIfEmpty($r['address_company'] ?? ''), 'street' => $this->nullIfEmpty($r['street'] ?? ''), 'additional' => $this->nullIfEmpty($r['address_additional'] ?? ''), 'postalCode' => $this->nullIfEmpty($r['postal_code'] ?? ''), 'city' => $this->nullIfEmpty($r['city'] ?? ''), 'region' => $this->nullIfEmpty($r['region'] ?? ''), 'country' => $this->nullIfEmpty($r['country'] ?? ''), ], 'contact' => [ 'phone' => $this->nullIfEmpty($r['phone'] ?? ''), 'fax' => $this->nullIfEmpty($r['fax'] ?? ''), 'email' => $this->nullIfEmpty($r['email'] ?? ''), ], 'links' => [ 'contact' => $this->resolveLink((string)($r['contact_link'] ?? '')), 'legal' => $this->resolveLinkLines((string)($r['legal_links'] ?? '')), ], 'marker' => [ 'label' => (string)($r['marker_label'] ?? '') !== '' ? (string)$r['marker_label'] : $name, 'color' => (string)($r['marker_color'] ?? '#ff6633'), 'size' => (float)($r['marker_size'] ?? 0.5), ], 'sorting' => (int)($r['sorting'] ?? 0), 'active' => true, ]; } private function nullIfEmpty(mixed $value): ?string { $value = trim((string)$value); return $value === '' ? null : $value; } /** Resolve a typolink parameter (t3://…, page uid, URL) to a URL/path. */ private function resolveLink(string $parameter): ?string { $parameter = trim($parameter); if ($parameter === '') { return null; } try { $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); $url = $cObj->typoLink_URL(['parameter' => $parameter]); return $url !== '' ? $url : $parameter; } catch (\Throwable $e) { return $parameter; } } /** @return array */ private function resolveLinkLines(string $lines): array { $out = []; foreach (preg_split('/\r\n|\r|\n/', $lines) ?: [] as $line) { $resolved = $this->resolveLink($line); if ($resolved !== null) { $out[] = $resolved; } } return $out; } }