Passed
Push — 4 ( cd0765...fc349d )
by
unknown
08:35
created

FormField::hasExtraClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms;
4
5
use LogicException;
6
use ReflectionClass;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Control\RequestHandler;
9
use SilverStripe\Core\ClassInfo;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataObjectInterface;
13
use SilverStripe\ORM\FieldType\DBField;
14
use SilverStripe\ORM\FieldType\DBHTMLText;
15
use SilverStripe\ORM\ValidationResult;
16
use SilverStripe\View\SSViewer;
17
18
/**
19
 * Represents a field in a form.
20
 *
21
 * A FieldList contains a number of FormField objects which make up the whole of a form.
22
 *
23
 * In addition to single fields, FormField objects can be "composite", for example, the
24
 * {@link TabSet} field. Composite fields let us define complex forms without having to resort to
25
 * custom HTML.
26
 *
27
 * To subclass:
28
 *
29
 * Define a {@link dataValue()} method that returns a value suitable for inserting into a single
30
 * database field.
31
 *
32
 * For example, you might tidy up the format of a date or currency field. Define {@link saveInto()}
33
 * to totally customise saving.
34
 *
35
 * For example, data might be saved to the filesystem instead of the data record, or saved to a
36
 * component of the data record instead of the data record itself.
37
 *
38
 * A form field can be represented as structured data through {@link FormSchema},
39
 * including both structure (name, id, attributes, etc.) and state (field value).
40
 * Can be used by for JSON data which is consumed by a front-end application.
41
 */
42
class FormField extends RequestHandler
43
{
44
    use FormMessage;
45
46
    /** @see $schemaDataType */
47
    const SCHEMA_DATA_TYPE_STRING = 'String';
48
49
    /** @see $schemaDataType */
50
    const SCHEMA_DATA_TYPE_HIDDEN = 'Hidden';
51
52
    /** @see $schemaDataType */
53
    const SCHEMA_DATA_TYPE_TEXT = 'Text';
54
55
    /** @see $schemaDataType */
56
    const SCHEMA_DATA_TYPE_HTML = 'HTML';
57
58
    /** @see $schemaDataType */
59
    const SCHEMA_DATA_TYPE_INTEGER = 'Integer';
60
61
    /** @see $schemaDataType */
62
    const SCHEMA_DATA_TYPE_DECIMAL = 'Decimal';
63
64
    /** @see $schemaDataType */
65
    const SCHEMA_DATA_TYPE_MULTISELECT = 'MultiSelect';
66
67
    /** @see $schemaDataType */
68
    const SCHEMA_DATA_TYPE_SINGLESELECT = 'SingleSelect';
69
70
    /** @see $schemaDataType */
71
    const SCHEMA_DATA_TYPE_DATE = 'Date';
72
73
    /** @see $schemaDataType */
74
    const SCHEMA_DATA_TYPE_DATETIME = 'Datetime';
75
76
    /** @see $schemaDataType */
77
    const SCHEMA_DATA_TYPE_TIME = 'Time';
78
79
    /** @see $schemaDataType */
80
    const SCHEMA_DATA_TYPE_BOOLEAN = 'Boolean';
81
82
    /** @see $schemaDataType */
83
    const SCHEMA_DATA_TYPE_CUSTOM = 'Custom';
84
85
    /** @see $schemaDataType */
86
    const SCHEMA_DATA_TYPE_STRUCTURAL = 'Structural';
87
88
    /**
89
     * @var Form
90
     */
91
    protected $form;
92
93
    /**
94
     * This is INPUT's type attribute value.
95
     *
96
     * @var string
97
     */
98
    protected $inputType = 'text';
99
100
    /**
101
     * @var string
102
     */
103
    protected $name;
104
105
    /**
106
     * @var null|string
107
     */
108
    protected $title;
109
110
    /**
111
     * @var mixed
112
     */
113
    protected $value;
114
115
    /**
116
     * @var string
117
     */
118
    protected $extraClass;
119
120
    /**
121
     * Adds a title attribute to the markup.
122
     *
123
     * @var string
124
     *
125
     * @todo Implement in all subclasses
126
     */
127
    protected $description;
128
129
    /**
130
     * Extra CSS classes for the FormField container.
131
     *
132
     * @var array
133
     */
134
    protected $extraClasses;
135
136
    /**
137
     * @config
138
     * @var array $default_classes The default classes to apply to the FormField
139
     */
140
    private static $default_classes = [];
0 ignored issues
show
introduced by
The private property $default_classes is not used, and could be removed.
Loading history...
141
142
    /**
143
     * Right-aligned, contextual label for the field.
144
     *
145
     * @var string
146
     */
147
    protected $rightTitle;
148
149
    /**
150
     * Left-aligned, contextual label for the field.
151
     *
152
     * @var string
153
     */
154
    protected $leftTitle;
155
156
    /**
157
     * Stores a reference to the FieldList that contains this object.
158
     *
159
     * @var FieldList
160
     */
161
    protected $containerFieldList;
162
163
    /**
164
     * @var bool
165
     */
166
    protected $readonly = false;
167
168
    /**
169
     * @var bool
170
     */
171
    protected $disabled = false;
172
173
    /**
174
     * @var bool
175
     */
176
    protected $autofocus = false;
177
178
    /**
179
     * Custom validation message for the field.
180
     *
181
     * @var string
182
     */
183
    protected $customValidationMessage = '';
184
185
    /**
186
     * @var Tip|null
187
     */
188
    private $titleTip;
189
190
    /**
191
     * Name of the template used to render this form field. If not set, then will look up the class
192
     * ancestry for the first matching template where the template name equals the class name.
193
     *
194
     * To explicitly use a custom template or one named other than the form field see
195
     * {@link setTemplate()}.
196
     *
197
     * @var string
198
     */
199
    protected $template;
200
201
    /**
202
     * Name of the template used to render this form field. If not set, then will look up the class
203
     * ancestry for the first matching template where the template name equals the class name.
204
     *
205
     * To explicitly use a custom template or one named other than the form field see
206
     * {@link setFieldHolderTemplate()}.
207
     *
208
     * @var string
209
     */
210
    protected $fieldHolderTemplate;
211
212
    /**
213
     * @var string
214
     */
215
    protected $smallFieldHolderTemplate;
216
217
    /**
218
     * All attributes on the form field (not the field holder).
219
     *
220
     * Partially determined based on other instance properties.
221
     *
222
     * @see getAttributes()
223
     *
224
     * @var array
225
     */
226
    protected $attributes = [];
227
228
    /**
229
     * The data type backing the field. Represents the type of value the
230
     * form expects to receive via a postback. Should be set in subclasses.
231
     *
232
     * The values allowed in this list include:
233
     *
234
     *   - String: Single line text
235
     *   - Hidden: Hidden field which is posted back without modification
236
     *   - Text: Multi line text
237
     *   - HTML: Rich html text
238
     *   - Integer: Whole number value
239
     *   - Decimal: Decimal value
240
     *   - MultiSelect: Select many from source
241
     *   - SingleSelect: Select one from source
242
     *   - Date: Date only
243
     *   - DateTime: Date and time
244
     *   - Time: Time only
245
     *   - Boolean: Yes or no
246
     *   - Custom: Custom type declared by the front-end component. For fields with this type,
247
     *     the component property is mandatory, and will determine the posted value for this field.
248
     *   - Structural: Represents a field that is NOT posted back. This may contain other fields,
249
     *     or simply be a block of stand-alone content. As with 'Custom',
250
     *     the component property is mandatory if this is assigned.
251
     *
252
     * Each value has an equivalent constant, e.g. {@link self::SCHEMA_DATA_TYPE_STRING}.
253
     *
254
     * @var string
255
     */
256
    protected $schemaDataType;
257
258
    /**
259
     * The type of front-end component to render the FormField as.
260
     *
261
     * @skipUpgrade
262
     * @var string
263
     */
264
    protected $schemaComponent;
265
266
    /**
267
     * Structured schema data representing the FormField.
268
     * Used to render the FormField as a ReactJS Component on the front-end.
269
     *
270
     * @var array
271
     */
272
    protected $schemaData = [];
273
274
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
275
        'FieldHolder' => 'HTMLFragment',
276
        'SmallFieldHolder' => 'HTMLFragment',
277
        'Field' => 'HTMLFragment',
278
        'AttributesHTML' => 'HTMLFragment', // property $AttributesHTML version
279
        'getAttributesHTML' => 'HTMLFragment', // method $getAttributesHTML($arg) version
280
        'Value' => 'Text',
281
        'extraClass' => 'Text',
282
        'ID' => 'Text',
283
        'isReadOnly' => 'Boolean',
284
        'HolderID' => 'Text',
285
        'Title' => 'Text',
286
        'RightTitle' => 'Text',
287
        'Description' => 'HTMLFragment',
288
    ];
