save-draft.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. <?php
  2. declare(strict_types=1);
  3. use App\App\Bootstrap;
  4. use App\Form\FormSchema;
  5. use App\Storage\FileUploadStore;
  6. use App\Storage\JsonStore;
  7. use App\Security\Csrf;
  8. use App\Security\RateLimiter;
  9. require dirname(__DIR__) . '/src/autoload.php';
  10. Bootstrap::init();
  11. if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  12. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Method not allowed'], 405);
  13. }
  14. $csrf = $_POST['csrf'] ?? '';
  15. if (!Csrf::validate(is_string($csrf) ? $csrf : null)) {
  16. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Ungültiges CSRF-Token.'], 419);
  17. }
  18. if (trim((string) ($_POST['website'] ?? '')) !== '') {
  19. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Anfrage blockiert.'], 400);
  20. }
  21. $email = strtolower(trim((string) ($_POST['email'] ?? '')));
  22. if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
  23. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Bitte gültige E-Mail eingeben.'], 422);
  24. }
  25. $app = Bootstrap::config('app');
  26. $limiter = new RateLimiter();
  27. $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
  28. $rateKey = sprintf('save:%s:%s', $ip, $email);
  29. if (!$limiter->allow($rateKey, (int) $app['rate_limit']['requests'], (int) $app['rate_limit']['window_seconds'])) {
  30. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Zu viele Speicheranfragen.'], 429);
  31. }
  32. $step = (int) ($_POST['step'] ?? 1);
  33. $formDataRaw = $_POST['form_data'] ?? [];
  34. $formData = [];
  35. if (is_array($formDataRaw)) {
  36. foreach ($formDataRaw as $key => $value) {
  37. if (!is_string($key)) {
  38. continue;
  39. }
  40. $formData[$key] = is_array($value) ? '' : trim((string) $value);
  41. }
  42. }
  43. $store = new JsonStore();
  44. try {
  45. $result = $store->withEmailLock($email, static function () use ($store, $email, $step, $formData): array {
  46. if ($store->hasSubmission($email)) {
  47. return [
  48. 'blocked' => true,
  49. 'message' => 'Für diese E-Mail wurde bereits ein Antrag abgeschlossen.',
  50. ];
  51. }
  52. return [
  53. 'blocked' => false,
  54. 'draft' => $store->saveDraft($email, [
  55. 'step' => max(1, $step),
  56. 'form_data' => $formData,
  57. 'uploads' => [],
  58. ]),
  59. ];
  60. });
  61. } catch (Throwable $e) {
  62. Bootstrap::log('app', 'save-draft lock error: ' . $e->getMessage());
  63. Bootstrap::jsonResponse(['ok' => false, 'message' => 'Speichern derzeit nicht möglich.'], 500);
  64. }
  65. if (($result['blocked'] ?? false) === true) {
  66. Bootstrap::jsonResponse([
  67. 'ok' => false,
  68. 'already_submitted' => true,
  69. 'message' => (string) ($result['message'] ?? 'Bereits abgeschlossen.'),
  70. ], 409);
  71. }
  72. $schema = new FormSchema();
  73. $uploadStore = new FileUploadStore();
  74. $uploadResult = $uploadStore->processUploads($_FILES, $schema->getUploadFields(), $store->emailKey($email));
  75. if (!empty($uploadResult['uploads'])) {
  76. try {
  77. $store->withEmailLock($email, static function () use ($store, $email, $step, $formData, $uploadResult): void {
  78. if ($store->hasSubmission($email)) {
  79. return;
  80. }
  81. $store->saveDraft($email, [
  82. 'step' => max(1, $step),
  83. 'form_data' => $formData,
  84. 'uploads' => $uploadResult['uploads'],
  85. ]);
  86. });
  87. } catch (Throwable $e) {
  88. Bootstrap::log('app', 'save-draft upload merge error: ' . $e->getMessage());
  89. }
  90. }
  91. $draft = $store->getDraft($email);
  92. Bootstrap::jsonResponse([
  93. 'ok' => true,
  94. 'message' => 'Entwurf gespeichert.',
  95. 'updated_at' => $draft['updated_at'] ?? null,
  96. 'upload_errors' => $uploadResult['errors'],
  97. 'uploads' => $draft['uploads'] ?? [],
  98. ]);