Completed
Push — master ( e34b87...931753 )
by Damian
10s
created

EditableFormField   F

Complexity

Total Complexity 117

Size/Duplication

Total Lines 887
Duplicated Lines 2.59 %

Coupling/Cohesion

Components 3
Dependencies 36

Importance

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

46 Methods

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

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

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
504
		if(empty($this->ID)) return true;
505
506
		if(is_numeric($this->ID)) return false;
507
508
		return stripos($this->ID, 'new') === 0;
509
	}
510
511
	/**
512
	 * checks if records is changed on stage
513
	 * @return boolean
514
	 */
515
	public function getIsModifiedOnStage() {
516
		// new unsaved fields could be never be published
517
		if($this->isNew()) return false;
518
519
		$stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
520
		$liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
521
522
		return ($stageVersion && $stageVersion != $liveVersion);
523
	}
524
525
	/**
526
	 * @deprecated since version 4.0
527
	 */
528
	public function getSettings() {
529
		Deprecation::notice('4.0', 'getSettings is deprecated');
530
		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...
531
	}
532
533
	/**
534
	 * @deprecated since version 4.0
535
	 */
536
	public function setSettings($settings = array()) {
537
		Deprecation::notice('4.0', 'setSettings is deprecated');
538
		$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...
539
	}
540
541
	/**
542
	 * @deprecated since version 4.0
543
	 */
544
	public function setSetting($key, $value) {
545
		Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
546
		$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...
547
		$settings[$key] = $value;
548
549
		$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...
550
	}
551
552
	/**
553
	 * Set the allowed css classes for the extraClass custom setting
554
	 *
555
	 * @param array The permissible CSS classes to add
556
	 */
557
	public function setAllowedCss(array $allowed) {
558
		if (is_array($allowed)) {
559
			foreach ($allowed as $k => $v) {
560
				self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
561
			}
562
		}
563
	}
564
565
	/**
566
	 * @deprecated since version 4.0
567
	 */
568
	public function getSetting($setting) {
569
		Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
570
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
		if(isset($settings) && count($settings) > 0) {
573
			if(isset($settings[$setting])) {
574
				return $settings[$setting];
575
			}
576
		}
577
		return '';
578
	}
579
580
	/**
581
	 * Get the path to the icon for this field type, relative to the site root.
582
	 *
583
	 * @return string
584
	 */
585
	public function getIcon() {
586
		return USERFORMS_DIR . '/images/' . strtolower($this->class) . '.png';
587
	}
588
589
	/**
590
	 * Return whether or not this field has addable options
591
	 * such as a dropdown field or radio set
592
	 *
593
	 * @return bool
594
	 */
595
	public function getHasAddableOptions() {
596
		return false;
597
	}
598
599
	/**
600
	 * Return whether or not this field needs to show the extra
601
	 * options dropdown list
602
	 *
603
	 * @return bool
604
	 */
605
	public function showExtraOptions() {
606
		return true;
607
	}
608
609
	/**
610
	 * Returns the Title for rendering in the front-end (with XML values escaped)
611
	 *
612
	 * @return string
613
	 */
614
	public function getEscapedTitle() {
615
		return Convert::raw2xml($this->Title);
616
	}
617
618
	/**
619
	 * Find the numeric indicator (1.1.2) that represents it's nesting value
620
	 *
621
	 * Only useful for fields attached to a current page, and that contain other fields such as pages
622
	 * or groups
623
	 *
624
	 * @return string
625
	 */
626
	public function getFieldNumber() {
627
		// Check if exists
628
		if(!$this->exists()) {
629
			return null;
630
		}
631
		// Check parent
632
		$form = $this->Parent();
633
		if(!$form || !$form->exists() || !($fields = $form->Fields())) {
634
			return null;
635
		}
636
637
		$prior = 0; // Number of prior group at this level
638
		$stack = array(); // Current stack of nested groups, where the top level = the page
639
		foreach($fields->map('ID', 'ClassName') as $id => $className) {
640
			if($className === 'EditableFormStep') {
641
				$priorPage = empty($stack) ? $prior : $stack[0];
642
				$stack = array($priorPage + 1);
643
				$prior = 0;
644
			} elseif($className === 'EditableFieldGroup') {
645
				$stack[] = $prior + 1;
646
				$prior = 0;
647
			} elseif($className === 'EditableFieldGroupEnd') {
648
				$prior = array_pop($stack);
649
			}
650
			if($id == $this->ID) {
651
				return implode('.', $stack);
652
			}
653
		}
654
		return null;
655
	}
656
657
	public function getCMSTitle() {
658
		return $this->i18n_singular_name() . ' (' . $this->Title . ')';
659
	}
660
661
	/**
662
	 * @deprecated since version 4.0
663
	 */
664
	public function getFieldName($field = false) {
665
		Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
666
		return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
667
	}
668
669
	/**
670
	 * @deprecated since version 4.0
671
	 */
672
	public function getSettingName($field) {
673
		Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
674
		$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...
675
676
		return $name . '[' . $field .']';
677
	}
678
679
	/**
680
	 * Append custom validation fields to the default 'Validation'
681
	 * section in the editable options view
682
	 *
683
	 * @return FieldList
684
	 */
