Completed
Push — master ( 708319...e0d939 )
by Daniel
22:58
created

EditableFormField::getErrorMessage()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
75
        "Name" => "Varchar",
76
        "Title" => "Varchar(255)",
77
        "Default" => "Varchar(255)",
78
        "Sort" => "Int",
79
        "Required" => "Boolean",
80
        "CustomErrorMessage" => "Varchar(255)",
81
82
        "CustomRules" => "Text", // @deprecated from 2.0
83
        "CustomSettings" => "Text", // @deprecated from 2.0
84
        "Migrated" => "Boolean", // set to true when migrated
85
86
        "ExtraClass" => "Text", // from CustomSettings
87
        "RightTitle" => "Varchar(255)", // from CustomSettings
88
        "ShowOnLoad" => "Boolean(1)", // from CustomSettings
89
        "ShowInSummary" => "Boolean",
90
    );
91
92
    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...
93
        'ShowOnLoad' => true,
94
    );
95
96
97
    /**
98
     * @config
99
     * @var array
100
     */
101
    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...
102
        "Parent" => "UserDefinedForm",
103
    );
104
105
    /**
106
     * Built in extensions required
107
     *
108
     * @config
109
     * @var array
110
     */
111
    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...
112
        "Versioned('Stage', 'Live')"
113
    );
114
115
    /**
116
     * @config
117
     * @var array
118
     */
119
    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...
120
        "DisplayRules" => "EditableCustomRule.Parent" // from CustomRules
121
    );
122
123
    /**
124
     * @var bool
125
     */
126
    protected $readonly;
127
128
    /**
129
     * Set the visibility of an individual form field
130
     *
131
     * @param bool
132
     */
133 1
    public function setReadonly($readonly = true)
134
    {
135 1
        $this->readonly = $readonly;
136 1
    }
137
138
    /**
139
     * Returns whether this field is readonly
140
     *
141
     * @return bool
142
     */
143 1
    private function isReadonly()
144
    {
145 1
        return $this->readonly;
146
    }
147
148
    /**
149
     * @return FieldList
150
     */
151 10
    public function getCMSFields()
