Passed
Pull Request — 4 (#10029)
by Steve
08:26
created

FormField::getTitleTip()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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
     * Name of the template used to render this form field. If not set, then will look up the class
187
     * ancestry for the first matching template where the template name equals the class name.
188
     *
189
     * To explicitly use a custom template or one named other than the form field see
190
     * {@link setTemplate()}.
191
     *
192
     * @var string
193
     */
194
    protected $template;
195
196
    /**
197
     * Name of the template used to render this form field. If not set, then will look up the class
198
     * ancestry for the first matching template where the template name equals the class name.
199
     *
200
     * To explicitly use a custom template or one named other than the form field see
201
     * {@link setFieldHolderTemplate()}.
202
     *
203
     * @var string
204
     */
205
    protected $fieldHolderTemplate;
206
207
    /**
208
     * @var string
209
     */
210
    protected $smallFieldHolderTemplate;
211
212
    /**
213
     * All attributes on the form field (not the field holder).
214
     *
215
     * Partially determined based on other instance properties.
216
     *
217
     * @see getAttributes()
218
     *
219
     * @var array
220
     */
221
    protected $attributes = [];
222
223
    /**
224
     * The data type backing the field. Represents the type of value the
225
     * form expects to receive via a postback. Should be set in subclasses.
226
     *
227
     * The values allowed in this list include:
228
     *
229
     *   - String: Single line text
230
     *   - Hidden: Hidden field which is posted back without modification
231
     *   - Text: Multi line text
232
     *   - HTML: Rich html text
233
     *   - Integer: Whole number value
234
     *   - Decimal: Decimal value
235
     *   - MultiSelect: Select many from source
236
     *   - SingleSelect: Select one from source
237
     *   - Date: Date only
238
     *   - DateTime: Date and time
239
     *   - Time: Time only
240
     *   - Boolean: Yes or no
241
     *   - Custom: Custom type declared by the front-end component. For fields with this type,
242
     *     the component property is mandatory, and will determine the posted value for this field.
243
     *   - Structural: Represents a field that is NOT posted back. This may contain other fields,
244
     *     or simply be a block of stand-alone content. As with 'Custom',
245
     *     the component property is mandatory if this is assigned.
246
     *
247
     * Each value has an equivalent constant, e.g. {@link self::SCHEMA_DATA_TYPE_STRING}.
248
     *
249
     * @var string
250
     */
251
    protected $schemaDataType;
252
253
    /**
254
     * The type of front-end component to render the FormField as.
255
     *
256
     * @skipUpgrade
257
     * @var string
258
     */
259
    protected $schemaComponent;
260
261
    /**
262
     * Structured schema data representing the FormField.
263
     * Used to render the FormField as a ReactJS Component on the front-end.
264
     *
265
     * @var array
266
     */
267
    protected $schemaData = [];
268
269
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
270
        'FieldHolder' => 'HTMLFragment',
271
        'SmallFieldHolder' => 'HTMLFragment',
272
        'Field' => 'HTMLFragment',
273
        'AttributesHTML' => 'HTMLFragment', // property $AttributesHTML version
274
        'getAttributesHTML' => 'HTMLFragment', // method $getAttributesHTML($arg) version
275
        'Value' => 'Text',
276
        'extraClass' => 'Text',
277
        'ID' => 'Text',
278
        'isReadOnly' => 'Boolean',
279
        'HolderID' => 'Text',
280
        'Title' => 'Text',
281
        'RightTitle' => 'Text',
282
        'Description' => 'HTMLFragment',
283
    ];
284
285
    /**
286
     * Structured schema state representing the FormField's current data and validation.
287
     * Used to render the FormField as a ReactJS Component on the front-end.
288
     *
289
     * @var array
290
     */
291
    protected $schemaState = [];
292
293
    /**
294
     * Takes a field name and converts camelcase to spaced words. Also resolves combined field
295
     * names with dot syntax to spaced words.
296
     *
297
     * Examples:
298
     *
299
     * - 'TotalAmount' will return 'Total amount'
300
     * - 'Organisation.ZipCode' will return 'Organisation zip code'
301
     *
302
     * @param string $fieldName
303
     *
304
     * @return string
305
     */
306
    public static function name_to_label($fieldName)
