Passed
Pull Request — master (#770)
by Jess
03:11
created

UserFormsRequiredFields::validateRequired()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 11
nc 10
nop 2
dl 0
loc 19
rs 7.7777
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\UserForms\Form;
4
5
use SilverStripe\Dev\Debug;
6
use SilverStripe\Forms\RequiredFields;
7
use SilverStripe\ORM\ArrayLib;
8
use SilverStripe\UserForms\Model\EditableFormField;
9
10
/**
11
 * An extension of RequiredFields which handles conditionally required fields.
12
 *
13
 * A conditionally required is a field that is required, but can be hidden by display rules.
14
 * When it is visible, (according to the submitted form data) it will be validated as required.
15
 * When it is hidden, it will skip required validation.
16
 *
17
 * Required fields will be validated as usual.
18
 * Conditionally required fields will be validated IF the display rules are satisfied in the submitted dataset.
19
 */
20
class UserFormsRequiredFields extends RequiredFields
21
{
22
    /**
23
     * Allows validation of fields via specification of a php function for
24
     * validation which is executed after the form is submitted.
25
     *
26
     * @param array $data
27
     *
28
     * @return boolean
29
     */
30
    public function php($data)
31
    {
32
        $valid = true;
33
        $fields = $this->form->Fields();
34
35
        foreach ($fields as $field) {
36
            $valid = ($field->validate($this) && $valid);
37
        }
38
39
        if (!$this->required) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->required of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
40
            return $valid;
41
        }
42
43
        foreach ($this->required as $fieldName) {
44
            if (!$fieldName) {
45
                continue;
46
            }
47
48
            // get form field
49
            if ($fieldName instanceof FormField) {
0 ignored issues
show
Bug introduced by
The type SilverStripe\UserForms\Form\FormField was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
                $formField = $fieldName;
51
                $fieldName = $fieldName->getName();
52
            } else {
53
                $formField = $fields->dataFieldByName($fieldName);
54
            }
55
56
            // get editable form field - owns display rules for field
57
            $editableFormField = $this->getEditableFormFieldByName($fieldName);
58
59
            $error = false;
60
61
            // validate if there are no display rules or the field is conditionally visible
62
            if (!$this->hasDisplayRules($editableFormField) ||
63
                $this->conditionalFieldEnabled($editableFormField, $data)) {
64
                $error = $this->validateRequired($formField, $data);
65
            }
66
67
            // handle error case
68
            if ($formField && $error) {
69
                $this->handleError($formField, $fieldName);
70
71
                $valid = false;
72
            }
73
        }
74
75
        return $valid;
76
    }
77
78
    private function getEditableFormFieldByName($name)
79
    {
80
        return EditableFormField::get()->filter(['name' => $name])->first();
81
    }
82
83
    private function getEditableFormFieldByID($id)
0 ignored issues
show
Unused Code introduced by
The method getEditableFormFieldByID() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
84
    {
85
        return EditableFormField::get_by_id($id);
0 ignored issues
show
Bug introduced by
The call to SilverStripe\ORM\DataObject::get_by_id() has too few arguments starting with id. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

85
        return EditableFormField::/** @scrutinizer ignore-call */ get_by_id($id);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
86
    }
87
88
    private function hasDisplayRules($field)
89
    {
90
        return ($field->DisplayRules()->count() > 0);
91
    }
92
93
    private function conditionalFieldEnabled($editableFormField, $data)
94
    {
95
        $displayRules = $editableFormField->DisplayRules();
96
97
        $conjunction = $editableFormField->DisplayRulesConjunctionNice();
98
99
        $displayed = ($editableFormField->ShowOnLoadNice() === 'show');
100
101
        // && start with true and find and condition that doesn't satisfy
102
        // || start with false and find and condition that satisfies
103
        $conditionsSatisfied = ($conjunction === '&&');
104
105
        foreach ($displayRules as $rule) {
106
            $controllingField = EditableFormField::get()->byID($rule->ConditionFieldID);
107
            $valid = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $valid is dead and can be removed.
Loading history...
108
109
            if ($controllingField->DisplayRules()->count() > 0) { // controllingField is also a conditional field
110
                // recursively check - if any of the dependant fields are hidden, then this field cannot be visible.
111
                if ($this->conditionalFieldEnabled($controllingField, $data)) {
112
                    return false;
113
                };
114
            }
115
116
            $ruleSatisfied = $rule->validateAgainstFormData($data);
117
118
            if ($conjunction === '||' && $ruleSatisfied) {
119
                $conditionsSatisfied = true;
120
                break;
121
            }
122
            if ($conjunction === '&&' && !$ruleSatisfied) {
123
                $conditionsSatisfied = false;
124
                break;
125
            }
126
        }
127
128
        // initially displayed - condition fails || initially hidden, condition passes
129
        return ($displayed xor $conditionsSatisfied);
130
    }
131
132
    private function validateRequired($field, $data)
133
    {
134
        $error = false;
135
        $fieldName = $field->getName();
136
        // submitted data for file upload fields come back as an array
137
        $value = isset($data[$fieldName]) ? $data[$fieldName] : null;
138
139
        if (is_array($value)) {
140
            if ($formField instanceof FileField && isset($value['error']) && $value['error']) {
0 ignored issues
show
Bug introduced by
The type SilverStripe\UserForms\Form\FileField was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
Comprehensibility Best Practice introduced by
The variable $formField seems to be never defined.
Loading history...
141
                $error = true;
142
            } else {
143
                $error = (count($value)) ? false : true;
144
            }
145
        } else {
146
            // assume a string or integer
147
            $error = (strlen($value)) ? false : true;
148
        }
149
150
        return $error;
151
    }
152
153
    private function handleError($formField, $fieldName)
154
    {
155
        $errorMessage = _t(
156
            'SilverStripe\\Forms\\Form.FIELDISREQUIRED',
157
            '{name} is required',
158
            array(
159
                'name' => strip_tags(
160
                    '"' . ($formField->Title() ? $formField->Title() : $fieldName) . '"'
161
                )
162
            )
163
        );
164
165
        if ($msg = $formField->getCustomValidationMessage()) {
166
            $errorMessage = $msg;
167
        }
168
169
        $this->validationError(
170
            $fieldName,
171
            $errorMessage,
172
            "required"
173
        );
174
    }
175
}
176