Completed
Pull Request — master (#478)
by
unknown
34:30
created

EditableFormField::getCMSValidator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

namespace YourVendor;

class YourClass { }

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

Loading history...
21
22
	/**
23
	 * Set to true to hide from class selector
24
	 *
25
	 * @config
26
	 * @var bool
27
	 */
28
	private static $hidden = false;
0 ignored issues
show
Unused Code introduced by
The property $hidden is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
29
30
	/**
31
	 * Define this field as abstract (not inherited)
32
	 *
33
	 * @config
34
	 * @var bool
35
	 */
36
	private static $abstract = true;
0 ignored issues
show
Unused Code introduced by
The property $abstract is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
37
38
	/**
39
	 * Flag this field type as non-data (e.g. literal, header, html)
40
	 *
41
	 * @config
42
	 * @var bool
43
	 */
44
	private static $literal = false;
0 ignored issues
show
Unused Code introduced by
The property $literal is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
45
46
	/**
47
	 * Default sort order
48
	 *
49
	 * @config
50
	 * @var string
51
	 */
52
	private static $default_sort = '"Sort"';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $default_sort is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
53
54
	/**
55
	 * A list of CSS classes that can be added
56
	 *
57
	 * @var array
58
	 */
59
	public static $allowed_css = array();
60
61
	/**
62
	 * @config
63
	 * @var array
64
	 */
65
	private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $summary_fields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
66
		'Title'
67
	);
68
69
	/**
70
	 * @config
71
	 * @var array
72
	 */
73
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
74
		"Name" => "Varchar",
75
		"Title" => "Varchar(255)",
76
		"Default" => "Varchar(255)",
77
		"Sort" => "Int",
78
		"Required" => "Boolean",
79
		"CustomErrorMessage" => "Varchar(255)",
80
81
		"CustomRules" => "Text", // @deprecated from 2.0
82
		"CustomSettings" => "Text", // @deprecated from 2.0
83
		"Migrated" => "Boolean", // set to true when migrated
84
85
		"ExtraClass" => "Text", // from CustomSettings
86
		"RightTitle" => "Varchar(255)", // from CustomSettings
87
		"ShowOnLoad" => "Boolean(1)", // from CustomSettings
88
	);
89
	
90
	private static $defaults = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
91
		'ShowOnLoad' => true,
92
	);
93
94
95
	/**
96
	 * @config
97
	 * @var array
98
	 */
99
	private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
100
		"Parent" => "UserDefinedForm",
101
	);
102
103
	/**
104
	 * Built in extensions required
105
	 *
106
	 * @config
107
	 * @var array
108
	 */
109
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
110
		"Versioned('Stage', 'Live')"
111
	);
112
113
	/**
114
	 * @config
115
	 * @var array
116
	 */
117
	private static $has_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
118
		"DisplayRules" => "EditableCustomRule.Parent" // from CustomRules
119
	);
120
121
	/**
122
	 * @var bool
123
	 */
124
	protected $readonly;
125
126
	/**
127
	 * Set the visibility of an individual form field
128
	 *
129
	 * @param bool
130
	 */
131
	public function setReadonly($readonly = true) {
132
		$this->readonly = $readonly;
133
	}
134
135
	/**
136
	 * Returns whether this field is readonly
137
	 *
138
	 * @return bool
139
	 */
140
	private function isReadonly() {
141
		return $this->readonly;
142
	}
143
144
	/**
145
	 * @return FieldList
146
	 */
