Completed
Pull Request — master (#647)
by Robbie
02:13
created

EditableCustomRule::canEdit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace SilverStripe\UserForms\Model;
4
5
use LogicException;
6
use SilverStripe\CMS\Controllers\CMSMain;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\ORM\DataObject;
10
use Silverstripe\Versioned\Versioned;
11
12
/**
13
 * A custom rule for showing / hiding an EditableFormField
14
 * based the value of another EditableFormField.
15
 *
16
 * @method EditableFormField Parent()
17
 * @package userforms
18
 *
19
 * @property string Display
20
 * @property string ConditionOption
21
 * @property string FieldValue
22
 */
23
class EditableCustomRule extends DataObject
24
{
25
    private static $condition_options = [
0 ignored issues
show
Unused Code introduced by
The property $condition_options is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
26
        'IsBlank' => 'Is blank',
27
        'IsNotBlank' => 'Is not blank',
28
        'HasValue' => 'Equals',
29
        'ValueNot' => 'Doesn\'t equal',
30
        'ValueLessThan' => 'Less than',
31
        'ValueLessThanEqual' => 'Less than or equal',
32
        'ValueGreaterThan' => 'Greater than',
33
        'ValueGreaterThanEqual' => 'Greater than or equal'
34
    ];
35
36
    private static $db = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
37
        'Display' => 'Enum("Show,Hide")',
38
        'ConditionOption' => 'Enum("IsBlank,IsNotBlank,HasValue,ValueNot,ValueLessThan,ValueLessThanEqual,ValueGreaterThan,ValueGreaterThanEqual")',
39
        'FieldValue' => 'Varchar(255)'
40
    ];
41
42
    private static $has_one = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
43
        'Parent' => EditableFormField::class,
44
        'ConditionField' => EditableFormField::class
45
    ];
46
47
    /**
48
     * Built in extensions required
49
     *
50
     * @config
51
     * @var array
52
     */
53
    private static $extensions = [
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
54 1
        Versioned::class . "('Stage', 'Live')"
55
    ];
56 1
57 1
    private static $table_name = 'EditableCustomRule';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $table_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
58
59
    /**
60
     * @param Member $member
61
     * @return bool
62
     */
63
    public function canDelete($member = null)
64
    {
65
        return $this->canEdit($member);
66
    }
67
68
    /**
69
     * @param Member $member
70
     * @return bool
71
     */
72
    public function canEdit($member = null)
73
    {
74
        return $this->Parent()->canEdit($member);
75
    }
76
77
    /**
78
     * @param Member $member
79
     * @return bool
80
     */
81
    public function canView($member = null)
82
    {
83
        return $this->Parent()->canView($member);
84
    }
85
86
    /**
87
     * Return whether a user can create an object of this type
88
     *
89
     * @param Member $member
90
     * @param array $context Virtual parameter to allow context to be passed in to check
91
     * @return bool
92
     */
93 View Code Duplication
    public function canCreate($member = null, $context = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
    {
95
        // Check parent page
96
        $parent = $this->getCanCreateContext(func_get_args());
97
        if ($parent) {
98
            return $parent->canEdit($member);
99
        }
100
101
        // Fall back to secure admin permissions
102
        return parent::canCreate($member);
103
    }
104
105
    /**
106
     * Helper method to check the parent for this object
107
     *
108
     * @param array $args List of arguments passed to canCreate
109
     * @return DataObject Some parent dataobject to inherit permissions from
110
     */
111 View Code Duplication
    protected function getCanCreateContext($args)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
    {
113
        // Inspect second parameter to canCreate for a 'Parent' context
114
        if (isset($args[1]['Parent'])) {
115
            return $args[1]['Parent'];
116
        }
117
        // Hack in currently edited page if context is missing
118
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
119
            return Controller::curr()->currentPage();
120
        }
121
122
        // No page being edited
123
        return null;
124
    }
125
126
    /**
127
     * @param Member $member
128
     * @return bool
129
     */
130
    public function canPublish($member = null)
131
    {
132
        return $this->canEdit($member);
133
    }