289
290
    /**
291
     * Structured schema state representing the FormField's current data and validation.
292
     * Used to render the FormField as a ReactJS Component on the front-end.
293
     *
294
     * @var array
295
     */
296
    protected $schemaState = [];
297
298
    /**
299
     * Takes a field name and converts camelcase to spaced words. Also resolves combined field
300
     * names with dot syntax to spaced words.
301
     *
302
     * Examples:
303
     *
304
     * - 'TotalAmount' will return 'Total amount'
305
     * - 'Organisation.ZipCode' will return 'Organisation zip code'
306
     *
307
     * @param string $fieldName
308
     *
309
     * @return string
310
     */
311
    public static function name_to_label($fieldName)
312
    {
313
        // Handle dot delimiters
314
        if (strpos($fieldName, '.') !== false) {
315
            $parts = explode('.', $fieldName);
316
            // Ensure that any letter following a dot is uppercased, so that the regex below can break it up
317
            // into words
318
            $label = implode(array_map('ucfirst', $parts));
319
        } else {
320
            $label = $fieldName;
321
        }
322
323
        // Replace any capital letter that is followed by a lowercase letter with a space, the lowercased
324
        // version of itself then the remaining lowercase letters.
325
        $labelWithSpaces = preg_replace_callback('/([A-Z])([a-z]+)/', function ($matches) {
326
            return ' ' . strtolower($matches[1]) . $matches[2];
327
        }, $label);
328
329
        // Add a space before any capital letter block that is at the end of the string
330
        $labelWithSpaces = preg_replace('/([a-z])([A-Z]+)$/', '$1 $2', $labelWithSpaces);
331
332
        // The first letter should be uppercase
333
        return ucfirst(trim($labelWithSpaces));
334
    }
335
336
    /**
337
     * Creates a new field.
338
     *
339
     * @param string $name The internal field name, passed to forms.
340
     * @param null|string|\SilverStripe\View\ViewableData $title The human-readable field label.
341
     * @param mixed $value The value of the field.
342
     */
343
    public function __construct($name, $title = null, $value = null)
344
    {
345
        $this->setName($name);
346
347
        if ($title === null) {
348
            $this->title = self::name_to_label($name);
349
        } else {
350
            $this->title = $title;
351
        }
352
353
        if ($value !== null) {
354
            $this->setValue($value);
355
        }
356
357
        parent::__construct();
358
359
        $this->setupDefaultClasses();
360
    }
361
362
    /**
363
     * Set up the default classes for the form. This is done on construct so that the default classes can be removed
364
     * after instantiation
365
     */
366
    protected function setupDefaultClasses()
367
    {
368
        $defaultClasses = $this->config()->get('default_classes');
369
        if ($defaultClasses) {
370
            foreach ($defaultClasses as $class) {
371
                $this->addExtraClass($class);
372
            }
373
        }
374
    }
375
376
    /**
377
     * Return a link to this field
378
     *
379
     * @param string $action
380
     * @return string
381
     * @throws LogicException If no form is set yet
382
     */
383
    public function Link($action = null)
384
    {
385
        if (!$this->form) {
386
            throw new LogicException(
387
                'Field must be associated with a form to call Link(). Please use $field->setForm($form);'
388
            );
389
        }
390
391
        $link = Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action);
392
        $this->extend('updateLink', $link, $action);
393
        return $link;
394
    }
395
396
    /**
397
     * Returns the HTML ID of the field.
398
     *
399
     * The ID is generated as FormName_FieldName. All Field functions should ensure that this ID is
400
     * included in the field.
401
     *
402
     * @return string
403
     */
404
    public function ID()