152
    {
153 10
        $fields = new FieldList(new TabSet('Root'));
154
155
        // Main tab
156
        $fields->addFieldsToTab(
157 1
            'Root.Main',
158
            array(
159
                ReadonlyField::create(
160 10
                    'Type',
161
                    _t('EditableFormField.TYPE', 'Type'),
162
                    $this->i18n_singular_name()
163
                ),
164 10
                CheckboxField::create('ShowInSummary', _t('EditableFormField.SHOWINSUMMARY', 'Show in summary gridfield')),
165
                LiteralField::create(
166
                    'MergeField',
167
                    _t(
168
                        'EditableFormField.MERGEFIELDNAME',
169
                        '<div class="field readonly">' .
170
                            '<label class="left">Merge field</label>' .
171
                            '<div class="middleColumn">' .
172
                                '<span class="readonly">$' . $this->Name . '</span>' .
173
                            '</div>' .
174
                        '</div>'
175
                    )
176
                ),
177
                TextField::create('Title'),
178
                TextField::create('Default', _t('EditableFormField.DEFAULT', 'Default value')),
179
                TextField::create('RightTitle', _t('EditableFormField.RIGHTTITLE', 'Right title')),
180
                SegmentField::create('Name')->setModifiers(array(
181
                    UnderscoreSegmentFieldModifier::create()->setDefault('FieldName'),
182
                    DisambiguationSegmentFieldModifier::create(),
183
                ))->setPreview($this->Name)
184
            )
185
        );
186
187
        // Custom settings
188
        if (!empty(self::$allowed_css)) {
189
            $cssList = array();
190
            foreach (self::$allowed_css as $k => $v) {
191
                if (!is_array($v)) {
192
                    $cssList[$k]=$v;
193
                } elseif ($k === $this->ClassName) {
194
                    $cssList = array_merge($cssList, $v);
195
                }
196
            }
197
198
            $fields->addFieldToTab('Root.Main',
199
                DropdownField::create(
200
                    'ExtraClass',
201
                    _t('EditableFormField.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
202
                    $cssList
203
                )->setDescription(_t(
204
                    'EditableFormField.EXTRACLASS_SELECT',
205
                    'Select from the list of allowed styles'
206
                ))
207
            );
208
        } else {
209
            $fields->addFieldToTab('Root.Main',
210
                TextField::create(
211
                    'ExtraClass',
212
                    _t('EditableFormField.EXTRACLASS_Title', 'Extra CSS classes')
213
                )->setDescription(_t(
214
                    'EditableFormField.EXTRACLASS_MULTIPLE',
215
                    'Separate each CSS class with a single space'
216
                ))
217
            );
218
        }
219
220
        // Validation
221
        $validationFields = $this->getFieldValidationOptions();
222
        if ($validationFields && $validationFields->count()) {
223
            $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...
224
        }
225
226
        // Add display rule fields
227
        $displayFields = $this->getDisplayRuleFields();
228
        if ($displayFields && $displayFields->count()) {
229
            $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...
230
        }
231
232
        $this->extend('updateCMSFields', $fields);
233
234
        return $fields;
235
    }
236
237
    /**
238
     * Return fields to display on the 'Display Rules' tab
239
     *
240
     * @return FieldList
241
     */
242
    protected function getDisplayRuleFields()
243
    {
244
        // Check display rules
245
        if ($this->Required) {
246
            return new FieldList(
247
                LabelField::create(_t(
248
                    'EditableFormField.DISPLAY_RULES_DISABLED',
249
                    'Display rules are not enabled for required fields. ' .
250
                    'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
251
                ))->addExtraClass('message warning')
252
            );
253
        }
254
        $self = $this;
255
        $allowedClasses = array_keys($this->getEditableFieldClasses(false));
256
        $editableColumns = new GridFieldEditableColumns();
257
        $editableColumns->setDisplayFields(array(
258
            'Display' => '',
259
            '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...
260
                return DropdownField::create(
261
                    $column,
262
                    '',
263
                    EditableFormField::get()
264
                        ->filter(array(
265
                            '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...
266
                            'ClassName' => $allowedClasses
267
                        ))
268
                        ->exclude(array(
269
                            'ID' => $self->ID
270
                        ))
271
                        ->map('ID', 'Title')
272
                    );
273
            },
274
            '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...
275
                $options = Config::inst()->get('EditableCustomRule', 'condition_options');
276
                return DropdownField::create($column, '', $options);
277
            },
278
            '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...
279
                return TextField::create($column);
280
            },
281
            '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...
282
                return HiddenField::create($column, '', $self->ID);
283
            }
284
        ));
285
286
        // Custom rules
287
        $customRulesConfig = GridFieldConfig::create()
288
            ->addComponents(
289
                $editableColumns,
290
                new GridFieldButtonRow(),
291
                new GridFieldToolbarHeader(),
292
                new GridFieldAddNewInlineButton(),
293
                new GridFieldDeleteAction()
294
            );
295
296
        return new FieldList(
297
            CheckboxField::create('ShowOnLoad')
298
                ->setDescription(_t(
299
                    'EditableFormField.SHOWONLOAD',
300
                    'Initial visibility before processing these rules'
301
                )),
302
            GridField::create(
303
                'DisplayRules',
304
                _t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
305
                $this->DisplayRules(),
306
                $customRulesConfig
307
            )
308
        );
309
    }
310
311 39
    public function onBeforeWrite()
312
    {
313 39
        parent::onBeforeWrite();
314
315
        // Set a field name.
316 39
        if (!$this->Name) {
317
            // New random name
318 25
            $this->Name = $this->generateName();
319 39
        } elseif ($this->Name === 'Field') {
320
            throw new ValidationException('Field name cannot be "Field"');
321
        }
322
323 39
        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...
324 39
            $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...
325 39
            $this->Sort = EditableFormField::get()
326 39
                ->filter('ParentID', $parentID)
327 39
                ->max('Sort') + 1;
328 39
        }
329 39
    }
330
331
    /**
332
     * Generate a new non-conflicting Name value
333
     *
334
     * @return string
335
     */
336 25
    protected function generateName()
337
    {
338
        do {
339
            // Generate a new random name after this class
340 25
            $class = get_class($this);
341 25
            $entropy = substr(sha1(uniqid()), 0, 5);
342 25
            $name = "{$class}_{$entropy}";
343
344
            // Check if it conflicts
345 25
            $exists = EditableFormField::get()->filter('Name', $name)->count() > 0;
346 25
        } while ($exists);
347 25
        return $name;
348
    }
349
350
    /**
351
     * Flag indicating that this field will set its own error message via data-msg='' attributes
352
     *
353
     * @return bool
354
     */
