Completed
Push — master ( e62057...929473 )
by Daniel
9s
created

EditableFormField   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 936
Duplicated Lines 2.46 %

Coupling/Cohesion

Components 3
Dependencies 36

Importance

Changes 10
Bugs 6 Features 0
Metric Value
wmc 117
c 10
b 6
f 0
lcom 3
cbo 36
dl 23
loc 936
rs 1.0434

46 Methods

Rating   Name   Duplication   Size   Complexity  
A setReadonly() 0 4 1
A isReadonly() 0 4 1
C getCMSFields() 0 84 9
A getDisplayRuleFields() 0 68 2
B onBeforeWrite() 0 19 5
A generateName() 0 13 2
A getSetsOwnError() 0 4 1
A canDelete() 0 4 1
C canEdit() 0 24 10
A canView() 0 9 3
A canCreate() 10 11 2
A getCanCreateContext() 13 14 4
A canPublish() 0 4 1
A canUnpublish() 0 4 1
A doPublish() 0 9 2
A doDeleteFromStage() 0 12 2
A isNew() 0 12 3
A getIsModifiedOnStage() 0 12 3
A getSettings() 0 5 2
A setSettings() 0 5 1
A setSetting() 0 8 1
A setAllowedCss() 0 8 4
A getSetting() 0 12 4
A getIcon() 0 4 1
A getHasAddableOptions() 0 4 1
A showExtraOptions() 0 4 1
A getEscapedTitle() 0 4 1
C getFieldNumber() 0 31 11
A getCMSTitle() 0 4 1
A getFieldName() 0 5 2
A getSettingName() 0 7 1
A getFieldValidationOptions() 0 12 1
A getFormField() 0 4 1
A doUpdateFormField() 0 6 1
B updateFormField() 0 30 5
A getSubmittedFormField() 0 4 1
A showInReports() 0 4 1
A getErrorMessage() 0 10 3
B migrateSettings() 0 15 6
A getInlineClassnameField() 0 4 1
A getInlineTitleField() 0 6 1
A getSelectorHolder() 0 4 1
A getSelectorField() 0 4 1
C getEditableFieldClasses() 0 28 7
A getCMSValidator() 0 5 1
A EffectiveDisplayRules() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EditableFormField often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EditableFormField, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use SilverStripe\Forms\SegmentField;
4
5
/**
6
 * Represents the base class of a editable form field
7
 * object like {@link EditableTextField}.
8
 *
9
 * @package userforms
10
 *
11
 * @property string $Name
12
 * @property string $Title
13
 * @property string $Default
14
 * @property int $Sort
15
 * @property bool $Required
16
 * @property string $CustomErrorMessage
17
 * @method UserDefinedForm Parent() Parent page
18
 * @method DataList DisplayRules() List of EditableCustomRule objects
19
 */
