Completed
Push — master ( b537e6...b70c66 )
by Daniel
35:35
created

EditableFormField::getCMSFields()   C

Complexity

Conditions 9
Paths 8

Size

Total Lines 83
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 83
rs 5.48
c 1
b 1
f 0
cc 9
eloc 57
nc 8
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
 * @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
	/**
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
     * @param Member $member
351
	 * @return bool
352
	 */
353
	public function canDelete($member = null) {
354
		return $this->canEdit($member);
355
		}
356
357
	/**
358
	 * Return whether a user can edit this form field
359
	 * based on whether they can edit the page
360
	 *
361
     * @param Member $member
362
	 * @return bool
363
	 */
364
	public function canEdit($member = null) {
365
        $parent = $this->Parent();
366
		if($parent && $parent->exists()) {
367
			return $parent->canEdit($member) && !$this->isReadonly();
368
		}
369
370
        // Fallback to secure admin permissions
371
		return parent::canEdit($member);
372
		}
373
374
    /**
375
     * Return whether a user can view this form field
376
     * based on whether they can view the page, regardless of the ReadOnly status of the field
377
     *
378
     * @param Member $member
379
     * @return bool
380
     */
381
	public function canView($member = null) {
382
		$parent = $this->Parent();
383
		if($parent && $parent->exists()) {
384
			return $parent->canView($member);
385
		}
386
387
		return true;
388
	}
389
390
	/**
391
	 * Return whether a user can create an object of this type
392
	 *
393
     * @param Member $member
394
     * @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...
395
	 * @return bool
396
	 */
397 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...
398
		// Check parent page
399
        $parent = $this->getCanCreateContext(func_get_args());
400
        if($parent) {
401
            return $parent->canEdit($member);
402
        }
403
404
        // Fall back to secure admin permissions
405
        return parent::canCreate($member);
406
	}
407
408
    /**
409
     * Helper method to check the parent for this object
410
     *
411
     * @param array $args List of arguments passed to canCreate
412
     * @return SiteTree Parent page instance
413
     */
414 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...
415
        // Inspect second parameter to canCreate for a 'Parent' context
416
        if(isset($args[1]['Parent'])) {
417
            return $args[1]['Parent'];
418
        }
419
        // Hack in currently edited page if context is missing
420
        if(Controller::has_curr() && Controller::curr() instanceof CMSMain) {
421
            return Controller::curr()->currentPage();
422
        }
423
424
        // No page being edited
425
        return null;
426
    }
427
428
    /**
429
     * Check if can publish
430
     *
431
     * @param Member $member
432
     * @return bool
433
     */
434
    public function canPublish($member = null) {
435
        return $this->canEdit($member);
436
    }
437
438
    /**
439
     * Check if can unpublish
440
     *
441
     * @param Member $member
442
     * @return bool
443
     */
444
    public function canUnpublish($member = null) {
445
        return $this->canDelete($member);
446
    }
447
448
	/**
449
	 * Publish this Form Field to the live site
450
	 *
451
	 * Wrapper for the {@link Versioned} publish function
452
	 */
453
	public function doPublish($fromStage, $toStage, $createNewVersion = false) {
454
		$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...
455
456
		// Don't forget to publish the related custom rules...
457
		foreach ($this->DisplayRules() as $rule) {
458
			$rule->doPublish($fromStage, $toStage, $createNewVersion);
459
		}
460
	}
461
462
	/**
463
	 * Delete this field from a given stage
464
	 *
465
	 * Wrapper for the {@link Versioned} deleteFromStage function
466
	 */
467
	public function doDeleteFromStage($stage) {
468
		// Remove custom rules in this stage
469
		$rules = Versioned::get_by_stage('EditableCustomRule', $stage)
470
			->filter('ParentID', $this->ID);
471
		foreach ($rules as $rule) {
472
			$rule->deleteFromStage($stage);
473
		}
474
475
		// Remove record
476
		$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...
477
	}
478
479
	/**
480
	 * checks wether record is new, copied from Sitetree
481
	 */
482
	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...
483
		if(empty($this->ID)) return true;
484
485
		if(is_numeric($this->ID)) return false;
486
487
		return stripos($this->ID, 'new') === 0;
488
	}
489
490
	/**
491
	 * checks if records is changed on stage
492
	 * @return boolean
493
	 */
494
	public function getIsModifiedOnStage() {
495
		// new unsaved fields could be never be published
496
		if($this->isNew()) return false;
497
498
		$stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
499
		$liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
500
501
		return ($stageVersion && $stageVersion != $liveVersion);
502
	}
503
504
	/**
505
	 * @deprecated since version 4.0
506
	 */
507
	public function getSettings() {
508
		Deprecation::notice('4.0', 'getSettings is deprecated');
509
		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...
510
	}
511
512
	/**
513
	 * @deprecated since version 4.0
514
	 */
515
	public function setSettings($settings = array()) {
516
		Deprecation::notice('4.0', 'setSettings is deprecated');
517
		$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...
518
	}
519
520
	/**
521
	 * @deprecated since version 4.0
522
	 */