355
    public function getSetsOwnError()
356
    {
357
        return false;
358
    }
359
360
    /**
361
     * Return whether a user can delete this form field
362
     * based on whether they can edit the page
363
     *
364
     * @param Member $member
365
     * @return bool
366
     */
367 1
    public function canDelete($member = null)
368
    {
369 1
        return $this->canEdit($member);
370
    }
371
372
    /**
373
     * Return whether a user can edit this form field
374
     * based on whether they can edit the page
375
     *
376
     * @param Member $member
377
     * @return bool
378
     */
379 1
    public function canEdit($member = null)
380
    {
381 1
        $parent = $this->Parent();
382 1
        if ($parent && $parent->exists()) {
383 1
            return $parent->canEdit($member) && !$this->isReadonly();
384
        } elseif (!$this->exists() && Controller::has_curr()) {
385
            // This is for GridFieldOrderableRows support as it checks edit permissions on
386
            // singleton of the class. Allows editing of User Defined Form pages by
387
            // 'Content Authors' and those with permission to edit the UDF page. (ie. CanEditType/EditorGroups)
388
            // This is to restore User Forms 2.x backwards compatibility.
389
            $controller = Controller::curr();
390
            if ($controller && $controller instanceof CMSPageEditController) {
391
                $parent = $controller->getRecord($controller->currentPageID());
392
                // Only allow this behaviour on pages using UserFormFieldEditorExtension, such
393
                // as UserDefinedForm page type.
394
                if ($parent && $parent->hasExtension('UserFormFieldEditorExtension')) {
395
                    return $parent->canEdit($member);
396
                }
397
            }
398
        }
399
400
        // Fallback to secure admin permissions
401
        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 401 which is incompatible with the return type documented by EditableFormField::canEdit of type boolean.
Loading history...
402
    }
403
404
    /**
405
     * Return whether a user can view this form field
406
     * based on whether they can view the page, regardless of the ReadOnly status of the field
407
     *
408
     * @param Member $member
409
     * @return bool
410
     */
411 1
    public function canView($member = null)
412
    {
413 1
        $parent = $this->Parent();
414 1
        if ($parent && $parent->exists()) {
415 1
            return $parent->canView($member);
416
        }
417
418
        return true;
419
    }
420
421
    /**
422
     * Return whether a user can create an object of this type
423
     *
424
     * @param Member $member
425
     * @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...
426
     * @return bool
427
     */
428 3 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...
429
    {
430
        // Check parent page
431 3
        $parent = $this->getCanCreateContext(func_get_args());
432 3
        if ($parent) {
433
            return $parent->canEdit($member);
434
        }
435
436
        // Fall back to secure admin permissions
437 3
        return parent::canCreate($member);
438
    }
439
440
    /**
441
     * Helper method to check the parent for this object
442
     *
443
     * @param array $args List of arguments passed to canCreate
444
     * @return SiteTree Parent page instance
445
     */
446 3 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...
447
    {
448
        // Inspect second parameter to canCreate for a 'Parent' context
449 3
        if (isset($args[1]['Parent'])) {
450
            return $args[1]['Parent'];
451
        }
452
        // Hack in currently edited page if context is missing
453 3
        if (Controller::has_curr() && Controller::curr() instanceof CMSMain) {
454
            return Controller::curr()->currentPage();
455
        }
456
457
        // No page being edited
458 3
        return null;
459
    }
460
461
    /**
462
     * Check if can publish
463
     *
464
     * @param Member $member
465
     * @return bool
466
     */
467
    public function canPublish($member = null)
468
    {
469
        return $this->canEdit($member);
470
    }
471
472
    /**
473
     * Check if can unpublish
474
     *
475
     * @param Member $member
476
     * @return bool
477
     */
478
    public function canUnpublish($member = null)
479
    {
480
        return $this->canDelete($member);
481
    }
482
483
    /**
484
     * Publish this Form Field to the live site
485
     *
486
     * Wrapper for the {@link Versioned} publish function
487
     */
488 9
    public function doPublish($fromStage, $toStage, $createNewVersion = false)