20
class EditableFormField extends DataObject
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
21
{
22
23
    /**
24
     * Set to true to hide from class selector
25
     *
26
     * @config
27
     * @var bool
28
     */
29
    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...
30
31
    /**
32
     * Define this field as abstract (not inherited)
33
     *
34
     * @config
35
     * @var bool
36
     */
37
    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...
38
39
    /**
40
     * Flag this field type as non-data (e.g. literal, header, html)
41
     *
42
     * @config
43
     * @var bool
44
     */
45
    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...
46
47
    /**
48
     * Default sort order
49
     *
50
     * @config
51
     * @var string
52
     */
53
    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...
54
55
    /**
56
     * A list of CSS classes that can be added
57
     *
58
     * @var array
59
     */
60
    public static $allowed_css = array();
61
62
    /**
63
     * @config
64
     * @var array
65
     */
66
    private static $summary_fields = array(
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...
67
        'Title'
68
    );
69
70
    /**
71
     * @config
72
     * @var array
73
     */
74
    private static $db = array(
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...
75
        "Name" => "Varchar",
76
        "Title" => "Varchar(255)",
77
        "Default" => "Varchar(255)",
78
        "Sort" => "Int",
79
        "Required" => "Boolean",
80
        "CustomErrorMessage" => "Varchar(255)",
81
82
        "CustomRules" => "Text", // @deprecated from 2.0
83
        "CustomSettings" => "Text", // @deprecated from 2.0
84
        "Migrated" => "Boolean", // set to true when migrated
85
86
        "ExtraClass" => "Text", // from CustomSettings
87
        "RightTitle" => "Varchar(255)", // from CustomSettings
88
        "ShowOnLoad" => "Boolean(1)", // from CustomSettings
89
    );
90
    
91
    private static $defaults = array(
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...
92
        'ShowOnLoad' => true,
93
    );
94
95
96
    /**
97
     * @config
98
     * @var array
99
     */
100
    private static $has_one = array(
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...
101
        "Parent" => "UserDefinedForm",
102
    );
103
104
    /**
105
     * Built in extensions required
106
     *
107
     * @config
108
     * @var array
109
     */
110
    private static $extensions = array(
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...
111
        "Versioned('Stage', 'Live')"
112
    );
113
114
    /**
115
     * @config
116
     * @var array
117
     */
118
    private static $has_many = array(
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...
119
        "DisplayRules" => "EditableCustomRule.Parent" // from CustomRules
120
    );
121
122
    /**
123
     * @var bool
124
     */
125
    protected $readonly;
126
127
    /**
128
     * Set the visibility of an individual form field
129
     *
130
     * @param bool
131
     */
132
    public function setReadonly($readonly = true)
133
    {
134
        $this->readonly = $readonly;
135
    }
136
137
    /**
138
     * Returns whether this field is readonly
139
     *
140
     * @return bool
141
     */
142
    private function isReadonly()
143
    {
144
        return $this->readonly;
145
    }
146
147
    /**
148
     * @return FieldList
149
     */
150
    public function getCMSFields()
151
    {
152
        $fields = new FieldList(new TabSet('Root'));
153
154
        // Main tab
155
        $fields->addFieldsToTab(
156
            'Root.Main',
157
            array(
158
                ReadonlyField::create(
159
                    'Type',
160
                    _t('EditableFormField.TYPE', 'Type'),
161
                    $this->i18n_singular_name()
162
                ),
163
                LiteralField::create(
164
                    'MergeField',
165
                    _t(
166
                        'EditableFormField.MERGEFIELDNAME',
167
                        '<div class="field readonly">' .
168
                            '<label class="left">Merge field</label>' .
169
                            '<div class="middleColumn">' .
170
                                '<span class="readonly">$' . $this->Name . '</span>' .
171
                            '</div>' .
172
                        '</div>'
173
                    )
174
                ),
175
                TextField::create('Title'),
176
                TextField::create('Default', _t('EditableFormField.DEFAULT', 'Default value')),
177
                TextField::create('RightTitle', _t('EditableFormField.RIGHTTITLE', 'Right title')),
178
                SegmentField::create('Name')->setModifiers(array(
179
                    UnderscoreSegmentFieldModifier::create()->setDefault('FieldName'),
180
                    DisambiguationSegmentFieldModifier::create(),
181
                ))->setPreview($this->Name)
182
            )
183
        );
184
185
        // Custom settings
186
        if (!empty(self::$allowed_css)) {
187
            $cssList = array();
188
            foreach (self::$allowed_css as $k => $v) {
189
                if (!is_array($v)) {
190
                    $cssList[$k]=$v;
191
                } elseif ($k === $this->ClassName) {
192
                    $cssList = array_merge($cssList, $v);
193
                }
194
            }
195
196
            $fields->addFieldToTab('Root.Main',
197
                DropdownField::create(
198
                    'ExtraClass',
199
                    _t('EditableFormField.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
200
                    $cssList
201
                )->setDescription(_t(
202
                    'EditableFormField.EXTRACLASS_SELECT',
203
                    'Select from the list of allowed styles'
204
                ))
205
            );
206
        } else {
207
            $fields->addFieldToTab('Root.Main',
208
                TextField::create(
209
                    'ExtraClass',
210
                    _t('EditableFormField.EXTRACLASS_Title', 'Extra CSS classes')
211
                )->setDescription(_t(
212
                    'EditableFormField.EXTRACLASS_MULTIPLE',
213
                    'Separate each CSS class with a single space'
214
                ))
215
            );
216
        }
217
218
        // Validation
219
        $validationFields = $this->getFieldValidationOptions();
220
        if ($validationFields && $validationFields->count()) {
221
            $fields->addFieldsToTab('Root.Validation', $validationFields);
0 ignored issues
show
Documentation introduced by
$validationFields is of type object<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...
222
        }
223
224
        // Add display rule fields
225
        $displayFields = $this->getDisplayRuleFields();
226
        if ($displayFields && $displayFields->count()) {
227
            $fields->addFieldsToTab('Root.DisplayRules', $displayFields);
0 ignored issues
show
Documentation introduced by
$displayFields is of type object<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...
228
        }
229
230
        $this->extend('updateCMSFields', $fields);
231
232
        return $fields;
233
    }
234
235
    /**
236
     * Return fields to display on the 'Display Rules' tab
237
     *
238
     * @return FieldList
239
     */
240
    protected function getDisplayRuleFields()
241
    {
242
        // Check display rules
243
        if ($this->Required) {
244
            return new FieldList(
245
                LabelField::create(_t(
246
                    'EditableFormField.DISPLAY_RULES_DISABLED',
247
                    'Display rules are not enabled for required fields. ' .
248
                    'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
249
                ))->addExtraClass('message warning')
250
            );
251
        }
252
        $self = $this;
253
        $allowedClasses = array_keys($this->getEditableFieldClasses(false));
254
        $editableColumns = new GridFieldEditableColumns();
255
        $editableColumns->setDisplayFields(array(
256
            'Display' => '',
257
            'ConditionFieldID' => function ($record, $column, $grid) use ($allowedClasses, $self) {
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...
258
                return DropdownField::create(
259
                    $column,
260
                    '',
261
                    EditableFormField::get()
262
                        ->filter(array(
263
                            'ParentID' => $self->ParentID,
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<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...
264
                            'ClassName' => $allowedClasses
265
                        ))
266
                        ->exclude(array(
267
                            'ID' => $self->ID
268
                        ))
269
                        ->map('ID', 'Title')
270
                    );
271
            },
272
            '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...
273
                $options = Config::inst()->get('EditableCustomRule', 'condition_options');
274
                return DropdownField::create($column, '', $options);
275
            },
276
            '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...
277
                return TextField::create($column);
278
            },
279
            'ParentID' => function ($record, $column, $grid) use ($self) {
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...
280
                return HiddenField::create($column, '', $self->ID);
281
            }
282
        ));
283
284
        // Custom rules
285
        $customRulesConfig = GridFieldConfig::create()
286
            ->addComponents(
287
                $editableColumns,
288
                new GridFieldButtonRow(),
289
                new GridFieldToolbarHeader(),
290
                new GridFieldAddNewInlineButton(),
291
                new GridFieldDeleteAction()
292
            );
293
294
        return new FieldList(
295
            CheckboxField::create('ShowOnLoad')
296
                ->setDescription(_t(
297
                    'EditableFormField.SHOWONLOAD',
298
                    'Initial visibility before processing these rules'
299
                )),
300
            GridField::create(
301
                'DisplayRules',
302
                _t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
303
                $this->DisplayRules(),
304
                $customRulesConfig
305
            )
306
        );
307
    }