147
	public function getCMSFields() {
148
		$fields = new FieldList(new TabSet('Root'));
149
150
		// Main tab
151
		$fields->addFieldsToTab(
152
			'Root.Main',
153
			array(
154
				ReadonlyField::create(
155
					'Type',
156
					_t('EditableFormField.TYPE', 'Type'),
157
					$this->i18n_singular_name()
158
				),
159
				LiteralField::create(
160
					'MergeField',
161
					_t(
162
						'EditableFormField.MERGEFIELDNAME',
163
						'<div class="field readonly">' .
164
							'<label class="left">Merge field</label>' .
165
							'<div class="middleColumn">' .
166
								'<span class="readonly">$' . $this->Name . '</span>' .
167
							'</div>' .
168
						'</div>'
169
					)
170
				),
171
				TextField::create('Title'),
172
				TextField::create('Default', _t('EditableFormField.DEFAULT', 'Default value')),
173
				TextField::create('RightTitle', _t('EditableFormField.RIGHTTITLE', 'Right title')),
174
				SegmentField::create('Name')->setModifiers(array(
175
					UnderscoreSegmentFieldModifier::create()->setDefault('FieldName'),
176
					DisambiguationSegmentFieldModifier::create(),
177
				))->setPreview($this->Name)
178
			)
179
		);
180
181
		// Custom settings
182
		if (!empty(self::$allowed_css)) {
183
			$cssList = array();
184
			foreach(self::$allowed_css as $k => $v) {
185
				if (!is_array($v)) {
186
					$cssList[$k]=$v;
187
				} elseif ($k === $this->ClassName) {
188
					$cssList = array_merge($cssList, $v);
189
				}
190
			}
191
192
			$fields->addFieldToTab('Root.Main',
193
				DropdownField::create(
194
					'ExtraClass',
195
					_t('EditableFormField.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
196
					$cssList
197
				)->setDescription(_t(
198
					'EditableFormField.EXTRACLASS_SELECT',
199
					'Select from the list of allowed styles'
200
				))
201
			);
202
		} else {
203
			$fields->addFieldToTab('Root.Main',
204
				TextField::create(
205
					'ExtraClass',
206
					_t('EditableFormField.EXTRACLASS_Title', 'Extra CSS classes')
207
				)->setDescription(_t(
208
					'EditableFormField.EXTRACLASS_MULTIPLE',
209
					'Separate each CSS class with a single space'
210
				))
211
			);
212
		}
213
214
		// Validation
215
		$validationFields = $this->getFieldValidationOptions();
216
		if($validationFields && $validationFields->count()) {
217
			$fields->addFieldsToTab('Root.Validation', $validationFields);
0 ignored issues
show
Documentation introduced by
$validationFields is of type object<FieldList>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
218
		}
219
220
		// Add display rule fields
221
		$displayFields = $this->getDisplayRuleFields();
222
		if($displayFields && $displayFields->count()) {
223
			$fields->addFieldsToTab('Root.DisplayRules', $displayFields);
0 ignored issues
show
Documentation introduced by
$displayFields is of type object<FieldList>, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
224
		}
225
226
		$this->extend('updateCMSFields', $fields);
227
228
		return $fields;
229
	}
230
231
	/**
232
	 * Return fields to display on the 'Display Rules' tab
233
	 *
234
	 * @return FieldList
235
	 */
236
	protected function getDisplayRuleFields() {
237
		// Check display rules
238
		if($this->Required) {
239
			return new FieldList(
240
				LabelField::create(_t(
241
					'EditableFormField.DISPLAY_RULES_DISABLED',
242
					'Display rules are not enabled for required fields. ' .
243
					'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
244
				))->addExtraClass('message warning')
245
			);
246
		}
247
		$self = $this;
248
		$allowedClasses = array_keys($this->getEditableFieldClasses(false));
249
		$editableColumns = new GridFieldEditableColumns();
250
		$editableColumns->setDisplayFields(array(
251
			'Display' => '',
252
			'ConditionFieldID' => function($record, $column, $grid) use ($allowedClasses, $self) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

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

Loading history...
253
				return DropdownField::create(
254
					$column,
255
					'',
256
					EditableFormField::get()
257
						->filter(array(
258
							'ParentID' => $self->ParentID,
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
259
							'ClassName' => $allowedClasses
260
						))
261
						->exclude(array(
262
							'ID' => $self->ID
263
						))
264
						->map('ID', 'Title')
265
					);
266
			},
267
			'ConditionOption' => function($record, $column, $grid) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

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

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

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

Loading history...
272
				return TextField::create($column);
273
			},
274
			'ParentID' => function($record, $column, $grid) use ($self) {
0 ignored issues
show
Unused Code introduced by
The parameter $grid is not used and could be removed.

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

Loading history...
275
				return HiddenField::create($column, '', $self->ID);
276
			}
277
		));
278
279
		// Custom rules
280
		$customRulesConfig = GridFieldConfig::create()
281
			->addComponents(
282
				$editableColumns,
283
				new GridFieldButtonRow(),
284
				new GridFieldToolbarHeader(),
285
				new GridFieldAddNewInlineButton(),
286
				new GridFieldDeleteAction()
287
			);
288
289
		return new FieldList(
290
			CheckboxField::create('ShowOnLoad')
291
				->setDescription(_t(
292
					'EditableFormField.SHOWONLOAD',
293
					'Initial visibility before processing these rules'
294
				)),
295
			GridField::create(
296
				'DisplayRules',
297
				_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
298
				$this->DisplayRules(),
299
				$customRulesConfig
300
			)
301
		);
302
	}
303
304
	public function onBeforeWrite() {
305
		parent::onBeforeWrite();
306
307
		// Set a field name.
308
		if(!$this->Name) {
309
			// New random name
310
			$this->Name = $this->generateName();
311
312
		} elseif($this->Name === 'Field') {
313
			throw new ValidationException('Field name cannot be "Field"');
314
		}
315
316
		if(!$this->Sort && $this->ParentID) {
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<EditableFormField>. Since you implemented __get, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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