Completed
Pull Request — master (#409)
by Damian
34:01
created

EditableFormField::getDisplayRuleFields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 67
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 67
rs 9.2815
c 1
b 1
f 0
cc 2
eloc 48
nc 2
nop 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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
 *
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
	/**
91
	 * @config
92
	 * @var array
93
	 */
94
	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...
95
		"Parent" => "UserDefinedForm",
96
	);
97
98
	/**
99
	 * Built in extensions required
100
	 *
101
	 * @config
102
	 * @var array
103
	 */
104
	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...
105
		"Versioned('Stage', 'Live')"
106
	);
107
108
	/**
109
	 * @config
110
	 * @var array
111
	 */
112
	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...
113
		"DisplayRules" => "EditableCustomRule.Parent" // from CustomRules
114
	);
115
116
	/**
117
	 * @var bool
118
	 */
119
	protected $readonly;
120
121
	/**
122
	 * Set the visibility of an individual form field
123
	 *
124
	 * @param bool
125
	 */
126
	public function setReadonly($readonly = true) {
127
		$this->readonly = $readonly;
128
	}
129
130
	/**
131
	 * Returns whether this field is readonly
132
	 *
133
	 * @return bool
134
	 */
135
	private function isReadonly() {
136
		return $this->readonly;
137
	}
138
139
	/**
140
	 * @return FieldList
141
	 */
142
	public function getCMSFields() {
143
		$fields = new FieldList(new TabSet('Root'));
144
145
		// Main tab
146
		$fields->addFieldsToTab(
147
			'Root.Main',
148
			array(
149
				ReadonlyField::create(
150
					'Type',
151
					_t('EditableFormField.TYPE', 'Type'),
152
					$this->i18n_singular_name()
153
				),
154
				LiteralField::create(
155
					'MergeField',
156
					_t(
157
						'EditableFormField.MERGEFIELDNAME',
158
						'<div class="field readonly">' .
159
							'<label class="left">Merge field</label>' .
160
							'<div class="middleColumn">' .
161
								'<span class="readonly">$' . $this->Name . '</span>' .
162
							'</div>' .
163
						'</div>'
164
					)
165
				),
166
				TextField::create('Title'),
167
				TextField::create('Default', _t('EditableFormField.DEFAULT', 'Default value')),
168
				TextField::create('RightTitle', _t('EditableFormField.RIGHTTITLE', 'Right title')),
169
				SegmentField::create('Name')->setModifiers(array(
170
					UnderscoreSegmentFieldModifier::create()->setDefault('FieldName'),
171
					DisambiguationSegmentFieldModifier::create(),
172
				))->setPreview($this->Name)
173
			)
174
		);
175
176
		// Custom settings
177
		if (!empty(self::$allowed_css)) {
178
			$cssList = array();
179
			foreach(self::$allowed_css as $k => $v) {
180
				if (!is_array($v)) {
181
					$cssList[$k]=$v;
182
				} elseif ($k === $this->ClassName) {
183
					$cssList = array_merge($cssList, $v);
184
				}
185
			}
186
187
			$fields->addFieldToTab('Root.Main',
188
				DropdownField::create(
189
					'ExtraClass',
190
					_t('EditableFormField.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
191
					$cssList
192
				)->setDescription(_t(
193
					'EditableFormField.EXTRACLASS_SELECT',
194
					'Select from the list of allowed styles'
195
				))
196
			);
197
		} else {
198
			$fields->addFieldToTab('Root.Main',
199
				TextField::create(
200
					'ExtraClass',
201
					_t('EditableFormField.EXTRACLASS_Title', 'Extra CSS classes')
202
				)->setDescription(_t(
203
					'EditableFormField.EXTRACLASS_MULTIPLE',
204
					'Separate each CSS class with a single space'
205
				))
206
			);
207
		}
208
209
		// Validation
210
		$validationFields = $this->getFieldValidationOptions();
211
		if($validationFields && $validationFields->count()) {
212
			$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...
213
		}
214
215
		// Add display rule fields
216
		$displayFields = $this->getDisplayRuleFields();
217
		if($displayFields && $displayFields->count()) {
218
			$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...
219
		}
220
221
		$this->extend('updateCMSFields', $fields);
222
223
		return $fields;
224
	}
225
226
	/**
227
	 * Return fields to display on the 'Display Rules' tab
228
	 *
229
	 * @return FieldList
230
	 */
231
	protected function getDisplayRuleFields() {
232
		// Check display rules
233
		if($this->Required) {
234
			return new FieldList(
235
				LabelField::create(_t(
236
					'EditableFormField.DISPLAY_RULES_DISABLED',
237
					'Display rules are not enabled for required fields. ' .
238
					'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
239
				))->addExtraClass('message warning')
240
			);
241
		}
242
		$self = $this;
243
		$allowedClasses = array_keys($this->getEditableFieldClasses(false));
244
		$editableColumns = new GridFieldEditableColumns();
245
		$editableColumns->setDisplayFields(array(
246
			'Display' => '',
247
			'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...
248
				return DropdownField::create(
249
					$column,
250
					'',
251
					EditableFormField::get()
252
						->filter(array(
253
							'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...
254
							'ClassName' => $allowedClasses
255
						))
256
						->exclude(array(
257
							'ID' => $self->ID
258
						))
259
						->map('ID', 'Title')
260
				);
261
			},
262
			'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...
263
				$options = Config::inst()->get('EditableCustomRule', 'condition_options');
264
				return DropdownField::create($column, '', $options);
265
			},
266
			'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...
267
				return TextField::create($column);
268
			},
269
			'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...
270
				return HiddenField::create($column, '', $self->ID);
271
			}