307
    {
308
        // Handle dot delimiters
309
        if (strpos($fieldName, '.') !== false) {
310
            $parts = explode('.', $fieldName);
311
            // Ensure that any letter following a dot is uppercased, so that the regex below can break it up
312
            // into words
313
            $label = implode(array_map('ucfirst', $parts));
314
        } else {
315
            $label = $fieldName;
316
        }
317
318
        // Replace any capital letter that is followed by a lowercase letter with a space, the lowercased
319
        // version of itself then the remaining lowercase letters.
320
        $labelWithSpaces = preg_replace_callback('/([A-Z])([a-z]+)/', function ($matches) {
321
            return ' ' . strtolower($matches[1]) . $matches[2];
322
        }, $label);
323
324
        // Add a space before any capital letter block that is at the end of the string
325
        $labelWithSpaces = preg_replace('/([a-z])([A-Z]+)$/', '$1 $2', $labelWithSpaces);
326
327
        // The first letter should be uppercase
328
        return ucfirst(trim($labelWithSpaces));
329
    }
330
331
    /**
332
     * Creates a new field.
333
     *
334
     * @param string $name The internal field name, passed to forms.
335
     * @param null|string|\SilverStripe\View\ViewableData $title The human-readable field label.
336
     * @param mixed $value The value of the field.
337
     */
338
    public function __construct($name, $title = null, $value = null)
339
    {
340
        $this->setName($name);
341
342
        if ($title === null) {
343
            $this->title = self::name_to_label($name);
344
        } else {
345
            $this->title = $title;
346
        }
347
348
        if ($value !== null) {
349
            $this->setValue($value);
350
        }
351
352
        parent::__construct();
353
354
        $this->setupDefaultClasses();
355
    }
356
357
    /**
358
     * Set up the default classes for the form. This is done on construct so that the default classes can be removed
359
     * after instantiation
360
     */
361
    protected function setupDefaultClasses()
362
    {
363
        $defaultClasses = $this->config()->get('default_classes');
364
        if ($defaultClasses) {
365
            foreach ($defaultClasses as $class) {
366
                $this->addExtraClass($class);
367
            }
368
        }
369
    }
370
371
    /**
372
     * Return a link to this field
373
     *
374
     * @param string $action
375
     * @return string
376
     * @throws LogicException If no form is set yet
377
     */
378
    public function Link($action = null)
379
    {
380
        if (!$this->form) {
381
            throw new LogicException(
382
                'Field must be associated with a form to call Link(). Please use $field->setForm($form);'
383
            );
384
        }
385
386
        $link = Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action);
387
        $this->extend('updateLink', $link, $action);
388
        return $link;
389
    }
390
391
    /**
392
     * Returns the HTML ID of the field.
393
     *
394
     * The ID is generated as FormName_FieldName. All Field functions should ensure that this ID is
395
     * included in the field.
396
     *
397
     * @return string
398
     */
399
    public function ID()
400
    {
401
        return $this->getTemplateHelper()->generateFieldID($this);
402
    }
403
404
    /**
405
     * Returns the HTML ID for the form field holder element.
406
     *
407
     * @return string
408
     */
409
    public function HolderID()
410
    {
411
        return $this->getTemplateHelper()->generateFieldHolderID($this);
412
    }
413
414
    /**
415
     * Returns the current {@link FormTemplateHelper} on either the parent
416
     * Form or the global helper set through the {@link Injector} layout.
417
     *
418
     * To customize a single {@link FormField}, use {@link setTemplate} and
419
     * provide a custom template name.
420
     *
421
     * @return FormTemplateHelper
422
     */
423
    public function getTemplateHelper()
424
    {
425
        if ($this->form) {
426
            return $this->form->getTemplateHelper();
427
        }
428
429
        return FormTemplateHelper::singleton();
430
    }
431
432
    /**
433
     * Returns the field name.
434
     *
435
     * @return string
436
     */
437
    public function getName()
438
    {
439
        return $this->name;
440
    }
441
442
    /**
443
     * Returns the field input name.
444
     *
445
     * @return string
446
     */
447
    public function getInputType()
448
    {
449
        return $this->inputType;
450
    }
451
452
    /**
453
     * Returns the field value.
454
     *
455
     * @see FormField::setSubmittedValue()
456
     * @return mixed
457
     */
458
    public function Value()
459
    {
460
        return $this->value;
461
    }
462
463
    /**
464
     * Method to save this form field into the given {@link DataObject}.
465
     *
466
     * By default, makes use of $this->dataValue()
467
     *
468
     * @param DataObject|DataObjectInterface $record DataObject to save data into
469
     */
470
    public function saveInto(DataObjectInterface $record)