489
    {
490 9
        $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...
491
492 9
		$seenIDs = array();
493
494
		// Don't forget to publish the related custom rules...
495 9
		foreach ($this->DisplayRules() as $rule) {
496 1
			$seenIDs[] = $rule->ID;
497 1
			$rule->doPublish($fromStage, $toStage, $createNewVersion);
498 1
			$rule->destroy();
499 9
		}
500
501
		// remove any orphans from the "fromStage"
502 9
        $rules = Versioned::get_by_stage('EditableCustomRule', $toStage)
503 9
            ->filter('ParentID', $this->ID);
504
505 9
		if (!empty($seenIDs)) {
506 1
            $rules = $rules->exclude('ID', $seenIDs);
507 1
        }
508
509 9
		foreach ($rules as $rule) {
510 1
		    $rule->deleteFromStage($toStage);
511 9
		}
512 9
	}
513
514
    /**
515
     * Delete this field from a given stage
516
     *
517
     * Wrapper for the {@link Versioned} deleteFromStage function
518
     */
519 1
    public function doDeleteFromStage($stage)
520
    {
521
        // Remove custom rules in this stage
522 1
        $rules = Versioned::get_by_stage('EditableCustomRule', $stage)
523 1
            ->filter('ParentID', $this->ID);
524 1
        foreach ($rules as $rule) {
525
            $rule->deleteFromStage($stage);
526 1
        }
527
528
        // Remove record
529 1
        $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...
530 1
    }
531
532
    /**
533
     * checks wether record is new, copied from Sitetree
534
     */
535
    public function isNew()
536
    {
537
        if (empty($this->ID)) {
538
            return true;
539
        }
540
541
        if (is_numeric($this->ID)) {
542
            return false;
543
        }
544
545
        return stripos($this->ID, 'new') === 0;
546
    }
547
548
    /**
549
     * checks if records is changed on stage
550
     * @return boolean
551
     */
552
    public function getIsModifiedOnStage()
553
    {
554
        // new unsaved fields could be never be published
555
        if ($this->isNew()) {
556
            return false;
557
        }
558
559
        $stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
560
        $liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
561
562
        return ($stageVersion && $stageVersion != $liveVersion);
563
    }
564
565
    /**
566
     * @deprecated since version 4.0
567
     */
568
    public function getSettings()
569
    {
570
        Deprecation::notice('4.0', 'getSettings is deprecated');
571
        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...
572
    }
573
574
    /**
575
     * @deprecated since version 4.0
576
     */
577
    public function setSettings($settings = array())
578
    {
579
        Deprecation::notice('4.0', 'setSettings is deprecated');
580
        $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...
581
    }
582
583
    /**
584
     * @deprecated since version 4.0
585
     */
586
    public function setSetting($key, $value)
587
    {
588
        Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
589
        $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...
590
        $settings[$key] = $value;
591
592
        $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...
593
    }
594
595
    /**
596
     * Set the allowed css classes for the extraClass custom setting
597
     *
598
     * @param array The permissible CSS classes to add
599
     */
600
    public function setAllowedCss(array $allowed)
601
    {
602
        if (is_array($allowed)) {
603
            foreach ($allowed as $k => $v) {
604
                self::$allowed_css[$k] = (!is_null($v)) ? $v : $k;
605
            }
606
        }
607
    }
608
609
    /**
610
     * @deprecated since version 4.0
611
     */
612
    public function getSetting($setting)
613
    {
614
        Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
615
616
        $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...
617
        if (isset($settings) && count($settings) > 0) {
618
            if (isset($settings[$setting])) {
619
                return $settings[$setting];
620
            }
621
        }
622
        return '';
623
    }
624
625
    /**
626
     * Get the path to the icon for this field type, relative to the site root.
627
     *
628
     * @return string
629
     */
630
    public function getIcon()
631
    {
632
        return USERFORMS_DIR . '/images/' . strtolower($this->class) . '.png';
633
    }
634
635
    /**
636
     * Return whether or not this field has addable options
637
     * such as a dropdown field or radio set
638
     *
639
     * @return bool
640
     */
641
    public function getHasAddableOptions()
642
    {
643
        return false;
644
    }
645
646
    /**
647
     * Return whether or not this field needs to show the extra
648
     * options dropdown list
649
     *
650
     * @return bool
651
     */
652
    public function showExtraOptions()
653
    {
654
        return true;
655
    }
656
657
    /**
658
     * Returns the Title for rendering in the front-end (with XML values escaped)
659
     *
660
     * @return string
661
     */
662 18
    public function getEscapedTitle()
663
    {
664 18
        return Convert::raw2xml($this->Title);
665
    }
