Completed
Push — master ( 016a0b...25fc98 )
by Franco
12s
created

EditableFormField::formatDisplayRules()   C

Complexity

Conditions 7
Paths 20

Size

Total Lines 43
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 26
nc 20
nop 0
1
<?php
2
3
namespace SilverStripe\UserForms\Model;
4
5
use SilverStripe\CMS\Controllers\CMSMain;
6
use SilverStripe\CMS\Controllers\CMSPageEditController;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Core\ClassInfo;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Core\Manifest\ModuleLoader;
12
use SilverStripe\Dev\Deprecation;
13
use SilverStripe\Forms\CheckboxField;
14
use SilverStripe\Forms\DropdownField;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\GridField\GridField;
17
use SilverStripe\Forms\GridField\GridFieldButtonRow;
18
use SilverStripe\Forms\GridField\GridFieldConfig;
19
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
20
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
21
use SilverStripe\Forms\LabelField;
22
use SilverStripe\Forms\LiteralField;
23
use SilverStripe\Forms\ReadonlyField;
24
use SilverStripe\Forms\SegmentField;
25
use SilverStripe\Forms\TabSet;
26
use SilverStripe\Forms\TextField;
27
use SilverStripe\ORM\ArrayList;
28
use SilverStripe\ORM\DataObject;
29
use SilverStripe\ORM\FieldType\DBField;
30
use SilverStripe\ORM\ValidationException;
31
use SilverStripe\UserForms\Extension\UserFormFieldEditorExtension;
32
use SilverStripe\UserForms\Model\UserDefinedForm;
33
use SilverStripe\UserForms\Model\EditableCustomRule;
34
use SilverStripe\UserForms\Model\EditableFormField\EditableFieldGroup;
35
use SilverStripe\UserForms\Model\EditableFormField\EditableFieldGroupEnd;
36
use SilverStripe\UserForms\Model\EditableFormField\EditableFormStep;
37
use SilverStripe\UserForms\Model\Submission\SubmittedFormField;
38
use SilverStripe\UserForms\Modifier\DisambiguationSegmentFieldModifier;
39
use SilverStripe\UserForms\Modifier\UnderscoreSegmentFieldModifier;
40
use SilverStripe\Versioned\Versioned;
41
use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton;
42
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
43
44
/**
45
 * Represents the base class of a editable form field
46
 * object like {@link EditableTextField}.
47
 *
48
 * @package userforms
49
 *
50
 * @property string $Name
51
 * @property string $Title
52
 * @property string $Default
53
 * @property int $Sort
54
 * @property bool $Required
55
 * @property string $CustomErrorMessage
56
 * @property boolean $ShowOnLoad
57
 * @property string $DisplayRulesConjunction
58
 * @method UserDefinedForm Parent() Parent page
59
 * @method DataList DisplayRules() List of EditableCustomRule objects
60
 * @mixin Versioned
61
 */
62
class EditableFormField extends DataObject
63
{
64
    /**
65
     * Set to true to hide from class selector
66
     *
67
     * @config
68
     * @var bool
69
     */
70
    private static $hidden = false;
0 ignored issues
show
Unused Code introduced by
The property $hidden 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...
71
72
    /**
73
     * Define this field as abstract (not inherited)
74
     *
75
     * @config
76
     * @var bool
77
     */
78
    private static $abstract = true;
0 ignored issues
show
Unused Code introduced by
The property $abstract 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...
79
80
    /**
81
     * Flag this field type as non-data (e.g. literal, header, html)
82
     *
83
     * @config
84
     * @var bool
85
     */
86
    private static $literal = false;
0 ignored issues
show
Unused Code introduced by
The property $literal 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...
87
88
    /**
89
     * Default sort order
90
     *
91
     * @config
92
     * @var string
93
     */
94
    private static $default_sort = '"Sort"';
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 $default_sort 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...
95
96
    /**
97
     * A list of CSS classes that can be added
98
     *
99
     * @var array
100
     */
101
    public static $allowed_css = [];
102
103
    /**
104
     * Set this to true to enable placeholder field for any given class
105
     * @config
106
     * @var bool
107
     */
108
    private static $has_placeholder = false;
0 ignored issues
show
Unused Code introduced by
The property $has_placeholder 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...
109
110
    /**
111
     * @config
112
     * @var array
113
     */
114
    private static $summary_fields = [
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 $summary_fields 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...
115
        'Title'
116
    ];
117
118
    /**
119
     * @config
120
     * @var array
121
     */
122
    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...
123
        'Name' => 'Varchar',
124
        'Title' => 'Varchar(255)',
125
        'Default' => 'Varchar(255)',
126
        'Sort' => 'Int',
127
        'Required' => 'Boolean',
128
        'CustomErrorMessage' => 'Varchar(255)',
129
        'ExtraClass' => 'Text',
130
        'RightTitle' => 'Varchar(255)',
131
        'ShowOnLoad' => 'Boolean(1)',
132
        'ShowInSummary' => 'Boolean',
133
        'Placeholder' => 'Varchar(255)',
134
        'DisplayRulesConjunction' => 'Enum("And,Or","Or")',
135
    ];
