Completed
Push — master ( 4ee78f...ce1053 )
by Damian
14:52 queued 39s
created

FormField::getSchemaValidation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms;
4
5
use InvalidArgumentException;
6
use SilverStripe\ORM\DataObject;
7
use SilverStripe\ORM\DataObjectInterface;
8
use SilverStripe\ORM\FieldType\DBField;
9
use SilverStripe\ORM\FieldType\DBHTMLText;
10
use SilverStripe\Core\Convert;
11
use SilverStripe\Core\ClassInfo;
12
use SilverStripe\Dev\Deprecation;
13
use SilverStripe\Control\Controller;
14
use SilverStripe\Control\RequestHandler;
15
use SilverStripe\View\SSViewer;
16
use ReflectionClass;
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
	/** @see $schemaDataType */
45
	const SCHEMA_DATA_TYPE_STRING = 'String';
46
47
	/** @see $schemaDataType */
48
	const SCHEMA_DATA_TYPE_HIDDEN = 'Hidden';
49
50
	/** @see $schemaDataType */
51
	const SCHEMA_DATA_TYPE_TEXT = 'Text';
52
53
	/** @see $schemaDataType */
54
	const SCHEMA_DATA_TYPE_HTML = 'HTML';
55
56
	/** @see $schemaDataType */
57
	const SCHEMA_DATA_TYPE_INTEGER = 'Integer';
58
59
	/** @see $schemaDataType */
60
	const SCHEMA_DATA_TYPE_DECIMAL = 'Decimal';
61
62
	/** @see $schemaDataType */
63
	const SCHEMA_DATA_TYPE_MULTISELECT = 'MultiSelect';
64
65
	/** @see $schemaDataType */
66
	const SCHEMA_DATA_TYPE_SINGLESELECT = 'SingleSelect';
67
68
	/** @see $schemaDataType */
69
	const SCHEMA_DATA_TYPE_DATE = 'Date';
70
71
	/** @see $schemaDataType */
72
	const SCHEMA_DATA_TYPE_DATETIME = 'DateTime';
73
74
	/** @see $schemaDataType */
75
	const SCHEMA_DATA_TYPE_TIME = 'Time';
76
77
	/** @see $schemaDataType */
78
	const SCHEMA_DATA_TYPE_BOOLEAN = 'Boolean';
79
80
	/** @see $schemaDataType */
81
	const SCHEMA_DATA_TYPE_CUSTOM = 'Custom';
82
83
	/** @see $schemaDataType */
84
	const SCHEMA_DATA_TYPE_STRUCTURAL = 'Structural';
85
86
	/**
87
	 * @var Form
88
	 */
89
	protected $form;
90
91
	/**
92
	 * @var string
93
	 */
94
	protected $name;
95
96
	/**
97
	 * @var null|string
98
	 */
99
	protected $title;
100
101
	/**
102
	 * @var mixed
103
	 */
104
	protected $value;
105
106
	/**
107
	 * @var string
108
	 */
109
	protected $message;
110
111
	/**
112
	 * @var string
113
	 */
114
	protected $messageType;
115
116
	/**
117
	 * @var string
118
	 */
119
	protected $extraClass;
120
121
	/**
122
	 * Adds a title attribute to the markup.
123
	 *
124
	 * @var string
125
	 *
126
	 * @todo Implement in all subclasses
127
	 */
128
	protected $description;
129
130
	/**
131
	 * Extra CSS classes for the FormField container.
132
	 *
133
	 * @var array
134
	 */
135
	protected $extraClasses;
136
137
	/**
138
	 * @config
139
	 * @var array $default_classes The default classes to apply to the FormField
140
	 */
141
	private static $default_classes = [];
142
143
	/**
144
	 * Right-aligned, contextual label for the field.
145
	 *
146
	 * @var string
147
	 */
148
	protected $rightTitle;
149
150
	/**
151
	 * Left-aligned, contextual label for the field.
152
	 *
153
	 * @var string
154
	 */
155
	protected $leftTitle;
156
157
	/**
158
	 * Stores a reference to the FieldList that contains this object.
159
	 *
160
	 * @var FieldList
161
	 */
162
	protected $containerFieldList;
163
164
	/**
165
	 * @var bool
166
	 */
167
	protected $readonly = false;
168
169
	/**
170
	 * @var bool
171
	 */
172
	protected $disabled = false;
173
174
	/**
175
	 * Custom validation message for the field.
176
	 *
177
	 * @var string
178
	 */
179
	protected $customValidationMessage = '';
180
181
	/**
182
	 * Name of the template used to render this form field. If not set, then will look up the class
183
	 * ancestry for the first matching template where the template name equals the class name.
184
	 *
185
	 * To explicitly use a custom template or one named other than the form field see
186
	 * {@link setTemplate()}.
187
	 *
188
	 * @var string
189
	 */
190
	protected $template;
191
192
	/**
193
	 * Name of the template used to render this form field. If not set, then will look up the class
194
	 * ancestry for the first matching template where the template name equals the class name.
195
	 *
196
	 * To explicitly use a custom template or one named other than the form field see
197
	 * {@link setFieldHolderTemplate()}.
198
	 *
199
	 * @var string
200
	 */
