171 lines
6.5 KiB
PHP
Executable File
171 lines
6.5 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\Middleware;
|
|
|
|
use Evomedien\Vitec\Forms\Delivery\DeliveryInterface;
|
|
use Evomedien\Vitec\Forms\Delivery\EmailDelivery;
|
|
use Evomedien\Vitec\Forms\Delivery\SalesforceDelivery;
|
|
use Evomedien\Vitec\Forms\FormDefinitions;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use Psr\Http\Server\MiddlewareInterface;
|
|
use Psr\Http\Server\RequestHandlerInterface;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Http\JsonResponse;
|
|
use TYPO3\CMS\Core\Service\FlexFormService;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Headless form endpoint: POST /api/vitec/form/<formKey>
|
|
*
|
|
* 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<string,mixed> */
|
|
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<string,mixed>
|
|
*/
|
|
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<string,string> $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]
|
|
);
|
|
}
|
|
}
|