Extend PHP Validation to validate required fields by custom rules #131

Open
wilr opened this Issue Jun 3, 2013 · 9 comments

Comments

Projects
None yet
6 participants
@wilr
Member

wilr commented Jun 3, 2013

From #116

Create textfield test.
Create new textfield test1. Mark it required & set it to hide by default.
Add custom rule to test1: Show This Field When test is Value test

Try submitting the form with field test set to check. The form won't validate. Now set it to test. Field test1 appears correctly. And if it is unset, the form won't pass validation. Setting test1 validates the form.

@tazzydemon

This comment has been minimized.

Show comment Hide comment
@tazzydemon

tazzydemon Apr 9, 2014

Contributor

Any progress on this issue which has reared its ugly head with me. I'm almost prepared to put up with no php validation at this point,.

Contributor

tazzydemon commented Apr 9, 2014

Any progress on this issue which has reared its ugly head with me. I'm almost prepared to put up with no php validation at this point,.

@wilr

This comment has been minimized.

Show comment Hide comment
@wilr

wilr Apr 9, 2014

Member

No progress on it from me. Happy to review patches if you want to give it a go. If you want to disable PHP validation you may as well make the field not required and just put (required) as a label text or something.

Member

wilr commented Apr 9, 2014

No progress on it from me. Happy to review patches if you want to give it a go. If you want to disable PHP validation you may as well make the field not required and just put (required) as a label text or something.

@jyrkij

This comment has been minimized.

Show comment Hide comment
@jyrkij

jyrkij Nov 3, 2014

Contributor

If I'll try patching this at some point where should I start my searches for PHP side of validation. As I mentioned in #116 I couldn't find the place then...

Contributor

jyrkij commented Nov 3, 2014

If I'll try patching this at some point where should I start my searches for PHP side of validation. As I mentioned in #116 I couldn't find the place then...

@wilr

This comment has been minimized.

Show comment Hide comment
@wilr

wilr Nov 4, 2014

Member

The PHP side of validation uses the normal SilverStripe API. See UserDefinedForm::getRequiredFields(). One way would be to not use the core SilverStripe validate() method on form fields and manually invoke validate methods in the submit form method.

Member

wilr commented Nov 4, 2014

The PHP side of validation uses the normal SilverStripe API. See UserDefinedForm::getRequiredFields(). One way would be to not use the core SilverStripe validate() method on form fields and manually invoke validate methods in the submit form method.

@tazzydemon

This comment has been minimized.

Show comment Hide comment
@tazzydemon

tazzydemon Jan 15, 2015

Contributor

Since I just failed so spectacularly on identifying a jquery validation error, ironically because of this, I was wondering if anybody had any more to offer. I cant see a way round it other than to not require the conditional fields and the client is demanding that they are required. A problem and perhaps time for another custom form!

Contributor

tazzydemon commented Jan 15, 2015

Since I just failed so spectacularly on identifying a jquery validation error, ironically because of this, I was wondering if anybody had any more to offer. I cant see a way round it other than to not require the conditional fields and the client is demanding that they are required. A problem and perhaps time for another custom form!

@wilr

This comment has been minimized.

Show comment Hide comment
@wilr

wilr Jan 15, 2015

Member

@tazzydemon What I would aim to do is ticking that required box in the cms didn't actually set the PHP required fields and validation is controlled after the rules are parsed in the form handler. I'm keen to build this when I can get around to funding it, if you want to tackle it PR's welcome.

Member

wilr commented Jan 15, 2015

@tazzydemon What I would aim to do is ticking that required box in the cms didn't actually set the PHP required fields and validation is controlled after the rules are parsed in the form handler. I'm keen to build this when I can get around to funding it, if you want to tackle it PR's welcome.

@Doniz

This comment has been minimized.

Show comment Hide comment
@Doniz

Doniz Jul 19, 2016

Hi guys, i a little bit working on this. I created a method which is remove field from required fields if it's not pass the rules of dependencies. But this method need to be improved with radio, checkbox and checkbox groups.

