Passed
Push — master ( a1fd67...e2e77c )
by Garion
04:50 queued 10s
created

EditableCustomRule::validateAgainstFormData()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 45
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 35
nc 10
nop 1
dl 0
loc 45
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\UserForms\Model;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use SilverStripe\CMS\Controllers\CMSMain;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Core\Convert;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\Security\Member;
12
use SilverStripe\Versioned\Versioned;
13
14
/**
15
 * A custom rule for showing / hiding an EditableFormField
16
 * based the value of another EditableFormField.
17
 *
18
 * @method EditableFormField Parent()
19
 * @method EditableFormField ConditionField()
20
 *
21
 * @property string Display
22
 * @property string ConditionOption
23
 * @property string FieldValue
24
 */
25
class EditableCustomRule extends DataObject
26
{
27
    private static $condition_options = [
0 ignored issues
show
introduced by
The private property $condition_options is not used, and could be removed.
Loading history...
28
        'IsBlank' => 'Is blank',
29
        'IsNotBlank' => 'Is not blank',
30
        'HasValue' => 'Equals',
31
        'ValueNot' => 'Doesn\'t equal',
32
        'ValueLessThan' => 'Less than',
33
        'ValueLessThanEqual' => 'Less than or equal',
34
        'ValueGreaterThan' => 'Greater than',
35
        'ValueGreaterThanEqual' => 'Greater than or equal'
36
    ];
37
38
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
39
        'Display' => 'Enum("Show,Hide")',
40
        'ConditionOption' => 'Enum("IsBlank,IsNotBlank,HasValue,ValueNot,ValueLessThan,ValueLessThanEqual,ValueGreaterThan,ValueGreaterThanEqual")',
41
        'FieldValue' => 'Varchar(255)'
42
    ];
43
44
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
45
        'Parent' => EditableFormField::class,
46
        'ConditionField' => EditableFormField::class
47
    ];
48
49
    /**
50
     * Built in extensions required
51
     *
52
     * @config
53
     * @var array
54 1
     */
55
    private static $extensions = [
0 ignored issues
show
introduced by
The private property $extensions is not used, and could be removed.
Loading history...
56 1
        Versioned::class . "('Stage', 'Live')"
57 1
    ];
58
59
    private static $table_name = 'EditableCustomRule';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
60
61
    /**
62
     * @param Member $member
63
     * @return bool
64
     */
65
    public function canDelete($member = null)
66
    {
67
        return $this->canEdit($member);
68
    }
69
70
    /**
71
     * @param Member $member
72
     * @return bool
73
     */
74
    public function canEdit($member = null)
75
    {
76
        return $this->Parent()->canEdit($member);
77
    }
78
79
    /**
80
     * @param Member $member
81
     * @return bool
82
     */
83
    public function canView($member = null)
84
    {
85
        return $this->Parent()->canView($member);
86
    }
87
88
    /**
89
     * Return whether a user can create an object of this type
90
     *
91
     * @param Member $member
92
     * @param array  $context Virtual parameter to allow context to be passed in to check
93
     * @return bool
94
     */
95
    public function canCreate($member = null, $context = [])
96
    {
97
        // Check parent page
98
        $parent = $this->getCanCreateContext(func_get_args());
99
        if ($parent) {
0 ignored issues
show
introduced by
$parent is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
100
            return $parent->canEdit($member);
101
        }
102
103
        // Fall back to secure admin permissions
104
        return parent::canCreate($member);
105
    }
106
107
    /**
108
     * Helper method to check the parent for this object
109
     *
110
     * @param array $args List of arguments passed to canCreate
111
     * @return DataObject Some parent dataobject to inherit permissions from
112
     */
113
    protected function getCanCreateContext($args)
114
    {
115
        // Inspect second parameter to canCreate for a 'Parent' context
116
        if (isset($args[1]['Parent'])) {
117
            return $args[1]['Parent'];
118
        }
119
        // Hack in currently edited page if context is missing
120
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
121
            return Controller::curr()->currentPage();
122
        }
123
124
        // No page being edited
125
        return null;
126
    }
127
128
    /**
129
     * @param Member $member
130
     * @return bool
131
     */
132
    public function canPublish($member = null)
133
    {
134
        return $this->canEdit($member);
135
    }
136
137
    /**
138
     * @param Member $member
139
     * @return bool
140
     */
141
    public function canUnpublish($member = null)
142
    {
143
        return $this->canDelete($member);
144
    }
145
146
    /**
147
     * Substitutes configured rule logic with it's JS equivalents and returns them as array elements
148
     *
149
     * @return array
150
     * @throws LogicException If the provided condition option was not able to be handled
151
     */
152
    public function buildExpression()
