75 $class = str_replace(
'\\',
'/', str_replace(__NAMESPACE__ .
'\\',
'', $class));
76 $file = __DIR__ .
'/' . $class .
'.php';
78 if (file_exists($file)) {
83spl_autoload_register(__NAMESPACE__ .
'\autoload');
126 'en' => [
'us',
'gb',
'ie',
'au',
'nz'],
127 'de' => [
'de',
'at',
'ch'],
128 'fr' => [
'fr',
'ch',
'be',
'lu',
'ca'],
204 private $currentStepId;
254 $this->isAutoSaveRequest = isset($_POST[
'formAutosave']) && $_POST[
'formAutosave'] ===
"true";
256 $this->url = parse_url($_SERVER[
'REQUEST_URI']);
258 parent::__construct(
$name, $parameters, $this);
260 $this->url = parse_url($this->submitURL);
261 if (empty($this->successURL)) {
264 if (empty($this->cancelURL)) {
268 $this->currentStepId = isset($_GET[
'step']) ? $_GET[
'step'] : 0;
270 $this->startSession();
272 $this->valid = (isset($this->sessionSlot[
'formIsValid'])) ? $this->sessionSlot[
'formIsValid'] :
null;
275 if (!isset($this->sessionSlot[
'formCsrfToken'])) {
279 if (!isset($this->sessionSlot[
'formFinalPost'])) {
280 $this->sessionSlot[
'formFinalPost'] =
false;
284 $this->addHidden(
'formName')->setValue($this->name);
287 $this->addHidden(
'formStep')->setValue($this->currentStepId);
290 $this->addHidden(
'formCsrfToken')->setValue($this->sessionSlot[
'formCsrfToken']);
305 parent::setDefaults();
307 $this->defaults[
'label'] =
'submit';
308 $this->defaults[
'cancelLabel'] =
'';
309 $this->defaults[
'backLabel'] =
'';
310 $this->defaults[
'class'] =
'';
311 $this->defaults[
'method'] =
'post';
313 $this->defaults[
'submitURL'] = $_SERVER[
'REQUEST_URI'];
314 $this->defaults[
'successURL'] =
null;
315 $this->defaults[
'cancelURL'] =
null;
316 $this->defaults[
'validator'] =
null;
317 $this->defaults[
'ttl'] = 60 * 60;
318 $this->defaults[
'jsValidation'] =
'blur';
319 $this->defaults[
'jsAutosave'] =
'false';
330 private function startSession()
334 $params = session_get_cookie_params();
335 $sessionName = session_name();
337 session_set_cookie_params(
347 if (isset($_COOKIE[$sessionName])) {
350 $_COOKIE[$sessionName],
359 $this->sessionSlotName =
'htmlform-' . $this->name .
'-data';
362 $this->sessionExpiry();
372 private function sessionExpiry()
374 if (isset($this->ttl) && is_numeric($this->ttl)) {
378 isset($this->sessionSlot[
'formTimestamp'])
379 && ($timestamp - $this->sessionSlot[
'formTimestamp'] > $this->ttl)
385 $this->sessionSlot[
'formTimestamp'] = $timestamp;
395 return !isset($this->sessionSlot[
'formName']);
405 return base64_encode(openssl_random_pseudo_bytes(16));
423 $newElement = parent::addElement($type,
$name, $parameters);
425 if ($newElement instanceof
Elements\Step) {
426 $this->steps[] = $newElement;
428 if ($newElement instanceof
Abstracts\Input) {
446 if ($element->getName() ===
$name) {
456 private function getCurrentElements(): array
458 $currentElements = [];
460 foreach ($this->elements as $element) {
461 if ($element instanceof
Abstracts\Container) {
463 !($element instanceof
Elements\Step)
464 || (isset($this->steps[$this->currentStepId]) && ($element == $this->steps[$this->currentStepId]))
466 $currentElements = array_merge($currentElements, $element->getElements());
469 $currentElements[] = $element;
473 return $currentElements;
483 $this->namespaces[] = $namespace;
501 private function inCurrentStep(
string $name): bool
503 return in_array($this->
getElement($name), $this->getCurrentElements());
517 if (!is_null($step)) {
518 $this->currentStepId = $step;
520 if (!is_numeric($this->currentStepId)
521 || ($this->currentStepId > count($this->steps) - 1)
522 || ($this->currentStepId < 0)
547 return $this->currentStepId;
560 if (count($this->steps) > 0) {
561 foreach ($this->steps as $stepNumber => $step) {
562 if (!$step->validate()) {
572 return count($this->steps) - 1;
585 $url = isset($this->url[
'scheme']) ? $this->url[
'scheme'] .
'://' :
'';
586 $url .= $this->url[
'host'] ??
'';
587 $url .= isset($this->url[
'port']) ?
':' . $this->url[
'port'] :
'';
588 $url .= $this->url[
'path'] ??
'';
603 if (isset($this->url[
'query']) && $this->url[
'query'] !=
"") {
605 $query = html_entity_decode($this->url[
'query']);
608 parse_str($query, $queryParts);
611 foreach ($args as
$name => $value) {
613 $queryParts[
$name] = $value;
614 } elseif (isset($queryParts[
$name])) {
615 unset($queryParts[
$name]);
620 $query = http_build_query($queryParts);
646 $element->setSessionSlot($this->sessionSlot);
651 isset($_POST[
'formName']) && ($_POST[
'formName'] === $this->name)
652 && $this->inCurrentStep(
$name)
653 && isset($_POST[
'formCsrfToken']) && $_POST[
'formCsrfToken'] === $this->sessionSlot[
'formCsrfToken']
657 $oldValue = isset($this->sessionSlot[
$name]) ? $this->sessionSlot[
$name] :
null;
658 $this->sessionSlot[
$name] = $element->handleUploadedFiles($oldValue);
659 } elseif (!$element->getDisabled()) {
661 $value = isset($_POST[
$name]) ? $_POST[
$name] :
null;
662 $this->sessionSlot[
$name] = $element->setValue($value);
663 } elseif (!isset($this->sessionSlot[
$name])) {
665 $this->sessionSlot[
$name] = $element->setValue($element->getDefaultValue());
669 elseif (isset($this->sessionSlot[
$name])) {
670 $element->setValue($this->sessionSlot[
$name]);
683 $this->sessionSlot[
$name] = $element->clearValue();
695 public function populate(array|
object $data = []): void
698 $name = $element->name;
699 if (!in_array(
$name, $this->internalFields)) {
700 if (is_array($data) && isset($data[
$name])) {
701 $value = $data[
$name];
702 } elseif (is_object($data) && isset($data->$name)) {
703 $value = $data->$name;
707 $element->setDefaultValue($value);
708 if ($element->getDisabled() && !isset($this->sessionSlot[
$name])) {
709 $this->sessionSlot[
$name] = $value;
732 if (isset($_POST[
'formName']) && ($_POST[
'formName'] === $this->name)) {
734 $this->sessionSlot[
'formFinalPost'] = count($this->steps) == 0 || $_POST[
'formStep'] + 1 == count($this->steps)
737 if (!empty($this->cancelLabel) && isset($_POST[
'formSubmit']) && $_POST[
'formSubmit'] === $this->cancelLabel) {
741 } elseif ($this->isAutoSaveRequest) {
744 } elseif (!empty($this->backLabel) && isset($_POST[
'formSubmit']) && $_POST[
'formSubmit'] === $this->backLabel) {
748 $this->sessionSlot[
'formFinalPost'] =
false;
749 $prevStep = $this->currentStepId - 1;
753 $this->
redirect($this->
buildUrl([
'step' => ($prevStep <= 0) ?
'' : $prevStep]));
763 $nextStep = $this->currentStepId + 1;
765 if ($nextStep > $firstInvalidStep) {
766 $nextStep = $firstInvalidStep;
768 if ($nextStep > count($this->steps)) {
769 $nextStep = count($this->steps) - 1;
771 $this->
redirect($this->
buildUrl([
'step' => ($nextStep == 0) ?
'' : $nextStep]));
792 if ($this->valid && !is_null($this->validator)) {
793 if (is_callable($this->validator)) {
794 $this->valid = call_user_func($this->validator, $this, $this->
getValues());
796 throw new exceptions\validatorNotCallable(
"The validator paramater must be callable");
799 $this->valid = $this->valid && $this->sessionSlot[
'formFinalPost'];
845 if (isset($_POST[
'formCsrfToken'])) {
846 $hasCorrectToken = $_POST[
'formCsrfToken'] === $this->sessionSlot[
'formCsrfToken'];
847 $this->valid = $this->valid && $hasCorrectToken;
849 if (!$hasCorrectToken) {
851 $this->
log(
"HtmlForm: Requst invalid because of incorrect CsrfToken");
858 if ($this->isAutoSaveRequest
859 || (isset($this->sessionSlot[
'formIsAutosaved'])
860 && $this->sessionSlot[
'formIsAutosaved'] ===
true)
862 $this->valid =
false;
877 if (isset($this->sessionSlot)) {
879 return array_diff_key($this->sessionSlot, array_fill_keys($this->internalFields,
''));
893 $valuesWithLabel = [];
894 if (isset($values)) {
895 foreach ($values as $element => $value) {
899 $valuesWithLabel[$element] = [
901 "label" => $elem->getLabel(),
906 return $valuesWithLabel;
918 header(
'Location: ' .
$url);
919 die(
"Tried to redirect you to <a href=\"$url\">$url</a>");
928 http_response_code($code);
939 if ($clearCsrfToken) {
943 unset($_SESSION[$this->sessionSlotName]);
944 unset($this->sessionSlot);
948 if (!$element->getDisabled() && !in_array($element->name, $this->internalFields)) {
949 unset($this->sessionSlot[$element->name]);
964 if (empty($_SESSION)) {
967 foreach ($_SESSION as $key => &$val) {
968 if (preg_match($pattern, $key)
969 && isset($val[
'formTimestamp'])
970 && ($timestamp - $val[
'formTimestamp'] >
$ttl)
972 unset($_SESSION[$key]);
983 $this->dataAttr[
'jsautosave'] = $this->jsAutosave ===
true ?
"true" :
$this->jsAutosave;
985 return parent::htmlDataAttributes();
992 $step = $this->currentStepId != 0 ? $this->currentStepId :
'';
1006 $renderedElements =
'';
1010 $label = $this->htmlLabel();
1011 $cancellabel = $this->htmlCancelLabel();
1012 $backlabel = $this->htmlBackLabel();
1013 $class = $this->htmlClass();
1014 $method = $this->htmlMethod();
1017 $disabledAttr = $this->disabled ?
" disabled=\"disabled\"" :
"";
1019 foreach ($this->elementsAndHtml as $element) {
1021 if (!($element instanceof elements\step)
1022 || (isset($this->steps[$this->currentStepId]) && $this->steps[$this->currentStepId] == $element)
1024 $renderedElements .= $element;
1028 if (!empty($this->cancelLabel)) {
1029 $cancel =
"<p id=\"{$this->name}-cancel\" class=\"cancel\"><input type=\"submit\" name=\"formSubmit\" value=\"{$cancellabel}\"$disabledAttr></p>\n";
1031 if (!empty($this->backLabel) && $this->currentStepId > 0) {
1032 $back =
"<p id=\"{$this->name}-back\" class=\"back\"><input type=\"submit\" name=\"formSubmit\" value=\"{$backlabel}\"$disabledAttr></p>\n";
1034 if (!empty($this->label)) {
1035 $submit =
"<p id=\"{$this->name}-submit\" class=\"submit\"><input type=\"submit\" name=\"formSubmit\" value=\"{$label}\"$disabledAttr></p>\n";
1039 return "<form id=\"{$this->name}\" name=\"{$this->name}\" class=\"depage-form {$class}\" method=\"{$method}\" action=\"{$submitURL}\"{$dataAttr} enctype=\"multipart/form-data\">" .
"\n" .
container element base class
addChildElements()
Sub-element generator hook.
$form
Parent form object reference.
clearValue()
Deletes values of all child elements.
getElement(string $name, bool $includeFieldsets=false)
Gets subelement by name.
getElements(bool $includeFieldsets=false)
Returns containers subelements.
log(string $argument, string $type=null)
error & warning logger
$dataAttr
Extra information about the data that is saved inside the element.
htmlEscape(array|string $options=[])
Escapes HTML in strings and arrays of strings.
thrown when there are duplicate element names
$valid
Form validation result/status.
$isAutoSaveRequest
true if form request is from autosave call
registerNamespace(string $namespace)
Stores element namespaces for adding.
clearInputValue(string $name)
clearInputValue
$method
HTML form method attribute.
getCurrentStepId()
Returns the current step id.
$label
Contains the submit button label of the form.
validate()
Validates the forms subelements.
httpResponseCode(int $code)
Sets the HTTP response code.
$jsAutosave
Contains the javascript autosave type of the form.
$class
Contains the additional class value of the form.
$cancelURL
Specifies where the user is redirected to, once the form-data is cancelled.
getSteps()
Returns an array of steps.
__construct(string $name, array $parameters=[], HtmlForm|null $form=null)
HtmlForm class constructor.
$jsValidation
Contains the javascript validation type of the form.
getFirstInvalidStep()
Returns first step that didn't pass validation.
htmlDataAttributes()
Returns dataAttr escaped as attribute string.
$namespaces
Namespace strings for addible element classes.
onValidate()
Validation hook.
validateAutosave()
If the form is autosaving the validation property is defaulted to false.
$ttl
Time until session expiry (seconds)
getValues()
Gets form-data from current PHP session.
static clearOldSessions(int $ttl=3600, string $pattern="/^htmlform-.*/")
clearOldSessions
__toString()
Renders form to HTML.
process()
Calls form validation and handles redirects.
$submitURL
HTML form action attribute.
redirect(string $url)
Redirects Browser to a different URL.
checkElementName(string $name)
Checks for duplicate subelement names.
buildUrlQuery(array $args=[])
Adding step parameter to already existing query.
$successURL
Specifies where the user is redirected to, once the form-data is valid.
updateInputValue(string $name)
Updates the value of an associated input element.
$sessionSlot
PHP session handle.
clearSession(bool $clearCsrfToken=true)
Deletes the current forms' PHP session data.
$internalFields
List of internal fieldnames that are not part of the results.
$validator
Contains the validator function of the form.
$cancelLabel
Contains the cancel button label of the form.
populate(array|object $data=[])
Fills subelement values.
setCurrentStep(int|null $step=null)
Validates step number of GET request.
htmlSubmitURL()
Returns form url escaped as attribute string.
getValuesWithLabel()
Gets form-data from current PHP session but also contain elemnt labels.
setDefaults()
Collects initial values across subclasses.
$sessionSlotName
Contains the name of the array in the PHP session, holding the form-data.
$url
url of the current page
$backLabel
Contains the back button label of the form.
getNamespaces()
Returns list of registered namespaces.
isEmpty()
Returns wether form has been submitted before or not.
buildUrl(array $args=[])
Builds URL from parts.
getNewCsrfToken()
Returns new XSRF token.
addElement(string $type, string $name, array $parameters)
Adds input or fieldset elements to htmlform.
Abstract element classes.
Classes for HTML input-elements.
htmlform class and autoloader
autoload($class)
PHP autoloader.