308
309
    public function onBeforeWrite()
310
    {
311
        parent::onBeforeWrite();
312
313
        // Set a field name.
314
        if (!$this->Name) {
315
            // New random name
316
            $this->Name = $this->generateName();
317
        } elseif ($this->Name === 'Field') {
318
            throw new ValidationException('Field name cannot be "Field"');
319
        }
320
321
        if (!$this->Sort && $this->ParentID) {
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<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...
322
            $parentID = $this->ParentID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<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...
323
            $this->Sort = EditableFormField::get()
324
                ->filter('ParentID', $parentID)
325
                ->max('Sort') + 1;
326
        }
327
    }
328
329
    /**
330
     * Generate a new non-conflicting Name value
331
     *
332
     * @return string
333
     */
334
    protected function generateName()
335
    {
336
        do {
337
            // Generate a new random name after this class
338
            $class = get_class($this);
339
            $entropy = substr(sha1(uniqid()), 0, 5);
340
            $name = "{$class}_{$entropy}";
341
342
            // Check if it conflicts
343
            $exists = EditableFormField::get()->filter('Name', $name)->count() > 0;
344
        } while ($exists);
345
        return $name;
346
    }
347
348
    /**
349
     * Flag indicating that this field will set its own error message via data-msg='' attributes
350
     *
351
     * @return bool
352
     */
