Completed
Push — master ( 2357ce...f9bf40 )
by Robbie
43:41 queued 08:38
created

EditableFormField::updateFormField()   C

Complexity

Conditions 7
Paths 48

Size

Total Lines 40
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.2944

Importance

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