* * Accepts JSON (or form-encoded) submissions from the React frontend for * the VITEC form plugins (contact | demo | helpdesk): * * 1. honeypot check (silently accepted, nothing stored) * 2. server-side validation against FormDefinitions (single source of truth) * 3. submission stored in tx_vitec_form_submission (backup log) * 4. delivery via strategy: email (active) or salesforce (prepared stub) — * configured in the FlexForm of the form's plugin element * * A stored-but-undelivered submission still responds success:true — the * data is safe in the log and the delivery status is visible in the backend. */ final class FormSubmissionMiddleware implements MiddlewareInterface { private const PATH_PATTERN = '#^/api/vitec/form/([a-z]+)/?$#'; public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $path = $request->getUri()->getPath(); if (preg_match(self::PATH_PATTERN, $path, $m) !== 1) { return $handler->handle($request); } $formKey = $m[1]; if (strtoupper($request->getMethod()) !== 'POST') { return new JsonResponse(['success' => false, 'errors' => ['_form' => 'POST only']], 405); } if (FormDefinitions::get($formKey) === null) { return new JsonResponse(['success' => false, 'errors' => ['_form' => 'Unknown form']], 404); } try { $payload = $this->parseBody($request); // Honeypot: pretend success, store nothing. if (trim((string)($payload[FormDefinitions::HONEYPOT_FIELD] ?? '')) !== '') { return new JsonResponse(['success' => true]); } $errors = FormDefinitions::validate($formKey, $payload); if ($errors !== []) { return new JsonResponse(['success' => false, 'errors' => $errors], 422); } $clean = FormDefinitions::filterPayload($formKey, $payload); $settings = $this->pluginSettings($formKey); $method = (string)($settings['delivery'] ?? 'email'); $submissionUid = $this->store($formKey, $clean, $method); try { $this->delivery($method)->deliver($formKey, $clean, $settings); $this->updateStatus($submissionUid, 'sent', ''); } catch (\Throwable $e) { $this->updateStatus($submissionUid, 'failed', $e->getMessage()); } // Data is safely logged either way. return new JsonResponse(['success' => true]); } catch (\Throwable $e) { return new JsonResponse(['success' => false, 'errors' => ['_form' => 'Unexpected error']], 500); } } /** @return array */ private function parseBody(ServerRequestInterface $request): array { $contentType = $request->getHeaderLine('Content-Type'); if (str_contains($contentType, 'application/json')) { $decoded = json_decode((string)$request->getBody(), true); return is_array($decoded) ? $decoded : []; } $parsed = $request->getParsedBody(); return is_array($parsed) ? $parsed : []; } /** * Delivery settings come from the FlexForm of the (first) plugin element * of this form type — one configuration per form type. * * @return array */ private function pluginSettings(string $formKey): array { try { $ctype = array_search($formKey, FormDefinitions::CTYPE_MAP, true); if ($ctype === false) { return []; } $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content'); $row = $qb ->select('pi_flexform') ->from('tt_content') ->where( $qb->expr()->eq('CType', $qb->createNamedParameter($ctype)), $qb->expr()->eq('deleted', 0), $qb->expr()->eq('hidden', 0) ) ->orderBy('uid', 'ASC') ->setMaxResults(1) ->executeQuery() ->fetchAssociative(); if (!$row) { return []; } $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); $data = $flexFormService->convertFlexFormContentToArray((string)$row['pi_flexform']); return $data['settings'] ?? []; } catch (\Throwable $e) { return []; } } private function delivery(string $method): DeliveryInterface { return $method === 'salesforce' ? GeneralUtility::makeInstance(SalesforceDelivery::class) : GeneralUtility::makeInstance(EmailDelivery::class); } /** @param array $payload */ private function store(string $formKey, array $payload, string $method): int { $conn = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable('tx_vitec_form_submission'); $conn->insert('tx_vitec_form_submission', [ 'pid' => 0, 'crdate' => time(), 'tstamp' => time(), 'form_key' => $formKey, 'payload' => (string)json_encode($payload, JSON_UNESCAPED_UNICODE), 'delivery_method' => $method, 'delivery_status' => 'pending', ]); return (int)$conn->lastInsertId(); } private function updateStatus(int $uid, string $status, string $error): void { GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable('tx_vitec_form_submission') ->update( 'tx_vitec_form_submission', ['delivery_status' => $status, 'delivery_error' => mb_substr($error, 0, 1000), 'tstamp' => time()], ['uid' => $uid] ); } }