153
    {
154
        /** @var EditableFormField $formFieldWatch */
155
        $formFieldWatch = $this->ConditionField();
156
        //Encapsulated the action to the object
157
        $action = $formFieldWatch->getJsEventHandler();
158
159 2
        // is this field a special option field
160
        $checkboxField = $formFieldWatch->isCheckBoxField();
161
        $radioField = $formFieldWatch->isRadioField();
162 2
        $target = sprintf('$("%s")', $formFieldWatch->getSelectorFieldOnly());
163
        $fieldValue = Convert::raw2js($this->FieldValue);
164 2
165
        $conditionOptions = [
166
            'ValueLessThan'         => '<',
167 2
            'ValueLessThanEqual'    => '<=',
168 2
            'ValueGreaterThan'      => '>',
169 2
            'ValueGreaterThanEqual' => '>='
170 2
        ];
171
172
        // and what should we evaluate
173 2
        switch ($this->ConditionOption) {
174 2
            case 'IsNotBlank':
175 2
            case 'IsBlank':
176
                $expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")" : "{$target}.val() == ''";
0 ignored issues
show
introduced by
The condition $radioField is always false.
Loading history...
177 2
                if ((string) $this->ConditionOption === 'IsNotBlank') {
178
                    //Negate
179 2
                    $expression = "!({$expression})";
180 2
                }
181 2
                break;
182
            case 'HasValue':
183
            case 'ValueNot':
184
                if ($checkboxField) {
0 ignored issues
show
introduced by
The condition $checkboxField is always false.
Loading history...
185
                    if ($formFieldWatch->isCheckBoxGroupField()) {
186
                        $expression = sprintf(
187
                            "$.inArray('%s', %s.filter(':checked').map(function(){ return $(this).val();}).get()) > -1",
188 2
                            $fieldValue,
189 2
                            $target
190 2
                        );
191
                    } else {
192
                        $expression = "{$target}.prop('checked')";
193
                    }
194
                } elseif ($radioField) {
0 ignored issues
show
introduced by
The condition $radioField is always false.
Loading history...
195
                    // We cannot simply get the value of the radio group, we need to find the checked option first.
196
                    $expression = sprintf(
197
                        '%s.closest(".field, .control-group").find("input:checked").val() == "%s"',
198
                        $target,
199
                        $fieldValue
200 2
                    );
201
                } else {
202
                    $expression = sprintf('%s.val() == "%s"', $target, $fieldValue);
203
                }
204
205
                if ((string) $this->ConditionOption === 'ValueNot') {
206
                    //Negate
207
                    $expression = "!({$expression})";
208 2
                }
209
                break;
210
            case 'ValueLessThan':
211 2
            case 'ValueLessThanEqual':
212
            case 'ValueGreaterThan':
213
            case 'ValueGreaterThanEqual':
214
                $expression = sprintf(
215 2
                    '%s.val() %s parseFloat("%s")',
216 1
                    $target,
217 1
                    $conditionOptions[$this->ConditionOption],
218 1
                    $fieldValue
219 1
                );
220 1
                break;
221 1
            default:
222 1
                throw new LogicException("Unhandled rule {$this->ConditionOption}");
223 1
                break;
224
        }
225 1
226 1
        $result = [
227
            'operation' => $expression,
228
            'event'     => $action,
229
        ];
230 2
231
        return $result;
232
    }
233 2
234 2
235 2
    /**
236
     * Determines whether the rule is satisfied, based on provided form data.
237 2
     * Used for php validation of required conditional fields
238
     *
239
     * @param array $data Submitted form data
240
     * @return boolean
241
     * @throws LogicException Invalid ConditionOption is set for this rule.
242
     */
243
    public function validateAgainstFormData(array $data)
244
    {
245
246
        $controllingField = $this->ConditionField();
247 2
248
        if (!isset($data[$controllingField->Name])) {
249 2
            return false;
250
        }
251
252 1
        $valid = false;
253
254
        $targetFieldValue = $this->FieldValue;
255
        $actualFieldValue = $data[$controllingField->Name];
256
257
        switch ($this->ConditionOption) {
258
            case 'IsNotBlank':
259
                $valid = ($actualFieldValue !== '');
260
                break;
261
            case 'IsBlank':
262
                $valid = ($actualFieldValue === '');
263
                break;
264
            case 'HasValue':
265
                $valid = ($actualFieldValue === $targetFieldValue);
266
                break;
267
            case 'ValueNot':
268
                $valid = ($actualFieldValue !== $targetFieldValue);
269
                break;
270
            case 'ValueLessThan':
271
                $valid = ($actualFieldValue < $targetFieldValue);
272
                break;
273
            case 'ValueLessThanEqual':
274
                $valid = ($actualFieldValue <= $targetFieldValue);
275
                break;
276
            case 'ValueGreaterThan':
277
                $valid = ($actualFieldValue > $targetFieldValue);
278
                break;
279
            case 'ValueGreaterThanEqual':
280
                $valid = ($actualFieldValue >= $targetFieldValue);
281
                break;
282
            default:
283
                throw new LogicException("Unhandled rule {$this->ConditionOption}");
284
                break;
285
        }
286
287
        return $valid;
288
    }
289
290
291
    /**
292
     * Returns the opposite visibility function for the value of the initial visibility field, e.g. show/hide. This
293
     * will toggle the "hide" class either way, which is handled by CSS.
294
     *
295
     * @param string $initialState
296
     * @param boolean $invert
297
     * @return string
298
     */
299
    public function toggleDisplayText($initialState, $invert = false)
300
    {
301
        $action = strtolower($initialState) === 'hide' ? 'removeClass' : 'addClass';
302
        if ($invert) {
303
            $action = $action === 'removeClass' ? 'addClass' : 'removeClass';
304
        }
305
        return sprintf('%s("hide")', $action);
306
    }
307
308
    /**
309
     * Returns an event name to be dispatched when the field is changed. Matches up with the visibility classes
310
     * added or removed in `toggleDisplayText()`.
311
     *
312
     * @param string $initialState
313
     * @param bool $invert
314
     * @return string
315
     */
316
    public function toggleDisplayEvent($initialState, $invert = false)
317
    {
318
        $action = strtolower($initialState) === 'hide' ? 'show' : 'hide';
319
        if ($invert) {
320
            $action = $action === 'hide' ? 'show' : 'hide';
321
        }
322
        return sprintf('userform.field.%s', $action);
323
    }
324
}
325