272
		));
273
274
		// Custom rules
275
		$customRulesConfig = GridFieldConfig::create()
276
			->addComponents(
277
				$editableColumns,
278
				new GridFieldButtonRow(),
279
				new GridFieldToolbarHeader(),
280
				new GridFieldAddNewInlineButton(),
281
				new GridFieldDeleteAction()
282
			);
283
284
		return new FieldList(
285
			CheckboxField::create('ShowOnLoad')
286
				->setDescription(_t(
287
					'EditableFormField.SHOWONLOAD',
288
					'Initial visibility before processing these rules'
289
				)),
290
			GridField::create(
291
				'DisplayRules',
292
				_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
293
				$this->DisplayRules(),
294
				$customRulesConfig
295
			)
296
		);
297
	}
298
299
	public function onBeforeWrite() {
300
		parent::onBeforeWrite();
301
302
		// Set a field name.
303
		if(!$this->Name) {
304
			// New random name
305
			$this->Name = $this->generateName();
306
307
		} elseif($this->Name === 'Field') {
308
			throw new ValidationException('Field name cannot be "Field"');
309
		}
310
311
		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...
312
			$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...
313
			$this->Sort = EditableFormField::get()
314
				->filter('ParentID', $parentID)
315
				->max('Sort') + 1;
316
		}
317
	}
318
319
	/**
320
	 * Generate a new non-conflicting Name value
321
	 *
322
	 * @return string
323
	 */
324
	protected function generateName() {
325
		do {
326
			// Generate a new random name after this class
327
			$class = get_class($this);
328
			$entropy = substr(sha1(uniqid()), 0, 5);
329
			$name = "{$class}_{$entropy}";
330
331
			// Check if it conflicts
332
			$exists = EditableFormField::get()->filter('Name', $name)->count() > 0;
333
		} while($exists);
334
		return $name;
335
	}
336
337
	/**
338
	 * Flag indicating that this field will set its own error message via data-msg='' attributes
339
	 *
340
	 * @return bool
341
	 */
342
	public function getSetsOwnError() {
343
		return false;
344
	}
345
346
	/**
347
	 * Return whether a user can delete this form field
348
	 * based on whether they can edit the page
349
	 *
350
	 * @return bool
351
	 */
352 View Code Duplication
	public function canDelete($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...
353
		if($this->Parent()) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on EditableFormField. Did you maybe mean parentClass()?

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...
354
			return $this->Parent()->canEdit($member) && !$this->isReadonly();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on EditableFormField. Did you maybe mean parentClass()?

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...
355
		}
356
357
		return true;
358
	}
359
360
	/**
361
	 * Return whether a user can edit this form field
362
	 * based on whether they can edit the page
363
	 *
364
	 * @return bool
365
	 */