136
137
    private static $table_name = 'EditableFormField';
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...
138
139
140
    private static $defaults = [
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 $defaults 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...
141
        'ShowOnLoad' => true,
142
    ];
143
144
145
    /**
146
     * @config
147
     * @var array
148
     */
149
    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...
150
        'Parent' => UserDefinedForm::class,
151
    ];
152
153
    /**
154
     * Built in extensions required
155
     *
156
     * @config
157
     * @var array
158
     */
159
    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...
160
        Versioned::class . "('Stage', 'Live')"
161
    ];
162
163
    /**
164
     * @config
165
     * @var array
166
     */
167
    private static $has_many = [
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_many 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...
168
        'DisplayRules' => EditableCustomRule::class . '.Parent'
169
    ];
170
171
    private static $owns = [
0 ignored issues
show
Unused Code introduced by
The property $owns 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...
172
        'DisplayRules',
173
    ];
174
175
    private static $cascade_deletes = [
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 $cascade_deletes 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...
176
        'DisplayRules',
177
    ];
178
179
    /**
180
     * @var bool
181
     */
182
    protected $readonly;
183
184
    /**
185
     * Property holds the JS event which gets fired for this type of element
186
     *
187
     * @var string
188
     */
189
    protected $jsEventHandler = 'change';
190
191
    /**
192
     * Returns the jsEventHandler property for the current object. Bearing in mind it could've been overridden.
193
     * @return string
194
     */
195
    public function getJsEventHandler()
196
    {
197
        return $this->jsEventHandler;
198
    }
199
200
    /**
201
     * Set the visibility of an individual form field
202
     *
203
     * @param bool
204
     * @return $this
205
     */
206
    public function setReadonly($readonly = true)
207
    {
208
        $this->readonly = $readonly;
209
        return $this;
210
    }
211
212
    /**
213
     * Returns whether this field is readonly
214
     *
215
     * @return bool
216
     */
217
    private function isReadonly()
218
    {
219
        return $this->readonly;
220
    }
221
222
    /**
223
     * @return FieldList
224
     */
225
    public function getCMSFields()