685
	public function getFieldValidationOptions() {
686
		$fields = new FieldList(
687
			CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?'))
688
				->setDescription(_t('EditableFormField.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
689
			TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR','Custom Error Message'))
690
		);
691
692
		$this->extend('updateFieldValidationOptions', $fields);
693
694
		return $fields;
695
	}
696
697
	/**
698
	 * Return a FormField to appear on the front end. Implement on
699
	 * your subclass.
700
	 *
701
	 * @return FormField
702
	 */
703
	public function getFormField() {
704
		user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
705
	}
706
707
	/**
708
	 * Updates a formfield with extensions
709
	 *
710
	 * @param FormField $field
711
	 */
712
	public function doUpdateFormField($field) {
713
		$this->extend('beforeUpdateFormField', $field);
714
		$this->updateFormField($field);
715
		$this->extend('afterUpdateFormField', $field);
716
	}
717
718
	/**
719
	 * Updates a formfield with the additional metadata specified by this field
720
	 *
721
	 * @param FormField $field
722
	 */
723
	protected function updateFormField($field) {
724
		// set the error / formatting messages
725
		$field->setCustomValidationMessage($this->getErrorMessage()->RAW());
726
727
		// set the right title on this field
728
		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...
729
			// Since this field expects raw html, safely escape the user data prior
730
			$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...
731
		}
732
733
		// if this field is required add some
734
		if($this->Required) {
735
			// Required validation can conflict so add the Required validation messages as input attributes
736
			$errorMessage = $this->getErrorMessage()->HTML();
737
			$field->addExtraClass('requiredField');
738
			$field->setAttribute('data-rule-required', 'true');
739
			$field->setAttribute('data-msg-required', $errorMessage);
0 ignored issues
show
Bug introduced by
It seems like $errorMessage defined by $this->getErrorMessage()->HTML() on line 736 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...
740
741
			if($identifier = UserDefinedForm::config()->required_identifier) {
742
				$title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
743
				$field->setTitle($title);
744
			}
745
		}
746
747
		// if this field has an extra class
748
		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...
749
			$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...
750
		}
751
	}
752
753
	/**
754
	 * Return the instance of the submission field class
755
	 *
756
	 * @return SubmittedFormField
757
	 */
758
	public function getSubmittedFormField() {
759
		return new SubmittedFormField();
760
	}
761
762
763
	/**
764
	 * Show this form field (and its related value) in the reports and in emails.
765
	 *
766
	 * @return bool
767
	 */
768
	public function showInReports() {
769
		return true;
770
	}
771
772
	/**
773
	 * Return the error message for this field. Either uses the custom
774
	 * one (if provided) or the default SilverStripe message
775
	 *
776
	 * @return Varchar
777
	 */
778
	public function getErrorMessage() {
779
		$title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
780
		$standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
781
782
		// only use CustomErrorMessage if it has a non empty value
783
		$errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
784
785
		return DBField::create_field('Varchar', $errorMessage);
786
	}
787
788
	/**
789
	 * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
790
	 * to the field proper
791
	 *
792
	 * @param array $data Unserialised data
793
	 */
794
	public function migrateSettings($data) {
795
		// Map 'Show' / 'Hide' to boolean
796
		if(isset($data['ShowOnLoad'])) {
797
			$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...
798
			unset($data['ShowOnLoad']);
799
		}
800
801
		// Migrate all other settings
802
		foreach($data as $key => $value) {
803
			if($this->hasField($key)) {
804
				$this->setField($key, $value);
805
			}
806
		}
807
	}
808
809
	/**
810
	 * Get the formfield to use when editing this inline in gridfield
811
	 *
812
	 * @param string $column name of column
813
	 * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
814
	 * @return FormField
815
	 */
816
	public function getInlineClassnameField($column, $fieldClasses) {
817
		return DropdownField::create($column, false, $fieldClasses);
818
	}
819
820
	/**
821
	 * Get the formfield to use when editing the title inline
822
	 *
823
	 * @param string $column
824
	 * @return FormField
825
	 */
826
	public function getInlineTitleField($column) {
827
		return TextField::create($column, false)
828
			->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
829
			->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
830
	}
831
832
	/**
833
	 * Get the JS expression for selecting the holder for this field
834
	 *
835
	 * @return string
836
	 */
837
	public function getSelectorHolder() {
838
		return "$(\"#{$this->Name}\")";
839
	}
840
841
	/**
842
	 * Gets the JS expression for selecting the value for this field
843
	 *
844
	 * @param EditableCustomRule $rule Custom rule this selector will be used with
845
	 * @param bool $forOnLoad Set to true if this will be invoked on load
846
	 */
847
	public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
848
		return "$(\"input[name='{$this->Name}']\")";
849
	}
850
851
852
	/**
853
	 * Get the list of classes that can be selected and used as data-values
854
	 *
855
	 * @param $includeLiterals Set to false to exclude non-data fields
856
	 * @return array
857
	 */
858
	public function getEditableFieldClasses($includeLiterals = true) {
859
		$classes = ClassInfo::getValidSubClasses('EditableFormField');
860
861
		// Remove classes we don't want to display in the dropdown.
862
		$editableFieldClasses = array();
863
		foreach ($classes as $class) {
864
			// Skip abstract / hidden classes
865
			if(Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
866
			) {
867
				continue;
868
			}
869
870
			if(!$includeLiterals && Config::inst()->get($class, 'literal')) {
871
				continue;
872
			}
873
874
			$singleton = singleton($class);
875
			if(!$singleton->canCreate()) {
876
				continue;
877
			}
878
879
			$editableFieldClasses[$class] = $singleton->i18n_singular_name();
880
		}
881
882
		asort($editableFieldClasses);
883
		return $editableFieldClasses;
884
	}
885
886
	/**
887
	 * @return EditableFormFieldValidator
888
	 */
889
	public function getCMSValidator() {
890
		return EditableFormFieldValidator::create()
891
			->setRecord($this);
892
	}
893
894
	/**
895
	 * Determine effective display rules for this field.
896
	 *
897
	 * @return SS_List
898
	 */
899
	public function EffectiveDisplayRules() {
900
		if($this->Required) {
901
			return new ArrayList();
902
		}
903
		return $this->DisplayRules();
904
	}
905
906
}
907