366 View Code Duplication
	public function canEdit($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...
367
		if($this->Parent()) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on EditableFormField. Did you maybe mean parentClass()?

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...
368
			return $this->Parent()->canEdit($member) && !$this->isReadonly();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on EditableFormField. Did you maybe mean parentClass()?

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...
369
		}
370
371
		return true;
372
	}
373
374
	/**
375
	 * Publish this Form Field to the live site
376
	 *
377
	 * Wrapper for the {@link Versioned} publish function
378
	 */
379
	public function doPublish($fromStage, $toStage, $createNewVersion = false) {
380
		$this->publish($fromStage, $toStage, $createNewVersion);
0 ignored issues
show
Bug introduced by
The method publish() does not exist on EditableFormField. Did you maybe mean doPublish()?

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...
381
382
		// Don't forget to publish the related custom rules...
383
		foreach ($this->DisplayRules() as $rule) {
384
			$rule->doPublish($fromStage, $toStage, $createNewVersion);
385
		}
386
	}
387
388
	/**
389
	 * Delete this field from a given stage
390
	 *
391
	 * Wrapper for the {@link Versioned} deleteFromStage function
392
	 */
393
	public function doDeleteFromStage($stage) {
394
		// Remove custom rules in this stage
395
		$rules = Versioned::get_by_stage('EditableCustomRule', $stage)
396
			->filter('ParentID', $this->ID);
397
		foreach ($rules as $rule) {
398
			$rule->deleteFromStage($stage);
399
		}
400
401
		// Remove record
402
		$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...
403
	}
404
405
	/**
406
	 * checks wether record is new, copied from Sitetree
407
	 */
408
	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...
409
		if(empty($this->ID)) return true;
410
411
		if(is_numeric($this->ID)) return false;
412
413
		return stripos($this->ID, 'new') === 0;
414
	}
415
416
	/**
417
	 * checks if records is changed on stage
418
	 * @return boolean
419
	 */
420
	public function getIsModifiedOnStage() {
421
		// new unsaved fields could be never be published
422
		if($this->isNew()) return false;
423
424
		$stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
425
		$liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
426
427
		return ($stageVersion && $stageVersion != $liveVersion);
428
	}
429
430
	/**
431
	 * @deprecated since version 4.0
432
	 */
433
	public function getSettings() {
434
		Deprecation::notice('4.0', 'getSettings is deprecated');
435
		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...
436
	}
437
438
	/**
439
	 * @deprecated since version 4.0
440
	 */
441
	public function setSettings($settings = array()) {
442
		Deprecation::notice('4.0', 'setSettings is deprecated');
443
		$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...
444
	}
445
446
	/**
447
	 * @deprecated since version 4.0
448
	 */
449
	public function setSetting($key, $value) {
450
		Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
451
		$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...
452
		$settings[$key] = $value;
453
454
		$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...
455
	}
456
457
	/**
458
	 * Set the allowed css classes for the extraClass custom setting
459
	 *
460
	 * @param array The permissible CSS classes to add
461
	 */
462
	public function setAllowedCss(array $allowed) {
463
		if (is_array($allowed)) {
464
			foreach ($allowed as $k => $v) {
465
				self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
466
			}
467
		}
468
	}
469
470
	/**
471
	 * @deprecated since version 4.0
472
	 */
473
	public function getSetting($setting) {
474
		Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
475
476
		$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...
477
		if(isset($settings) && count($settings) > 0) {
478
			if(isset($settings[$setting])) {
479
				return $settings[$setting];
480
			}
481
		}
482
		return '';
483
	}
484
485
	/**
486
	 * Get the path to the icon for this field type, relative to the site root.
487
	 *
488
	 * @return string
489
	 */
490
	public function getIcon() {
491
		return USERFORMS_DIR . '/images/' . strtolower($this->class) . '.png';
492
	}
493
494
	/**
495
	 * Return whether or not this field has addable options
496
	 * such as a dropdown field or radio set
497
	 *
498
	 * @return bool
499
	 */
500
	public function getHasAddableOptions() {
501
		return false;
502
	}
503
504
	/**
505
	 * Return whether or not this field needs to show the extra
506
	 * options dropdown list
507
	 *
508
	 * @return bool
509
	 */
510
	public function showExtraOptions() {
511
		return true;
512
	}
513
514
	/**
515
	 * Returns the Title for rendering in the front-end (with XML values escaped)
516
	 *
517
	 * @return string
518
	 */