405
    {
406
        return $this->getTemplateHelper()->generateFieldID($this);
407
    }
408
409
    /**
410
     * Returns the HTML ID for the form field holder element.
411
     *
412
     * @return string
413
     */
414
    public function HolderID()
415
    {
416
        return $this->getTemplateHelper()->generateFieldHolderID($this);
417
    }
418
419
    /**
420
     * Returns the current {@link FormTemplateHelper} on either the parent
421
     * Form or the global helper set through the {@link Injector} layout.
422
     *
423
     * To customize a single {@link FormField}, use {@link setTemplate} and
424
     * provide a custom template name.
425
     *
426
     * @return FormTemplateHelper
427
     */
428
    public function getTemplateHelper()
429
    {
430
        if ($this->form) {
431
            return $this->form->getTemplateHelper();
432
        }
433
434
        return FormTemplateHelper::singleton();
435
    }
436
437
    /**
438
     * Returns the field name.
439
     *
440
     * @return string
441
     */
442
    public function getName()
443
    {
444
        return $this->name;
445
    }
446
447
    /**
448
     * Returns the field input name.
449
     *
450
     * @return string
451
     */
452
    public function getInputType()
453
    {
454
        return $this->inputType;
455
    }
456
457
    /**
458
     * Returns the field value.
459
     *
460
     * @see FormField::setSubmittedValue()
461
     * @return mixed
462
     */
463
    public function Value()
464
    {
465
        return $this->value;
466
    }
467
468
    /**
469
     * Method to save this form field into the given {@link DataObject}.
470
     *
471
     * By default, makes use of $this->dataValue()
472
     *
473
     * @param DataObject|DataObjectInterface $record DataObject to save data into
474
     */
475
    public function saveInto(DataObjectInterface $record)
476
    {
477
        $component = $record;
478
        $fieldName = $this->name;
479
480
        // Allow for dot syntax
481
        if (($pos = strrpos($this->name, '.')) !== false) {
482
            $relation = substr($this->name, 0, $pos);
483
            $fieldName = substr($this->name, $pos + 1);
484
            $component = $record->relObject($relation);
0 ignored issues
show
Bug introduced by
The method relObject() does not exist on SilverStripe\ORM\DataObjectInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SilverStripe\ORM\DataObjectInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

484
            /** @scrutinizer ignore-call */ 
485
            $component = $record->relObject($relation);
Loading history...
485
        }
486
487
        if ($fieldName) {
488
            $component->setCastedField($fieldName, $this->dataValue());
489
        }
490
    }
491
492
    /**
493
     * Returns the field value suitable for insertion into the data object.
494
     * @see Formfield::setValue()
495
     * @return mixed
496
     */
497
    public function dataValue()
498
    {
499
        return $this->value;
500
    }
501
502
    /**
503
     * Returns the field label - used by templates.
504
     *
505
     * @return string
506
     */
507
    public function Title()
508
    {
509
        return $this->title;
510
    }
511
512
    /**
513
     * Set the title of this formfield.
514
     * Note: This expects escaped HTML.
515
     *
516
     * @param string $title Escaped HTML for title
517
     * @return $this
518
     */
519
    public function setTitle($title)
520
    {
521
        $this->title = $title;
522
        return $this;
523
    }
524
525
    /**
526
     * Gets the contextual label than can be used for additional field description.
527
     * Can be shown to the right or under the field in question.
528
     *
529
     * @return string Contextual label text
530
     */
531
    public function RightTitle()
532
    {
533
        return $this->rightTitle;
534
    }
535
536
    /**
537
     * Sets the right title for this formfield
538
     *
539
     * @param string|DBField $rightTitle Plain text string, or a DBField with appropriately escaped HTML
540
     * @return $this
541
     */
542
    public function setRightTitle($rightTitle)
543
    {
544
        $this->rightTitle = $rightTitle;
545
        return $this;
546
    }
547
548
    /**
549
     * @return string
550
     */
551
    public function LeftTitle()
552
    {
553
        return $this->leftTitle;
554
    }
555
556
    /**
557
     * @param string $leftTitle
558
     *
559
     * @return $this
560
     */
561
    public function setLeftTitle($leftTitle)
562
    {
563
        $this->leftTitle = $leftTitle;
564
565
        return $this;
566
    }
567
568
    /**
569
     * Compiles all CSS-classes. Optionally includes a "form-group--no-label" class if no title was set on the
570
     * FormField.
571
     *
572
     * Uses {@link Message()} and {@link MessageType()} to add validation error classes which can
573
     * be used to style the contained tags.
574
     *
575
     * @return string
576
     */
577
    public function extraClass()
578
    {
579
        $classes = [];
580
581
        $classes[] = $this->Type();
582
583
        if ($this->extraClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extraClasses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
584
            $classes = array_merge(
585
                $classes,
586
                array_values($this->extraClasses)
587
            );
588
        }
589
590
        if (!$this->Title()) {
591
            $classes[] = 'form-group--no-label';
592
        }
593
594
        // Allow custom styling of any element in the container based on validation errors,
595
        // e.g. red borders on input tags.
596
        //
597
        // CSS class needs to be different from the one rendered through {@link FieldHolder()}.
598
        if ($this->getMessage()) {
599
            $classes[] .= 'holder-' . $this->getMessageType();
600
        }
601
602
        return implode(' ', $classes);
603
    }
604
605
    /**
606
     * Check if a CSS-class has been added to the form container.
607
     *
608
     * @param string $class A string containing a classname or several class
609
     * names delimited by a single space.
610
     * @return boolean True if all of the classnames passed in have been added.
611
     */
612
    public function hasExtraClass($class)
613
    {
614
        //split at white space
615
        $classes = preg_split('/\s+/', $class);
616
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
617
            if (!isset($this->extraClasses[$class])) {
618
                return false;
619
            }
620
        }
621
        return true;
622
    }
623
624
    /**
625
     * Add one or more CSS-classes to the FormField container.
626
     *
627
     * Multiple class names should be space delimited.
628
     *
629
     * @param string $class
630
     *
631
     * @return $this
632
     */
633
    public function addExtraClass($class)
634
    {
635
        $classes = preg_split('/\s+/', $class);
636
637
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
638
            $this->extraClasses[$class] = $class;
639
        }