353
    public function getSetsOwnError()
354
    {
355
        return false;
356
    }
357
358
    /**
359
     * Return whether a user can delete this form field
360
     * based on whether they can edit the page
361
     *
362
     * @param Member $member
363
     * @return bool
364
     */
365
    public function canDelete($member = null)
366
    {
367
        return $this->canEdit($member);
368
    }
369
370
    /**
371
     * Return whether a user can edit this form field
372
     * based on whether they can edit the page
373
     *
374
     * @param Member $member
375
     * @return bool
376
     */
377
    public function canEdit($member = null)
378
    {
379
        $parent = $this->Parent();
380
        if ($parent && $parent->exists()) {
381
            return $parent->canEdit($member) && !$this->isReadonly();
382
        } elseif (!$this->exists() && Controller::has_curr()) {
383
            // This is for GridFieldOrderableRows support as it checks edit permissions on 
384
            // singleton of the class. Allows editing of User Defined Form pages by 
385
            // 'Content Authors' and those with permission to edit the UDF page. (ie. CanEditType/EditorGroups)
386
            // This is to restore User Forms 2.x backwards compatibility.
387
            $controller = Controller::curr();
388
            if ($controller && $controller instanceof CMSPageEditController) {
389
                $parent = $controller->getRecord($controller->currentPageID());
390
                // Only allow this behaviour on pages using UserFormFieldEditorExtension, such
391
                // as UserDefinedForm page type.
392
                if ($parent && $parent->hasExtension('UserFormFieldEditorExtension')) {
393
                    return $parent->canEdit($member);
394
                }
395
            }
396
        }
397
398
        // Fallback to secure admin permissions
399
        return parent::canEdit($member);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::canEdit($member); of type boolean|string|null adds the type string to the return on line 399 which is incompatible with the return type documented by EditableFormField::canEdit of type boolean.
Loading history...
400
    }
401
402
    /**
403
     * Return whether a user can view this form field
404
     * based on whether they can view the page, regardless of the ReadOnly status of the field
405
     *
406
     * @param Member $member
407
     * @return bool
408
     */
409
    public function canView($member = null)
410
    {
411
        $parent = $this->Parent();
412
        if ($parent && $parent->exists()) {
413
            return $parent->canView($member);
414
        }
415
416
        return true;
417
    }
418
419
    /**
420
     * Return whether a user can create an object of this type
421
     *
422
     * @param Member $member
423
     * @param array $context Virtual parameter to allow context to be passed in to check
0 ignored issues
show
Bug introduced by
There is no parameter named $context. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
424
     * @return bool
425
     */
426 View Code Duplication
    public function canCreate($member = null)
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...
427
    {
428
        // Check parent page
429
        $parent = $this->getCanCreateContext(func_get_args());
430
        if ($parent) {
431
            return $parent->canEdit($member);
432
        }
433
434
        // Fall back to secure admin permissions
435
        return parent::canCreate($member);
436
    }
