{mode: "detail", product: {…} | null} * on failure {mode: "error", error} * * Always USER_INT: the audit must show the database as it is right now, * so neither the TYPO3 page cache nor the `check` variant may stick. * `check` is excluded from the cHash in ext_localconf.php. The front end * is expected to load the detail variant client-side from /_json (one * request per opened row), not to navigate the SSR page with the query. */ final class ProductCheckJsonRenderer { protected ?ContentObjectRenderer $cObj = null; public function setContentObjectRenderer(ContentObjectRenderer $cObj): void { $this->cObj = $cObj; } #[AsAllowedCallable] public function render(string $content, array $conf): string { if ($this->cObj !== null && $this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) { $this->cObj->convertToUserIntObject(); return ''; } try { $service = GeneralUtility::makeInstance(ProductCheckService::class); $queryParams = $GLOBALS['TYPO3_REQUEST']?->getQueryParams() ?? []; $check = (int)($queryParams['check'] ?? 0); if ($check > 0) { $payload = ['mode' => 'detail', 'product' => $service->detail($check)]; } else { $payload = ['mode' => 'overview'] + $service->overview(); } } catch (\Throwable $e) { $payload = ['mode' => 'error', 'error' => $e->getMessage()]; } return (string)json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); } }