226
    {
227
        $fields = FieldList::create(TabSet::create('Root'));
228
229
        // Main tab
230
        $fields->addFieldsToTab(
231
            'Root.Main',
232
            [
233
                ReadonlyField::create(
234
                    'Type',
235
                    _t(__CLASS__.'.TYPE', 'Type'),
236
                    $this->i18n_singular_name()
237
                ),
238
                CheckboxField::create('ShowInSummary', _t(__CLASS__.'.SHOWINSUMMARY', 'Show in summary gridfield')),
239
                LiteralField::create(
240
                    'MergeField',
241
                    _t(
242
                        __CLASS__.'.MERGEFIELDNAME',
243
                        '<div class="field readonly">' .
244
                            '<label class="left">' . _t(__CLASS__.'.MERGEFIELDNAME', 'Merge field') . '</label>' .
245
                            '<div class="middleColumn">' .
246
                                '<span class="readonly">$' . $this->Name . '</span>' .
247
                            '</div>' .
248
                        '</div>'
249
                    )
250
                ),
251
                TextField::create('Title', _t(__CLASS__.'.TITLE', 'Title')),
252
                TextField::create('Default', _t(__CLASS__.'.DEFAULT', 'Default value')),
253
                TextField::create('RightTitle', _t(__CLASS__.'.RIGHTTITLE', 'Right title')),
254
                SegmentField::create('Name', _t(__CLASS__.'.NAME', 'Name'))->setModifiers([
255
                    UnderscoreSegmentFieldModifier::create()->setDefault('FieldName'),
256
                    DisambiguationSegmentFieldModifier::create(),
257
                ])->setPreview($this->Name)
258
            ]
259
        );
260
        $fields->fieldByName('Root.Main')->setTitle(_t('SilverStripe\\CMS\\Model\\SiteTree.TABMAIN', 'Main'));
261
262
        // Custom settings
263
        if (!empty(self::$allowed_css)) {
264
            $cssList = [];
265
            foreach (self::$allowed_css as $k => $v) {
266
                if (!is_array($v)) {
267
                    $cssList[$k]=$v;
268
                } elseif ($k === $this->ClassName) {
269
                    $cssList = array_merge($cssList, $v);
270
                }
271
            }
272
273
            $fields->addFieldToTab(
274
                'Root.Main',
275
                DropdownField::create(
276
                    'ExtraClass',
277
                    _t(__CLASS__.'.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
278
                    $cssList
279
                )->setDescription(_t(
280
                    __CLASS__.'.EXTRACLASS_SELECT',
281
                    'Select from the list of allowed styles'
282
                ))
283
            );
284
        } else {
285
            $fields->addFieldToTab(
286
                'Root.Main',
287
                TextField::create(
288
                    'ExtraClass',
289
                    _t(__CLASS__.'.EXTRACLASS_Title', 'Extra CSS classes')
290
                )->setDescription(_t(
291
                    __CLASS__.'.EXTRACLASS_MULTIPLE',
292
                    'Separate each CSS class with a single space'
293
                ))
294
            );
295
        }
296
297
        // Validation
298
        $validationFields = $this->getFieldValidationOptions();
299
        if ($validationFields && $validationFields->count()) {
300
            $fields->addFieldsToTab('Root.Validation', $validationFields);
0 ignored issues
show
Documentation introduced by
$validationFields is of type object<SilverStripe\Forms\FieldList>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
301
            $fields->fieldByName('Root.Validation')->setTitle(_t(__CLASS__.'.VALIDATION', 'Validation'));
302
        }
303
304
        // Add display rule fields
305
        $displayFields = $this->getDisplayRuleFields();
306
        if ($displayFields && $displayFields->count()) {
307
            $fields->addFieldsToTab('Root.DisplayRules', $displayFields);
0 ignored issues
show
Documentation introduced by
$displayFields is of type object<SilverStripe\Forms\FieldList>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
308
        }
309
310
        // Placeholder
311
        if ($this->config()->has_placeholder) {
312
            $fields->addFieldToTab(
313
                'Root.Main',
314
                TextField::create(
315
                    'Placeholder',
316
                    _t(__CLASS__.'.PLACEHOLDER', 'Placeholder')
317
                )
318
            );
319
        }
320
321
        $this->extend('updateCMSFields', $fields);
322
323
        return $fields;
324
    }
325
326
    /**
327
     * Return fields to display on the 'Display Rules' tab
328
     *
329
     * @return FieldList
330
     */
331
    protected function getDisplayRuleFields()
332
    {
333
        // Check display rules
334
        if ($this->Required) {
335
            return new FieldList(
336
                LabelField::create(
337
                    _t(
338
                        __CLASS__.'.DISPLAY_RULES_DISABLED',
339
                        'Display rules are not enabled for required fields. Please uncheck "Is this field Required?" under "Validation" to re-enable.'
340
                    )
341
                )
342
                ->addExtraClass('message warning')
343
            );
344
        }
345
346
        $allowedClasses = array_keys($this->getEditableFieldClasses(false));
347
        $editableColumns = new GridFieldEditableColumns();
348
        $editableColumns->setDisplayFields([
349
            'ConditionFieldID' => function ($record, $column, $grid) use ($allowedClasses) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
350
                    return DropdownField::create($column, '', EditableFormField::get()->filter([
351
                            'ParentID' => $this->ParentID,
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
352
                            'ClassName' => $allowedClasses,
353
                        ])->exclude([
354
                            'ID' => $this->ID,
355
                        ])->map('ID', 'Title'));
356
            },
357
            'ConditionOption' => function ($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
358
                $options = Config::inst()->get(EditableCustomRule::class, 'condition_options');
359
360
                return DropdownField::create($column, '', $options);
361
            },
362
            'FieldValue' => function ($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
363
                return TextField::create($column);
364
            }
365
        ]);
366
367
        // Custom rules
368
        $customRulesConfig = GridFieldConfig::create()
369
            ->addComponents(
370
                $editableColumns,
371
                new GridFieldButtonRow(),
372
                new GridFieldToolbarHeader(),
373
                new GridFieldAddNewInlineButton(),
374
                new GridFieldDeleteAction()
375
            );
376
377
        return new FieldList(
378
            DropdownField::create(
379
                'ShowOnLoad',
380
                _t(__CLASS__.'.INITIALVISIBILITY', 'Initial visibility'),
381
                [
382
                    1 => 'Show',
383
                    0 => 'Hide',
384
                ]
385
            ),
386
            DropdownField::create(
387
                'DisplayRulesConjunction',
388
                _t(__CLASS__.'.DISPLAYIF', 'Toggle visibility when'),
389
                [
390
                    'Or'  => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFOR', 'Any conditions are true'),
391
                    'And' => _t('SilverStripe\\UserForms\\Model\\UserDefinedForm.SENDIFAND', 'All conditions are true'),
392
                ]
393
            ),
394
            GridField::create(
395
                'DisplayRules',
396
                _t(__CLASS__.'.CUSTOMRULES', 'Custom Rules'),
397
                $this->DisplayRules(),
398
                $customRulesConfig
399
            )
400
        );
401
    }