437
438
    /**
439
     * Helper method to check the parent for this object
440
     *
441
     * @param array $args List of arguments passed to canCreate
442
     * @return SiteTree Parent page instance
443
     */
444 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...
445
    {
446
        // Inspect second parameter to canCreate for a 'Parent' context
447
        if (isset($args[1]['Parent'])) {
448
            return $args[1]['Parent'];
449
        }
450
        // Hack in currently edited page if context is missing
451
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
452
            return Controller::curr()->currentPage();
453
        }
454
455
        // No page being edited
456
        return null;
457
    }
458
459
    /**
460
     * Check if can publish
461
     *
462
     * @param Member $member
463
     * @return bool
464
     */
465
    public function canPublish($member = null)
466
    {
467
        return $this->canEdit($member);
468
    }
469
470
    /**
471
     * Check if can unpublish
472
     *
473
     * @param Member $member
474
     * @return bool
475
     */
476
    public function canUnpublish($member = null)
477
    {
478
        return $this->canDelete($member);
479
    }
480
481
    /**
482
     * Publish this Form Field to the live site
483
     *
484
     * Wrapper for the {@link Versioned} publish function
485
     */
486
    public function doPublish($fromStage, $toStage, $createNewVersion = false)
487
    {
488
        $this->publish($fromStage, $toStage, $createNewVersion);
0 ignored issues
show
Bug introduced by
The method publish() does not exist on EditableFormField. Did you maybe mean canPublish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
489
490
        // Don't forget to publish the related custom rules...
491
        foreach ($this->DisplayRules() as $rule) {
492
            $rule->doPublish($fromStage, $toStage, $createNewVersion);
493
        }
494
    }
495
496
    /**
497
     * Delete this field from a given stage
498
     *
499
     * Wrapper for the {@link Versioned} deleteFromStage function
500
     */
501
    public function doDeleteFromStage($stage)
502
    {
503
        // Remove custom rules in this stage
504
        $rules = Versioned::get_by_stage('EditableCustomRule', $stage)
505
            ->filter('ParentID', $this->ID);
506
        foreach ($rules as $rule) {
507
            $rule->deleteFromStage($stage);
508
        }
509
510
        // Remove record
511
        $this->deleteFromStage($stage);
0 ignored issues
show
Bug introduced by
The method deleteFromStage() does not exist on EditableFormField. Did you maybe mean doDeleteFromStage()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
512
    }
513
514
    /**
515
     * checks wether record is new, copied from Sitetree
516
     */
517
    public function isNew()
518
    {
519
        if (empty($this->ID)) {
520
            return true;
521
        }
522
523
        if (is_numeric($this->ID)) {
524
            return false;
525
        }
526
527
        return stripos($this->ID, 'new') === 0;
528
    }
529
530
    /**
531
     * checks if records is changed on stage
532
     * @return boolean
533
     */
534
    public function getIsModifiedOnStage()
535
    {
536
        // new unsaved fields could be never be published
537
        if ($this->isNew()) {
538
            return false;
539
        }
540
541
        $stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
542
        $liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
543
544
        return ($stageVersion && $stageVersion != $liveVersion);
545
    }
546
547
    /**
548
     * @deprecated since version 4.0
549
     */
550
    public function getSettings()
551
    {
552
        Deprecation::notice('4.0', 'getSettings is deprecated');
553
        return (!empty($this->CustomSettings)) ? unserialize($this->CustomSettings) : array();
0 ignored issues
show
Documentation introduced by
The property CustomSettings does not exist on object<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...
554
    }
555
556
    /**
557
     * @deprecated since version 4.0
558
     */
559
    public function setSettings($settings = array())
