Completed
Pull Request — master (#539)
by
unknown
23:38
created

EditableFormField::getCanCreateContext()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 13
Ratio 92.86 %

Code Coverage

Tests 4
CRAP Score 4.5923

Importance

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