471
    {
472
        $component = $record;
473
        $fieldName = $this->name;
474
475
        // Allow for dot syntax
476
        if (($pos = strrpos($this->name, '.')) !== false) {
477
            $relation = substr($this->name, 0, $pos);
478
            $fieldName = substr($this->name, $pos + 1);
479
            $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

479
            /** @scrutinizer ignore-call */ 
480
            $component = $record->relObject($relation);
Loading history...
480
        }
481
482
        if ($fieldName) {
483
            $component->setCastedField($fieldName, $this->dataValue());
484
        }
485
    }
486
487
    /**
488
     * Returns the field value suitable for insertion into the data object.
489
     * @see Formfield::setValue()
490
     * @return mixed
491
     */
492
    public function dataValue()
493
    {
494
        return $this->value;
495
    }
496
497
    /**
498
     * Returns the field label - used by templates.
499
     *
500
     * @return string
501
     */
502
    public function Title()
503
    {
504
        return $this->title;
505
    }
506
507
    /**
508
     * Set the title of this formfield.
509
     * Note: This expects escaped HTML.
510
     *
511
     * @param string $title Escaped HTML for title
512
     * @return $this
513
     */
514
    public function setTitle($title)
515
    {
516
        $this->title = $title;
517
        return $this;
518
    }
519
520
    /**
521
     * Gets the contextual label than can be used for additional field description.
522
     * Can be shown to the right or under the field in question.
523
     *
524
     * @return string Contextual label text
525
     */
526
    public function RightTitle()
527
    {
528
        return $this->rightTitle;
529
    }
530
531
    /**
532
     * Sets the right title for this formfield
533
     *
534
     * @param string|DBField $rightTitle Plain text string, or a DBField with appropriately escaped HTML
535
     * @return $this
536
     */
537
    public function setRightTitle($rightTitle)
538
    {
539
        $this->rightTitle = $rightTitle;
540
        return $this;
541
    }
542
543
    /**
544
     * @return string
545
     */
546
    public function LeftTitle()
547
    {
548
        return $this->leftTitle;
549
    }
550
551
    /**
552
     * @param string $leftTitle
553
     *
554
     * @return $this
555
     */
556
    public function setLeftTitle($leftTitle)
557
    {
558
        $this->leftTitle = $leftTitle;
559
560
        return $this;
561
    }
562
563
    /**
564
     * Compiles all CSS-classes. Optionally includes a "form-group--no-label" class if no title was set on the
565
     * FormField.
566
     *
567
     * Uses {@link Message()} and {@link MessageType()} to add validation error classes which can
568
     * be used to style the contained tags.
569
     *
570
     * @return string
571
     */
572
    public function extraClass()
573
    {
574
        $classes = [];
575
576
        $classes[] = $this->Type();
577
578
        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...
579
            $classes = array_merge(
580
                $classes,
581
                array_values($this->extraClasses)
582
            );
583
        }
584
585
        if (!$this->Title()) {
586
            $classes[] = 'form-group--no-label';
587
        }
588
589
        // Allow custom styling of any element in the container based on validation errors,
590
        // e.g. red borders on input tags.
591
        //
592
        // CSS class needs to be different from the one rendered through {@link FieldHolder()}.
593
        if ($this->getMessage()) {
594
            $classes[] .= 'holder-' . $this->getMessageType();
595
        }
596
597
        return implode(' ', $classes);
598
    }
599
600
    /**
601
     * Add one or more CSS-classes to the FormField container.
602
     *
603
     * Multiple class names should be space delimited.
604
     *
605
     * @param string $class
606
     *
607
     * @return $this
608
     */
609
    public function addExtraClass($class)
610
    {
611
        $classes = preg_split('/\s+/', $class);
612
613
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
614
            $this->extraClasses[$class] = $class;
615
        }
616
617
        return $this;
618
    }
619
620
    /**
621
     * Remove one or more CSS-classes from the FormField container.
622
     *
623
     * @param string $class
624
     *
625
     * @return $this
626
     */
627
    public function removeExtraClass($class)
628
    {
629
        $classes = preg_split('/\s+/', $class);
630
631
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
632
            unset($this->extraClasses[$class]);
633
        }
634
635
        return $this;
636
    }