402
403
    public function onBeforeWrite()
404
    {
405
        parent::onBeforeWrite();
406
407
        // Set a field name.
408
        if (!$this->Name) {
409
            // New random name
410
            $this->Name = $this->generateName();
411
        } elseif ($this->Name === 'Field') {
412
            throw new ValidationException('Field name cannot be "Field"');
413
        }
414
415
        if (!$this->Sort && $this->ParentID) {
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
416
            $parentID = $this->ParentID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
417
            $this->Sort = EditableFormField::get()
418
                ->filter('ParentID', $parentID)
419
                ->max('Sort') + 1;
420
        }
421
    }
422
423
    /**
424
     * Generate a new non-conflicting Name value
425
     *
426
     * @return string
427
     */
428
    protected function generateName()
429
    {
430
        do {
431
            // Generate a new random name after this class (handles namespaces)
432
            $classNamePieces = explode('\\', static::class);
433
            $class = array_pop($classNamePieces);
434
            $entropy = substr(sha1(uniqid()), 0, 5);
435
            $name = "{$class}_{$entropy}";
436
437
            // Check if it conflicts
438
            $exists = EditableFormField::get()->filter('Name', $name)->count() > 0;
439
        } while ($exists);
440
        return $name;
441
    }
442
443
    /**
444
     * Flag indicating that this field will set its own error message via data-msg='' attributes
445
     *
446
     * @return bool
447
     */
448
    public function getSetsOwnError()
449
    {
450
        return false;
451
    }
452
453
    /**
454
     * Return whether a user can delete this form field
455
     * based on whether they can edit the page
456
     *
457
     * @param Member $member
458
     * @return bool
459
     */
460
    public function canDelete($member = null)
461
    {
462
        return $this->canEdit($member);
463
    }
464
465
    /**
466
     * Return whether a user can edit this form field
467
     * based on whether they can edit the page
468
     *
469
     * @param Member $member
470
     * @return bool
471
     */
472
    public function canEdit($member = null)
473
    {
474
        $parent = $this->Parent();
475
        if ($parent && $parent->exists()) {
476
            return $parent->canEdit($member) && !$this->isReadonly();
477
        } elseif (!$this->exists() && Controller::has_curr()) {
478
            // This is for GridFieldOrderableRows support as it checks edit permissions on
479
            // singleton of the class. Allows editing of User Defined Form pages by
480
            // 'Content Authors' and those with permission to edit the UDF page. (ie. CanEditType/EditorGroups)
481
            // This is to restore User Forms 2.x backwards compatibility.
482
            $controller = Controller::curr();
483
            if ($controller && $controller instanceof CMSPageEditController) {
484
                $parent = $controller->getRecord($controller->currentPageID());
485
                // Only allow this behaviour on pages using UserFormFieldEditorExtension, such
486
                // as UserDefinedForm page type.
487
                if ($parent && $parent->hasExtension(UserFormFieldEditorExtension::class)) {
488
                    return $parent->canEdit($member);
489
                }
490
            }
491
        }
492
493
        // Fallback to secure admin permissions
494
        return parent::canEdit($member);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::canEdit($member); of type boolean|string adds the type string to the return on line 494 which is incompatible with the return type documented by SilverStripe\UserForms\M...tableFormField::canEdit of type boolean.
Loading history...
495
    }
496
497
    /**
498
     * Return whether a user can view this form field
499
     * based on whether they can view the page, regardless of the ReadOnly status of the field
500
     *
501
     * @param Member $member
502
     * @return bool
503
     */
504
    public function canView($member = null)
505
    {
506
        $parent = $this->Parent();
507
        if ($parent && $parent->exists()) {
508
            return $parent->canView($member);
509
        }
510
511
        return true;
512
    }
513
514
    /**
515
     * Return whether a user can create an object of this type
516
     *
517
     * @param Member $member
518
     * @param array $context Virtual parameter to allow context to be passed in to check
519
     * @return bool
520
     */