560
    {
561
        Deprecation::notice('4.0', 'setSettings is deprecated');
562
        $this->CustomSettings = serialize($settings);
0 ignored issues
show
Documentation introduced by
The property CustomSettings does not exist on object<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...
563
    }
564
565
    /**
566
     * @deprecated since version 4.0
567
     */
568
    public function setSetting($key, $value)
569
    {
570
        Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
571
        $settings = $this->getSettings();
0 ignored issues
show
Deprecated Code introduced by
The method EditableFormField::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...
572
        $settings[$key] = $value;
573
574
        $this->setSettings($settings);
0 ignored issues
show
Deprecated Code introduced by
The method EditableFormField::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...
575
    }
576
577
    /**
578
     * Set the allowed css classes for the extraClass custom setting
579
     *
580
     * @param array The permissible CSS classes to add
581
     */
582
    public function setAllowedCss(array $allowed)
583
    {
584
        if (is_array($allowed)) {
585
            foreach ($allowed as $k => $v) {
586
                self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
587
            }
588
        }
589
    }
590
591
    /**
592
     * @deprecated since version 4.0
593
     */
594
    public function getSetting($setting)
595
    {
596
        Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
597
598
        $settings = $this->getSettings();
0 ignored issues
show
Deprecated Code introduced by
The method EditableFormField::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...
599
        if (isset($settings) && count($settings) > 0) {
600
            if (isset($settings[$setting])) {
601
                return $settings[$setting];
602
            }
603
        }
604
        return '';
605
    }
606
607
    /**
608
     * Get the path to the icon for this field type, relative to the site root.
609
     *
610
     * @return string
611
     */
612
    public function getIcon()
613
    {
614
        return USERFORMS_DIR . '/images/' . strtolower($this->class) . '.png';
615
    }
616
617
    /**
618
     * Return whether or not this field has addable options
619
     * such as a dropdown field or radio set
620
     *
621
     * @return bool
622
     */
623
    public function getHasAddableOptions()
624
    {
625
        return false;
626
    }
627
628
    /**
629
     * Return whether or not this field needs to show the extra
630
     * options dropdown list
631
     *
632
     * @return bool
633
     */
634
    public function showExtraOptions()
635
    {
636
        return true;
637
    }
638
639
    /**
640
     * Returns the Title for rendering in the front-end (with XML values escaped)
641
     *
642
     * @return string
643
     */
644
    public function getEscapedTitle()
645
    {
646
        return Convert::raw2xml($this->Title);
647
    }
648
649
    /**
650
     * Find the numeric indicator (1.1.2) that represents it's nesting value
651
     *
652
     * Only useful for fields attached to a current page, and that contain other fields such as pages
653
     * or groups
654
     *
655
     * @return string
656
     */
657
    public function getFieldNumber()
658
    {
659
        // Check if exists
660
        if (!$this->exists()) {
661
            return null;
662
        }
663
        // Check parent
664
        $form = $this->Parent();
665
        if (!$form || !$form->exists() || !($fields = $form->Fields())) {
666
            return null;
667
        }
668
669
        $prior = 0; // Number of prior group at this level
670
        $stack = array(); // Current stack of nested groups, where the top level = the page
671
        foreach ($fields->map('ID', 'ClassName') as $id => $className) {
672
            if ($className === 'EditableFormStep') {
673
                $priorPage = empty($stack) ? $prior : $stack[0];
674
                $stack = array($priorPage + 1);
675
                $prior = 0;
676
            } elseif ($className === 'EditableFieldGroup') {
677
                $stack[] = $prior + 1;
678
                $prior = 0;
679
            } elseif ($className === 'EditableFieldGroupEnd') {
680
                $prior = array_pop($stack);
681
            }
682
            if ($id == $this->ID) {
683
                return implode('.', $stack);
684
            }
685
        }
686
        return null;
687
    }
688
689
    public function getCMSTitle()
690
    {
691
        return $this->i18n_singular_name() . ' (' . $this->Title . ')';
692
    }
693
694
    /**
695
     * @deprecated since version 4.0
696
     */
