| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- <?php
- declare(strict_types=1);
- use App\App\Bootstrap;
- use App\Form\FormSchema;
- use App\Form\Validator;
- use App\Mail\Mailer;
- use App\Storage\FileUploadStore;
- use App\Storage\JsonStore;
- use App\Security\Csrf;
- use App\Security\RateLimiter;
- require dirname(__DIR__) . '/src/autoload.php';
- Bootstrap::init();
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Method not allowed'], 405);
- }
- $csrf = $_POST['csrf'] ?? '';
- if (!Csrf::validate(is_string($csrf) ? $csrf : null)) {
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Ungültiges CSRF-Token.'], 419);
- }
- if (trim((string) ($_POST['website'] ?? '')) !== '') {
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Anfrage blockiert.'], 400);
- }
- $email = strtolower(trim((string) ($_POST['email'] ?? '')));
- if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Bitte gültige E-Mail eingeben.'], 422);
- }
- $app = Bootstrap::config('app');
- $limiter = new RateLimiter();
- $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
- $rateKey = sprintf('submit:%s:%s', $ip, $email);
- if (!$limiter->allow($rateKey, (int) $app['rate_limit']['requests'], (int) $app['rate_limit']['window_seconds'])) {
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Zu viele Anfragen.'], 429);
- }
- $formDataRaw = $_POST['form_data'] ?? [];
- $formData = [];
- if (is_array($formDataRaw)) {
- foreach ($formDataRaw as $key => $value) {
- if (!is_string($key)) {
- continue;
- }
- $formData[$key] = is_array($value) ? '' : trim((string) $value);
- }
- }
- $store = new JsonStore();
- $schema = new FormSchema();
- $uploadStore = new FileUploadStore();
- $validator = new Validator($schema);
- // Fast fail before upload processing to avoid writing files for known duplicates.
- if ($store->hasSubmission($email)) {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'already_submitted' => true,
- 'message' => 'Für diese E-Mail liegt bereits ein abgeschlossener Antrag vor.',
- ], 409);
- }
- try {
- $submitResult = $store->withEmailLock($email, static function () use ($store, $email, $formData, $validator, $uploadStore, $schema): array {
- if ($store->hasSubmission($email)) {
- return [
- 'ok' => false,
- 'already_submitted' => true,
- 'message' => 'Für diese E-Mail liegt bereits ein abgeschlossener Antrag vor.',
- ];
- }
- $uploadResult = $uploadStore->processUploads($_FILES, $schema->getUploadFields(), $store->emailKey($email));
- if (!empty($uploadResult['errors'])) {
- return [
- 'ok' => false,
- 'already_submitted' => false,
- 'message' => 'Fehler bei Uploads.',
- 'errors' => $uploadResult['errors'],
- ];
- }
- $draft = $store->getDraft($email) ?? [];
- $mergedFormData = array_merge((array) ($draft['form_data'] ?? []), $formData);
- $mergedUploads = (array) ($draft['uploads'] ?? []);
- foreach ($uploadResult['uploads'] as $field => $items) {
- $mergedUploads[$field] = array_values(array_merge((array) ($mergedUploads[$field] ?? []), $items));
- }
- $errors = $validator->validateSubmit($mergedFormData, $mergedUploads);
- if (!empty($errors)) {
- $store->saveDraft($email, [
- 'step' => 4,
- 'form_data' => $mergedFormData,
- 'uploads' => $uploadResult['uploads'],
- ]);
- return [
- 'ok' => false,
- 'already_submitted' => false,
- 'message' => 'Bitte Pflichtfelder prüfen.',
- 'errors' => $errors,
- ];
- }
- $submission = $store->saveSubmission($email, [
- 'step' => 4,
- 'form_data' => $mergedFormData,
- 'uploads' => $mergedUploads,
- ]);
- return [
- 'ok' => true,
- 'submission' => $submission,
- ];
- });
- } catch (Throwable $e) {
- Bootstrap::log('app', 'submit lock error: ' . $e->getMessage());
- Bootstrap::jsonResponse(['ok' => false, 'message' => 'Abschluss derzeit nicht möglich.'], 500);
- }
- if (($submitResult['ok'] ?? false) !== true) {
- $status = ($submitResult['already_submitted'] ?? false) ? 409 : 422;
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'already_submitted' => (bool) ($submitResult['already_submitted'] ?? false),
- 'message' => (string) ($submitResult['message'] ?? 'Abschluss fehlgeschlagen.'),
- 'errors' => $submitResult['errors'] ?? [],
- ], $status);
- }
- $submission = $submitResult['submission'];
- $mailer = new Mailer();
- $mailer->sendSubmissionMails($submission);
- Bootstrap::jsonResponse([
- 'ok' => true,
- 'message' => 'Antrag erfolgreich übermittelt.',
- 'application_key' => $submission['application_key'] ?? null,
- ]);
|