521 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...
522
    {
523
        // Check parent page
524
        $parent = $this->getCanCreateContext(func_get_args());
525
        if ($parent) {
526
            return $parent->canEdit($member);
527
        }
528
529
        // Fall back to secure admin permissions
530
        return parent::canCreate($member);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::canCreate($member); of type boolean|string adds the type string to the return on line 530 which is incompatible with the return type documented by SilverStripe\UserForms\M...bleFormField::canCreate of type boolean.
Loading history...
531
    }
532
533
    /**
534
     * Helper method to check the parent for this object
535
     *
536
     * @param array $args List of arguments passed to canCreate
537
     * @return SiteTree Parent page instance
538
     */
539 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...
540
    {
541
        // Inspect second parameter to canCreate for a 'Parent' context
542
        if (isset($args[1]['Parent'])) {
543
            return $args[1]['Parent'];
544
        }
545
        // Hack in currently edited page if context is missing
546
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
547
            return Controller::curr()->currentPage();
548
        }
549
550
        // No page being edited
551
        return null;
552
    }
553
554
    /**
555
     * checks whether record is new, copied from SiteTree
556
     */
557
    public function isNew()
558
    {
559
        if (empty($this->ID)) {
560
            return true;
561
        }
562
563
        if (is_numeric($this->ID)) {
564
            return false;
565
        }
566
567
        return stripos($this->ID, 'new') === 0;
568
    }
569
570
    /**
571
     * @deprecated since version 4.0
572
     */
573
    public function getSettings()
574
    {
575
        Deprecation::notice('4.0', 'getSettings is deprecated');
576
        return (!empty($this->CustomSettings)) ? unserialize($this->CustomSettings) : array();
0 ignored issues
show
Documentation introduced by
The property CustomSettings does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
577
    }
578
579
    /**
580
     * @deprecated since version 4.0
581
     */
582
    public function setSettings($settings = array())
583
    {
584
        Deprecation::notice('4.0', 'setSettings is deprecated');
585
        $this->CustomSettings = serialize($settings);
0 ignored issues
show
Documentation introduced by
The property CustomSettings does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
586
    }
587
588
    /**
589
     * @deprecated since version 4.0
590
     */
591
    public function setSetting($key, $value)
592
    {
593
        Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
594
        $settings = $this->getSettings();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\UserForms\M...ormField::getSettings() has been deprecated with message: since version 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
595
        $settings[$key] = $value;
596
597
        $this->setSettings($settings);
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\UserForms\M...ormField::setSettings() has been deprecated with message: since version 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
598
    }
599
600
    /**
601
     * Set the allowed css classes for the extraClass custom setting
602
     *
603
     * @param array $allowed The permissible CSS classes to add
604
     */
605
    public function setAllowedCss(array $allowed)
606
    {
607
        if (is_array($allowed)) {
608
            foreach ($allowed as $k => $v) {
609
                self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
610
            }
611
        }
612
    }
613
614
    /**
615
     * @deprecated since version 4.0
616
     */
617
    public function getSetting($setting)
618
    {
619
        Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
620
621
        $settings = $this->getSettings();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\UserForms\M...ormField::getSettings() has been deprecated with message: since version 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
622
        if (isset($settings) && count($settings) > 0) {
623
            if (isset($settings[$setting])) {
624
                return $settings[$setting];
625
            }
626
        }
627
        return '';
628
    }
629
630
    /**
631
     * Get the path to the icon for this field type, relative to the site root.
632
     *
633
     * @return string
634
     */
635
    public function getIcon()
636
    {
637
        return ModuleLoader::getModule('silverstripe/userforms')
638
            ->getRelativeResourcePath('images/' . strtolower($this->class) . '.png');
0 ignored issues
show
Documentation introduced by
The property class does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
639
    }
640
641
    /**
642
     * Return whether or not this field has addable options
643
     * such as a dropdown field or radio set
644
     *
645
     * @return bool
646
     */
647
    public function getHasAddableOptions()
648
    {
649
        return false;
650
    }
651
652
    /**
653
     * Return whether or not this field needs to show the extra
654
     * options dropdown list
655
     *
656
     * @return bool
657
     */
658
    public function showExtraOptions()
659
    {
660
        return true;
661
    }
662
663
    /**
664
     * Returns the Title for rendering in the front-end (with XML values escaped)
665
     *
666
     * @return string
667
     */
668
    public function getEscapedTitle()
669
    {
670
        return Convert::raw2xml($this->Title);
671
    }
672
673
    /**
674
     * Find the numeric indicator (1.1.2) that represents it's nesting value
675
     *
676
     * Only useful for fields attached to a current page, and that contain other fields such as pages
677
     * or groups
678
     *
679
     * @return string
680
     */
681
    public function getFieldNumber()