523
	public function setSetting($key, $value) {
524
		Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
525
		$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...
526
		$settings[$key] = $value;
527
528
		$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...
529
	}
530
531
	/**
532
	 * Set the allowed css classes for the extraClass custom setting
533
	 *
534
	 * @param array The permissible CSS classes to add
535
	 */
536
	public function setAllowedCss(array $allowed) {
537
		if (is_array($allowed)) {
538
			foreach ($allowed as $k => $v) {
539
				self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
540
			}
541
		}
542
	}
543
544
	/**
545
	 * @deprecated since version 4.0
546
	 */
547
	public function getSetting($setting) {
548
		Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
549
550
		$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...
551
		if(isset($settings) && count($settings) > 0) {
552
			if(isset($settings[$setting])) {
553
				return $settings[$setting];
554
			}
555
		}
556
		return '';
557
	}
558
559
	/**
560
	 * Get the path to the icon for this field type, relative to the site root.
561
	 *
562
	 * @return string
563
	 */
564
	public function getIcon() {
565
		return USERFORMS_DIR . '/images/' . strtolower($this->class) . '.png';
566
	}
567
568
	/**
569
	 * Return whether or not this field has addable options
570
	 * such as a dropdown field or radio set
571
	 *
572
	 * @return bool
573
	 */
574
	public function getHasAddableOptions() {
575
		return false;
576
	}
577
578
	/**
579
	 * Return whether or not this field needs to show the extra
580
	 * options dropdown list
581
	 *
582
	 * @return bool
583
	 */
584
	public function showExtraOptions() {
585
		return true;
586
	}
587
588
	/**
589
	 * Returns the Title for rendering in the front-end (with XML values escaped)
590
	 *
591
	 * @return string
592
	 */
593
	public function getEscapedTitle() {
594
		return Convert::raw2xml($this->Title);
595
	}
596
597
	/**
598
	 * Find the numeric indicator (1.1.2) that represents it's nesting value
599
	 *
600
	 * Only useful for fields attached to a current page, and that contain other fields such as pages
601
	 * or groups
602
	 *
603
	 * @return string
604
	 */
605
	public function getFieldNumber() {
606
		// Check if exists
607
		if(!$this->exists()) {
608
			return null;
609
		}
610
		// Check parent
611
		$form = $this->Parent();
612
		if(!$form || !$form->exists() || !($fields = $form->Fields())) {
613
			return null;
614
		}
615
616
		$prior = 0; // Number of prior group at this level
617
		$stack = array(); // Current stack of nested groups, where the top level = the page
618
		foreach($fields->map('ID', 'ClassName') as $id => $className) {
619
			if($className === 'EditableFormStep') {
620
				$priorPage = empty($stack) ? $prior : $stack[0];
621
				$stack = array($priorPage + 1);
622
				$prior = 0;
623
			} elseif($className === 'EditableFieldGroup') {
624
				$stack[] = $prior + 1;
625
				$prior = 0;
626
			} elseif($className === 'EditableFieldGroupEnd') {
627
				$prior = array_pop($stack);
628
			}
629
			if($id == $this->ID) {
630
				return implode('.', $stack);
631
			}
632
		}
633
		return null;
634
	}
635
636
	public function getCMSTitle() {
637
		return $this->i18n_singular_name() . ' (' . $this->Title . ')';
638
	}
639
640
	/**
641
	 * @deprecated since version 4.0
642
	 */
643
	public function getFieldName($field = false) {
644
		Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
645
		return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
646
	}
647
648
	/**
649
	 * @deprecated since version 4.0
650
	 */
651
	public function getSettingName($field) {
652
		Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
653
		$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...
654
655
		return $name . '[' . $field .']';
656
	}
657
658
	/**
659
	 * Append custom validation fields to the default 'Validation'
660
	 * section in the editable options view
661
	 *
662
	 * @return FieldList
663
	 */
664
	public function getFieldValidationOptions() {
665
		$fields = new FieldList(
666
			CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?'))
667
				->setDescription(_t('EditableFormField.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
668
			TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR','Custom Error Message'))
669
		);
670
671
		$this->extend('updateFieldValidationOptions', $fields);
672
673
		return $fields;
674
	}
675
676
	/**
677
	 * Return a FormField to appear on the front end. Implement on
678
	 * your subclass.
679
	 *
680
	 * @return FormField
681
	 */
682
	public function getFormField() {
683
		user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
684
	}
685
686
	/**
687
	 * Updates a formfield with extensions
688
	 *
689
	 * @param FormField $field
690
	 */
691
	public function doUpdateFormField($field) {
692
		$this->extend('beforeUpdateFormField', $field);
693
		$this->updateFormField($field);
694
		$this->extend('afterUpdateFormField', $field);
695
	}
696
697
	/**
698
	 * Updates a formfield with the additional metadata specified by this field
699
	 *
700
	 * @param FormField $field
701
	 */
702
	protected function updateFormField($field) {
703
		// set the error / formatting messages
704
		$field->setCustomValidationMessage($this->getErrorMessage()->RAW());
705
706
		// set the right title on this field
707
		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...
708
			// Since this field expects raw html, safely escape the user data prior
709
			$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...
710
		}
711
712
		// if this field is required add some
713
		if($this->Required) {
714
			// Required validation can conflict so add the Required validation messages as input attributes
715
			$errorMessage = $this->getErrorMessage()->HTML();
716
			$field->addExtraClass('requiredField');
717
			$field->setAttribute('data-rule-required', 'true');
718
			$field->setAttribute('data-msg-required', $errorMessage);
0 ignored issues
show
Bug introduced by
It seems like $errorMessage defined by $this->getErrorMessage()->HTML() on line 715 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...
719
720
			if($identifier = UserDefinedForm::config()->required_identifier) {
721
				$title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
722
				$field->setTitle($title);
723
			}
724
		}
725
726
		// if this field has an extra class
727
		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...
728
			$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...
729
		}
730
	}