697
    public function getFieldName($field = false)
698
    {
699
        Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
700
        return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
701
    }
702
703
    /**
704
     * @deprecated since version 4.0
705
     */
706
    public function getSettingName($field)
707
    {
708
        Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
709
        $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 EditableFormField::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...
710
711
        return $name . '[' . $field .']';
712
    }
713
714
    /**
715
     * Append custom validation fields to the default 'Validation'
716
     * section in the editable options view
717
     *
718
     * @return FieldList
719
     */
720
    public function getFieldValidationOptions()
721
    {
722
        $fields = new FieldList(
723
            CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?'))
724
                ->setDescription(_t('EditableFormField.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
725
            TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR', 'Custom Error Message'))
726
        );
727
728
        $this->extend('updateFieldValidationOptions', $fields);
729
730
        return $fields;
731
    }
732
733
    /**
734
     * Return a FormField to appear on the front end. Implement on
735
     * your subclass.
736
     *
737
     * @return FormField
738
     */
739
    public function getFormField()
740
    {
741
        user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
742
    }
743
744
    /**
745
     * Updates a formfield with extensions
746
     *
747
     * @param FormField $field
748
     */
749
    public function doUpdateFormField($field)
750
    {
751
        $this->extend('beforeUpdateFormField', $field);
752
        $this->updateFormField($field);
753
        $this->extend('afterUpdateFormField', $field);
754
    }
755
756
    /**
757
     * Updates a formfield with the additional metadata specified by this field
758
     *
759
     * @param FormField $field
760
     */
761
    protected function updateFormField($field)
762
    {
763
        // set the error / formatting messages
764
        $field->setCustomValidationMessage($this->getErrorMessage()->RAW());
765
766
        // set the right title on this field
767
        if ($this->RightTitle) {
0 ignored issues
show
Documentation introduced by
The property RightTitle does not exist on object<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...
768
            // Since this field expects raw html, safely escape the user data prior
769
            $field->setRightTitle(Convert::raw2xml($this->RightTitle));
0 ignored issues
show
Documentation introduced by
The property RightTitle does not exist on object<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...
Bug introduced by
It seems like \Convert::raw2xml($this->RightTitle) targeting Convert::raw2xml() can also be of type array; however, FormField::setRightTitle() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
770
        }
771
772
        // if this field is required add some
773
        if ($this->Required) {
774
            // Required validation can conflict so add the Required validation messages as input attributes
775
            $errorMessage = $this->getErrorMessage()->HTML();
776
            $field->addExtraClass('requiredField');
777
            $field->setAttribute('data-rule-required', 'true');
778
            $field->setAttribute('data-msg-required', $errorMessage);
0 ignored issues
show
Bug introduced by
It seems like $errorMessage defined by $this->getErrorMessage()->HTML() on line 775 can also be of type array; however, FormField::setAttribute() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
779
780
            if ($identifier = UserDefinedForm::config()->required_identifier) {
781
                $title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
782
                $field->setTitle($title);
783
            }
784
        }
785
786
        // if this field has an extra class
787
        if ($this->ExtraClass) {
0 ignored issues
show
Documentation introduced by
The property ExtraClass does not exist on object<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...
788
            $field->addExtraClass($this->ExtraClass);
0 ignored issues
show
Documentation introduced by
The property ExtraClass does not exist on object<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...
789
        }
790
    }
791
792
    /**
793
     * Return the instance of the submission field class
794
     *
795
     * @return SubmittedFormField
796
     */
797
    public function getSubmittedFormField()
798
    {
799
        return new SubmittedFormField();
800
    }
801
802
803
    /**
804
     * Show this form field (and its related value) in the reports and in emails.
805
     *
806
     * @return bool
807
     */
808
    public function showInReports()
809
    {
810
        return true;
811
    }
812
813
    /**
814
     * Return the error message for this field. Either uses the custom
815
     * one (if provided) or the default SilverStripe message
816
     *
817
     * @return Varchar
818
     */
819
    public function getErrorMessage()
820
    {
821
        $title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
822
        $standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
823
824
        // only use CustomErrorMessage if it has a non empty value
825
        $errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
826
827
        return DBField::create_field('Varchar', $errorMessage);
828
    }
829
830
    /**
831
     * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
832
     * to the field proper
833
     *
834
     * @param array $data Unserialised data
835
     */
836
    public function migrateSettings($data)
837
    {
838
        // Map 'Show' / 'Hide' to boolean
839
        if (isset($data['ShowOnLoad'])) {
840
            $this->ShowOnLoad = $data['ShowOnLoad'] === '' || ($data['ShowOnLoad'] && $data['ShowOnLoad'] !== 'Hide');
0 ignored issues
show
Documentation introduced by
The property ShowOnLoad does not exist on object<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...
841
            unset($data['ShowOnLoad']);
842
        }
843
844
        // Migrate all other settings
845
        foreach ($data as $key => $value) {
846
            if ($this->hasField($key)) {
847
                $this->setField($key, $value);
848
            }
849
        }
850
    }
851
852
    /**
853
     * Get the formfield to use when editing this inline in gridfield
854
     *
855
     * @param string $column name of column
856
     * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
857
     * @return FormField
858
     */
859
    public function getInlineClassnameField($column, $fieldClasses)
860
    {
861
        return DropdownField::create($column, false, $fieldClasses);
862
    }
863
864
    /**
865
     * Get the formfield to use when editing the title inline
866
     *
867
     * @param string $column
868
     * @return FormField
869
     */
870
    public function getInlineTitleField($column)
871
    {
872
        return TextField::create($column, false)
873
            ->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
874
            ->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
875
    }
876
877
    /**
878
     * Get the JS expression for selecting the holder for this field
879
     *
880
     * @return string
881
     */
882
    public function getSelectorHolder()
883
    {
884
        return "$(\"#{$this->Name}\")";
885
    }
886
887
    /**
888
     * Gets the JS expression for selecting the value for this field
889
     *
890
     * @param EditableCustomRule $rule Custom rule this selector will be used with
891
     * @param bool $forOnLoad Set to true if this will be invoked on load
892
     */
893
    public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
894
    {
895
        return "$(\"input[name='{$this->Name}']\")";
896
    }
897
898
899
    /**
900
     * Get the list of classes that can be selected and used as data-values
901
     *
902
     * @param $includeLiterals Set to false to exclude non-data fields
903
     * @return array
904
     */
905
    public function getEditableFieldClasses($includeLiterals = true)
906
    {
907
        $classes = ClassInfo::getValidSubClasses('EditableFormField');
908
909
        // Remove classes we don't want to display in the dropdown.
910
        $editableFieldClasses = array();
911
        foreach ($classes as $class) {
912
            // Skip abstract / hidden classes
913
            if (Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
914
            ) {
915
                continue;
916
            }
917
918
            if (!$includeLiterals && Config::inst()->get($class, 'literal')) {
919
                continue;
920
            }
921
922
            $singleton = singleton($class);
923
            if (!$singleton->canCreate()) {
924
                continue;
925
            }
926
927
            $editableFieldClasses[$class] = $singleton->i18n_singular_name();
928
        }
929
930
        asort($editableFieldClasses);
931
        return $editableFieldClasses;
932
    }
933
934
    /**
935
     * @return EditableFormFieldValidator
936
     */
937
    public function getCMSValidator()
938
    {
939
        return EditableFormFieldValidator::create()
940
            ->setRecord($this);
941
    }
942
943
    /**
944
     * Determine effective display rules for this field.
945
     *
946
     * @return SS_List
947
     */
948
    public function EffectiveDisplayRules()
949
    {
950
        if ($this->Required) {
951
            return new ArrayList();
952
        }
953
        return $this->DisplayRules();
954
    }
955
}
956