640
641
        return $this;
642
    }
643
644
    /**
645
     * Remove one or more CSS-classes from the FormField container.
646
     *
647
     * @param string $class
648
     *
649
     * @return $this
650
     */
651
    public function removeExtraClass($class)
652
    {
653
        $classes = preg_split('/\s+/', $class);
654
655
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
656
            unset($this->extraClasses[$class]);
657
        }
658
659
        return $this;
660
    }
661
662
    /**
663
     * Set an HTML attribute on the field element, mostly an input tag.
664
     *
665
     * Some attributes are best set through more specialized methods, to avoid interfering with
666
     * built-in behaviour:
667
     *
668
     * - 'class': {@link addExtraClass()}
669
     * - 'title': {@link setDescription()}
670
     * - 'value': {@link setValue}
671
     * - 'name': {@link setName}
672
     *
673
     * Caution: this doesn't work on most fields which are composed of more than one HTML form
674
     * field.
675
     *
676
     * @param string $name
677
     * @param string $value
678
     *
679
     * @return $this
680
     */
681
    public function setAttribute($name, $value)
682
    {
683
        $this->attributes[$name] = $value;
684
685
        return $this;
686
    }
687
688
    /**
689
     * Get an HTML attribute defined by the field, or added through {@link setAttribute()}.
690
     *
691
     * Caution: this doesn't work on all fields, see {@link setAttribute()}.
692
     *
693
     * @param string $name
694
     * @return string
695
     */
696
    public function getAttribute($name)
697
    {
698
        $attributes = $this->getAttributes();
699
700
        if (isset($attributes[$name])) {
701
            return $attributes[$name];
702
        }
703
704
        return null;
705
    }
706
707
    /**
708
     * Allows customization through an 'updateAttributes' hook on the base class.
709
     * Existing attributes are passed in as the first argument and can be manipulated,
710
     * but any attributes added through a subclass implementation won't be included.
711
     *
712
     * @return array
713
     */
714
    public function getAttributes()
715
    {
716
        $attributes = [
717
            'type' => $this->getInputType(),
718
            'name' => $this->getName(),
719
            'value' => $this->Value(),
720
            'class' => $this->extraClass(),
721
            'id' => $this->ID(),
722
            'disabled' => $this->isDisabled(),
723
            'readonly' => $this->isReadonly(),
724
            'autofocus' => $this->isAutofocus()
725
        ];
726
727
        if ($this->Required()) {
728
            $attributes['required'] = 'required';
729
            $attributes['aria-required'] = 'true';
730
        }
731
732
        $attributes = array_merge($attributes, $this->attributes);
733
734
        $this->extend('updateAttributes', $attributes);
735
736
        return $attributes;
737
    }
738
739
    /**
740
     * Custom attributes to process. Falls back to {@link getAttributes()}.
741
     *
742
     * If at least one argument is passed as a string, all arguments act as excludes, by name.
743
     *
744
     * @param array $attributes
745
     *
746
     * @return string
747
     */
748
    public function getAttributesHTML($attributes = null)
749
    {
750
        $exclude = null;
751
752
        if (is_string($attributes)) {
753
            $exclude = func_get_args();
754
        }
755
756
        if (!$attributes || is_string($attributes)) {
757
            $attributes = $this->getAttributes();
758
        }
759
760
        $attributes = (array) $attributes;
761
762
        $attributes = array_filter($attributes, function ($v) {
763
            return ($v || $v === 0 || $v === '0');
764
        });
765
766
        if ($exclude) {
767
            $attributes = array_diff_key(
768
                $attributes,
769
                array_flip($exclude)
770
            );
771
        }
772
773
        // Create markup
774
        $parts = [];
775
776
        foreach ($attributes as $name => $value) {
777
            if ($value === true) {
778
                $value = $name;
779
            } else {
780
                if (is_scalar($value)) {
781
                    $value = (string) $value;
782
                } else {
783
                    $value = json_encode($value);
784
                }
785
            }
786
787
            $parts[] = sprintf('%s="%s"', Convert::raw2att($name), Convert::raw2att($value));
0 ignored issues
show
Bug introduced by
It seems like SilverStripe\Core\Convert::raw2att($name) can also be of type array and array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

787
            $parts[] = sprintf('%s="%s"', /** @scrutinizer ignore-type */ Convert::raw2att($name), Convert::raw2att($value));
Loading history...
788
        }
789
790
        return implode(' ', $parts);
791
    }
792
793
    /**
794
     * Returns a version of a title suitable for insertion into an HTML attribute.
795
     *
796
     * @return string
797
     */
798
    public function attrTitle()
799
    {
800
        return Convert::raw2att($this->title);
801
    }
802
803
    /**
804
     * Returns a version of a title suitable for insertion into an HTML attribute.
805
     *
806
     * @return string
807
     */
808
    public function attrValue()
809
    {
810
        return Convert::raw2att($this->value);
811
    }
812
813
    /**
814
     * Set the field value.
815
     *
816
     * If a FormField requires specific behaviour for loading content from either the database
817
     * or a submitted form value they should override setSubmittedValue() instead.
818
     *
819
     * @param mixed $value Either the parent object, or array of source data being loaded
820
     * @param array|DataObject $data {@see Form::loadDataFrom}
821
     * @return $this
822
     */
823
    public function setValue($value, $data = null)
824
    {
825
        $this->value = $value;
826
        return $this;
827
    }
828
829
    /**
830
     * Set value assigned from a submitted form postback.
831
     * Can be overridden to handle custom behaviour for user-localised
832
     * data formats.
833
     *
834
     * @param mixed $value
835
     * @param array|DataObject $data
836
     * @return $this
837
     */
838
    public function setSubmittedValue($value, $data = null)
839
    {
840
        return $this->setValue($value, $data);
841
    }
842
843
    /**
844
     * Set the field name.
845
     *
846
     * @param string $name
847
     *
848
     * @return $this
849
     */
850
    public function setName($name)
851
    {
852
        $this->name = $name;
853
854
        return $this;
855
    }
856
857
    /**
858
     * Set the field input type.
859
     *
860
     * @param string $type
861
     *
862
     * @return $this
863
     */
864
    public function setInputType($type)