731
732
	/**
733
	 * Return the instance of the submission field class
734
	 *
735
	 * @return SubmittedFormField
736
	 */
737
	public function getSubmittedFormField() {
738
		return new SubmittedFormField();
739
	}
740
741
742
	/**
743
	 * Show this form field (and its related value) in the reports and in emails.
744
	 *
745
	 * @return bool
746
	 */
747
	public function showInReports() {
748
		return true;
749
	}
750
751
	/**
752
	 * Return the error message for this field. Either uses the custom
753
	 * one (if provided) or the default SilverStripe message
754
	 *
755
	 * @return Varchar
756
	 */
757
	public function getErrorMessage() {
758
		$title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
759
		$standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
760
761
		// only use CustomErrorMessage if it has a non empty value
762
		$errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
763
764
		return DBField::create_field('Varchar', $errorMessage);
765
	}
766
767
	/**
768
	 * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
769
	 * to the field proper
770
	 *
771
	 * @param array $data Unserialised data
772
	 */
773
	public function migrateSettings($data) {
774
		// Map 'Show' / 'Hide' to boolean
775
		if(isset($data['ShowOnLoad'])) {
776
			$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...
777
			unset($data['ShowOnLoad']);
778
		}
779
780
		// Migrate all other settings
781
		foreach($data as $key => $value) {
782
			if($this->hasField($key)) {
783
				$this->setField($key, $value);
784
			}
785
		}
786
	}
787
788
	/**
789
	 * Get the formfield to use when editing this inline in gridfield
790
	 *
791
	 * @param string $column name of column
792
	 * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
793
	 * @return FormField
794
	 */
795
	public function getInlineClassnameField($column, $fieldClasses) {
796
		return DropdownField::create($column, false, $fieldClasses);
797
	}
798
799
	/**
800
	 * Get the formfield to use when editing the title inline
801
	 *
802
	 * @param string $column
803
	 * @return FormField
804
	 */
805
	public function getInlineTitleField($column) {
806
		return TextField::create($column, false)
807
			->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
808
			->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
809
	}
810
811
	/**
812
	 * Get the JS expression for selecting the holder for this field
813
	 *
814
	 * @return string
815
	 */
816
	public function getSelectorHolder() {
817
		return "$(\"#{$this->Name}\")";
818
	}
819
820
	/**
821
	 * Gets the JS expression for selecting the value for this field
822
	 *
823
	 * @param EditableCustomRule $rule Custom rule this selector will be used with
824
	 * @param bool $forOnLoad Set to true if this will be invoked on load
825
	 */
826
	public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
827
		return "$(\"input[name='{$this->Name}']\")";
828
	}
829
830
831
	/**
832
	 * Get the list of classes that can be selected and used as data-values
833
	 *
834
	 * @param $includeLiterals Set to false to exclude non-data fields
835
	 * @return array
836
	 */
837
	public function getEditableFieldClasses($includeLiterals = true) {
838
		$classes = ClassInfo::getValidSubClasses('EditableFormField');
839
840
		// Remove classes we don't want to display in the dropdown.
841
		$editableFieldClasses = array();
842
		foreach ($classes as $class) {
843
			// Skip abstract / hidden classes
844
			if(Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
845
			) {
846
				continue;
847
			}
848
849
			if(!$includeLiterals && Config::inst()->get($class, 'literal')) {
850
				continue;
851
			}
852
853
			$singleton = singleton($class);
854
			if(!$singleton->canCreate()) {
855
				continue;
856
			}
857
858
			$editableFieldClasses[$class] = $singleton->i18n_singular_name();
859
		}
860
861
		asort($editableFieldClasses);
862
		return $editableFieldClasses;
863
	}
864
865
	/**
866
	 * @return EditableFormFieldValidator
867
	 */
868
	public function getCMSValidator() {
869
		return EditableFormFieldValidator::create()
870
			->setRecord($this);
871
	}
872
873
	/**
874
	 * Determine effective display rules for this field.
875
	 *
876
	 * @return SS_List
877
	 */
878
	public function EffectiveDisplayRules() {
879
		if($this->Required) {
880
			return new ArrayList();
881
		}
882
		return $this->DisplayRules();
883
	}
884
885
}
886