Completed
Pull Request — master (#593)
by
unknown
19:00
created

EditableFormField::getSelectorHolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
901
            $field->setAttribute('placeholder', $this->Placeholder);
0 ignored issues
show
Bug introduced by
The property Placeholder does not seem to exist. Did you mean has_placeholder?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
902
        }
903 17
    }
904
905
    /**
906
     * Return the instance of the submission field class
907
     *
908
     * @return SubmittedFormField
909
     */
910 2
    public function getSubmittedFormField()
911
    {
912 2
        return new SubmittedFormField();
913
    }
914
915
916
    /**
917
     * Show this form field (and its related value) in the reports and in emails.
918
     *
919
     * @return bool
920
     */
921 2
    public function showInReports()
922
    {
923 2
        return true;
924
    }
925
926
    /**
927
     * Return the error message for this field. Either uses the custom
928
     * one (if provided) or the default SilverStripe message
929
     *
930
     * @return Varchar
931
     */
932 17
    public function getErrorMessage()
933
    {
934 17
        $title = strip_tags("'". ($this->Title ? $this->Title : $this->Name) . "'");
935 17
        $standard = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
936
937
        // only use CustomErrorMessage if it has a non empty value
938 17
        $errorMessage = (!empty($this->CustomErrorMessage)) ? $this->CustomErrorMessage : $standard;
939
940
        /** @var Varchar $field */
941 17
        $field = DBField::create_field('Varchar', $errorMessage);
942
943 17
        return $field;
944
    }
945
946
    /**
947
     * Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
948
     * to the field proper
949
     *
950
     * @param array $data Unserialised data
951
     */
952 2
    public function migrateSettings($data)
953
    {
954
        // Map 'Show' / 'Hide' to boolean
955 2
        if (isset($data['ShowOnLoad'])) {
956 2
            $this->ShowOnLoad = $data['ShowOnLoad'] === '' || ($data['ShowOnLoad'] && $data['ShowOnLoad'] !== 'Hide');
957 2
            unset($data['ShowOnLoad']);
958 2
        }
959
960
        // Migrate all other settings
961 2
        foreach ($data as $key => $value) {
962 2
            if ($this->hasField($key)) {
963 2
                $this->setField($key, $value);
964 2
            }
965 2
        }
966 2
    }
967
968
    /**
969
     * Get the formfield to use when editing this inline in gridfield
970
     *
971
     * @param string $column name of column
972
     * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class
973
     * @return FormField
974
     */
975
    public function getInlineClassnameField($column, $fieldClasses)
976
    {
977
        return DropdownField::create($column, false, $fieldClasses);
978
    }
979
980
    /**
981
     * Get the formfield to use when editing the title inline
982
     *
983
     * @param string $column
984
     * @return FormField
985
     */
986
    public function getInlineTitleField($column)
987
    {
988
        return TextField::create($column, false)
989
            ->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
990
            ->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
991
    }
992
993
    /**
994
     * Get the JS expression for selecting the holder for this field
995
     *
996
     * @return string
997
     */
998
    public function getSelectorHolder()
999
    {
1000
        return sprintf('$("%s")', $this->getSelectorOnly());
1001
    }
1002
1003
    /**
1004
     * Returns only the JS identifier of a string, less the $(), which can be inserted elsewhere, for example when you
1005
     * want to perform selections on multiple selectors
1006
     * @return string
1007
     */
1008 9
    public function getSelectorOnly()
1009
    {
1010 9
        return "#{$this->Name}";
1011
    }
1012
1013
    /**
1014
     * Gets the JS expression for selecting the value for this field
1015
     *
1016
     * @param EditableCustomRule $rule Custom rule this selector will be used with
1017
     * @param bool $forOnLoad Set to true if this will be invoked on load
1018
     *
1019
     * @return string
1020
     */
1021
    public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
1022
    {
1023
        return sprintf("$(%s)", $this->getSelectorFieldOnly());
1024
    }
1025
1026
    /**
1027
     * @return string
1028
     */
1029 2
    public function getSelectorFieldOnly()
1030
    {
1031 2
        return "[name='{$this->Name}']";
1032
    }
1033
1034
1035
    /**
1036
     * Get the list of classes that can be selected and used as data-values
1037
     *
1038
     * @param bool $includeLiterals Set to false to exclude non-data fields
1039
     * @return array
1040
     */
1041 3
    public function getEditableFieldClasses($includeLiterals = true)
