…
"|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;
}
}