865
    {
866
        $this->inputType = $type;
867
868
        return $this;
869
    }
870
871
    /**
872
     * Set the container form.
873
     *
874
     * This is called automatically when fields are added to forms.
875
     *
876
     * @param Form $form
877
     *
878
     * @return $this
879
     */
880
    public function setForm($form)
881
    {
882
        $this->form = $form;
883
884
        return $this;
885
    }
886
887
    /**
888
     * Get the currently used form.
889
     *
890
     * @return Form
891
     */
892
    public function getForm()
893
    {
894
        return $this->form;
895
    }
896
897
    /**
898
     * Return true if security token protection is enabled on the parent {@link Form}.
899
     *
900
     * @return bool
901
     */
902
    public function securityTokenEnabled()
903
    {
904
        $form = $this->getForm();
905
906
        if (!$form) {
0 ignored issues
show
introduced by
$form is of type SilverStripe\Forms\Form, thus it always evaluated to true.
Loading history...
907
            return false;
908
        }
909
910
        return $form->getSecurityToken()->isEnabled();
911
    }
912
913
    public function castingHelper($field)
914
    {
915
        // Override casting for field message
916
        if (strcasecmp($field, 'Message') === 0 && ($helper = $this->getMessageCastingHelper())) {
917
            return $helper;
918
        }
919
        return parent::castingHelper($field);
920
    }
921
922
    /**
923
     * Set the custom error message to show instead of the default format.
924
     *
925
     * Different from setError() as that appends it to the standard error messaging.
926
     *
927
     * @param string $customValidationMessage
928
     *
929
     * @return $this
930
     */
931
    public function setCustomValidationMessage($customValidationMessage)
932
    {
933
        $this->customValidationMessage = $customValidationMessage;
934
935
        return $this;
936
    }
937
938
    /**
939
     * Get the custom error message for this form field. If a custom message has not been defined
940
     * then just return blank. The default error is defined on {@link Validator}.
941
     *
942
     * @return string
943
     */
944
    public function getCustomValidationMessage()
945
    {
946
        return $this->customValidationMessage;
947
    }
948
949
    /**
950
     * Set name of template (without path or extension).
951
     *
952
     * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
953
     * method on the subclass for support.
954
     *
955
     * @param string $template
956
     *
957
     * @return $this
958
     */
959
    public function setTemplate($template)
960
    {
961
        $this->template = $template;
962
963
        return $this;
964
    }
965
966
    /**
967
     * @return string
968
     */
969
    public function getTemplate()
970
    {
971
        return $this->template;
972
    }
973
974
    /**
975
     * @return string
976
     */
977
    public function getFieldHolderTemplate()
978
    {
979
        return $this->fieldHolderTemplate;
980
    }
981
982
    /**
983
     * Set name of template (without path or extension) for the holder, which in turn is
984
     * responsible for rendering {@link Field()}.
985
     *
986
     * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
987
     * method on the subclass for support.
988
     *
989
     * @param string $fieldHolderTemplate
990
     *
991
     * @return $this
992
     */
993
    public function setFieldHolderTemplate($fieldHolderTemplate)
994
    {
995
        $this->fieldHolderTemplate = $fieldHolderTemplate;
996
997
        return $this;
998
    }
999
1000
    /**
1001
     * @return string
1002
     */
1003
    public function getSmallFieldHolderTemplate()
1004
    {
1005
        return $this->smallFieldHolderTemplate;
1006
    }
1007
1008
    /**
1009
     * Set name of template (without path or extension) for the small holder, which in turn is
1010
     * responsible for rendering {@link Field()}.
1011
     *
1012
     * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
1013
     * method on the subclass for support.
1014
     *
1015
     * @param string $smallFieldHolderTemplate
1016
     *
1017
     * @return $this
1018
     */
1019
    public function setSmallFieldHolderTemplate($smallFieldHolderTemplate)
1020
    {
1021
        $this->smallFieldHolderTemplate = $smallFieldHolderTemplate;
1022
1023
        return $this;
1024
    }
1025
1026
    /**
1027
     * Returns the form field.
1028
     *
1029
     * Although FieldHolder is generally what is inserted into templates, all of the field holder
1030
     * templates make use of $Field. It's expected that FieldHolder will give you the "complete"
1031
     * representation of the field on the form, whereas Field will give you the core editing widget,
1032
     * such as an input tag.
1033
     *
1034
     * @param array $properties
1035
     * @return DBHTMLText
1036
     */
1037
    public function Field($properties = [])
1038
    {
1039
        $context = $this;
1040
1041
        $this->extend('onBeforeRender', $context, $properties);
1042
1043
        if (count($properties)) {
1044
            $context = $context->customise($properties);
1045
        }
1046
1047
        $result = $context->renderWith($this->getTemplates());
1048
1049
        // Trim whitespace from the result, so that trailing newlines are suppressed. Works for strings and HTMLText values
1050
        if (is_string($result)) {
1051
            $result = trim($result);
1052
        } elseif ($result instanceof DBField) {
1053
            $result->setValue(trim($result->getValue()));
1054
        }
1055
1056
        return $result;
1057
    }
1058
1059
    /**
1060
     * Returns a "field holder" for this field.
1061
     *
1062
     * Forms are constructed by concatenating a number of these field holders.
1063
     *
1064
     * The default field holder is a label and a form field inside a div.
1065
     *
1066
     * @see FieldHolder.ss
1067
     *
1068
     * @param array $properties
1069
     *
1070
     * @return DBHTMLText
1071
     */
1072
    public function FieldHolder($properties = [])
1073
    {
1074
        $context = $this;
1075
1076
        $this->extend('onBeforeRenderHolder', $context, $properties);
1077
1078
        if (count($properties)) {
1079
            $context = $this->customise($properties);
1080
        }
1081
1082
        return $context->renderWith($this->getFieldHolderTemplates());
1083
    }
1084
1085
    /**
1086
     * Returns a restricted field holder used within things like FieldGroups.
1087
     *
1088
     * @param array $properties
1089
     *
1090
     * @return string
1091
     */
1092
    public function SmallFieldHolder($properties = [])
1093
    {
1094
        $context = $this;
1095
1096
        if (count($properties)) {
1097
            $context = $this->customise($properties);
1098
        }
1099
1100
        return $context->renderWith($this->getSmallFieldHolderTemplates());
1101
    }
