Completed
Push — 4 ( ec6b01...dc3b90 )
by Steve
05:20 queued 05:10
created

FormField::getTitleTip()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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
     * Add one or more CSS-classes to the FormField container.
607
     *
608
     * Multiple class names should be space delimited.
609
     *
610
     * @param string $class
611
     *
612
     * @return $this
613
     */
614
    public function addExtraClass($class)
615
    {
616
        $classes = preg_split('/\s+/', $class);
617
618
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
619
            $this->extraClasses[$class] = $class;
620
        }
621
622
        return $this;
623
    }
624
625
    /**
626
     * Remove one or more CSS-classes from the FormField container.
627
     *
628
     * @param string $class
629
     *
630
     * @return $this
631
     */
632
    public function removeExtraClass($class)
633
    {
634
        $classes = preg_split('/\s+/', $class);
635
636
        foreach ($classes as $class) {
0 ignored issues
show
introduced by
$class is overwriting one of the parameters of this function.
Loading history...
637
            unset($this->extraClasses[$class]);
638
        }
639
640
        return $this;
641
    }
642
643
    /**
644
     * Set an HTML attribute on the field element, mostly an input tag.
645
     *
646
     * Some attributes are best set through more specialized methods, to avoid interfering with
647
     * built-in behaviour:
648
     *
649
     * - 'class': {@link addExtraClass()}
650
     * - 'title': {@link setDescription()}
651
     * - 'value': {@link setValue}
652
     * - 'name': {@link setName}
653
     *
654
     * Caution: this doesn't work on most fields which are composed of more than one HTML form
655
     * field.
656
     *
657
     * @param string $name
658
     * @param string $value
659
     *
660
     * @return $this
661
     */
662
    public function setAttribute($name, $value)
663
    {
664
        $this->attributes[$name] = $value;
665
666
        return $this;
667
    }
668
669
    /**
670
     * Get an HTML attribute defined by the field, or added through {@link setAttribute()}.
671
     *
672
     * Caution: this doesn't work on all fields, see {@link setAttribute()}.
673
     *
674
     * @param string $name
675
     * @return string
676
     */
677
    public function getAttribute($name)
678
    {
679
        $attributes = $this->getAttributes();
680
681
        if (isset($attributes[$name])) {
682
            return $attributes[$name];
683
        }
684
685
        return null;
686
    }
687
688
    /**
689
     * Allows customization through an 'updateAttributes' hook on the base class.
690
     * Existing attributes are passed in as the first argument and can be manipulated,
691
     * but any attributes added through a subclass implementation won't be included.
692
     *
693
     * @return array
694
     */
695
    public function getAttributes()
696
    {
697
        $attributes = [
698
            'type' => $this->getInputType(),
699
            'name' => $this->getName(),
700
            'value' => $this->Value(),
701
            'class' => $this->extraClass(),
702
            'id' => $this->ID(),
703
            'disabled' => $this->isDisabled(),
704
            'readonly' => $this->isReadonly(),
705
            'autofocus' => $this->isAutofocus()
706
        ];
707
708
        if ($this->Required()) {
709
            $attributes['required'] = 'required';
710
            $attributes['aria-required'] = 'true';
711
        }
712
713
        $attributes = array_merge($attributes, $this->attributes);
714
715
        $this->extend('updateAttributes', $attributes);
716
717
        return $attributes;
718
    }
719
720
    /**
721
     * Custom attributes to process. Falls back to {@link getAttributes()}.
722
     *
723
     * If at least one argument is passed as a string, all arguments act as excludes, by name.
724
     *
725
     * @param array $attributes
726
     *
727
     * @return string
728
     */
729
    public function getAttributesHTML($attributes = null)
730
    {
731
        $exclude = null;
732
733
        if (is_string($attributes)) {
734
            $exclude = func_get_args();
735
        }
736
737
        if (!$attributes || is_string($attributes)) {
738
            $attributes = $this->getAttributes();
739
        }
740
741
        $attributes = (array) $attributes;
742
743
        $attributes = array_filter($attributes, function ($v) {
744
            return ($v || $v === 0 || $v === '0');
745
        });
746
747
        if ($exclude) {
748
            $attributes = array_diff_key(
749
                $attributes,
750
                array_flip($exclude)
751
            );
752
        }
753
754
        // Create markup
755
        $parts = [];
756
757
        foreach ($attributes as $name => $value) {
758
            if ($value === true) {
759
                $value = $name;
760
            } else {
761
                if (is_scalar($value)) {
762
                    $value = (string) $value;
763
                } else {
764
                    $value = json_encode($value);
765
                }
766
            }
767
768
            $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

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

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

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