here is the code userforms/code/model/UserDefinedForm.php of UserDefinedForm_Controller:


    /**
     * Method will checks all required fields within post data, and check did it
     * need to remove field from required fields if field rules (dependencies) are negative.
     *
     * @param RequiredFields $requiredFields Set required fields
     * @param array          $post $_POST data
     *
     * @return bool | returns false when no post array are given and true when scripts end.
     */
    public function requiredFieldsByRules(RequiredFields &$requiredFields, array $post = []) {
        if(count($post) <= 0) return false;

        foreach($requiredFields->getRequired() as $name) {
            if(!isset($post[$name]) || empty($post[$name])) {
                // field is required, but is not set or empty
                // check the rules expression, maybe this field is hidden
                if(($fields = $this->Fields()->filter('Name', $name)) && $fields->exists()) {
                    /** @var EditableFormField $field */
                    $field = $fields->first();
                    $remove = false;

                    foreach($field->CustomRules() as $rule) {
                        $rule = $rule->toMap();
                        $remove = (string) $rule['Display'] === 'Show' ? false : true;
                        $expectedValue = $rule['Value'];
                        $conditionFieldName = $rule['ConditionField'];
                        $postConditionFieldValue = isset($post[$conditionFieldName]) ? $post[$conditionFieldName] : '';
                        $conditionField = !empty($conditionFieldName) ? $this->Fields()->filter('Name', $conditionFieldName) : null;

                        if(!is_null($conditionField) && $conditionField->exists()) {
                            /** @var EditableFormField $conditionField */
                            $conditionField = $conditionField->first();
                        }

                        // todo: improve with radio, checkbox, and checkbox group inputs
                        switch($rule['ConditionOption']) {
                            case 'IsNotBlank':

                                if(empty($postConditionFieldValue)) {
                                    $remove = !$remove;
                                }

                                break;
                            case 'IsBlank':

                                if(!empty($postConditionFieldValue)) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'HasValue':

                                if($postConditionFieldValue != $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueLessThan':

                                if((float) $postConditionFieldValue >= (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueLessThanEqual':

                                if((float) $postConditionFieldValue > (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueGreaterThan':

                                if((float) $postConditionFieldValue <= (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueGreaterThanEqual':

                                if((float) $postConditionFieldValue < (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            default: // ==HasNotValue

                                if($postConditionFieldValue == $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;
                        }
                    }

                    if($remove) {
                        // remove field from required fields
                        $requiredFields->removeRequiredField($name);
                    }
                }
            }
        }

        return true;
    }

and in the Form method need to add this line:


// remove required fields if it's not pass the dependencies
$this->requiredFieldsByRules($required, Convert::raw2xml($_POST));

example:


    /**
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
     * on a UserDefinedForm extension.
     *
     * @return Form|false
     */
    public function Form() {
        $fields = $this->getFormFields();
        if(!$fields || !$fields->exists()) return false;

        $actions = $this->getFormActions();

        // get the required fields including the validation
        $required = $this->getRequiredFields();

        // remove required fields if it's not pass the dependencies
        $this->requiredFieldsByRules($required, Convert::raw2xml($_POST));

        // generate the conditional logic 
        $this->generateConditionalJavascript();

        $form = new Form($this, "Form", $fields, $actions, $required);
        $form->setRedirectToFormOnValidationError(true);

        $data = Session::get("FormInfo.{$form->FormName()}.data");

        if(is_array($data)) $form->loadDataFrom($data);

        $this->extend('updateForm', $form);

        if($this->DisableCsrfSecurityToken) {
            $form->disableSecurityToken();
        }

        return $form;
    }

So when field will have a rules, this method will check is it set as required field and try to check if rules are negative.

Doniz commented Jul 19, 2016

Hi guys, i a little bit working on this. I created a method which is remove field from required fields if it's not pass the rules of dependencies. But this method need to be improved with radio, checkbox and checkbox groups.

here is the code userforms/code/model/UserDefinedForm.php of UserDefinedForm_Controller:


    /**
     * Method will checks all required fields within post data, and check did it
     * need to remove field from required fields if field rules (dependencies) are negative.
     *
     * @param RequiredFields $requiredFields Set required fields
     * @param array          $post $_POST data
     *
     * @return bool | returns false when no post array are given and true when scripts end.
     */
    public function requiredFieldsByRules(RequiredFields &$requiredFields, array $post = []) {
        if(count($post) <= 0) return false;

        foreach($requiredFields->getRequired() as $name) {
            if(!isset($post[$name]) || empty($post[$name])) {
                // field is required, but is not set or empty
                // check the rules expression, maybe this field is hidden
                if(($fields = $this->Fields()->filter('Name', $name)) && $fields->exists()) {
                    /** @var EditableFormField $field */
                    $field = $fields->first();
                    $remove = false;

                    foreach($field->CustomRules() as $rule) {
                        $rule = $rule->toMap();
                        $remove = (string) $rule['Display'] === 'Show' ? false : true;
                        $expectedValue = $rule['Value'];
                        $conditionFieldName = $rule['ConditionField'];
                        $postConditionFieldValue = isset($post[$conditionFieldName]) ? $post[$conditionFieldName] : '';
                        $conditionField = !empty($conditionFieldName) ? $this->Fields()->filter('Name', $conditionFieldName) : null;

                        if(!is_null($conditionField) && $conditionField->exists()) {
                            /** @var EditableFormField $conditionField */
                            $conditionField = $conditionField->first();
                        }

                        // todo: improve with radio, checkbox, and checkbox group inputs
                        switch($rule['ConditionOption']) {
                            case 'IsNotBlank':

                                if(empty($postConditionFieldValue)) {
                                    $remove = !$remove;
                                }

                                break;
                            case 'IsBlank':

                                if(!empty($postConditionFieldValue)) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'HasValue':

                                if($postConditionFieldValue != $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueLessThan':

                                if((float) $postConditionFieldValue >= (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueLessThanEqual':

                                if((float) $postConditionFieldValue > (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueGreaterThan':

                                if((float) $postConditionFieldValue <= (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            case 'ValueGreaterThanEqual':

                                if((float) $postConditionFieldValue < (float) $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;

                            default: // ==HasNotValue

                                if($postConditionFieldValue == $expectedValue) {
                                    $remove = !$remove;
                                }

                                break;
                        }
                    }

                    if($remove) {
                        // remove field from required fields
                        $requiredFields->removeRequiredField($name);
                    }
                }
            }
        }

        return true;
    }

and in the Form method need to add this line:


// remove required fields if it's not pass the dependencies
$this->requiredFieldsByRules($required, Convert::raw2xml($_POST));

example:


    /**
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
     * on a UserDefinedForm extension.
     *
     * @return Form|false
     */
    public function Form() {
        $fields = $this->getFormFields();
        if(!$fields || !$fields->exists()) return false;

        $actions = $this->getFormActions();

        // get the required fields including the validation
        $required = $this->getRequiredFields();

        // remove required fields if it's not pass the dependencies
        $this->requiredFieldsByRules($required, Convert::raw2xml($_POST));

        // generate the conditional logic 
        $this->generateConditionalJavascript();

        $form = new Form($this, "Form", $fields, $actions, $required);
        $form->setRedirectToFormOnValidationError(true);

        $data = Session::get("FormInfo.{$form->FormName()}.data");

        if(is_array($data)) $form->loadDataFrom($data);

        $this->extend('updateForm', $form);

        if($this->DisableCsrfSecurityToken) {
            $form->disableSecurityToken();
        }

        return $form;
    }

So when field will have a rules, this method will check is it set as required field and try to check if rules are negative.

@dhensby

This comment has been minimized.

Show comment Hide comment
@dhensby

dhensby Jul 19, 2016

Member

@Doniz thanks for taking the time to do this - it may be best to move this discussion to a pull request - would you mind opening one with your code changes?

Member

dhensby commented Jul 19, 2016

@Doniz thanks for taking the time to do this - it may be best to move this discussion to a pull request - would you mind opening one with your code changes?

@tractorcow

This comment has been minimized.

Show comment Hide comment
@tractorcow

tractorcow Aug 2, 2016

Contributor

Another challenge to overcome is the configuration of jquery-validate on the frontend; It will require to be updated whenever field visibility is modified in order to not require hidden fields.

Also please note the PHP server side validation logic which fails when you mark a field as both required and having custom rules. :)

It's not going to be an easy challenge to solve in any case!

I'm going to downgrade this from a bug, as the bug of "required field is conditionally hidden" was actually prevented by the aforementioned validation rule. See

Contributor

tractorcow commented Aug 2, 2016

Another challenge to overcome is the configuration of jquery-validate on the frontend; It will require to be updated whenever field visibility is modified in order to not require hidden fields.

Also please note the PHP server side validation logic which fails when you mark a field as both required and having custom rules. :)

It's not going to be an easy challenge to solve in any case!

I'm going to downgrade this from a bug, as the bug of "required field is conditionally hidden" was actually prevented by the aforementioned validation rule. See

@tractorcow tractorcow added type/enhancement and removed type/bug labels Aug 2, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment