> */ private array $fields; public function __construct(FormSchema $schema) { $this->fields = $schema->getAllFields(); } /** * @param array $data * @param array>> $uploads * @return array */ public function validateSubmit(array $data, array $uploads): array { $errors = []; foreach ($this->fields as $key => $field) { $type = $field['type'] ?? 'text'; $visible = $this->isVisible($field, $data); if (!$visible) { continue; } $required = $this->isRequired($field, $data); if ($type === 'file') { $hasFile = !empty($uploads[$key]); if ($required && !$hasFile) { $errors[$key] = 'Dieses Upload-Feld ist erforderlich.'; } continue; } $value = $data[$key] ?? null; if ($required && $this->isEmptyValue($value, $type, $field)) { $errors[$key] = 'Dieses Feld ist erforderlich.'; continue; } if ($this->isEmptyValue($value, $type, $field)) { continue; } if ($type === 'email' && filter_var((string) $value, FILTER_VALIDATE_EMAIL) === false) { $errors[$key] = 'Bitte eine gültige E-Mail-Adresse eingeben.'; } if ($type === 'select' && isset($field['options']) && is_array($field['options'])) { $selected = (string) $value; $validSelection = false; foreach ($field['options'] as $option) { if (!is_array($option)) { continue; } if ((string) ($option['value'] ?? '') !== $selected) { continue; } $validSelection = $this->isOptionVisible($option, $data); break; } if (!$validSelection) { $errors[$key] = 'Ungültige Auswahl.'; } } if (isset($field['max_length']) && is_int($field['max_length'])) { if (strlen((string) $value) > $field['max_length']) { $errors[$key] = 'Eingabe ist zu lang.'; } } } return $errors; } /** @param array $field */ private function isRequired(array $field, array $data): bool { if (!$this->isVisible($field, $data)) { return false; } if (($field['required'] ?? false) === true) { return true; } if (!isset($field['required_if']) || !is_array($field['required_if'])) { return false; } return $this->ruleMatches($field['required_if'], $data); } /** @param array $field */ private function isVisible(array $field, array $data): bool { if (!isset($field['visible_if']) || !is_array($field['visible_if'])) { return true; } return $this->ruleMatches($field['visible_if'], $data); } /** @param array $rule */ private function ruleMatches(array $rule, array $data): bool { $sourceField = (string) ($rule['field'] ?? ''); $equals = (string) ($rule['equals'] ?? ''); if ($sourceField === '') { return false; } return $this->resolveRuleValue($sourceField, $data) === $equals; } /** @param array $data */ private function resolveRuleValue(string $sourceField, array $data): string { if (array_key_exists($sourceField, $data)) { return (string) ($data[$sourceField] ?? ''); } $derived = $this->derivedRuleValues($data); return (string) ($derived[$sourceField] ?? ''); } /** * @param array $data * @return array */ private function derivedRuleValues(array $data): array { $adultFlag = $this->deriveAdultFlag((string) ($data['geburtsdatum'] ?? '')); if ($adultFlag === null) { return [ 'is_minor' => '', ]; } return [ 'is_minor' => $adultFlag ? '0' : '1', ]; } private function deriveAdultFlag(string $birthdate): ?bool { $birthdate = trim($birthdate); if ($birthdate === '') { return null; } $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $birthdate); if (!$date || $date->format('Y-m-d') !== $birthdate) { return null; } $today = new \DateTimeImmutable('today'); if ($date > $today) { return null; } $age = $date->diff($today)->y; return $age >= 18; } /** @param array $option */ private function isOptionVisible(array $option, array $data): bool { if (isset($option['visible_if']) && is_array($option['visible_if']) && !$this->ruleMatches($option['visible_if'], $data)) { return false; } if (isset($option['hidden_if']) && is_array($option['hidden_if']) && $this->ruleMatches($option['hidden_if'], $data)) { return false; } return true; } /** @param array $field */ private function isEmptyValue(mixed $value, string $type, array $field): bool { if ($type === 'checkbox') { return !in_array((string) $value, ['1', 'on', 'true'], true); } if ($type === 'table') { return $this->isTableValueEmpty($value, $field); } return $value === null || trim((string) $value) === ''; } /** @param array $field */ private function isTableValueEmpty(mixed $value, array $field): bool { $raw = trim((string) $value); if ($raw === '') { return true; } $lines = preg_split('/\R/', $raw); if (!is_array($lines)) { return true; } $rows = []; foreach ($lines as $line) { $line = trim((string) $line); if ($line === '') { continue; } $rows[] = str_getcsv($line); } if (empty($rows)) { return true; } $columnHeaders = []; if (isset($field['columns']) && is_array($field['columns'])) { foreach ($field['columns'] as $column) { if (!is_array($column)) { continue; } $label = trim((string) ($column['label'] ?? '')); if ($label !== '') { $columnHeaders[] = $label; } } } $dataRows = $rows; if (!empty($columnHeaders) && $this->tableRowMatchesHeader($rows[0], $columnHeaders)) { $dataRows = array_slice($rows, 1); } if (empty($dataRows)) { return true; } foreach ($dataRows as $dataRow) { foreach ((array) $dataRow as $cell) { if (trim((string) $cell) !== '') { return false; } } } return true; } /** * @param array $row * @param array $header */ private function tableRowMatchesHeader(array $row, array $header): bool { if (count($row) !== count($header)) { return false; } foreach ($header as $index => $headerValue) { if (strtolower(trim((string) ($row[$index] ?? ''))) !== strtolower(trim($headerValue))) { return false; } } return true; } }