1102
1103
    /**
1104
     * Returns an array of templates to use for rendering {@link FieldHolder}.
1105
     *
1106
     * @return array
1107
     */
1108
    public function getTemplates()
1109
    {
1110
        return $this->_templates($this->getTemplate());
1111
    }
1112
1113
    /**
1114
     * Returns an array of templates to use for rendering {@link FieldHolder}.
1115
     *
1116
     * @return array
1117
     */
1118
    public function getFieldHolderTemplates()
1119
    {
1120
        return $this->_templates(
1121
            $this->getFieldHolderTemplate(),
1122
            '_holder'
1123
        );
1124
    }
1125
1126
    /**
1127
     * Returns an array of templates to use for rendering {@link SmallFieldHolder}.
1128
     *
1129
     * @return array
1130
     */
1131
    public function getSmallFieldHolderTemplates()
1132
    {
1133
        return $this->_templates(
1134
            $this->getSmallFieldHolderTemplate(),
1135
            '_holder_small'
1136
        );
1137
    }
1138
1139
1140
    /**
1141
     * Generate an array of class name strings to use for rendering this form field into HTML.
1142
     *
1143
     * @param string $customTemplate
1144
     * @param string $customTemplateSuffix
1145
     *
1146
     * @return array
1147
     */
1148
    protected function _templates($customTemplate = null, $customTemplateSuffix = null)
1149
    {
1150
        $templates = SSViewer::get_templates_by_class(static::class, $customTemplateSuffix, __CLASS__);
1151
        // Prefer any custom template
1152
        if ($customTemplate) {
1153
            // Prioritise direct template
1154
            array_unshift($templates, $customTemplate);
1155
        }
1156
        return $templates;
1157
    }
1158
1159
    /**
1160
     * Returns true if this field is a composite field.
1161
     *
1162
     * To create composite field types, you should subclass {@link CompositeField}.
1163
     *
1164
     * @return bool
1165
     */
1166
    public function isComposite()
1167
    {
1168
        return false;
1169
    }
1170
1171
    /**
1172
     * Returns true if this field has its own data.
1173
     *
1174
     * Some fields, such as titles and composite fields, don't actually have any data. It doesn't
1175
     * make sense for data-focused methods to look at them. By overloading hasData() to return
1176
     * false, you can prevent any data-focused methods from looking at it.
1177
     *
1178
     * @see FieldList::collateDataFields()
1179
     *
1180
     * @return bool
1181
     */
1182
    public function hasData()
1183
    {
1184
        return true;
1185
    }
1186
1187
    /**
1188
     * @return bool
1189
     */
1190
    public function isReadonly()
1191
    {
1192
        return $this->readonly;
1193
    }
1194
1195
    /**
1196
     * Sets a read-only flag on this FormField.
1197
     *
1198
     * Use performReadonlyTransformation() to transform this instance.
1199
     *
1200
     * Setting this to false has no effect on the field.
1201
     *
1202
     * @param bool $readonly
1203
     *
1204
     * @return $this
1205
     */
1206
    public function setReadonly($readonly)
1207
    {
1208
        $this->readonly = $readonly;
1209
        return $this;
1210
    }
1211
1212
    /**
1213
     * @return bool
1214
     */
1215
    public function isDisabled()
1216
    {
1217
        return $this->disabled;
1218
    }
1219
1220
    /**
1221
     * Sets a disabled flag on this FormField.
1222
     *
1223
     * Use performDisabledTransformation() to transform this instance.
1224
     *
1225
     * Setting this to false has no effect on the field.
1226
     *
1227
     * @param bool $disabled
1228
     *
1229
     * @return $this
1230
     */
1231
    public function setDisabled($disabled)
1232
    {
1233
        $this->disabled = $disabled;
1234
1235
        return $this;
1236
    }
1237
1238
    /**
1239
     * @return bool
1240
     */
1241
    public function isAutofocus()
1242
    {
1243
        return $this->autofocus;
1244
    }
1245
1246
    /**
1247
     * Sets a autofocus flag on this FormField.
1248
     *
1249
     * @param bool $autofocus
1250
     * @return $this
1251
     */
1252
    public function setAutofocus($autofocus)
1253
    {
1254
        $this->autofocus = $autofocus;
1255
        return $this;
1256
    }
1257
1258
    /**
1259
     * Returns a read-only version of this field.
1260
     *
1261
     * @return FormField
1262
     */
1263
    public function performReadonlyTransformation()
1264
    {
1265
        $readonlyClassName = static::class . '_Readonly';
1266
1267
        if (ClassInfo::exists($readonlyClassName)) {
1268
            $clone = $this->castedCopy($readonlyClassName);
1269
        } else {
1270
            $clone = $this->castedCopy(ReadonlyField::class);
1271
        }
1272
1273
        $clone->setReadonly(true);
1274
1275
        return $clone;
1276
    }
1277
1278
    /**
1279
     * Return a disabled version of this field.
1280
     *
1281
     * Tries to find a class of the class name of this field suffixed with "_Disabled", failing
1282
     * that, finds a method {@link setDisabled()}.
1283
     *
1284
     * @return FormField
1285
     */
1286
    public function performDisabledTransformation()
1287
    {
1288
        $disabledClassName = static::class . '_Disabled';
1289
1290
        if (ClassInfo::exists($disabledClassName)) {
1291
            $clone = $this->castedCopy($disabledClassName);
1292
        } else {
1293
            $clone = clone $this;
1294
        }
1295
1296
        $clone->setDisabled(true);
1297
1298
        return $clone;
1299
    }
1300
1301
    /**
1302
     * @param FormTransformation $transformation
1303
     *
1304
     * @return mixed
1305
     */
1306
    public function transform(FormTransformation $transformation)
1307
    {
1308
        return $transformation->transform($this);
1309
    }
1310
1311
    /**
1312
     * Returns whether the current field has the given class added
1313
     *
1314
     * @param string $class
1315
     *
1316
     * @return bool
1317
     */
1318
    public function hasClass($class)
1319
    {
1320
        $classes = explode(' ', strtolower($this->extraClass()));
1321
        return in_array(strtolower(trim($class)), $classes);
1322
    }