637
638
    /**
639
     * Set an HTML attribute on the field element, mostly an input tag.
640
     *
641
     * Some attributes are best set through more specialized methods, to avoid interfering with
642
     * built-in behaviour:
643
     *
644
     * - 'class': {@link addExtraClass()}
645
     * - 'title': {@link setDescription()}
646
     * - 'value': {@link setValue}
647
     * - 'name': {@link setName}
648
     *
649
     * Caution: this doesn't work on most fields which are composed of more than one HTML form
650
     * field.
651
     *
652
     * @param string $name
653
     * @param string $value
654
     *
655
     * @return $this
656
     */
657
    public function setAttribute($name, $value)
658
    {
659
        $this->attributes[$name] = $value;
660
661
        return $this;
662
    }
663
664
    /**
665
     * Get an HTML attribute defined by the field, or added through {@link setAttribute()}.
666
     *
667
     * Caution: this doesn't work on all fields, see {@link setAttribute()}.
668
     *
669
     * @param string $name
670
     * @return string
671
     */
672
    public function getAttribute($name)
673
    {
674
        $attributes = $this->getAttributes();
675
676
        if (isset($attributes[$name])) {
677
            return $attributes[$name];
678
        }
679
680
        return null;
681
    }
682
683
    /**
684
     * Allows customization through an 'updateAttributes' hook on the base class.
685
     * Existing attributes are passed in as the first argument and can be manipulated,
686
     * but any attributes added through a subclass implementation won't be included.
687
     *
688
     * @return array
689
     */
690
    public function getAttributes()
691
    {
692
        $attributes = [
693
            'type' => $this->getInputType(),
694
            'name' => $this->getName(),
695
            'value' => $this->Value(),
696
            'class' => $this->extraClass(),
697
            'id' => $this->ID(),
698
            'disabled' => $this->isDisabled(),
699
            'readonly' => $this->isReadonly(),
700
            'autofocus' => $this->isAutofocus()
701
        ];
702
703
        if ($this->Required()) {
704
            $attributes['required'] = 'required';
705
            $attributes['aria-required'] = 'true';
706
        }
707
708
        $attributes = array_merge($attributes, $this->attributes);
709
710
        $this->extend('updateAttributes', $attributes);
711
712
        return $attributes;
713
    }
714
715
    /**
716
     * Custom attributes to process. Falls back to {@link getAttributes()}.
717
     *
718
     * If at least one argument is passed as a string, all arguments act as excludes, by name.
719
     *
720
     * @param array $attributes
721
     *
722
     * @return string
723
     */
724
    public function getAttributesHTML($attributes = null)
725
    {
726
        $exclude = null;
727
728
        if (is_string($attributes)) {
729
            $exclude = func_get_args();
730
        }
731
732
        if (!$attributes || is_string($attributes)) {
733
            $attributes = $this->getAttributes();
734
        }
735
736
        $attributes = (array) $attributes;
737
738
        $attributes = array_filter($attributes, function ($v) {
739
            return ($v || $v === 0 || $v === '0');
740
        });
741
742
        if ($exclude) {
743
            $attributes = array_diff_key(
744
                $attributes,
745
                array_flip($exclude)
746
            );
747
        }
748
749
        // Create markup
750
        $parts = [];
751
752
        foreach ($attributes as $name => $value) {
753
            if ($value === true) {
754
                $value = $name;
755
            } else {
756
                if (is_scalar($value)) {
757
                    $value = (string) $value;
758
                } else {
759
                    $value = json_encode($value);
760
                }
761
            }
762
763
            $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

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

1326
    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...
1327
    {
1328
        return true;
1329
    }
1330
1331
    /**
1332
     * Describe this field, provide help text for it.
1333
     *
1334
     * By default, renders as a span class="description" underneath the form field.
1335
     *
1336
     * @param string $description
1337
     *
1338
     * @return $this
1339
     */
1340
    public function setDescription($description)
1341
    {
1342
        $this->description = $description;
1343
1344
        return $this;
1345
    }
1346
1347
    /**
1348
     * @return string
1349
     */
1350
    public function getDescription()
1351
    {
1352
        return $this->description;
1353
    }
1354
1355
    /**
1356
     * @return string
1357
     */
1358
    public function debug()
1359
    {
1360
        $strValue = is_string($this->value) ? $this->value : print_r($this->value, true);
1361
1362
        return sprintf(
1363
            '%s (%s: %s : <span style="color:red;">%s</span>) = %s',
1364
            Convert::raw2att(static::class),
1365
            Convert::raw2att($this->name),
1366
            Convert::raw2att($this->title),
1367
            $this->getMessageCast() == ValidationResult::CAST_HTML ? Convert::raw2xml($this->message) : $this->message,
1368
            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

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