682
    {
683
        // Check if exists
684
        if (!$this->exists()) {
685
            return null;
686
        }
687
        // Check parent
688
        $form = $this->Parent();
689
        if (!$form || !$form->exists() || !($fields = $form->Fields())) {
690
            return null;
691
        }
692
693
        $prior = 0; // Number of prior group at this level
694
        $stack = []; // Current stack of nested groups, where the top level = the page
695
        foreach ($fields->map('ID', 'ClassName') as $id => $className) {
696
            if ($className === EditableFormStep::class) {
697
                $priorPage = empty($stack) ? $prior : $stack[0];
698
                $stack = array($priorPage + 1);
699
                $prior = 0;
700
            } elseif ($className === EditableFieldGroup::class) {
701
                $stack[] = $prior + 1;
702
                $prior = 0;
703
            } elseif ($className === EditableFieldGroupEnd::class) {
704
                $prior = array_pop($stack);
705
            }
706
            if ($id == $this->ID) {
707
                return implode('.', $stack);
708
            }
709
        }
710
        return null;
711
    }
712
713
    public function getCMSTitle()
714
    {
715
        return $this->i18n_singular_name() . ' (' . $this->Title . ')';
716
    }
717
718
    /**
719
     * @deprecated since version 4.0
720
     */
721
    public function getFieldName($field = false)
722
    {
723
        Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
724
        return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
725
    }
726
727
    /**
728
     * @deprecated since version 4.0
729
     */
730
    public function getSettingName($field)
731
    {
732
        Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
733
        $name = $this->getFieldName('CustomSettings');
0 ignored issues
show
Documentation introduced by
'CustomSettings' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method SilverStripe\UserForms\M...rmField::getFieldName() has been deprecated with message: since version 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
734
735
        return $name . '[' . $field .']';
736
    }
737
738
    /**
739
     * Append custom validation fields to the default 'Validation'
740
     * section in the editable options view
741
     *
742
     * @return FieldList
743
     */
744
    public function getFieldValidationOptions()