1323
1324
    /**
1325
     * Returns the field type.
1326
     *
1327
     * The field type is the class name with the word Field dropped off the end, all lowercase.
1328
     *
1329
     * It's handy for assigning HTML classes. Doesn't signify the input type attribute.
1330
     *
1331
     * @see {@link getAttributes()}.
1332
     *
1333
     * @return string
1334
     */
1335
    public function Type()
1336
    {
1337
        $type = new ReflectionClass($this);
1338
        return strtolower(preg_replace('/Field$/', '', $type->getShortName()));
1339
    }
1340
1341
    /**
1342
     * Abstract method each {@link FormField} subclass must implement, determines whether the field
1343
     * is valid or not based on the value.
1344
     *
1345
     * @todo Make this abstract.
1346
     *
1347
     * @param Validator $validator
1348
     * @return bool
1349
     */
1350
    public function validate($validator)
0 ignored issues
show
Unused Code introduced by
The parameter $validator is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1350
    public function validate(/** @scrutinizer ignore-unused */ $validator)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1351
    {
1352
        return true;
1353
    }
1354
1355
    /**
1356
     * Describe this field, provide help text for it.
1357
     *
1358
     * By default, renders as a span class="description" underneath the form field.
1359
     *
1360
     * @param string $description
1361
     *
1362
     * @return $this
1363
     */
1364
    public function setDescription($description)
1365
    {
1366
        $this->description = $description;
1367
1368
        return $this;
1369
    }
1370
1371
    /**
1372
     * @return string
1373
     */
1374
    public function getDescription()
1375
    {
1376
        return $this->description;
1377
    }
1378
1379
    /**
1380
     * @return string
1381
     */
1382
    public function debug()
1383
    {
1384
        $strValue = is_string($this->value) ? $this->value : print_r($this->value, true);
1385
1386
        return sprintf(
1387
            '%s (%s: %s : <span style="color:red;">%s</span>) = %s',
1388
            Convert::raw2att(static::class),
1389
            Convert::raw2att($this->name),
1390
            Convert::raw2att($this->title),
1391
            $this->getMessageCast() == ValidationResult::CAST_HTML ? Convert::raw2xml($this->message) : $this->message,
1392
            Convert::raw2att($strValue)
0 ignored issues
show
Bug introduced by
It seems like $strValue can also be of type true; however, parameter $val of SilverStripe\Core\Convert::raw2att() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1392
            Convert::raw2att(/** @scrutinizer ignore-type */ $strValue)
Loading history...
1393
        );
1394
    }
1395
1396
    /**
1397
     * This function is used by the template processor. If you refer to a field as a $ variable, it
1398
     * will return the $Field value.
1399
     *
1400
     * @return string
1401
     */
1402
    public function forTemplate()
1403
    {
1404
        return $this->Field();
1405
    }
1406
1407
    /**
1408
     * @return bool
1409
     */
1410
    public function Required()
1411
    {
1412
        if ($this->form && ($validator = $this->form->getValidator())) {
1413
            return $validator->fieldIsRequired($this->name);
1414
        }
1415
1416
        return false;
1417
    }
1418
1419
    /**
1420
     * Set the FieldList that contains this field.
1421
     *
1422
     * @param FieldList $containerFieldList
1423
     * @return $this
1424
     */
1425
    public function setContainerFieldList($containerFieldList)
1426
    {
1427
        $this->containerFieldList = $containerFieldList;
1428
        return $this;
1429
    }
1430
1431
    /**
1432
     * Get the FieldList that contains this field.
1433
     *
1434
     * @return FieldList
1435
     */
1436
    public function getContainerFieldList()
1437
    {
1438
        return $this->containerFieldList;
1439
    }
1440
1441
    /**
1442
     * @return null|FieldList
1443
     */
1444
    public function rootFieldList()
1445
    {
1446
        if ($this->containerFieldList) {
1447
            return $this->containerFieldList->rootFieldList();
1448
        }
1449
1450
        $class = static::class;
1451
        throw new \RuntimeException("rootFieldList() called on {$class} object without a containerFieldList");
1452
    }
1453
1454
    /**
1455
     * Returns another instance of this field, but "cast" to a different class. The logic tries to
1456
     * retain all of the instance properties, and may be overloaded by subclasses to set additional
1457
     * ones.
1458
     *
1459
     * Assumes the standard FormField parameter signature with its name as the only mandatory
1460
     * argument. Mainly geared towards creating *_Readonly or *_Disabled subclasses of the same
1461
     * type, or casting to a {@link ReadonlyField}.
1462
     *
1463
     * Does not copy custom field templates, since they probably won't apply to the new instance.
1464
     *
1465
     * @param mixed $classOrCopy Class name for copy, or existing copy instance to update
1466
     *
1467
     * @return FormField
1468
     */
1469
    public function castedCopy($classOrCopy)
1470
    {
1471
        $field = $classOrCopy;
1472
1473
        if (!is_object($field)) {
1474
            $field = $classOrCopy::create($this->name);
1475
        }
1476
1477
        $field
1478
            ->setValue($this->value)
1479
            ->setForm($this->form)
1480
            ->setTitle($this->Title())
1481
            ->setLeftTitle($this->LeftTitle())
1482
            ->setRightTitle($this->RightTitle())
1483
            ->addExtraClass($this->extraClass) // Don't use extraClass(), since this merges calculated values
1484
            ->setDescription($this->getDescription());
1485
1486
        // Only include built-in attributes, ignore anything set through getAttributes().
1487
        // Those might change important characteristics of the field, e.g. its "type" attribute.
1488
        foreach ($this->attributes as $attributeKey => $attributeValue) {
1489
            $field->setAttribute($attributeKey, $attributeValue);
1490
        }
1491
1492
        return $field;
1493
    }
1494
1495
    /**
1496
     * Determine if the value of this formfield accepts front-end submitted values and is saveable.
1497
     *
1498
     * @return bool
1499
     */
1500
    public function canSubmitValue()
1501
    {
1502
        return $this->hasData() && !$this->isReadonly() && !$this->isDisabled();
1503
    }
1504
1505
    /**
1506
     * Sets the component type the FormField will be rendered as on the front-end.
1507
     *
1508
     * @param string $componentType
1509
     * @return FormField
1510
     */