201
	protected $fieldHolderTemplate;
202
203
	/**
204
	 * @var string
205
	 */
206
	protected $smallFieldHolderTemplate;
207
208
	/**
209
	 * All attributes on the form field (not the field holder).
210
	 *
211
	 * Partially determined based on other instance properties.
212
	 *
213
	 * @see getAttributes()
214
	 *
215
	 * @var array
216
	 */
217
	protected $attributes = [];
218
219
	/**
220
	 * The data type backing the field. Represents the type of value the
221
	 * form expects to receive via a postback. Should be set in subclasses.
222
	 *
223
	 * The values allowed in this list include:
224
	 *
225
	 *   - String: Single line text
226
	 *   - Hidden: Hidden field which is posted back without modification
227
	 *   - Text: Multi line text
228
	 *   - HTML: Rich html text
229
	 *   - Integer: Whole number value
230
	 *   - Decimal: Decimal value
231
	 *   - MultiSelect: Select many from source
232
	 *   - SingleSelect: Select one from source
233
	 *   - Date: Date only
234
	 *   - DateTime: Date and time
235
	 *   - Time: Time only
236
	 *   - Boolean: Yes or no
237
	 *   - Custom: Custom type declared by the front-end component. For fields with this type,
238
	 *     the component property is mandatory, and will determine the posted value for this field.
239
	 *   - Structural: Represents a field that is NOT posted back. This may contain other fields,
240
	 *     or simply be a block of stand-alone content. As with 'Custom',
241
	 *     the component property is mandatory if this is assigned.
242
	 *
243
	 * Each value has an equivalent constant, e.g. {@link self::SCHEMA_DATA_TYPE_STRING}.
244
	 *
245
	 * @var string
246
	 */
247
	 protected $schemaDataType;
248
249
	/**
250
	 * The type of front-end component to render the FormField as.
251
	 *
252
	 * @skipUpgrade
253
	 * @var string
254
	 */
255
	protected $schemaComponent;
256
257
	/**
258
	 * Structured schema data representing the FormField.
259
	 * Used to render the FormField as a ReactJS Component on the front-end.
260
	 *
261
	 * @var array
262
	 */
263
	protected $schemaData = [];
264
265
	private static $casting = array(
266
		'FieldHolder' => 'HTMLFragment',
267
		'Field' => 'HTMLFragment',
268
		'AttributesHTML' => 'HTMLFragment', // property $AttributesHTML version
269
		'getAttributesHTML' => 'HTMLFragment', // method $getAttributesHTML($arg) version
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270
		'Value' => 'Text',
271
		'extraClass' => 'Text',
272
		'ID' => 'Text',
273
		'isReadOnly' => 'Boolean',
274
		'HolderID' => 'Text',
275
		'Title' => 'Text',
276
		'RightTitle' => 'Text',
277
		'MessageType' => 'Text',
278
		'Message' => 'HTMLFragment',
279
		'Description' => 'HTMLFragment',
280
	);
281
282
	/**
283
	 * Structured schema state representing the FormField's current data and validation.
284
	 * Used to render the FormField as a ReactJS Component on the front-end.
285
	 *
286
	 * @var array
287
	 */
288
	protected $schemaState = [];
289
290
	/**
291
	 * Takes a field name and converts camelcase to spaced words. Also resolves combined field
292
	 * names with dot syntax to spaced words.
293
	 *
294
	 * Examples:
295
	 *
296
	 * - 'TotalAmount' will return 'Total Amount'
297
	 * - 'Organisation.ZipCode' will return 'Organisation Zip Code'
298
	 *
299
	 * @param string $fieldName
300
	 *
301
	 * @return string
302
	 */
303
	public static function name_to_label($fieldName) {
304
		if(strpos($fieldName, '.') !== false) {
305
			$parts = explode('.', $fieldName);
306
307
			$label = $parts[count($parts) - 2] . ' ' . $parts[count($parts) - 1];
308
		} else {
309
			$label = $fieldName;
310
		}
311
312
		return preg_replace('/([a-z]+)([A-Z])/', '$1 $2', $label);
313
	}
314
315
	/**
316
	 * Construct and return HTML tag.
317
	 *
318
	 * @param string $tag
319
	 * @param array $attributes
320
	 * @param null|string $content
321
	 *
322
	 * @return string
323
	 */
324
	public static function create_tag($tag, $attributes, $content = null) {
325
		$preparedAttributes = '';
326
327
		foreach($attributes as $attributeKey => $attributeValue) {
328
			if(!empty($attributeValue) || $attributeValue === '0' || ($attributeKey == 'value' && $attributeValue !== null)) {
329
				$preparedAttributes .= sprintf(
330
					' %s="%s"', $attributeKey, Convert::raw2att($attributeValue)
331
				);
332
			}
333
		}
334
335
		if($content || $tag != 'input') {
0 ignored issues
show
Bug Best Practice introduced by
The expression $content of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
336
			return sprintf(
337
				'<%s%s>%s</%s>', $tag, $preparedAttributes, $content, $tag
338
			);
339
		}
340
341
		return sprintf(
342
			'<%s%s />', $tag, $preparedAttributes
343
		);
344
	}
