submissionformatter.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Mail;
  4. use App\Form\FormSchema;
  5. /**
  6. * Formats raw submission data into human-readable label/value pairs,
  7. * respecting visibility conditions and field types from the form schema.
  8. */
  9. final class SubmissionFormatter
  10. {
  11. private FormSchema $schema;
  12. public function __construct(FormSchema $schema)
  13. {
  14. $this->schema = $schema;
  15. }
  16. /**
  17. * @param array<string, mixed> $submission
  18. * @return array<int, array{title: string, fields: array<int, array{label: string, value: string}>}>
  19. */
  20. public function formatSteps(array $submission): array
  21. {
  22. $formData = (array) ($submission['form_data'] ?? []);
  23. $formData['is_minor'] = $this->deriveIsMinor($formData);
  24. $submissionEmail = trim((string) ($submission['email'] ?? ''));
  25. $result = [];
  26. foreach ($this->schema->getSteps() as $step) {
  27. $title = (string) ($step['title'] ?? '');
  28. $fields = [];
  29. foreach (($step['fields'] ?? []) as $fieldDef) {
  30. $key = (string) ($fieldDef['key'] ?? '');
  31. $type = (string) ($fieldDef['type'] ?? 'text');
  32. if ($key === '' || $type === 'file') {
  33. continue;
  34. }
  35. if (!$this->isVisible($fieldDef, $formData)) {
  36. continue;
  37. }
  38. $raw = $formData[$key] ?? null;
  39. if ($raw === null || $raw === '' || $raw === []) {
  40. continue;
  41. }
  42. $label = (string) ($fieldDef['label'] ?? $key);
  43. $value = $this->formatValue($raw, $fieldDef);
  44. $fields[] = ['label' => $label, 'value' => $value];
  45. }
  46. if ($this->isPersonalDataStep($title) && $submissionEmail !== '' && !$this->hasEmailField($fields)) {
  47. $fields[] = ['label' => 'E-Mail', 'value' => $submissionEmail];
  48. }
  49. if ($fields !== []) {
  50. $result[] = ['title' => $title, 'fields' => $fields];
  51. }
  52. }
  53. return $result;
  54. }
  55. /**
  56. * @param array<string, mixed> $submission
  57. * @return array<int, array{label: string, files: array<int, string>}>
  58. */
  59. public function formatUploads(array $submission): array
  60. {
  61. $uploads = (array) ($submission['uploads'] ?? []);
  62. $uploadFields = $this->schema->getUploadFields();
  63. $result = [];
  64. foreach ($uploadFields as $key => $fieldDef) {
  65. $files = $uploads[$key] ?? [];
  66. if (!is_array($files) || $files === []) {
  67. continue;
  68. }
  69. $label = (string) ($fieldDef['label'] ?? $key);
  70. $names = [];
  71. foreach ($files as $file) {
  72. if (is_array($file)) {
  73. $names[] = (string) ($file['original_filename'] ?? 'Datei');
  74. }
  75. }
  76. if ($names !== []) {
  77. $result[] = ['label' => $label, 'files' => $names];
  78. }
  79. }
  80. return $result;
  81. }
  82. /** @param array<string, mixed> $fieldDef */
  83. private function formatValue(mixed $raw, array $fieldDef): string
  84. {
  85. $type = (string) ($fieldDef['type'] ?? 'text');
  86. if ($type === 'date' && is_string($raw)) {
  87. return $this->formatDate($raw);
  88. }
  89. if ($type === 'checkbox') {
  90. return ((string) $raw === '1' || $raw === true) ? 'Ja' : 'Nein';
  91. }
  92. if ($type === 'select' && is_string($raw)) {
  93. return $this->resolveSelectLabel($raw, $fieldDef);
  94. }
  95. if ($type === 'table' && is_array($raw)) {
  96. return $this->formatTable($raw, $fieldDef);
  97. }
  98. if (is_scalar($raw)) {
  99. return (string) $raw;
  100. }
  101. return json_encode($raw, JSON_UNESCAPED_UNICODE) ?: '';
  102. }
  103. private function formatDate(string $value): string
  104. {
  105. $ts = strtotime($value);
  106. if ($ts === false) {
  107. return $value;
  108. }
  109. return date('d.m.Y', $ts);
  110. }
  111. /** @param array<string, mixed> $fieldDef */
  112. private function resolveSelectLabel(string $value, array $fieldDef): string
  113. {
  114. foreach (($fieldDef['options'] ?? []) as $opt) {
  115. if (is_array($opt) && (string) ($opt['value'] ?? '') === $value) {
  116. return (string) ($opt['label'] ?? $value);
  117. }
  118. }
  119. return $value;
  120. }
  121. /** @param array<string, mixed> $fieldDef */
  122. private function formatTable(array $rows, array $fieldDef): string
  123. {
  124. $columns = $fieldDef['columns'] ?? [];
  125. if (!is_array($columns) || $columns === []) {
  126. return '';
  127. }
  128. $lines = [];
  129. foreach ($rows as $row) {
  130. if (!is_array($row)) {
  131. continue;
  132. }
  133. $cells = [];
  134. $hasContent = false;
  135. foreach ($columns as $i => $col) {
  136. $cellValue = (string) ($row[$i] ?? '');
  137. $colType = (string) ($col['type'] ?? 'text');
  138. if ($colType === 'date' && $cellValue !== '') {
  139. $cellValue = $this->formatDate($cellValue);
  140. }
  141. if ($cellValue !== '') {
  142. $hasContent = true;
  143. }
  144. $colLabel = (string) ($col['label'] ?? '');
  145. $cells[] = $colLabel . ': ' . $cellValue;
  146. }
  147. if ($hasContent) {
  148. $lines[] = implode(' | ', $cells);
  149. }
  150. }
  151. return implode("\n", $lines);
  152. }
  153. /** @param array<string, mixed> $fieldDef */
  154. private function isVisible(array $fieldDef, array $formData): bool
  155. {
  156. if (isset($fieldDef['visible_if'])) {
  157. $cond = $fieldDef['visible_if'];
  158. $field = (string) ($cond['field'] ?? '');
  159. $expected = (string) ($cond['equals'] ?? '');
  160. $actual = (string) ($formData[$field] ?? '');
  161. if ($actual !== $expected) {
  162. return false;
  163. }
  164. }
  165. if (isset($fieldDef['hidden_if'])) {
  166. $cond = $fieldDef['hidden_if'];
  167. $field = (string) ($cond['field'] ?? '');
  168. $expected = (string) ($cond['equals'] ?? '');
  169. $actual = (string) ($formData[$field] ?? '');
  170. if ($actual === $expected) {
  171. return false;
  172. }
  173. }
  174. return true;
  175. }
  176. private function isPersonalDataStep(string $title): bool
  177. {
  178. return trim($title) === 'Persönliche Daten';
  179. }
  180. /** @param array<int, array{label: string, value: string}> $fields */
  181. private function hasEmailField(array $fields): bool
  182. {
  183. foreach ($fields as $field) {
  184. if (strtolower(trim((string) ($field['label'] ?? ''))) === 'e-mail') {
  185. return true;
  186. }
  187. }
  188. return false;
  189. }
  190. /** @param array<string, mixed> $formData */
  191. private function deriveIsMinor(array $formData): string
  192. {
  193. $birthdate = trim((string) ($formData['geburtsdatum'] ?? ''));
  194. if ($birthdate === '') {
  195. return '0';
  196. }
  197. $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $birthdate);
  198. if (!$date || $date->format('Y-m-d') !== $birthdate) {
  199. return '0';
  200. }
  201. $today = new \DateTimeImmutable('today');
  202. if ($date > $today) {
  203. return '0';
  204. }
  205. return $date->diff($today)->y < 18 ? '1' : '0';
  206. }
  207. }