745
    {
746
        $fields = new FieldList(
747
            CheckboxField::create('Required', _t(__CLASS__.'.REQUIRED', 'Is this field Required?'))
748
                ->setDescription(_t(__CLASS__.'.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
749
            TextField::create('CustomErrorMessage', _t(__CLASS__.'.CUSTOMERROR', 'Custom Error Message'))
750
        );
751
752
        $this->extend('updateFieldValidationOptions', $fields);
753
754
        return $fields;
755
    }
756
757
    /**
758
     * Return a FormField to appear on the front end. Implement on
759
     * your subclass.
760
     *
761
     * @return FormField
762
     */
763
    public function getFormField()
764
    {
765
        user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
766
    }
767
768
    /**
769
     * Updates a formfield with extensions
770
     *
771
     * @param FormField $field
772
     */
773
    public function doUpdateFormField($field)
774
    {
775
        $this->extend('beforeUpdateFormField', $field);
776
        $this->updateFormField($field);
777
        $this->extend('afterUpdateFormField', $field);
778
    }
779
780
    /**
781
     * Updates a formfield with the additional metadata specified by this field
782
     *
783
     * @param FormField $field
784
     */
785
    protected function updateFormField($field)
786
    {
787
        // set the error / formatting messages
788
        $field->setCustomValidationMessage($this->getErrorMessage()->RAW());
789
790
        // set the right title on this field
791
        if ($this->RightTitle) {
0 ignored issues
show
Documentation introduced by
The property RightTitle does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
792
            // Since this field expects raw html, safely escape the user data prior
793
            $field->setRightTitle(Convert::raw2xml($this->RightTitle));
0 ignored issues
show
Documentation introduced by
The property RightTitle does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
794
        }
795
796
        // if this field is required add some
797
        if ($this->Required) {
798
            // Required validation can conflict so add the Required validation messages as input attributes
799
            $errorMessage = $this->getErrorMessage()->HTML();
800
            $field->addExtraClass('requiredField');
801
            $field->setAttribute('data-rule-required', 'true');
802
            $field->setAttribute('data-msg-required', $errorMessage);
803
804
            if ($identifier = UserDefinedForm::config()->required_identifier) {
805
                $title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
806
                $field->setTitle($title);
807
            }
808
        }
809
810
        // if this field has an extra class
811
        if ($this->ExtraClass) {
0 ignored issues
show
Documentation introduced by
The property ExtraClass does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
812
            $field->addExtraClass($this->ExtraClass);
0 ignored issues
show
Documentation introduced by
The property ExtraClass does not exist on object<SilverStripe\User...odel\EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
813
        }
814
815
        // if ShowOnLoad is false hide the field
816
        if (!$this->ShowOnLoad) {
817
            $field->addExtraClass($this->ShowOnLoadNice());
818
        }
819
820
        // if this field has a placeholder
821
        if ($this->Placeholder) {
0 ignored issues
show
Bug introduced by
The property Placeholder does not seem to exist. Did you mean has_placeholder?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
822
            $field->setAttribute('placeholder', $this->Placeholder);
0 ignored issues
show
Bug introduced by
The property Placeholder does not seem to exist. Did you mean has_placeholder?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
823
        }
824
    }
825
826
    /**
827
     * Return the instance of the submission field class
828
     *
829
     * @return SubmittedFormField
830
     */
831
    public function getSubmittedFormField()
832
    {
833
        return SubmittedFormField::create();
834
    }
835
836
837
    /**
838
     * Show this form field (and its related value) in the reports and in emails.
839
     *
840
     * @return bool
841
     */
842
    public function showInReports()
843
    {
844
        return true;
845
    }
846
847
    /**
848
     * Return the error message for this field. Either uses the custom
849
     * one (if provided) or the default SilverStripe message
850
     *
851
     * @return Varchar
852
     */
853
    public function getErrorMessage()
854
    {
855
        $title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
856
        $standard = _t('SilverStripe\\Forms\\Form.FIELDISREQUIRED', '{field} is required.', ['field' => $title]);
857
858
        // only use CustomErrorMessage if it has a non empty value
859
        $errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
860
861
        return DBField::create_field('Varchar', $errorMessage);
862
    }
863
864
    /**
865
     * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
866
     * to the field proper
867
     *
868
     * @param array $data Unserialised data
869
     */
870
    public function migrateSettings($data)
871
    {
872
        // Map 'Show' / 'Hide' to boolean
873
        if (isset($data['ShowOnLoad'])) {
874
            $this->ShowOnLoad = $data['ShowOnLoad'] === '' || ($data['ShowOnLoad'] && $data['ShowOnLoad'] !== 'Hide');
875
            unset($data['ShowOnLoad']);
876
        }
877
878
        // Migrate all other settings
879
        foreach ($data as $key => $value) {
880
            if ($this->hasField($key)) {
881
                $this->setField($key, $value);
882
            }
883
        }
884
    }
885
886
    /**
887
     * Get the formfield to use when editing this inline in gridfield
888
     *
889
     * @param string $column name of column
890
     * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
891
     * @return FormField
892
     */
893
    public function getInlineClassnameField($column, $fieldClasses)
894
    {
895
        return DropdownField::create($column, false, $fieldClasses);
896
    }
897
898
    /**
899
     * Get the formfield to use when editing the title inline
900
     *
901
     * @param string $column
902
     * @return FormField
903
     */
904
    public function getInlineTitleField($column)
905
    {
906
        return TextField::create($column, false)
907
            ->setAttribute('placeholder', _t(__CLASS__.'.TITLE', 'Title'))
908
            ->setAttribute('data-placeholder', _t(__CLASS__.'.TITLE', 'Title'));
909
    }
910
911
    /**
912
     * Get the JS expression for selecting the holder for this field
913
     *
914
     * @return string
915
     */
916
    public function getSelectorHolder()
917
    {
918
        return sprintf('$("%s")', $this->getSelectorOnly());
919
    }
920
921
    /**
922
     * Returns only the JS identifier of a string, less the $(), which can be inserted elsewhere, for example when you
923
     * want to perform selections on multiple selectors
924
     * @return string
925
     */
926
    public function getSelectorOnly()
927
    {
928
        return "#{$this->Name}";
929
    }
930
931
    /**
932
     * Gets the JS expression for selecting the value for this field
933
     *
934
     * @param EditableCustomRule $rule Custom rule this selector will be used with
935
     * @param bool $forOnLoad Set to true if this will be invoked on load
936
     *
937
     * @return string
938
     */
939
    public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
940
    {
941
        return sprintf("$(%s)", $this->getSelectorFieldOnly());
942
    }
943
944
    /**
945
     * @return string
946
     */
947
    public function getSelectorFieldOnly()
948
    {
949
        return "[name='{$this->Name}']";
950
    }
951
952
953
    /**
954
     * Get the list of classes that can be selected and used as data-values
955
     *
956
     * @param $includeLiterals Set to false to exclude non-data fields
957
     * @return array
958
     */
959
    public function getEditableFieldClasses($includeLiterals = true)
960
    {
961
        $classes = ClassInfo::getValidSubClasses(EditableFormField::class);
962
963
        // Remove classes we don't want to display in the dropdown.
964
        $editableFieldClasses = [];
965
        foreach ($classes as $class) {
966
            // Skip abstract / hidden classes
967
            if (Config::inst()->get($class, 'abstract', Config::UNINHERITED)
968
                || Config::inst()->get($class, 'hidden')
969
            ) {
970
                continue;
971
            }
972
973
            if (!$includeLiterals && Config::inst()->get($class, 'literal')) {
974
                continue;
975
            }
976
977
            $singleton = singleton($class);
978
            if (!$singleton->canCreate()) {
979
                continue;
980
            }
981
982
            $editableFieldClasses[$class] = $singleton->i18n_singular_name();
983
        }
984
985
        asort($editableFieldClasses);
986
        return $editableFieldClasses;
987
    }
988
989
    /**
990
     * @return EditableFormField\Validator
991
     */
992
    public function getCMSValidator()
993
    {
994
        return EditableFormField\Validator::create()
995
            ->setRecord($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<SilverStripe\UserFo...odel\EditableFormField>, but the function expects a object<SilverStripe\User...ield\EditableFormField>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
996
    }
997
998
    /**
999
     * Determine effective display rules for this field.
1000
     *
1001
     * @return SS_List
1002
     */
1003
    public function EffectiveDisplayRules()
1004
    {
1005
        if ($this->Required) {
1006
            return ArrayList::create();
1007
        }
1008
        return $this->DisplayRules();
1009
    }
1010
1011
    /**
1012
     * Extracts info from DisplayRules into array so UserDefinedForm->buildWatchJS can run through it.
1013
     * @return array|null
1014
     */
1015
    public function formatDisplayRules()
1016
    {
1017
        $holderSelector = $this->getSelectorOnly();
1018
        $result = [
1019
            'targetFieldID' => $holderSelector,
1020
            'conjunction'   => $this->DisplayRulesConjunctionNice(),
1021
            'selectors'     => [],
1022
            'events'        => [],
1023
            'operations'    => [],
1024
            'initialState'  => $this->ShowOnLoadNice(),
1025
            'view'          => [],
1026
            'opposite'      => [],
1027
        ];
1028
1029
        // Check for field dependencies / default
1030
        /** @var EditableCustomRule $rule */
1031
        foreach ($this->EffectiveDisplayRules() as $rule) {
1032
            // Get the field which is effected
1033
            /** @var EditableFormField $formFieldWatch */
1034
            $formFieldWatch = DataObject::get_by_id(EditableFormField::class, $rule->ConditionFieldID);
1035
            // Skip deleted fields
1036
            if (! $formFieldWatch) {
1037
                continue;
1038
            }
1039
            $fieldToWatch = $formFieldWatch->getSelectorFieldOnly();
1040
1041
            $expression = $rule->buildExpression();
1042
            if (!in_array($fieldToWatch, $result['selectors'])) {
1043
                $result['selectors'][] = $fieldToWatch;
1044
            }
1045
            if (!in_array($expression['event'], $result['events'])) {
1046
                $result['events'][] = $expression['event'];
1047
            }
1048
            $result['operations'][] = $expression['operation'];
1049
1050
            // View/Show should read
1051
            $opposite = ($result['initialState'] === 'hide') ? 'show' : 'hide';
1052
            $result['view'] = $rule->toggleDisplayText($result['initialState']);
1053
            $result['opposite'] = $rule->toggleDisplayText($opposite);
1054
        }
1055
1056
        return (count($result['selectors'])) ? $result : null;
1057
    }
1058
1059
    /**
1060
     * Replaces the set DisplayRulesConjunction with their JS logical operators
1061
     * @return string
1062
     */
1063
    public function DisplayRulesConjunctionNice()
1064
    {
1065
        return (strtolower($this->DisplayRulesConjunction) === 'or') ? '||' : '&&';
1066
    }
1067
1068
    /**
1069
     * Replaces boolean ShowOnLoad with its JS string equivalent
1070
     * @return string
1071
     */
1072
    public function ShowOnLoadNice()
1073
    {
1074
        return ($this->ShowOnLoad) ? 'show' : 'hide';
1075
    }
1076
1077
    /**
1078
     * Returns whether this is of type EditableCheckBoxField
1079
     * @return bool
1080
     */
1081
    public function isCheckBoxField()
1082
    {
1083
        return false;
1084
    }
1085
1086
    /**
1087
     * Returns whether this is of type EditableRadioField
1088
     * @return bool
1089
     */
1090
    public function isRadioField()
1091
    {
1092
        return false;
1093
    }
1094
1095
    /**
1096
     * Determined is this is of type EditableCheckboxGroupField
1097
     * @return bool
1098
     */
1099
    public function isCheckBoxGroupField()
1100
    {
1101
        return false;
1102
    }
1103
}
1104