345
346
	/**
347
	 * Creates a new field.
348
	 *
349
	 * @param string $name The internal field name, passed to forms.
350
	 * @param null|string $title The human-readable field label.
351
	 * @param mixed $value The value of the field.
352
	 */
353
	public function __construct($name, $title = null, $value = null) {
354
		$this->setName($name);
355
356
		if($title === null) {
357
			$this->title = self::name_to_label($name);
358
		} else {
359
			$this->title = $title;
360
		}
361
362
		if($value !== null) {
363
			$this->setValue($value);
364
		}
365
366
		parent::__construct();
367
368
		$this->setupDefaultClasses();
369
	}
370
371
	/**
372
	 * Set up the default classes for the form. This is done on construct so that the default classes can be removed
373
	 * after instantiation
374
	 */
375
	protected function setupDefaultClasses() {
376
		$defaultClasses = self::config()->get('default_classes');
377
		if ($defaultClasses) {
378
			foreach ($defaultClasses as $class) {
379
				$this->addExtraClass($class);
380
			}
381
		}
382
	}
383
384
	/**
385
	 * Return a link to this field.
386
	 *
387
	 * @param string $action
388
	 *
389
	 * @return string
390
	 */
391
	public function Link($action = null) {
392
		return Controller::join_links($this->form->FormAction(), 'field/' . $this->name, $action);
393
	}
394
395
	/**
396
	 * Returns the HTML ID of the field.
397
	 *
398
	 * The ID is generated as FormName_FieldName. All Field functions should ensure that this ID is
399
	 * included in the field.
400
	 *
401
	 * @return string
402
	 */
403
	public function ID() {
404
		return $this->getTemplateHelper()->generateFieldID($this);
405
	}
406
407
	/**
408
	 * Returns the HTML ID for the form field holder element.
409
	 *
410
	 * @return string
411
	 */
412
	public function HolderID() {
413
		return $this->getTemplateHelper()->generateFieldHolderID($this);
414
	}
415
416
	/**
417
	 * Returns the current {@link FormTemplateHelper} on either the parent
418
	 * Form or the global helper set through the {@link Injector} layout.
419
	 *
420
	 * To customize a single {@link FormField}, use {@link setTemplate} and
421
	 * provide a custom template name.
422
	 *
423
	 * @return FormTemplateHelper
424
	 */
425
	public function getTemplateHelper() {
426
		if($this->form) {
427
			return $this->form->getTemplateHelper();
428
		}
429
430
		return FormTemplateHelper::singleton();
431
	}
432
433
	/**
434
	 * Returns the field name.
435
	 *
436
	 * @return string
437
	 */
438
	public function getName() {
439
		return $this->name;
440
	}
441
442
	/**
443
	 * Returns the field message, used by form validation.
444
	 *
445
	 * Use {@link setError()} to set this property.
446
	 *
447
	 * @return string
448
	 */
449
	public function Message() {
450
		return $this->message;
451
	}
452
453
	/**
454
	 * Returns the field message type.
455
	 *
456
	 * Arbitrary value which is mostly used for CSS classes in the rendered HTML, e.g "required".
457
	 *
458
	 * Use {@link setError()} to set this property.
459
	 *
460
	 * @return string
461
	 */
462
	public function MessageType() {
463
		return $this->messageType;
464
	}
465
466
	/**
467
	 * Returns the field value.
468
	 *
469
	 * @return mixed
470
	 */
471
	public function Value() {
472
		return $this->value;
473
	}
474
475
	/**
476
	 * Method to save this form field into the given {@link DataObject}.
477
	 *
478
	 * By default, makes use of $this->dataValue()
479
	 *
480
	 * @param DataObject|DataObjectInterface $record DataObject to save data into
481
	 */
482
	public function saveInto(DataObjectInterface $record) {
483
		if($this->name) {
484
			$record->setCastedField($this->name, $this->dataValue());
485
		}
486
	}
487
488
	/**
489
	 * Returns the field value suitable for insertion into the data object.
490
	 *
491
	 * @return mixed
492
	 */
493
	public function dataValue() {
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
		return $this->title;
504
	}
505
506
	/**
507
	 * Set the title of this formfield.
508
	 * Note: This expects escaped HTML.
509
	 *
510
	 * @param string $title Escaped HTML for title
511
	 * @return $this
512
	 */
513
	public function setTitle($title) {
514
		$this->title = $title;
515
		return $this;
516
	}
517
518
	/**
519
	 * Gets the contextual label than can be used for additional field description.
520
	 * Can be shown to the right or under the field in question.
521
	 *
522
	 * @return string Contextual label text.
523
	 */
524
	public function RightTitle() {
525
		return $this->rightTitle;
526
	}
527
528
	/**
529
	 * Sets the right title for this formfield
530
	 * Note: This expects escaped HTML.
531
	 *
532
	 * @param string $rightTitle Escaped HTML for title
533
	 * @return $this
534
	 */