666
667
    /**
668
     * Find the numeric indicator (1.1.2) that represents it's nesting value
669
     *
670
     * Only useful for fields attached to a current page, and that contain other fields such as pages
671
     * or groups
672
     *
673
     * @return string
674
     */
675
    public function getFieldNumber()
676
    {
677
        // Check if exists
678
        if (!$this->exists()) {
679
            return null;
680
        }
681
        // Check parent
682
        $form = $this->Parent();
683
        if (!$form || !$form->exists() || !($fields = $form->Fields())) {
684
            return null;
685
        }
686
687
        $prior = 0; // Number of prior group at this level
688
        $stack = array(); // Current stack of nested groups, where the top level = the page
689
        foreach ($fields->map('ID', 'ClassName') as $id => $className) {
690
            if ($className === 'EditableFormStep') {
691
                $priorPage = empty($stack) ? $prior : $stack[0];
692
                $stack = array($priorPage + 1);
693
                $prior = 0;
694
            } elseif ($className === 'EditableFieldGroup') {
695
                $stack[] = $prior + 1;
696
                $prior = 0;
697
            } elseif ($className === 'EditableFieldGroupEnd') {
698
                $prior = array_pop($stack);
699
            }
700
            if ($id == $this->ID) {
701
                return implode('.', $stack);
702
            }
703
        }
704
        return null;
705
    }
706
707
    public function getCMSTitle()
708
    {
709
        return $this->i18n_singular_name() . ' (' . $this->Title . ')';
710
    }
711
712
    /**
713
     * @deprecated since version 4.0
714
     */
715
    public function getFieldName($field = false)
716
    {
717
        Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
718
        return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
719
    }
720
721
    /**
722
     * @deprecated since version 4.0
723
     */
724
    public function getSettingName($field)
725
    {
726
        Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
727
        $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...
728
729
        return $name . '[' . $field .']';
730
    }
731
732
    /**
733
     * Append custom validation fields to the default 'Validation'
734
     * section in the editable options view
735
     *
736
     * @return FieldList
737
     */
738
    public function getFieldValidationOptions()
739
    {
740
        $fields = new FieldList(
741
            CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?'))
742
                ->setDescription(_t('EditableFormField.REQUIRED_DESCRIPTION', 'Please note that conditional fields can\'t be required')),
743
            TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR', 'Custom Error Message'))
744
        );
745
746
        $this->extend('updateFieldValidationOptions', $fields);
747
748
        return $fields;
749
    }
750
751
    /**
752
     * Return a FormField to appear on the front end. Implement on
753
     * your subclass.
754
     *
755
     * @return FormField
756
     */
757
    public function getFormField()
758
    {
759
        user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
760
    }
761
762
    /**
763
     * Updates a formfield with extensions
764
     *
765
     * @param FormField $field
766
     */
767 17
    public function doUpdateFormField($field)
768
    {
769 17
        $this->extend('beforeUpdateFormField', $field);
770 17
        $this->updateFormField($field);
771 17
        $this->extend('afterUpdateFormField', $field);
772 17
    }
773
774
    /**
775
     * Updates a formfield with the additional metadata specified by this field
776
     *
777
     * @param FormField $field
778
     */
779 17
    protected function updateFormField($field)
780
    {
781
        // set the error / formatting messages
782 17
        $field->setCustomValidationMessage($this->getErrorMessage()->RAW());
783
784
        // set the right title on this field
785 17
        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...
786
            // Since this field expects raw html, safely escape the user data prior
787 1
            $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...
788 1
        }
789
790
        // if this field is required add some
791 17
        if ($this->Required) {
792
            // Required validation can conflict so add the Required validation messages as input attributes
793 3
            $errorMessage = $this->getErrorMessage()->HTML();
794 3
            $field->addExtraClass('requiredField');
795 3
            $field->setAttribute('data-rule-required', 'true');
796 3
            $field->setAttribute('data-msg-required', $errorMessage);
0 ignored issues
show
Bug introduced by
It seems like $errorMessage defined by $this->getErrorMessage()->HTML() on line 793 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...
797
798 3
            if ($identifier = UserDefinedForm::config()->required_identifier) {
799 1
                $title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
800 1
                $field->setTitle($title);
801 1
            }
802 3
        }
803
804
        // if this field has an extra class
805 17
        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...
806
            $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...
807
        }
808 17
    }