519
	public function getEscapedTitle() {
520
		return Convert::raw2xml($this->Title);
521
	}
522
523
	/**
524
	 * Find the numeric indicator (1.1.2) that represents it's nesting value
525
	 *
526
	 * Only useful for fields attached to a current page, and that contain other fields such as pages
527
	 * or groups
528
	 *
529
	 * @return string
530
	 */
531
	public function getFieldNumber() {
532
		// Check if exists
533
		if(!$this->exists()) {
534
			return null;
535
		}
536
		// Check parent
537
		$form = $this->Parent();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on EditableFormField. Did you maybe mean parentClass()?

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...
538
		if(!$form || !$form->exists() || !($fields = $form->Fields())) {
539
			return null;
540
		}
541
542
		$prior = 0; // Number of prior group at this level
543
		$stack = array(); // Current stack of nested groups, where the top level = the page
544
		foreach($fields->map('ID', 'ClassName') as $id => $className) {
545
			if($className === 'EditableFormStep') {
546
				$priorPage = empty($stack) ? $prior : $stack[0];
547
				$stack = array($priorPage + 1);
548
				$prior = 0;
549
			} elseif($className === 'EditableFieldGroup') {
550
				$stack[] = $prior + 1;
551
				$prior = 0;
552
			} elseif($className === 'EditableFieldGroupEnd') {
553
				$prior = array_pop($stack);
554
			}
555
			if($id == $this->ID) {
556
				return implode('.', $stack);
557
			}
558
		}
559
		return null;
560
	}
561
562
	public function getCMSTitle() {
563
		return $this->i18n_singular_name() . ' (' . $this->Title . ')';
564
	}
565
566
	/**
567
	 * @deprecated since version 4.0
568
	 */
569
	public function getFieldName($field = false) {
570
		Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
571
		return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
572
	}
573
574
	/**
575
	 * @deprecated since version 4.0
576
	 */
577
	public function getSettingName($field) {
578
		Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
579
		$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...
580
581
		return $name . '[' . $field .']';
582
	}
583
584
	/**
585
	 * Append custom validation fields to the default 'Validation'
586
	 * section in the editable options view
587
	 *
588
	 * @return FieldList
589
	 */
590
	public function getFieldValidationOptions() {
591
		$fields = new FieldList(
592
			CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?'))
593
				->setDescription(_t('EditableFormField.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
594
			TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR','Custom Error Message'))
595
		);
596
597
		$this->extend('updateFieldValidationOptions', $fields);
598
599
		return $fields;
600
	}
601
602
	/**
603
	 * Return a FormField to appear on the front end. Implement on
604
	 * your subclass.
605
	 *
606
	 * @return FormField
607
	 */
608
	public function getFormField() {
609
		user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
610
	}
611
612
	/**
613
	 * Updates a formfield with extensions
614
	 *
615
	 * @param FormField $field
616
	 */
617
	public function doUpdateFormField($field) {
618
		$this->extend('beforeUpdateFormField', $field);
619
		$this->updateFormField($field);
620
		$this->extend('afterUpdateFormField', $field);
621
	}
622
623
	/**
624
	 * Updates a formfield with the additional metadata specified by this field
625
	 *
626
	 * @param FormField $field
627
	 */
628
	protected function updateFormField($field) {
629
		// set the error / formatting messages
630
		$field->setCustomValidationMessage($this->getErrorMessage()->RAW());
631
632
		// set the right title on this field
633
		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...
634
			// Since this field expects raw html, safely escape the user data prior
635
			$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...
636
		}
637
638
		// if this field is required add some
639
		if($this->Required) {
640
			// Required validation can conflict so add the Required validation messages as input attributes
641
			$errorMessage = $this->getErrorMessage()->HTML();
642
			$field->addExtraClass('requiredField');
643
			$field->setAttribute('data-rule-required', 'true');
644
			$field->setAttribute('data-msg-required', $errorMessage);
0 ignored issues
show
Bug introduced by
It seems like $errorMessage defined by $this->getErrorMessage()->HTML() on line 641 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...
645
646
			if($identifier = UserDefinedForm::config()->required_identifier) {
647
				$title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
648
				$field->setTitle($title);
649
			}
650
		}
651
652
		// if this field has an extra class
653
		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...
654
			$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...
655
		}
656
	}
