$definition The inner definition (elements, email_key, success_message). * @return list Human-readable problems; empty when the definition is valid. */ public function validate(array $definition): array { $elements = is_array($definition['elements'] ?? null) ? $definition['elements'] : []; $symbols = $this->symbols($elements); $engine = $this->engine(); $errors = []; // Templates reference submitted values by form-element name. foreach ($this->templates($definition, $elements) as $label => $template) { foreach ($engine->validate($template, $symbols) as $problem) { $errors[] = "{$label}: {$problem}"; } } // A form that collects fields must give the visitor a way to send them. if ($elements !== [] && !$this->has_submit($elements)) { $errors[] = 'Form must contain a submit button.'; } // Validate membership bindings if present. $errors = array_merge($errors, $this->validate_membership_bindings($definition)); return $errors; } /** * Names that templates may reference — the form's symbol table. * * @param array $elements * @return list */ private function symbols(array $elements): array { $names = RsvFormCalculatedValues::names(); // calculated values are referencable too foreach ($elements as $el) { $name = is_array($el) ? ($el['name'] ?? '') : ''; if (is_string($name) && $name !== '') { $names[] = $name; } } return $names; } /** * The definition's admin-authored templates, keyed by a label used to * prefix any problems found in them. * * @param array $definition * @param array $elements * @return array */ private function templates(array $definition, array $elements): array { $templates = []; $success = $definition['success_message'] ?? ''; if (is_string($success) && trim($success) !== '') { $templates['Success message'] = $success; } foreach ($elements as $el) { if (!is_array($el) || ($el['type'] ?? '') !== 'reservation') { continue; } $email_templates = $el['email_templates'] ?? []; if (!is_array($email_templates)) { continue; } foreach (['on_accepted' => 'accepted', 'on_refused' => 'refused'] as $key => $human) { $tpl = $email_templates[$key] ?? []; if (!is_array($tpl)) { continue; } foreach (['subject', 'body'] as $part) { $value = $tpl[$part] ?? ''; if (is_string($value) && trim($value) !== '') { $templates["Email ({$human} {$part})"] = $value; } } } } return $templates; } /** @param array $elements */ private function has_submit(array $elements): bool { foreach ($elements as $el) { if (is_array($el) && ($el['type'] ?? '') === 'button') { return true; } } return false; } private function engine(): RsvTemplateEngine { global $rsv_template_registry; return new RsvTemplateEngine(registry: $rsv_template_registry); } /** * @param array $definition * @return list */ private function validate_membership_bindings(array $definition): array { $errors = []; $membership = $definition['membership'] ?? []; if (!is_array($membership)) { return $errors; } $bindings = $membership['bindings'] ?? []; if (!is_array($bindings)) { return $errors; } foreach ($bindings as $idx => $binding) { if (!is_array($binding)) { continue; } $program_id = intval($binding['program_id'] ?? 0); $discount = floatval($binding['discount'] ?? 0.0); if ($program_id <= 0) { $errors[] = "Membership binding {$idx}: program_id must be a positive integer."; } if ($discount < 0.0 || $discount > 100.0) { $errors[] = "Membership binding {$idx}: discount must be between 0 and 100."; } } return $errors; } }