1511
    public function setSchemaComponent($componentType)
1512
    {
1513
        $this->schemaComponent = $componentType;
1514
        return $this;
1515
    }
1516
1517
    /**
1518
     * Gets the type of front-end component the FormField will be rendered as.
1519
     *
1520
     * @return string
1521
     */
1522
    public function getSchemaComponent()
1523
    {
1524
        return $this->schemaComponent;
1525
    }
1526
1527
    /**
1528
     * Sets the schema data used for rendering the field on the front-end.
1529
     * Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
1530
     * Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
1531
     * If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
1532
     *
1533
     * @param array $schemaData - The data to be merged with $this->schemaData.
1534
     * @return FormField
1535
     *
1536
     * @todo Add deep merging of arrays like `data` and `attributes`.
1537
     */
1538
    public function setSchemaData($schemaData = [])
1539
    {
1540
        $defaults = $this->getSchemaData();
1541
        $this->schemaData = array_merge($this->schemaData, array_intersect_key($schemaData, $defaults));
1542
        return $this;
1543
    }
1544
1545
    /**
1546
     * Gets the schema data used to render the FormField on the front-end.
1547
     *
1548
     * @return array
1549
     */
1550
    public function getSchemaData()
1551
    {
1552
        $defaults = $this->getSchemaDataDefaults();
1553
        return array_replace_recursive($defaults, array_intersect_key($this->schemaData, $defaults));
1554
    }
1555
1556
    /**
1557
     * @todo Throw exception if value is missing, once a form field schema is mandatory across the CMS
1558
     *
1559
     * @return string
1560
     */
1561
    public function getSchemaDataType()
1562
    {
1563
        return $this->schemaDataType;
1564
    }
1565
1566
    /**
1567
     * Gets the defaults for $schemaData.
1568
     * The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaData()} are ignored.
1569
     * Instead the `data` array should be used to pass around ad hoc data.
1570
     *
1571
     * @return array
1572
     */
1573
    public function getSchemaDataDefaults()
1574
    {
1575
        $data = [
1576
            'name' => $this->getName(),
1577
            'id' => $this->ID(),
1578
            'type' => $this->getInputType(),
1579
            'schemaType' => $this->getSchemaDataType(),
1580
            'component' => $this->getSchemaComponent(),
1581
            'holderId' => $this->HolderID(),
1582
            'title' => $this->obj('Title')->getSchemaValue(),
1583
            'source' => null,
1584
            'extraClass' => $this->extraClass(),
1585
            'description' => $this->obj('Description')->getSchemaValue(),
1586
            'rightTitle' => $this->obj('RightTitle')->getSchemaValue(),
1587
            'leftTitle' => $this->obj('LeftTitle')->getSchemaValue(),
1588
            'readOnly' => $this->isReadonly(),
1589
            'disabled' => $this->isDisabled(),
1590
            'customValidationMessage' => $this->getCustomValidationMessage(),
1591
            'validation' => $this->getSchemaValidation(),
1592
            'attributes' => [],
1593
            'autoFocus' => $this->isAutofocus(),
1594
            'data' => [],
1595
        ];
1596
        $titleTip = $this->getTitleTip();
1597
        if ($titleTip instanceof Tip) {
1598
            $data['titleTip'] = $titleTip->getTipSchema();
1599
        }
1600
        return $data;
1601
    }
1602
1603
    /**
1604
     * Sets the schema data used for rendering the field on the front-end.
1605
     * Merges the passed array with the current `$schemaState` or {@link getSchemaStateDefaults()}.
1606
     * Any passed keys that are not defined in {@link getSchemaStateDefaults()} are ignored.
1607
     * If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
1608
     *
1609
     * @param array $schemaState The data to be merged with $this->schemaData.
1610
     * @return FormField
1611
     *
1612
     * @todo Add deep merging of arrays like `data` and `attributes`.
1613
     */
1614
    public function setSchemaState($schemaState = [])
1615
    {
1616
        $defaults = $this->getSchemaState();
1617
        $this->schemaState = array_merge($this->schemaState, array_intersect_key($schemaState, $defaults));
1618
        return $this;
1619
    }
1620
1621
    /**
1622
     * Gets the schema state used to render the FormField on the front-end.
1623
     *
1624
     * @return array
1625
     */
1626
    public function getSchemaState()
1627
    {
1628
        $defaults = $this->getSchemaStateDefaults();
1629
        return array_merge($defaults, array_intersect_key($this->schemaState, $defaults));
1630
    }
1631
1632
    /**
1633
     * Gets the defaults for $schemaState.
1634
     * The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaState()} are ignored.
1635
     * Instead the `data` array should be used to pass around ad hoc data.
1636
     * Includes validation data if the field is associated to a {@link Form},
1637
     * and {@link Form->validate()} has been called.
1638
     *
1639
     * @todo Make form / field messages not always stored as html; Store value / casting as separate values.
1640
     * @return array
1641
     */
1642
    public function getSchemaStateDefaults()
1643
    {
1644
        $state = [
1645
            'name' => $this->getName(),
1646
            'id' => $this->ID(),
1647
            'value' => $this->Value(),
1648
            'message' => $this->getSchemaMessage(),
1649
            'data' => [],
1650
        ];
1651
1652
        return $state;
1653
    }
1654
1655
    /**
1656
     * Return list of validation rules. Each rule is a key value pair.
1657
     * The key is the rule name. The value is any information the frontend
1658
     * validation handler can understand, or just `true` to enable.
1659
     *
1660
     * @return array
1661
     */
1662
    public function getSchemaValidation()
1663
    {
1664
        $validationList = [];
1665
        if ($this->Required()) {
1666
            $validationList['required'] = true;
1667
        }
1668
        $this->extend('updateSchemaValidation', $validationList);
1669
        return $validationList;
1670
    }
1671
1672
    /**
1673
     * @return Tip
1674
     */
1675
    public function getTitleTip(): ?Tip
1676
    {
1677
        return $this->titleTip;
1678
    }
1679
1680
    /**
1681
     * @param Tip|null $tip
1682
     * @return $this
1683
     */
1684
    public function setTitleTip(?Tip $tip): self
1685
    {
1686
        $this->titleTip = $tip;
1687
        return $this;
1688
    }
1689
}
1690