657
658
	/**
659
	 * Return the instance of the submission field class
660
	 *
661
	 * @return SubmittedFormField
662
	 */
663
	public function getSubmittedFormField() {
664
		return new SubmittedFormField();
665
	}
666
667
668
	/**
669
	 * Show this form field (and its related value) in the reports and in emails.
670
	 *
671
	 * @return bool
672
	 */
673
	public function showInReports() {
674
		return true;
675
	}
676
677
	/**
678
	 * Return the error message for this field. Either uses the custom
679
	 * one (if provided) or the default SilverStripe message
680
	 *
681
	 * @return Varchar
682
	 */
683
	public function getErrorMessage() {
684
		$title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
685
		$standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
686
687
		// only use CustomErrorMessage if it has a non empty value
688
		$errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
689
690
		return DBField::create_field('Varchar', $errorMessage);
691
	}
692
693
	/**
694
	 * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
695
	 * to the field proper
696
	 *
697
	 * @param array $data Unserialised data
698
	 */
699
	public function migrateSettings($data) {
700
		// Map 'Show' / 'Hide' to boolean
701
		if(isset($data['ShowOnLoad'])) {
702
			$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...
703
			unset($data['ShowOnLoad']);
704
		}
705
706
		// Migrate all other settings
707
		foreach($data as $key => $value) {
708
			if($this->hasField($key)) {
709
				$this->setField($key, $value);
710
			}
711
		}
712
	}
713
714
	/**
715
	 * Get the formfield to use when editing this inline in gridfield
716
	 *
717
	 * @param string $column name of column
718
	 * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
719
	 * @return FormField
720
	 */
721
	public function getInlineClassnameField($column, $fieldClasses) {
722
		return DropdownField::create($column, false, $fieldClasses);
723
	}
724
725
	/**
726
	 * Get the formfield to use when editing the title inline
727
	 *
728
	 * @param string $column
729
	 * @return FormField
730
	 */
731
	public function getInlineTitleField($column) {
732
		return TextField::create($column, false)
733
			->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
734
			->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
735
	}
736
737
	/**
738
	 * Get the JS expression for selecting the holder for this field
739
	 *
740
	 * @return string
741
	 */
742
	public function getSelectorHolder() {
743
		return "$(\"#{$this->Name}\")";
744
	}
745
746
	/**
747
	 * Gets the JS expression for selecting the value for this field
748
	 *
749
	 * @param EditableCustomRule $rule Custom rule this selector will be used with
750
	 * @param bool $forOnLoad Set to true if this will be invoked on load
751
	 */
752
	public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
753
		return "$(\"input[name='{$this->Name}']\")";
754
	}
755
756
757
	/**
758
	 * Get the list of classes that can be selected and used as data-values
759
	 *
760
	 * @param $includeLiterals Set to false to exclude non-data fields
761
	 * @return array
762
	 */
763
	public function getEditableFieldClasses($includeLiterals = true) {
764
		$classes = ClassInfo::getValidSubClasses('EditableFormField');
765
766
		// Remove classes we don't want to display in the dropdown.
767
		$editableFieldClasses = array();
768
		foreach ($classes as $class) {
769
			// Skip abstract / hidden classes
770
			if(Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
771
			) {
772
				continue;
773
			}
774
775
			if(!$includeLiterals && Config::inst()->get($class, 'literal')) {
776
				continue;
777
			}
778
779
			$singleton = singleton($class);
780
			if(!$singleton->canCreate()) {
781
				continue;
782
			}
783
784
			$editableFieldClasses[$class] = $singleton->i18n_singular_name();
785
		}
786
787
		asort($editableFieldClasses);
788
		return $editableFieldClasses;
789
	}
790
791
	/**
792
	 * @return EditableFormFieldValidator
793
	 */
794
	public function getCMSValidator() {
795
		return EditableFormFieldValidator::create()
796
			->setRecord($this);
797
	}
798
799
	/**
800
	 * Determine effective display rules for this field.
801
	 *
802
	 * @return SS_List
803
	 */
804
	public function EffectiveDisplayRules() {
805
		if($this->Required) {
806
			return new ArrayList();
807
		}
808
		return $this->DisplayRules();
809
	}
810
811
}
812