134
135
    /**
136
     * @param Member $member
137
     * @return bool
138
     */
139
    public function canUnpublish($member = null)
140
    {
141
        return $this->canDelete($member);
142
    }
143
144
    /**
145
     * Substitutes configured rule logic with it's JS equivalents and returns them as array elements
146
     *
147
     * @return array
148
     * @throws LogicException If the provided condition option was not able to be handled
149
     */
150
    public function buildExpression()
151
    {
152
        /** @var EditableFormField $formFieldWatch */
153
        $formFieldWatch = $this->ConditionField();
0 ignored issues
show
Documentation Bug introduced by
The method ConditionField does not exist on object<SilverStripe\User...del\EditableCustomRule>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
154
        //Encapsulated the action to the object
155
        $action = $formFieldWatch->getJsEventHandler();
156
157
        // is this field a special option field
158
        $checkboxField = $formFieldWatch->isCheckBoxField();
159 2
        $radioField = $formFieldWatch->isRadioField();
160
        $target = sprintf('$("%s")', $formFieldWatch->getSelectorFieldOnly());
161
        $fieldValue = Convert::raw2js($this->FieldValue);
162 2
163
        $conditionOptions = [
164 2
            'ValueLessThan'         => '<',
165
            'ValueLessThanEqual'    => '<=',
166
            'ValueGreaterThan'      => '>',
167 2
            'ValueGreaterThanEqual' => '>='
168 2
        ];
169 2
170 2
        // and what should we evaluate
171
        switch ($this->ConditionOption) {
172
            case 'IsNotBlank':
173 2
            case 'IsBlank':
174 2
                $expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")" : "{$target}.val() == ''";
175 2
                if ($this->ConditionOption == 'IsNotBlank') {
176
                    //Negate
177 2
                    $expression = "!({$expression})";
178
                }
179 2
                break;
180 2
            case 'HasValue':
181 2
            case 'ValueNot':
182
                if ($checkboxField) {
183
                    if ($formFieldWatch->isCheckBoxGroupField()) {
184
                        $expression = sprintf(
185
                            "$.inArray('%s', %s.filter(':checked').map(function(){ return $(this).val();}).get()) > -1",
186
                            $fieldValue,
187
                            $target
188 2
                        );
189 2
                    } else {
190 2
                        $expression = "{$target}.prop('checked')";
191
                    }
192
                } elseif ($radioField) {
193
                    // We cannot simply get the value of the radio group, we need to find the checked option first.
194
                    $expression = sprintf(
195
                        '%s.closest(".field, .control-group").find("input:checked").val() == "%s"',
196
                        $target,
197
                        $fieldValue
198
                    );
199
                } else {
200 2
                    $expression = sprintf('%s.val() == "%s"', $target, $fieldValue);
201
                }
202
203
                if ($this->ConditionOption == 'ValueNot') {
204
                    //Negate
205
                    $expression = "!({$expression})";
206
                }
207
                break;
208 2
            case 'ValueLessThan':
209
            case 'ValueLessThanEqual':
210
            case 'ValueGreaterThan':
211 2
            case 'ValueGreaterThanEqual':
212
                $expression = sprintf(
213
                    '%s.val() %s parseFloat("%s")',
214
                    $target,
215 2
                    $conditionOptions[$this->ConditionOption],
216 1
                    $fieldValue
217 1
                );
218 1
                break;
219 1
            default:
220 1
                throw new LogicException("Unhandled rule {$this->ConditionOption}");
221 1
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
222 1
        }
223 1
224
        $result = [
225 1
            'operation' => $expression,
226 1
            'event'     => $action,
227
        ];
228
229
        return $result;
230 2
    }
231
232
    /**
233 2
     * Returns the opposite visibility function for the value of the initial visibility field, e.g. show/hide. This
234 2
     * will toggle the "hide" class either way, which is handled by CSS.
235 2
     *
236
     * @param string $text
237 2
     * @return string
238
     */
239
    public function toggleDisplayText($text)
240
    {
241
        return (strtolower($text) === 'hide') ? 'removeClass("hide")' : 'addClass("hide")';
242
    }
243
}
244