Passed
Pull Request — master (#770)
by Jess
04:46
created

UserFormsRequiredFields::hasDisplayRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\UserForms\Form;
4
5
use SilverStripe\Dev\Debug;
6
use SilverStripe\Forms\FileField;
7
use SilverStripe\Forms\FormField;
8
use SilverStripe\Forms\RequiredFields;
9
use SilverStripe\ORM\ArrayLib;
10
use SilverStripe\UserForms\Model\EditableFormField;
11
12
/**
13
 * An extension of RequiredFields which handles conditionally required fields.
14
 *
15
 * A conditionally required is a field that is required, but can be hidden by display rules.
16
 * When it is visible, (according to the submitted form data) it will be validated as required.
17
 * When it is hidden, it will skip required validation.
18
 *
19
 * Required fields will be validated as usual.
20
 * Conditionally required fields will be validated IF the display rules are satisfied in the submitted dataset.
21
 */
22
class UserFormsRequiredFields extends RequiredFields
23
{
24
    /**
25
     * Allows validation of fields via specification of a php function for
26
     * validation which is executed after the form is submitted.
27
     *
28
     * @param array $data
29
     *
30
     * @return boolean
31
     */
32
    public function php($data)
33
    {
34
        $valid = true;
35
        $fields = $this->form->Fields();
36
37
        foreach ($fields as $field) {
38
            $valid = ($field->validate($this) && $valid);
39
        }
40
41
        if (empty($this->required)) {
42
            return $valid;
43
        }
44
45
        foreach ($this->required as $fieldName) {
46
            if (!$fieldName) {
47
                continue;
48
            }
49
50
            // get form field
51
            if ($fieldName instanceof FormField) {
52
                $formField = $fieldName;
53
                $fieldName = $fieldName->getName();
54
            } else {
55
                $formField = $fields->dataFieldByName($fieldName);
56
            }
57
58
            // get editable form field - owns display rules for field
59
            $editableFormField = $this->getEditableFormFieldByName($fieldName);
60
61
            $error = false;
62
63
            // validate if there are no display rules or the field is conditionally visible
64
            if (!$this->hasDisplayRules($editableFormField) ||
65
                $this->conditionalFieldEnabled($editableFormField, $data)) {
66
                $error = $this->validateRequired($formField, $data);
67
            }
68
69
            // handle error case
70
            if ($formField && $error) {
71
                $this->handleError($formField, $fieldName);
72
73
                $valid = false;
74
            }
75
        }
76
77
        return $valid;
78
    }
79
80
    private function getEditableFormFieldByName($name)
81
    {
82
        return EditableFormField::get()->filter(['name' => $name])->first();
83
    }
84
85
    private function hasDisplayRules($field)
86
    {
87
        return ($field->DisplayRules()->count() > 0);
88
    }
89
90
    private function conditionalFieldEnabled($editableFormField, $data)
91
    {
92
        $displayRules = $editableFormField->DisplayRules();
93
94
        $conjunction = $editableFormField->DisplayRulesConjunctionNice();
95
96
        $displayed = ($editableFormField->ShowOnLoadNice() === 'show');
97
98
        // && start with true and find and condition that doesn't satisfy
99
        // || start with false and find and condition that satisfies
100
        $conditionsSatisfied = ($conjunction === '&&');
101
102
        foreach ($displayRules as $rule) {
103
            $controllingField = EditableFormField::get()->byID($rule->ConditionFieldID);
104
105
            if ($controllingField->DisplayRules()->count() > 0) { // controllingField is also a conditional field
106
                // recursively check - if any of the dependant fields are hidden, then this field cannot be visible.
107
                if ($this->conditionalFieldEnabled($controllingField, $data)) {
108
                    return false;
109
                };
110
            }
111
112
            $ruleSatisfied = $rule->validateAgainstFormData($data);
113
114
            if ($conjunction === '||' && $ruleSatisfied) {
115
                $conditionsSatisfied = true;
116
                break;
117
            }
118
            if ($conjunction === '&&' && !$ruleSatisfied) {
119
                $conditionsSatisfied = false;
120
                break;
121
            }
122
        }
123
124
        // initially displayed - condition fails || initially hidden, condition passes
125
        return ($displayed xor $conditionsSatisfied);
126
    }
127
128
    // logic replicated from php() method of parent class SilverStripe\Forms\RequiredFields
129
    // TODO refactor to share with parent (would require corrosponding change in framework)
130
    private function validateRequired($field, $data)
131
    {
132
        $error = false;
133
        $fieldName = $field->getName();
134
        // submitted data for file upload fields come back as an array
135
        $value = isset($data[$fieldName]) ? $data[$fieldName] : null;
136
137
        if (is_array($value)) {
138
            if ($field instanceof FileField && isset($value['error']) && $value['error']) {
139
                $error = true;
140
            } else {
141
                $error = (count($value)) ? false : true;
142
            }
143
        } else {
144
            // assume a string or integer
145
            $error = (strlen($value)) ? false : true;
146
        }
147
148
        return $error;
149
    }
150
151
    private function handleError($formField, $fieldName)
152
    {
153
        $errorMessage = _t(
154
            'SilverStripe\\Forms\\Form.FIELDISREQUIRED',
155
            '{name} is required',
156
            array(
157
                'name' => strip_tags(
158
                    '"' . ($formField->Title() ? $formField->Title() : $fieldName) . '"'
159
                )
160
            )
161
        );
162
163
        if ($msg = $formField->getCustomValidationMessage()) {
164
            $errorMessage = $msg;
165
        }
166
167
        $this->validationError(
168
            $fieldName,
169
            $errorMessage,
170
            "required"
171
        );
172
    }
173
}
174