1042
    {
1043 2
        $classes = ClassInfo::getValidSubClasses('EditableFormField');
1044
1045
        // Remove classes we don't want to display in the dropdown.
1046 2
        $editableFieldClasses = array();
1047 2
        foreach ($classes as $class) {
1048
            // Skip abstract / hidden classes
1049 2
            if (Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
1050 2
            ) {
1051 2
                continue;
1052
            }
1053
1054 2
            if (!$includeLiterals && Config::inst()->get($class, 'literal')) {
1055
                continue;
1056
            }
1057
1058 2
            $singleton = singleton($class);
1059 2
            if (!$singleton->canCreate()) {
1060
                continue;
1061
            }
1062
1063 2
            $editableFieldClasses[$class] = $singleton->i18n_singular_name();
1064 3
        }
1065
1066 2
        asort($editableFieldClasses);
1067 2
        return $editableFieldClasses;
1068 1
    }
1069
1070
    /**
1071
     * @return EditableFormFieldValidator
1072
     */
1073
    public function getCMSValidator()
1074
    {
1075
        return EditableFormFieldValidator::create()
1076
            ->setRecord($this);
1077
    }
1078
1079
    /**
1080
     * Determine effective display rules for this field.
1081
     *
1082
     * @return SS_List
1083
     */
1084 11
    public function EffectiveDisplayRules()
1085
    {
1086 11
        if ($this->Required) {
1087 4
            return new ArrayList();
1088
        }
1089 11
        return $this->DisplayRules();
1090
    }
1091
1092
    /**
1093
     * Extracts info from DisplayRules into array so UserDefinedForm->buildWatchJS can run through it.
1094
     * @return array|null
1095
     */
1096 9
    public function formatDisplayRules()
1097
    {
1098 9
        $holderSelector = $this->getSelectorOnly();
1099
        $result = array(
1100 9
            'targetFieldID' => $holderSelector,
1101 9
            'conjunction'   => $this->DisplayRulesConjunctionNice(),
1102 9
            'selectors'     => array(),
1103 9
            'events'        => array(),
1104 9
            'operations'    => array(),
1105 9
            'initialState'  => $this->ShowOnLoadNice(),
1106 9
            'view'          => array(),
1107 9
            'opposite'      => array(),
1108 9
        );
1109
        // Check for field dependencies / default
1110
        /** @var EditableCustomRule $rule */
1111 9
        foreach ($this->EffectiveDisplayRules() as $rule) {
1112
            // Get the field which is effected
1113
            /** @var EditableFormField $formFieldWatch */
1114 1
            $formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
1115
            // Skip deleted fields
1116 1
            if (! $formFieldWatch) {
1117
                continue;
1118
            }
1119 1
            $fieldToWatch = $formFieldWatch->getSelectorFieldOnly();
1120
1121 1
            $expression = $rule->buildExpression();
1122 1
            if (! in_array($fieldToWatch, $result['selectors'])) {
1123 1
                $result['selectors'][] = $fieldToWatch;
1124 1
            }
1125 1
            if (! in_array($expression['event'], $result['events'])) {
1126 1
                $result['events'][] = $expression['event'];
1127 1
            }
1128 1
            $result['operations'][] = $expression['operation'];
1129
1130
            //View/Show should read
1131 1
            $result['view'] = $rule->toggleDisplayText($result['initialState']);
1132 1
            $result['opposite'] = $rule->toggleDisplayText($result['view']);
1133 9
        }
1134
1135 9
        return (count($result['selectors'])) ? $result : null;
1136
    }
1137
1138
    /**
1139
     * Replaces the set DisplayRulesConjunction with their JS logical operators
1140
     * @return string
1141
     */
1142 9
    public function DisplayRulesConjunctionNice()
1143
    {
1144 9
        return (strtolower($this->DisplayRulesConjunction) === 'or') ? '||' : '&&';
1145
    }
1146
1147
    /**
1148
     * Replaces boolean ShowOnLoad with its JS string equivalent
1149
     * @return string
1150
     */
1151 9
    public function ShowOnLoadNice()
1152
    {
1153 9
        return ($this->ShowOnLoad) ? 'show' : 'hide';
1154
    }
1155
1156
    /**
1157
     * Returns whether this is of type EditableCheckBoxField
1158
     * @return bool
1159
     */
1160 2
    public function isCheckBoxField()
1161
    {
1162 2
        return false;
1163
    }
1164
1165
    /**
1166
     * Returns whether this is of type EditableRadioField
1167
     * @return bool
1168
     */
1169 2
    public function isRadioField()
1170
    {
1171 2
        return false;
1172
    }
1173
1174
    /**
1175
     * Determined is this is of type EditableCheckboxGroupField
1176
     * @return bool
1177
     */
1178
    public function isCheckBoxGroupField()
1179
    {
1180
        return false;
1181
    }
1182
}
1183