mirror of
https://github.com/processwire/processwire.git
synced 2025-08-11 17:24:46 +02:00
Improvements to InputfieldForm module, including a new isSubmitted() method for a better way to check form submission, among other minor updates.
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
* @property string $method Form method attribute (default="post")
|
* @property string $method Form method attribute (default="post")
|
||||||
* @property string $action Form action attribute (default="./")
|
* @property string $action Form action attribute (default="./")
|
||||||
*
|
*
|
||||||
|
* @method bool process() Process the form and return true on success, false on error (3.0.205+).
|
||||||
* @method void renderOrProcessReady($type) Hook called before form render or process (3.0.171+)
|
* @method void renderOrProcessReady($type) Hook called before form render or process (3.0.171+)
|
||||||
*
|
*
|
||||||
* Optional classes:
|
* Optional classes:
|
||||||
@@ -29,21 +30,38 @@
|
|||||||
* ~~~~~
|
* ~~~~~
|
||||||
* $form = $modules->get('InputfieldForm');
|
* $form = $modules->get('InputfieldForm');
|
||||||
*
|
*
|
||||||
* $field = $modules->get('InputfieldText');
|
* $f = $form->InputfieldText;
|
||||||
* $field->attr('name', 'your_name');
|
* $f->attr('name', 'your_name');
|
||||||
* $field->label = 'Your Name';
|
* $f->label = 'Your Name';
|
||||||
* $form->add($field);
|
* $form->add($f);
|
||||||
*
|
*
|
||||||
* $field = $modules->get('InputfieldEmail');
|
* $f = $form->InputfieldEmail;
|
||||||
* $field->attr('name', 'your_email');
|
* $f->attr('name', 'your_email');
|
||||||
* $field->label = 'Your Email Address';
|
* $f->label = 'Your Email Address';
|
||||||
* $field->required = true;
|
* $f->required = true;
|
||||||
* $form->add($field);
|
* $form->add($f);
|
||||||
*
|
*
|
||||||
* $submit = $modules->get('InputfieldSubmit');
|
* $f = $form->InputfieldSubmit;
|
||||||
* $submit->attr('name', 'submit_subscribe');
|
* $f->attr('name', 'submit_subscribe');
|
||||||
* $form->add($submit);
|
* $f->val('Subscribe');
|
||||||
|
* $form->add($f);
|
||||||
*
|
*
|
||||||
|
* // ProcessWire versions 3.0.205+
|
||||||
|
* if($form->isSubmitted('submit_subscribe')) {
|
||||||
|
* if($form->process()) {
|
||||||
|
* $name = $form->getValueByName('your_name');
|
||||||
|
* $email = $form->getValueByName('your_email');
|
||||||
|
* echo "<h3>Thank you, you have been subscribed!</h3>";
|
||||||
|
* } else {
|
||||||
|
* echo "<h3>There were errors, please fix</h3>";
|
||||||
|
* echo $form->render();
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* // form not submitted, just display it
|
||||||
|
* echo $form->render();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // same as above but works in any ProcessWire version
|
||||||
* if($input->post('submit_subscribe')) {
|
* if($input->post('submit_subscribe')) {
|
||||||
* // form submitted
|
* // form submitted
|
||||||
* $form->processInput($input->post);
|
* $form->processInput($input->post);
|
||||||
@@ -54,8 +72,8 @@
|
|||||||
* echo $form->render();
|
* echo $form->render();
|
||||||
* } else {
|
* } else {
|
||||||
* // successful submit (save $name and $email somewhere)
|
* // successful submit (save $name and $email somewhere)
|
||||||
* $name = $form->get('your_name')->attr('value');
|
* $name = $form->getChildByName('your_name')->attr('value');
|
||||||
* $email = $form->get('your_email')->attr('value');
|
* $email = $form->getChildByName('your_email')->attr('value');
|
||||||
* echo "<h3>Thank you, you have been subscribed!</h3>";
|
* echo "<h3>Thank you, you have been subscribed!</h3>";
|
||||||
* }
|
* }
|
||||||
* } else {
|
* } else {
|
||||||
@@ -88,6 +106,11 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
*/
|
*/
|
||||||
protected $_processInputData = null;
|
protected $_processInputData = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array|null
|
||||||
|
*/
|
||||||
|
protected $errorCache = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct
|
* Construct
|
||||||
*
|
*
|
||||||
@@ -113,6 +136,7 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
|
|
||||||
if($this->hasHook('renderOrProcessReady()')) $this->renderOrProcessReady('render');
|
if($this->hasHook('renderOrProcessReady()')) $this->renderOrProcessReady('render');
|
||||||
|
|
||||||
|
$method = strtolower($this->attr('method'));
|
||||||
$markup = self::getMarkup();
|
$markup = self::getMarkup();
|
||||||
$classes = self::getClasses();
|
$classes = self::getClasses();
|
||||||
if(!empty($classes['form'])) $this->addClass($classes['form']);
|
if(!empty($classes['form'])) $this->addClass($classes['form']);
|
||||||
@@ -121,7 +145,7 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
$this->addClass('InputfieldForm');
|
$this->addClass('InputfieldForm');
|
||||||
|
|
||||||
if($this->hasClass('InputfieldFormConfirm')) {
|
if($this->hasClass('InputfieldFormConfirm')) {
|
||||||
if($this->wire('modules')->isInstalled('FormSaveReminder')) {
|
if($this->wire()->modules->isInstalled('FormSaveReminder')) {
|
||||||
// let FormSaveReminder module have control, if it's installed
|
// let FormSaveReminder module have control, if it's installed
|
||||||
$this->removeClass('InputfieldFormConfirm');
|
$this->removeClass('InputfieldFormConfirm');
|
||||||
} else {
|
} else {
|
||||||
@@ -132,7 +156,7 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
$attrs = $this->getAttributes();
|
$attrs = $this->getAttributes();
|
||||||
unset($attrs['value']);
|
unset($attrs['value']);
|
||||||
|
|
||||||
if($this->wire('input')->get('modal') && strpos($attrs['action'], 'modal=1') === false) {
|
if($this->wire()->input->get('modal') && strpos($attrs['action'], 'modal=1') === false) {
|
||||||
// retain a modal=1 state in the form action
|
// retain a modal=1 state in the form action
|
||||||
$attrs['action'] .= (strpos($attrs['action'], '?') === false ? '?' : '&') . 'modal=1';
|
$attrs['action'] .= (strpos($attrs['action'], '?') === false ? '?' : '&') . 'modal=1';
|
||||||
}
|
}
|
||||||
@@ -142,30 +166,57 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
|
|
||||||
$attrStr = $this->getAttributesString($attrs);
|
$attrStr = $this->getAttributesString($attrs);
|
||||||
|
|
||||||
if($this->getSetting('protectCSRF') && strtolower($this->attr('method')) == 'post') {
|
if($this->getSetting('protectCSRF') && $method === 'post') {
|
||||||
$tokenField = $this->wire('session')->CSRF->renderInput();
|
$tokenField = $this->wire('session')->CSRF->renderInput();
|
||||||
} else {
|
} else {
|
||||||
$tokenField = '';
|
$tokenField = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @todo 3.0.125
|
if($method === 'post') {
|
||||||
$name = $this->getAttribute('name');
|
$className = $this->className();
|
||||||
if(!empty($name)) {
|
$formName = $this->wire()->sanitizer->entities($this->getFormName());
|
||||||
$name = $this->wire('sanitizer')->entities($name);
|
$landmark = "<input type='hidden' name='_$className' value='$formName' />";
|
||||||
$class = $this->className();
|
} else {
|
||||||
$tokenField .= "<input type='hidden' name='_$class' value='$name' />";
|
$landmark = '';
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
$out =
|
return
|
||||||
"<form $attrStr>" .
|
"<form $attrStr>" .
|
||||||
$description . $this->getSetting('prependMarkup') .
|
$description .
|
||||||
parent::___render() .
|
$this->getSetting('prependMarkup') .
|
||||||
$tokenField .
|
parent::___render() .
|
||||||
$this->getSetting('appendMarkup') .
|
$tokenField .
|
||||||
|
$this->getSetting('appendMarkup') .
|
||||||
|
$landmark .
|
||||||
"</form>";
|
"</form>";
|
||||||
|
}
|
||||||
|
|
||||||
return $out;
|
/**
|
||||||
|
* Process the form
|
||||||
|
*
|
||||||
|
* - Optionally use this rather than processInput() for processing forms.
|
||||||
|
* - Returns true on processing success or false if processed with errors.
|
||||||
|
* - Use the getErrors() method to retrieve errors.
|
||||||
|
*
|
||||||
|
* ~~~~~
|
||||||
|
* if($form->process()) {
|
||||||
|
* // form processed successfully without errors
|
||||||
|
* } else {
|
||||||
|
* $errors = $form->getErrors(); // array of error messages…
|
||||||
|
* $inputs = $form->getErrorInputfields(); // …or array of Inputfields with errors
|
||||||
|
* }
|
||||||
|
* ~~~~~
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws WireException
|
||||||
|
* @since 3.0.205
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function ___process() {
|
||||||
|
$input = $this->wire()->input;
|
||||||
|
$method = strtolower($this->attr('method'));
|
||||||
|
$this->processInput(($method === 'get' ? $input->get : $input->post));
|
||||||
|
return count($this->getErrors()) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,6 +228,7 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
*/
|
*/
|
||||||
public function ___processInput(WireInputData $input) {
|
public function ___processInput(WireInputData $input) {
|
||||||
|
|
||||||
|
$this->errorCache = null;
|
||||||
$this->_processInputData = $input;
|
$this->_processInputData = $input;
|
||||||
if($this->hasHook('renderOrProcessReady()')) $this->renderOrProcessReady('process');
|
if($this->hasHook('renderOrProcessReady()')) $this->renderOrProcessReady('process');
|
||||||
|
|
||||||
@@ -483,7 +535,7 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
if($allowCheckLabels) {
|
if($allowCheckLabels) {
|
||||||
if($inputfield instanceof InputfieldHasArrayValue) {
|
if($inputfield instanceof InputfieldHasArrayValue) {
|
||||||
$value2 = array(); // matching of labels rather than values
|
$value2 = array(); // matching of labels rather than values
|
||||||
foreach($value as $k => $v) {
|
foreach($value as $v) {
|
||||||
if(isset($options[$v])) $value2[] = $options[$v];
|
if(isset($options[$v])) $value2[] = $options[$v];
|
||||||
}
|
}
|
||||||
if(empty($value2)) $value2 = null;
|
if(empty($value2)) $value2 = null;
|
||||||
@@ -510,81 +562,93 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this form submitted?
|
* Is form submitted and ready to process?
|
||||||
*
|
*
|
||||||
* This should only be called after the form has been completely built and submit buttons added to it.
|
* - Optionally use this method to test if form is submitted before calling processInput().
|
||||||
* When using this option it is preferable (though not required) that your form has been given a name attribute.
|
* - Specify the submit button name to confirm that was the button used to submit.
|
||||||
* Optionally provide the name (or instance) of the submit button you want to check.
|
* - Returns the name of the submit button used when form is submitted, false otherwise.
|
||||||
|
* - If no arguments specified, requires that the form has one or more InputfieldSubmit fields.
|
||||||
|
* - Like with processInput(), make sure form is fully built before calling this.
|
||||||
|
* - If given $submitName argument that corresponds to InputfieldSubmit field then
|
||||||
|
* it will also be confirmed that the input value matches the field value prior to submit.
|
||||||
|
* - If given a $submitName argument that corresponds to some other Inputfield type then
|
||||||
|
* only its presence in the input will be confirmed.
|
||||||
*
|
*
|
||||||
* - Returns boolean false if not submitted.
|
* ~~~~~
|
||||||
* - Returns boolean true if submitted, but submit button not known or not used.
|
* if($form->isSubmitted()) {
|
||||||
* - Returns clicked/submitted InputfieldSubmit object instance when known and available.
|
* // form was submitted
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* @param string|InputfieldSubmit $submitName Name of submit button or instance of InputfieldSubmit
|
* // specify the button name to confirm it was used to submit the form
|
||||||
* @return bool|InputfieldSubmit
|
* if($form->isSubmitted('submit_save')) {
|
||||||
* @todo 3.0.125
|
* // form is submitted with button named 'submit_save'
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
|
* // omit button name to have it return button name used to submit
|
||||||
|
* $submit = $form->isSubmitted(true);
|
||||||
|
* if($submit === 'add') {
|
||||||
|
* // form was submitted with button named 'add'
|
||||||
|
* } else if($submit === 'save') {
|
||||||
|
* // form submitted with button named 'save'
|
||||||
|
* } else if($submit === false) {
|
||||||
|
* // form not submitted
|
||||||
|
* } else {
|
||||||
|
* // submitted using some other button (name in $submit)
|
||||||
|
* }
|
||||||
|
* ~~~~~
|
||||||
|
*
|
||||||
|
* @param string|Inputfield|bool $submitName Any one of the following:
|
||||||
|
* - Name (string) or Inputfield (object) of submit button or other input to check.
|
||||||
|
* - Boolean true to find and return the clicked submit button name.
|
||||||
|
* - Boolean false or omit to only return true or false if form submitted (default).
|
||||||
|
* @return bool|string Returns one of the following:
|
||||||
|
* - Boolean false if form not submitted.
|
||||||
|
* - Boolean true if form submitted and submit button name not requested.
|
||||||
|
* - Submit/input name (string) if form submitted and `$submitName` argument is true or string.
|
||||||
|
* @throws WireException
|
||||||
|
* @since 3.0.205
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function isSubmitted($submitName = '') {
|
public function isSubmitted($submitName = '') {
|
||||||
|
|
||||||
$method = strtoupper($this->attr('method'));
|
$session = $this->wire()->session;
|
||||||
$input = $this->wire('input');
|
$input = $this->wire()->input;
|
||||||
$submit = false;
|
|
||||||
$formName = $this->getAttribute('name');
|
$className = $this->className();
|
||||||
$submitNames = array();
|
$formName = $this->getFormName();
|
||||||
|
$method = $this->attr('method') ? strtolower($this->attr('method')) : 'post';
|
||||||
|
$checkToken = $this->getSetting('protectCSRF') && $method != 'get';
|
||||||
|
|
||||||
// if the current request method is not the same as the forms, exit early
|
|
||||||
if(!$input->requestMethod($method)) return false;
|
if(!$input->requestMethod($method)) return false;
|
||||||
|
if($method === 'post' && $input->$method("_$className") !== $formName) return false;
|
||||||
|
if($checkToken && !$session->CSRF()->hasValidToken()) return false;
|
||||||
|
|
||||||
if(!empty($submitName)) {
|
if(empty($submitName)) {
|
||||||
// given a specific submit button to check
|
// no submit button name requested
|
||||||
if($submitName instanceof InputfieldSubmit) {
|
$submitted = true;
|
||||||
$submitName = $submitName->attr('name');
|
|
||||||
}
|
} else if($submitName === true) {
|
||||||
if(is_string($submitName)) {
|
// find which submit button was slicked to return its name
|
||||||
$submitNames[] = $submitName;
|
$submitted = false;
|
||||||
|
foreach($this->getAll() as $f) {
|
||||||
|
if(!$f instanceof InputfieldSubmit) continue;
|
||||||
|
$name = $f->attr('name');
|
||||||
|
$submitted = $input->$method($name) === $f->val() ? $name : false;
|
||||||
|
if($submitted) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// confirm that requested submit button was clicked and has same value
|
||||||
|
if($submitName instanceof Inputfield) $submitName = $submitName->attr('name');
|
||||||
|
$value = $input->$method($submitName);
|
||||||
|
if($value === null) return false;
|
||||||
|
$f = $this->getChildByName($submitName);
|
||||||
|
if($f instanceof InputfieldSubmit && $f->val() != $value) return false;
|
||||||
|
$submitted = $submitName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($submitNames)) {
|
return $submitted;
|
||||||
// no specific submit button, so check all known submit button names
|
|
||||||
$submitNames = InputfieldSubmit::getSubmitNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!empty($formName)) {
|
|
||||||
// this form has a name attribute, so we can add that to our checks
|
|
||||||
$key = '_' . $this->className();
|
|
||||||
$value = $method === 'GET' ? $input->get($key) : $input->post($key);
|
|
||||||
if($value !== $formName) {
|
|
||||||
// submitted form name does not match this form
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// at this point we know this form as submitted
|
|
||||||
$submit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find out which submit button was clicked if possible
|
|
||||||
foreach($submitNames as $name) {
|
|
||||||
$value = $method === 'GET' ? $input->get($name) : $input->post($name);
|
|
||||||
// if value not present in input for this submit button then skip it
|
|
||||||
if($value === null) continue;
|
|
||||||
// value was submitted for button name
|
|
||||||
$f = $this->getChildByName($name);
|
|
||||||
// if value submitted is not the same as value on the button, skip it
|
|
||||||
if(!$f || $f->val() !== $value) continue;
|
|
||||||
// we found our submit button
|
|
||||||
$submit = $f;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if submitted and CSRF protection in place, check that token is valid
|
|
||||||
if($submit && $this->getSetting('protectCSRF') && $method === 'POST') {
|
|
||||||
$csrf = $this->wire('session')->CSRF();
|
|
||||||
if(!$csrf->hasValidToken()) $submit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $submit ? $submit : false;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For internal debugging purposes
|
* For internal debugging purposes
|
||||||
@@ -608,6 +672,38 @@ class InputfieldForm extends InputfieldWrapper {
|
|||||||
return $this->_processInputData;
|
return $this->_processInputData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of errors that occurred on any of the children during input processing.
|
||||||
|
*
|
||||||
|
* Should only be called after `process()` or `processInput()`.
|
||||||
|
*
|
||||||
|
* #pw-group-errors
|
||||||
|
*
|
||||||
|
* @param bool $clear Specify true to clear out the errors (default=false).
|
||||||
|
* @return array Array of error strings
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getErrors($clear = false) {
|
||||||
|
if($this->errorCache !== null) return $this->errorCache;
|
||||||
|
$errors = parent::getErrors($clear);
|
||||||
|
$this->errorCache = $clear ? null : $errors;
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get name for this form
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @since 3.0.205
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFormName() {
|
||||||
|
$formName = $this->attr('name');
|
||||||
|
if(empty($formName)) $formName = $this->attr('id');
|
||||||
|
if(empty($formName)) $formName = $this->className();
|
||||||
|
return (string) $formName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook called right before form is rendered or processed
|
* Hook called right before form is rendered or processed
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user