Completed
Push — master ( ebbf9a...c78942 )
by Damian
35:58
created

EditableFormField::formatDisplayRules()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 6

Importance

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