535
	public function setRightTitle($rightTitle) {
536
		$this->rightTitle = $rightTitle;
537
		return $this;
538
	}
539
540
	/**
541
	 * @return string
542
	 */
543
	public function LeftTitle() {
544
		return $this->leftTitle;
545
	}
546
547
	/**
548
	 * @param string $leftTitle
549
	 *
550
	 * @return $this
551
	 */
552
	public function setLeftTitle($leftTitle) {
553
		$this->leftTitle = $leftTitle;
554
555
		return $this;
556
	}
557
558
	/**
559
	 * Compiles all CSS-classes. Optionally includes a "form-group--no-label" class if no title was set on the
560
	 * FormField.
561
	 *
562
	 * Uses {@link Message()} and {@link MessageType()} to add validation error classes which can
563
	 * be used to style the contained tags.
564
	 *
565
	 * @return string
566
	 */
567
	public function extraClass() {
568
		$classes = array();
569
570
		$classes[] = $this->Type();
571
572
		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...
573
			$classes = array_merge(
574
				$classes,
575
				array_values($this->extraClasses)
576
			);
577
		}
578
579
		if(!$this->Title()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->Title() of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

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

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
773
	 * @return $this
774
	 */
775
	public function setValue($value) {
776
		$this->value = $value;
777
		return $this;
778
	}
779
780
	/**
781
	 * Set the field name.
782
	 *
783
	 * @param string $name
784
	 *
785
	 * @return $this
786
	 */
787
	public function setName($name) {
788
		$this->name = $name;
789
790
		return $this;
791
	}
792
793
	/**
794
	 * Set the container form.
795
	 *
796
	 * This is called automatically when fields are added to forms.
797
	 *
798
	 * @param Form $form
799
	 *
800
	 * @return $this
801
	 */
802
	public function setForm($form) {
803
		$this->form = $form;
804
805
		return $this;
806
	}
807
808
	/**
809
	 * Get the currently used form.
810
	 *
811
	 * @return Form
812
	 */
813
	public function getForm() {
814
		return $this->form;
815
	}
816
817
	/**
818
	 * Return true if security token protection is enabled on the parent {@link Form}.
819
	 *
820
	 * @return bool
821
	 */
822
	public function securityTokenEnabled() {
823
		$form = $this->getForm();
824
825
		if(!$form) {
826
			return false;
827
		}
828
829
		return $form->getSecurityToken()->isEnabled();
830
	}
831
832
	/**
833
	 * Sets the error message to be displayed on the form field.
834
	 *
835
	 * Allows HTML content, so remember to use Convert::raw2xml().
836
	 *
837
	 * @param string $message
838
	 * @param string $messageType
839
	 *
840
	 * @return $this
841
	 */
842
	public function setError($message, $messageType) {
843
		$this->message = $message;
844
		$this->messageType = $messageType;
845
846
		return $this;
847
	}
848
849
	/**
850
	 * Set the custom error message to show instead of the default format.
851
	 *
852
	 * Different from setError() as that appends it to the standard error messaging.
853
	 *
854
	 * @param string $customValidationMessage
855
	 *
856
	 * @return $this
857
	 */
858
	public function setCustomValidationMessage($customValidationMessage) {
859
		$this->customValidationMessage = $customValidationMessage;
860
861
		return $this;
862
	}
863
864
	/**
865
	 * Get the custom error message for this form field. If a custom message has not been defined
866
	 * then just return blank. The default error is defined on {@link Validator}.
867
	 *
868
	 * @return string
869
	 */
870
	public function getCustomValidationMessage() {
871
		return $this->customValidationMessage;
872
	}
873
874
	/**
875
	 * Set name of template (without path or extension).
876
	 *
877
	 * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
878
	 * method on the subclass for support.
879
	 *
880
	 * @param string $template
881
	 *
882
	 * @return $this
883
	 */
884
	public function setTemplate($template) {
885
		$this->template = $template;
886
887
		return $this;
888
	}
889
890
	/**
891
	 * @return string
892
	 */
893
	public function getTemplate() {
894
		return $this->template;
895
	}
896
897
	/**
898
	 * @return string
899
	 */
900
	public function getFieldHolderTemplate() {
901
		return $this->fieldHolderTemplate;
902
	}
903
904
	/**
905
	 * Set name of template (without path or extension) for the holder, which in turn is
906
	 * responsible for rendering {@link Field()}.
907
	 *
908
	 * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
909
	 * method on the subclass for support.
910
	 *
911
	 * @param string $fieldHolderTemplate
912
	 *
913
	 * @return $this
914
	 */
915
	public function setFieldHolderTemplate($fieldHolderTemplate) {
916
		$this->fieldHolderTemplate = $fieldHolderTemplate;
917
918
		return $this;
919
	}
920
921
	/**
922
	 * @return string
923
	 */
924
	public function getSmallFieldHolderTemplate() {
925
		return $this->smallFieldHolderTemplate;
926
	}
927
928
	/**
929
	 * Set name of template (without path or extension) for the small holder, which in turn is
930
	 * responsible for rendering {@link Field()}.
931
	 *
932
	 * Caution: Not consistently implemented in all subclasses, please check the {@link Field()}
933
	 * method on the subclass for support.
934
	 *
935
	 * @param string $smallFieldHolderTemplate
936
	 *
937
	 * @return $this
938
	 */
939
	public function setSmallFieldHolderTemplate($smallFieldHolderTemplate) {
940
		$this->smallFieldHolderTemplate = $smallFieldHolderTemplate;
941
942
		return $this;
943
	}
944
945
	/**
946
	 * Returns the form field.
947
	 *
948
	 * Although FieldHolder is generally what is inserted into templates, all of the field holder
949
	 * templates make use of $Field. It's expected that FieldHolder will give you the "complete"
950
	 * representation of the field on the form, whereas Field will give you the core editing widget,
951
	 * such as an input tag.
952
	 *
953
	 * @param array $properties
954
	 * @return DBHTMLText
955
	 */
956
	public function Field($properties = array()) {
957
		$context = $this;
958
959
		if(count($properties)) {
960
			$context = $context->customise($properties);
961
		}
962
963
		$this->extend('onBeforeRender', $this);
964
965
		$result = $context->renderWith($this->getTemplates());
966
967
		// Trim whitespace from the result, so that trailing newlines are supressed. Works for strings and HTMLText values
968
		if(is_string($result)) {
969
			$result = trim($result);
970
		} else if($result instanceof DBField) {
971
			$result->setValue(trim($result->getValue()));
972
		}
973
974
		return $result;
975
	}
976
977
	/**
978
	 * Returns a "field holder" for this field.
979
	 *
980
	 * Forms are constructed by concatenating a number of these field holders.
981
	 *
982
	 * The default field holder is a label and a form field inside a div.
983
	 *
984
	 * @see FieldHolder.ss
985
	 *
986
	 * @param array $properties
987
	 *
988
	 * @return DBHTMLText
989
	 */
990
	public function FieldHolder($properties = array()) {
991
		$context = $this;
992
993
		if(count($properties)) {
994
			$context = $this->customise($properties);
995
		}
996
997
		return $context->renderWith($this->getFieldHolderTemplates());
998
	}
999
1000
	/**
1001
	 * Returns a restricted field holder used within things like FieldGroups.
1002
	 *
1003
	 * @param array $properties
1004
	 *
1005
	 * @return string
1006
	 */
1007
	public function SmallFieldHolder($properties = array()) {
1008
		$context = $this;
1009
1010
		if(count($properties)) {
1011
			$context = $this->customise($properties);
1012
		}
1013
1014
		return $context->renderWith($this->getSmallFieldHolderTemplates());
1015
	}
1016
1017
	/**
1018
	 * Returns an array of templates to use for rendering {@link FieldHolder}.
1019
	 *
1020
	 * @return array
1021
	 */
1022
	public function getTemplates() {
1023
		return $this->_templates($this->getTemplate());
1024
	}
1025
1026
	/**
1027
	 * Returns an array of templates to use for rendering {@link FieldHolder}.
1028
	 *
1029
	 * @return array
1030
	 */
1031
	public function getFieldHolderTemplates() {
1032
		return $this->_templates(
1033
			$this->getFieldHolderTemplate(),
1034
			'_holder'
1035
		);
1036
	}
1037
1038
	/**
1039
	 * Returns an array of templates to use for rendering {@link SmallFieldHolder}.
1040
	 *
1041
	 * @return array
1042
	 */
1043
	public function getSmallFieldHolderTemplates() {
1044
		return $this->_templates(
1045
			$this->getSmallFieldHolderTemplate(),
1046
			'_holder_small'
1047
		);
1048
	}
1049
1050
1051
	/**
1052
	 * Generate an array of class name strings to use for rendering this form field into HTML.
1053
	 *
1054
	 * @param string $customTemplate
1055
	 * @param string $customTemplateSuffix
1056
	 *
1057
	 * @return array
1058
	 */
1059
	protected function _templates($customTemplate = null, $customTemplateSuffix = null) {
1060
		$templates = SSViewer::get_templates_by_class(get_class($this), $customTemplateSuffix, __CLASS__);
1061
		// Prefer any custom template
1062
		if($customTemplate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $customTemplate of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1063
			// Prioritise direct template
1064
			array_unshift($templates, $customTemplate);
1065
		}
1066
		return $templates;
1067
	}
1068
1069
	/**
1070
	 * Returns true if this field is a composite field.
1071
	 *
1072
	 * To create composite field types, you should subclass {@link CompositeField}.
1073
	 *
1074
	 * @return bool
1075
	 */
1076
	public function isComposite() {
1077
		return false;
1078
	}
1079
1080
	/**
1081
	 * Returns true if this field has its own data.
1082
	 *
1083
	 * Some fields, such as titles and composite fields, don't actually have any data. It doesn't
1084
	 * make sense for data-focused methods to look at them. By overloading hasData() to return
1085
	 * false, you can prevent any data-focused methods from looking at it.
1086
	 *
1087
	 * @see FieldList::collateDataFields()
1088
	 *
1089
	 * @return bool
1090
	 */
1091
	public function hasData() {
1092
		return true;
1093
	}
1094
1095
	/**
1096
	 * @return bool
1097
	 */
1098
	public function isReadonly() {
1099
		return $this->readonly;
1100
	}
1101
1102
	/**
1103
	 * Sets a read-only flag on this FormField.
1104
	 *
1105
	 * Use performReadonlyTransformation() to transform this instance.
1106
	 *
1107
	 * Setting this to false has no effect on the field.
1108
	 *
1109
	 * @param bool $readonly
1110
	 *
1111
	 * @return $this
1112
	 */
1113
	public function setReadonly($readonly) {
1114
		$this->readonly = $readonly;
1115
1116
		return $this;
1117
	}
1118
1119
	/**
1120
	 * @return bool
1121
	 */
1122
	public function isDisabled() {
1123
		return $this->disabled;
1124
	}
1125
1126
	/**
1127
	 * Sets a disabled flag on this FormField.
1128
	 *
1129
	 * Use performDisabledTransformation() to transform this instance.
1130
	 *
1131
	 * Setting this to false has no effect on the field.
1132
	 *
1133
	 * @param bool $disabled
1134
	 *
1135
	 * @return $this
1136
	 */
1137
	public function setDisabled($disabled) {
1138
		$this->disabled = $disabled;
1139
1140
		return $this;
1141
	}
1142
1143
	/**
1144
	 * Returns a read-only version of this field.
1145
	 *
1146
	 * @return FormField
1147
	 */
1148
	public function performReadonlyTransformation() {
1149
		$readonlyClassName = $this->class . '_Readonly';
1150
1151
		if(ClassInfo::exists($readonlyClassName)) {
1152
			$clone = $this->castedCopy($readonlyClassName);
1153
		} else {
1154
			$clone = $this->castedCopy('SilverStripe\\Forms\\ReadonlyField');
1155
		}
1156
1157
		$clone->setReadonly(true);
1158
1159
		return $clone;
1160
	}
1161
1162
	/**
1163
	 * Return a disabled version of this field.
1164
	 *
1165
	 * Tries to find a class of the class name of this field suffixed with "_Disabled", failing
1166
	 * that, finds a method {@link setDisabled()}.
1167
	 *
1168
	 * @return FormField
1169
	 */
1170
	public function performDisabledTransformation() {
1171
		$disabledClassName = $this->class . '_Disabled';
1172
1173
		if(ClassInfo::exists($disabledClassName)) {
1174
			$clone = $this->castedCopy($disabledClassName);
1175
		} else {
1176
			$clone = clone $this;
1177
		}
1178
1179
		$clone->setDisabled(true);
1180
1181
		return $clone;
1182
	}
1183
1184
	/**
1185
	 * @param FormTransformation $transformation
1186
	 *
1187
	 * @return mixed
1188
	 */
1189
	public function transform(FormTransformation $transformation) {
1190
		return $transformation->transform($this);
1191
	}
1192
1193
	/**
1194
	 * @param string $class
1195
	 *
1196
	 * @return int
1197
	 */
1198
	public function hasClass($class) {
1199
		$patten = '/' . strtolower($class) . '/i';
1200
1201
		$subject = strtolower($this->class . ' ' . $this->extraClass());
1202
1203
		return preg_match($patten, $subject);
1204
	}
1205
1206
	/**
1207
	 * Returns the field type.
1208
	 *
1209
	 * The field type is the class name with the word Field dropped off the end, all lowercase.
1210
	 *
1211
	 * It's handy for assigning HTML classes. Doesn't signify the <input type> attribute.
1212
	 *
1213
	 * @see {link getAttributes()}.
1214
	 *
1215
	 * @return string
1216
	 */
1217
	public function Type() {
1218
		$type = new ReflectionClass($this);
1219
		return strtolower(preg_replace('/Field$/', '', $type->getShortName()));
1220
	}
1221
1222
	/**
1223
	 * @deprecated 4.0 Use FormField::create_tag()
1224
	 *
1225
	 * @param string $tag
1226
	 * @param array $attributes
1227
	 * @param null|string $content
1228
	 *
1229
	 * @return string
1230
	 */
1231
	public function createTag($tag, $attributes, $content = null) {
1232
		Deprecation::notice('4.0', 'Use FormField::create_tag()');
1233
1234
		return self::create_tag($tag, $attributes, $content);
1235
	}
1236
1237
	/**
1238
	 * Abstract method each {@link FormField} subclass must implement, determines whether the field
1239
	 * is valid or not based on the value.
1240
	 *
1241
	 * @todo Make this abstract.
1242
	 *
1243
	 * @param Validator $validator
1244
	 * @return bool
1245
	 */
1246
	public function validate($validator) {
1247
		return true;
1248
	}
1249
1250
	/**
1251
	 * Describe this field, provide help text for it.
1252
	 *
1253
	 * By default, renders as a <span class="description"> underneath the form field.
1254
	 *
1255
	 * @param string $description
1256
	 *
1257
	 * @return $this
1258
	 */
1259
	public function setDescription($description) {
1260
		$this->description = $description;
1261
1262
		return $this;
1263
	}
1264
1265
	/**
1266
	 * @return string
1267
	 */
1268
	public function getDescription() {
1269
		return $this->description;
1270
	}
1271
1272
	/**
1273
	 * @return string
1274
	 */
1275
	public function debug() {
1276
		return sprintf(
1277
			'%s (%s: %s : <span style="color:red;">%s</span>) = %s',
1278
			$this->class,
1279
			$this->name,
1280
			$this->title,
1281
			$this->message,
1282
			$this->value
1283
		);
1284
	}
1285
1286
	/**
1287
	 * This function is used by the template processor. If you refer to a field as a $ variable, it
1288
	 * will return the $Field value.
1289
	 *
1290
	 * @return string
1291
	 */
1292
	public function forTemplate() {
1293
		return $this->Field();
1294
	}
1295
1296
	/**
1297
	 * @return bool
1298
	 */
1299
	public function Required() {
1300
		if($this->form && ($validator = $this->form->getValidator())) {
1301
			return $validator->fieldIsRequired($this->name);
1302
		}
1303
1304
		return false;
1305
	}
1306
1307
	/**
1308
	 * Set the FieldList that contains this field.
1309
	 *
1310
	 * @param FieldList $containerFieldList
1311
	 * @return $this
1312
	 */
1313
	public function setContainerFieldList($containerFieldList) {
1314
		$this->containerFieldList = $containerFieldList;
1315
		return $this;
1316
	}
1317
1318
	/**
1319
	 * Get the FieldList that contains this field.
1320
	 *
1321
	 * @return FieldList
1322
	 */
1323
	public function getContainerFieldList() {
1324
		return $this->containerFieldList;
1325
	}
1326
1327
	/**
1328
	 * @return null|FieldList
1329
	 */
1330
	public function rootFieldList() {
1331
		if(is_object($this->containerFieldList)) {
1332
			return $this->containerFieldList->rootFieldList();
1333
		}
1334
1335
		user_error(
1336
			"rootFieldList() called on $this->class object without a containerFieldList",
1337
			E_USER_ERROR
1338
		);
1339
1340
		return null;
1341
	}
1342
1343
	/**
1344
	 * Returns another instance of this field, but "cast" to a different class. The logic tries to
1345
	 * retain all of the instance properties, and may be overloaded by subclasses to set additional
1346
	 * ones.
1347
	 *
1348
	 * Assumes the standard FormField parameter signature with its name as the only mandatory
1349
	 * argument. Mainly geared towards creating *_Readonly or *_Disabled subclasses of the same
1350
	 * type, or casting to a {@link ReadonlyField}.
1351
	 *
1352
	 * Does not copy custom field templates, since they probably won't apply to the new instance.
1353
	 *
1354
	 * @param mixed $classOrCopy Class name for copy, or existing copy instance to update
1355
	 *
1356
	 * @return FormField
1357
	 */
1358
	public function castedCopy($classOrCopy) {
1359
		$field = $classOrCopy;
1360
1361
		if(!is_object($field)) {
1362
			$field = new $classOrCopy($this->name);
1363
		}
1364
1365
		$field
1366
			->setValue($this->value)
1367
			->setForm($this->form)
1368
			->setTitle($this->Title())
1369
			->setLeftTitle($this->LeftTitle())
1370
			->setRightTitle($this->RightTitle())
1371
			->addExtraClass($this->extraClass) // Don't use extraClass(), since this merges calculated values
1372
			->setDescription($this->getDescription());
1373
1374
		// Only include built-in attributes, ignore anything set through getAttributes().
1375
		// Those might change important characteristics of the field, e.g. its "type" attribute.
1376
		foreach($this->attributes as $attributeKey => $attributeValue) {
1377
			$field->setAttribute($attributeKey, $attributeValue);
1378
		}
1379
1380
		return $field;
1381
	}
1382
1383
	/**
1384
	 * Sets the component type the FormField will be rendered as on the front-end.
1385
	 *
1386
	 * @param string $componentType
1387
	 * @return FormField
1388
	 */
1389
	public function setSchemaComponent($componentType) {
1390
		$this->schemaComponent = $componentType;
1391
		return $this;
1392
	}
1393
1394
	/**
1395
	 * Gets the type of front-end component the FormField will be rendered as.
1396
	 *
1397
	 * @return string
1398
	 */
1399
	public function getSchemaComponent() {
1400
		return $this->schemaComponent;
1401
	}
1402
1403
	/**
1404
	 * Sets the schema data used for rendering the field on the front-end.
1405
	 * Merges the passed array with the current `$schemaData` or {@link getSchemaDataDefaults()}.
1406
	 * Any passed keys that are not defined in {@link getSchemaDataDefaults()} are ignored.
1407
	 * If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
1408
	 *
1409
	 * @param array $schemaData - The data to be merged with $this->schemaData.
1410
	 * @return FormField
1411
	 *
1412
	 * @todo Add deep merging of arrays like `data` and `attributes`.
1413
	 */
1414
	public function setSchemaData($schemaData = []) {
1415
		$defaults = $this->getSchemaData();
1416
		$this->schemaData = array_merge($this->schemaData, array_intersect_key($schemaData, $defaults));
1417
		return $this;
1418
	}
1419
1420
	/**
1421
	 * Gets the schema data used to render the FormField on the front-end.
1422
	 *
1423
	 * @return array
1424
	 */
1425
	public function getSchemaData() {
1426
		$defaults = $this->getSchemaDataDefaults();
1427
		return array_replace_recursive($defaults, array_intersect_key($this->schemaData, $defaults));
1428
	}
1429
1430
	/**
1431
	 * @todo Throw exception if value is missing, once a form field schema is mandatory across the CMS
1432
	 *
1433
	 * @return string
1434
	 */
1435
	public function getSchemaDataType() {
1436
		return $this->schemaDataType;
1437
	}
1438
1439
	/**
1440
	 * Gets the defaults for $schemaData.
1441
	 * The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaData()} are ignored.
1442
	 * Instead the `data` array should be used to pass around ad hoc data.
1443
	 *
1444
	 * @return array
1445
	 */
1446
	public function getSchemaDataDefaults() {
1447
		return [
1448
			'name' => $this->getName(),
1449
			'id' => $this->ID(),
1450
			'type' => $this->getSchemaDataType(),
1451
			'component' => $this->getSchemaComponent(),
1452
			'holderId' => $this->HolderID(),
1453
			'title' => $this->Title(),
1454
			'source' => null,
1455
			'extraClass' => $this->extraClass(),
1456
			'description' => $this->obj('Description')->getSchemaValue(),
1457
			'rightTitle' => $this->RightTitle(),
1458
			'leftTitle' => $this->LeftTitle(),
1459
			'readOnly' => $this->isReadonly(),
1460
			'disabled' => $this->isDisabled(),
1461
			'customValidationMessage' => $this->getCustomValidationMessage(),
1462
			'validation' => $this->getSchemaValidation(),
1463
			'attributes' => [],
1464
			'data' => [],
1465
		];
1466
	}
1467
1468
	/**
1469
	 * Sets the schema data used for rendering the field on the front-end.
1470
	 * Merges the passed array with the current `$schemaState` or {@link getSchemaStateDefaults()}.
1471
	 * Any passed keys that are not defined in {@link getSchemaStateDefaults()} are ignored.
1472
	 * If you want to pass around ad hoc data use the `data` array e.g. pass `['data' => ['myCustomKey' => 'yolo']]`.
1473
	 *
1474
	 * @param array $schemaState The data to be merged with $this->schemaData.
1475
	 * @return FormField
1476
	 *
1477
	 * @todo Add deep merging of arrays like `data` and `attributes`.
1478
	 */
1479
	public function setSchemaState($schemaState = []) {
1480
		$defaults = $this->getSchemaState();
1481
		$this->schemaState = array_merge($this->schemaState, array_intersect_key($schemaState, $defaults));
1482
		return $this;
1483
	}
1484
1485
	/**
1486
	 * Gets the schema state used to render the FormField on the front-end.
1487
	 *
1488
	 * @return array
1489
	 */
1490
	public function getSchemaState() {
1491
		$defaults = $this->getSchemaStateDefaults();
1492
		return array_merge($defaults, array_intersect_key($this->schemaState, $defaults));
1493
	}
1494
1495
	/**
1496
	 * Gets the defaults for $schemaState.
1497
	 * The keys defined here are immutable, meaning undefined keys passed to {@link setSchemaState()} are ignored.
1498
	 * Instead the `data` array should be used to pass around ad hoc data.
1499
	 * Includes validation data if the field is associated to a {@link Form},
1500
	 * and {@link Form->validate()} has been called.
1501
	 *
1502
	 * @todo Make form / field messages not always stored as html; Store value / casting as separate values.
1503
	 * @return array
1504
	 */
1505
	public function getSchemaStateDefaults() {
1506
		$state = [
1507
            'name' => $this->getName(),
1508
			'id' => $this->ID(),
1509
			'value' => $this->Value(),
1510
			'message' => null,
1511
			'data' => [],
1512
		];
1513
1514
		if ($message = $this->Message()) {
1515
			$state['message'] = [
1516
				'value' => ['html' => $message],
1517
				'type' => $this->MessageType(),
1518
			];
1519
		}
1520
1521
		return $state;
1522
	}
1523
1524
	/**
1525
	 * Return list of validation rules. Each rule is a key value pair.
1526
	 * The key is the rule name. The value is any information the frontend
1527
	 * validation handler can understand, or just `true` to enable.
1528
	 *
1529
	 * @return array
1530
	 */
1531
	public function getSchemaValidation() {
1532
		if ($this->Required()) {
1533
			return [ 'required' => true ];
1534
		}
1535
		return [];
1536
	}
1537
1538
}
1539