547 lines
14 KiB
PHP
Executable File
547 lines
14 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* FuzeWorks Forms Library
|
|
*
|
|
* The FuzeWorks PHP FrameWork
|
|
*
|
|
* Copyright (C) 2013-${YEAR} i15
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* @author i15
|
|
* @copyright Copyright (c) 2013 - ${YEAR}, i15. (http://i15.nl)
|
|
* @license https://opensource.org/licenses/MIT MIT License
|
|
*
|
|
* @link https://i15.nl
|
|
* @since Version 1.3.0
|
|
*
|
|
* @version Version 1.3.0
|
|
*/
|
|
|
|
namespace FuzeWorks\Forms;
|
|
|
|
use FuzeWorks\Events;
|
|
use FuzeWorks\Factory;
|
|
use FuzeWorks\Forms\Fields\HiddenField;
|
|
use FuzeWorks\Forms\Fields\SubmitField;
|
|
use FuzeWorks\Layout;
|
|
use FuzeWorks\Security;
|
|
|
|
class Form
|
|
{
|
|
|
|
/**
|
|
* The name of the form
|
|
*
|
|
* @var string
|
|
*/
|
|
protected string $name;
|
|
|
|
/**
|
|
* The name to display to the user
|
|
*
|
|
* @var string
|
|
*/
|
|
protected string $label;
|
|
|
|
/**
|
|
* Array of all Fields that this Form possesses.
|
|
*
|
|
* @var Field[]
|
|
*/
|
|
protected array $fields = [];
|
|
|
|
/**
|
|
* An associate array of all errors generated by fields of the form.
|
|
*
|
|
* Saved as 'fieldName' => ['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 <form> 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 <form> 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'];
|
|
}
|
|
|
|
} |