809
810
    /**
811
     * Return the instance of the submission field class
812
     *
813
     * @return SubmittedFormField
814
     */
815 2
    public function getSubmittedFormField()
816
    {
817 2
        return new SubmittedFormField();
818
    }
819
820
821
    /**
822
     * Show this form field (and its related value) in the reports and in emails.
823
     *
824
     * @return bool
825
     */
826 2
    public function showInReports()
827
    {
828 2
        return true;
829
    }
830
831
    /**
832
     * Return the error message for this field. Either uses the custom
833
     * one (if provided) or the default SilverStripe message
834
     *
835
     * @return Varchar
836
     */
837 17
    public function getErrorMessage()
838
    {
839 17
        $title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
840 17
        $standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
841
842
        // only use CustomErrorMessage if it has a non empty value
843 17
        $errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
844
845 17
        return DBField::create_field('Varchar', $errorMessage);
846
    }
847
848
    /**
849
     * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
850
     * to the field proper
851
     *
852
     * @param array $data Unserialised data
853
     */
854 2
    public function migrateSettings($data)
855
    {
856
        // Map 'Show' / 'Hide' to boolean
857 2
        if (isset($data['ShowOnLoad'])) {
858 2
            $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...
859 2
            unset($data['ShowOnLoad']);
860 2
        }
861
862
        // Migrate all other settings
863 2
        foreach ($data as $key => $value) {
864 2
            if ($this->hasField($key)) {
865 2
                $this->setField($key, $value);
866 2
            }
867 2
        }
868 2
    }
869
870
    /**
871
     * Get the formfield to use when editing this inline in gridfield
872
     *
873
     * @param string $column name of column
874
     * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
875
     * @return FormField
876
     */
877
    public function getInlineClassnameField($column, $fieldClasses)
878
    {
879
        return DropdownField::create($column, false, $fieldClasses);
880
    }
881
882
    /**
883
     * Get the formfield to use when editing the title inline
884
     *
885
     * @param string $column
886
     * @return FormField
887
     */
888
    public function getInlineTitleField($column)
889
    {
890
        return TextField::create($column, false)
891
            ->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
892
            ->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
893
    }
894
895
    /**
896
     * Get the JS expression for selecting the holder for this field
897
     *
898
     * @return string
899
     */
900 8
    public function getSelectorHolder()
901
    {
902 8
        return "$(\"#{$this->Name}\")";
903
    }
904
905
    /**
906
     * Gets the JS expression for selecting the value for this field
907
     *
908
     * @param EditableCustomRule $rule Custom rule this selector will be used with
909
     * @param bool $forOnLoad Set to true if this will be invoked on load
910
     */
911
    public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
912
    {
913
        return "$(\"input[name='{$this->Name}']\")";
914
    }
915
916
917
    /**
918
     * Get the list of classes that can be selected and used as data-values
919
     *
920
     * @param $includeLiterals Set to false to exclude non-data fields
921
     * @return array
922
     */
923 2
    public function getEditableFieldClasses($includeLiterals = true)
924
    {
925 2
        $classes = ClassInfo::getValidSubClasses('EditableFormField');
926
927
        // Remove classes we don't want to display in the dropdown.
928 2
        $editableFieldClasses = array();
929 2
        foreach ($classes as $class) {
930
            // Skip abstract / hidden classes
931 2
            if (Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
932 2
            ) {
933 2
                continue;
934
            }
935
936 2
            if (!$includeLiterals && Config::inst()->get($class, 'literal')) {
937
                continue;
938
            }
939
940 2
            $singleton = singleton($class);
941 2
            if (!$singleton->canCreate()) {
942
                continue;
943
            }
944
945 2
            $editableFieldClasses[$class] = $singleton->i18n_singular_name();
946 2
        }
947
948 2
        asort($editableFieldClasses);
949 2
        return $editableFieldClasses;
950
    }
951
952
    /**
953
     * @return EditableFormFieldValidator
954
     */
955
    public function getCMSValidator()
956
    {
957
        return EditableFormFieldValidator::create()
958
            ->setRecord($this);
959
    }
960
961
    /**
962
     * Determine effective display rules for this field.
963
     *
964
     * @return SS_List
965
     */
966 10
    public function EffectiveDisplayRules()
967
    {
968 10
        if ($this->Required) {
969 4
            return new ArrayList();
970
        }
971 10
        return $this->DisplayRules();
972
    }
973
}
974