['error1', 'error2'] * * @var array */ protected array $fieldErrors = []; /** * An iterative array of all errors belonging to the form as a whole. * * Saved as ['error1', 'error2'] * * @var array */ protected array $formErrors = []; /** * An iterative array of all errors belonging to the form as a whole. * * Saved as ['error1', 'error2'] * * @var array */ protected array $formWarnings = []; /** * The HTTP method to use to perform this form * * @var Method */ protected Method $method = Method::POST; /** * The url to send this form to. May be null to send to same page. * * @var string|null */ protected ?string $action = null; /** * CSS classnames to add to the
element * * @var array */ protected array $classNames = []; /** * Cross site request forgery prevention token. Taken care of internally. * * @var string|null */ protected ?string $csrfToken = null; /** * The source where values of fields are loaded from * * @var callable */ private $callable = null; /** * Whether the form has been validated * * @var bool */ protected bool $validated = false; /** * Whether, after validation, all fields pass their requirements * * @var bool */ protected bool $valid = false; /** * Conditions that verify the entire Form. * * Used for customization. Callables must return a bool. * * @var callable[] */ protected array $conditions = []; public function __construct(string $name, string $label = "") { $this->name = $name; $this->label = empty($label) ? $name : $label; // Add CSRF token /** @var Security $security */ $security = Factory::getInstance("security"); $hash = $security->get_csrf_hash(); if (!is_null($hash)) { $this->csrfToken = $security->get_csrf_token_name(); $field = new HiddenField($this->csrfToken); $field->setValue($hash); $this->field($field); } } /** * Add a field to the form * * @param Field $field * @return $this */ public function field(Field $field): static { // Set the form name $field->setFormName($this->name); // Add the field $this->fields[] = $field; // Return return $this; } /** * Return all fields * * @return Field[] */ public function getFields(): array { return $this->fields; } /** * Return the field with a specific name. * * Returns null if not found. * * @param string $name * @return Field|null */ public function getField(string $name): ?Field { // Find field by name and return it foreach ($this->fields as $field) if ($field->getName() == $name) return $field; return null; } /** * Return the csrf token hidden field * * @return Field|null */ public function getCsrfField(): ?Field { if (is_null($this->csrfToken)) return null; return $this->getField($this->csrfToken); } /** * Set the method for this form. * * @param Method $method * @return $this */ public function method(Method $method): static { $this->method = $method; return $this; } /** * Manually set the target of this form * * @param string $action * @return $this */ public function action(string $action): static { $this->action = $action; return $this; } /** * Adds the CSS classnames to be printed inside the element. * * @param array $classNames * @return $this */ public function class(array $classNames): static { foreach ($classNames as $className) $this->classNames[] = $className; return $this; } /** * Add one CSS classname to be printed inside the element * * @param string $class * @return $this */ public function addClass(string $class): static { $this->classNames[] = $class; return $this; } /** * Sets the CSS classnames to be printed inside the element * * @param array $classNames * @return $this */ public function setClasses(array $classNames): static { $this->classNames = $classNames; return $this; } /** * Sets the source where values of fields are loaded from. * * The source is a callable which must accept func(string $fieldName) and returns the value or null. * * @param callable $callable * @return $this */ public function setSource(callable $callable): static { $this->callable = $callable; return $this; } public function validate(): bool { // First check if any values have been set $found = false; foreach ($this->fields as $field) { $value = call_user_func($this->getCallable(), $field->getName()); if (!is_null($value)) $found = true; } // If not a single value has been set, return false but don't set the form as validated. if (!$found) return false; // Pass over each field $this->fieldErrors = []; $this->validated = true; $this->valid = true; foreach ($this->fields as $field) { // First set the value of each field $value = call_user_func($this->getCallable(), $field->getName()); if (!is_null($value)) $field->setValue($value); // Then validate it if (!$field->validate()) { $this->valid = false; $this->fieldErrors[$field->getName()] = $field->getErrors(); } } // Check for conditions foreach ($this->conditions as $condition) if (!call_user_func($condition, $this)) $this->valid = false; return $this->valid; } /** * Whether the form has been validated. * * @see validate * @return bool */ public function isValidated(): bool { return $this->validated; } /** * Whether the form has met all conditions during validation * * @return bool */ public function isValid(): bool { return $this->valid; } /** * Change the valid status of the form to false, if so required by post-checks. * * @param bool $fields Whether to invalidate all fields as well * @return void */ public function invalidate(bool $fields = false): void { $this->valid = false; if ($fields) foreach ($this->fields as $field) $field->invalidate(); } /** * Retrieve an associative value array of errors generated by fields, separated by fieldNames. * * @see Field::getErrors() * @return array */ public function getFieldErrors(): array { return $this->fieldErrors; } /** * Retrieve an iterative array of errors belonging to the form as a whole * * @return array */ public function getFormErrors(): array { return $this->formErrors; } /** * Add an error belonging to the form as a whole * * @param string $formError * @return void */ public function addFormError(string $formError): void { $this->formErrors[] = $formError; } /** * Retrieve an iterative array of warnings belonging to the form as a whole * * @return array */ public function getFormWarnings(): array { return $this->formWarnings; } /** * Add a warning belonging to the form as a whole * * @param string $formWarning * @return void */ public function addFormWarning(string $formWarning): void { $this->formWarnings[] = $formWarning; } /** * Retrieve the name of the form, used as an identifier. * * @return string */ public function getName(): string { return $this->name; } /** * Set the name of the form, used as an identifier. * * @param string $name */ public function setName(string $name): void { $this->name = $name; } /** * Get the label of the form, used as the name presented to the user. * * @return string */ public function getLabel(): string { return $this->label; } /** * Set the label of the form, used as the name presented to the user. * * @param string $label */ public function setLabel(string $label): void { $this->label = $label; } /** * Adds a manual condition to the field that it will be validated against. * * Callables shall receive the Form as their first argument, and must return a bool. * * @param callable $condition * @return $this */ public function condition(callable $condition): static { $this->conditions[] = $condition; return $this; } /** * Generate the HTML for this form. Defaults to the internal 'bootstrap' layout for generation. * * @param string $layoutFile * @return string */ public function generate(string $layoutFile): string { /** @var Layout $layouts */ $layouts = Factory::cloneInstance("layouts"); $layouts->reset(); $layouts->assign("form", $this); return $layouts->get($layoutFile); } /** * Generate only the attributes that go into the element. * * Useful when manually creating a form layout * * @return string */ public function generateHtmlFormAttributes(): string { $action = !is_null($this->action) ? "action='".$this->action."'" : ""; $method = $this->method == Method::POST ? "method='post'" : ""; $class = "class='".implode(" ", $this->classNames)."'"; return "$class $action $method"; } public function __toString() { return $this->generate("form/bootstrap"); } public function __get(string $name): Field { return $this->getField($name); } /** * Get the current callable for retrieving the values of fields * * @return callable */ private function getCallable(): callable { // If the current callable is manually set, use that one if (!is_null($this->callable)) return $this->callable; // If no callable is set, use the callable from web input $input = Factory::getInstance("input"); if ($this->method == Method::POST) return [$input, 'post']; else return [$input, 'get']; } }