Completed
Push — master ( c130e5...a809e8 )
by Sam
10:09
created

FieldList::setForm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use SilverStripe\ORM\ArrayList;
4
/**
5
 * A list designed to hold form field instances.
6
 *
7
 * @package    forms
8
 * @subpackage fields-structural
9
 */
10
class FieldList extends ArrayList {
11
12
	/**
13
	 * Cached flat representation of all fields in this set,
14
	 * including fields nested in {@link CompositeFields}.
15
	 *
16
	 * @uses self::collateDataFields()
17
	 * @var array
18
	 */
19
	protected $sequentialSet;
20
21
	/**
22
	 * @var array
23
	 */
24
	protected $sequentialSaveableSet;
25
26
	/**
27
	 * @todo Documentation
28
	 */
29
	protected $containerField;
30
31
	/**
32
	 * @var array Ordered list of regular expressions,
33
	 * see {@link setTabPathRewrites()}.
34
	 */
35
	protected $tabPathRewrites = array();
36
37
	public function __construct($items = array()) {
38
		if (!is_array($items) || func_num_args() > 1) {
39
			$items = func_get_args();
40
		}
41
42
		parent::__construct($items);
43
44
		foreach ($items as $item) {
45
			if ($item instanceof FormField) $item->setContainerFieldList($this);
46
		}
47
	}
48
49
	public function __clone() {
50
		// Clone all fields in this list
51
		foreach($this->items as $key => $field) {
52
			$this->items[$key] = clone $field;
53
		}
54
	}
55
56
	/**
57
	 * Return a sequential set of all fields that have data.  This excludes wrapper composite fields
58
	 * as well as heading / help text fields.
59
	 */
60
	public function dataFields() {
61
		if(!$this->sequentialSet) $this->collateDataFields($this->sequentialSet);
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sequentialSet 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...
62
		return $this->sequentialSet;
63
	}
64
65
	public function saveableFields() {
66
		if(!$this->sequentialSaveableSet) $this->collateDataFields($this->sequentialSaveableSet, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sequentialSaveableSet 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...
67
		return $this->sequentialSaveableSet;
68
	}
69
70
	protected function flushFieldsCache() {
71
		$this->sequentialSet = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $sequentialSet.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
72
		$this->sequentialSaveableSet = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $sequentialSaveableSet.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
73
	}
74
75
	protected function collateDataFields(&$list, $saveableOnly = false) {
76
		foreach($this as $field) {
77
			if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
78
79
			if($saveableOnly) {
80
				$isIncluded =  ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
81
			} else {
82
				$isIncluded =  ($field->hasData());
83
			}
84
			if($isIncluded) {
85
				$name = $field->getName();
86
				if(isset($list[$name])) {
87
					$errSuffix = "";
88
					if($this->form) {
89
						$errSuffix = " in your '{$this->form->class}' form called '" . $this->form->Name() . "'";
90
					} else {
91
						$errSuffix = '';
92
					}
93
					user_error("collateDataFields() I noticed that a field called '$name' appears twice$errSuffix.",
94
						E_USER_ERROR);
95
				}
96
				$list[$name] = $field;
97
			}
98
		}
99
	}
100
101
	/**
102
	 * Add an extra field to a tab within this FieldList.
103
	 * This is most commonly used when overloading getCMSFields()
104
	 *
105
	 * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab
106
	 *                        or TabSet.Tab.Subtab. This function will create any missing tabs.
107
	 * @param FormField $field The {@link FormField} object to add to the end of that tab.
108
	 * @param string $insertBefore The name of the field to insert before.  Optional.
109
	 */
110
	public function addFieldToTab($tabName, $field, $insertBefore = null) {
111
		// This is a cache that must be flushed
112
		$this->flushFieldsCache();
113
114
		// Find the tab
115
		$tab = $this->findOrMakeTab($tabName);
116
117
		// Add the field to the end of this set
118
		if($insertBefore) $tab->insertBefore($insertBefore, $field);
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertBefore 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...
119
		else $tab->push($field);
120
	}
121
122
	/**
123
	 * Add a number of extra fields to a tab within this FieldList.
124
	 * This is most commonly used when overloading getCMSFields()
125
	 *
126
	 * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab
127
	 *                        or TabSet.Tab.Subtab.
128
	 * This function will create any missing tabs.
129
	 * @param array $fields An array of {@link FormField} objects.
130
	 */
131
	public function addFieldsToTab($tabName, $fields, $insertBefore = null) {
132
		$this->flushFieldsCache();
133
134
		// Find the tab
135
		$tab = $this->findOrMakeTab($tabName);
136
137
		// Add the fields to the end of this set
138
		foreach($fields as $field) {
139
			// Check if a field by the same name exists in this tab
140
			if($insertBefore) {
141
				$tab->insertBefore($insertBefore, $field);
142
			} elseif(($name = $field->getName()) && $tab->fieldByName($name)) {
143
				// It exists, so we need to replace the old one
144
				$this->replaceField($field->getName(), $field);
145
			} else {
146
				$tab->push($field);
147
			}
148
		}
149
	}
150
151
	/**
152
	 * Remove the given field from the given tab in the field.
153
	 *
154
	 * @param string $tabName The name of the tab
155
	 * @param string $fieldName The name of the field
156
	 */
157
	public function removeFieldFromTab($tabName, $fieldName) {
158
		$this->flushFieldsCache();
159
160
		// Find the tab
161
		$tab = $this->findOrMakeTab($tabName);
162
		$tab->removeByName($fieldName);
163
	}
164
165
	/**
166
	 * Removes a number of fields from a Tab/TabSet within this FieldList.
167
	 *
168
	 * @param string $tabName The name of the Tab or TabSet field
169
	 * @param array $fields A list of fields, e.g. array('Name', 'Email')
170
	 */
171
	public function removeFieldsFromTab($tabName, $fields) {
172
		$this->flushFieldsCache();
173
174
		// Find the tab
175
		$tab = $this->findOrMakeTab($tabName);
176
177
		// Add the fields to the end of this set
178
		foreach($fields as $field) $tab->removeByName($field);
179
	}
180
181
	/**
182
	 * Remove a field or fields from this FieldList by Name.
183
	 * The field could also be inside a CompositeField.
184
	 *
185
	 * @param string|array $fieldName The name of, or an array with the field(s) or tab(s)
186
	 * @param boolean $dataFieldOnly If this is true, then a field will only
187
	 * be removed if it's a data field.  Dataless fields, such as tabs, will
188
	 * be left as-is.
189
	 */
190
	public function removeByName($fieldName, $dataFieldOnly = false) {
191
		if(!$fieldName) {
192
			user_error('FieldList::removeByName() was called with a blank field name.', E_USER_WARNING);
193
		}
194
195
		// Handle array syntax
196
		if(is_array($fieldName)) {
197
			foreach($fieldName as $field){
198
				$this->removeByName($field, $dataFieldOnly);
199
			}
200
			return;
201
		}
202
203
		$this->flushFieldsCache();
204
		foreach($this->items as $i => $child) {
205
			if(is_object($child)){
206
				$childName = $child->getName();
207
				if(!$childName) $childName = $child->Title();
208
209
				if(($childName == $fieldName) && (!$dataFieldOnly || $child->hasData())) {
210
					array_splice( $this->items, $i, 1 );
211
					break;
212
				} else if($child->isComposite()) {
213
					$child->removeByName($fieldName, $dataFieldOnly);
214
				}
215
			}
216
		}
217
	}
218
219
	/**
220
	 * Replace a single field with another.  Ignores dataless fields such as Tabs and TabSets
221
	 *
222
	 * @param string $fieldName The name of the field to replace
223
	 * @param FormField $newField The field object to replace with
224
	 * @return boolean TRUE field was successfully replaced
225
	 * 					 FALSE field wasn't found, nothing changed
226
	 */
227
	public function replaceField($fieldName, $newField) {
228
		$this->flushFieldsCache();
229
		foreach($this->items as $i => $field) {
230
			if(is_object($field)) {
231
				if($field->getName() == $fieldName && $field->hasData()) {
232
					$this->items[$i] = $newField;
233
					return true;
234
235
				} else if($field->isComposite()) {
236
					if($field->replaceField($fieldName, $newField)) return true;
237
				}
238
			}
239
		}
240
		return false;
241
	}
242
243
	/**
244
	 * Rename the title of a particular field name in this set.
245
	 *
246
	 * @param string $fieldName Name of field to rename title of
247
	 * @param string $newFieldTitle New title of field
248
	 * @return boolean
249
	 */
250
	public function renameField($fieldName, $newFieldTitle) {
251
		$field = $this->dataFieldByName($fieldName);
252
		if(!$field) return false;
253
254
		$field->setTitle($newFieldTitle);
255
256
		return $field->Title() == $newFieldTitle;
257
	}
258
259
	/**
260
	 * @return boolean
261
	 */
262
	public function hasTabSet() {
263
		foreach($this->items as $i => $field) {
264
			if(is_object($field) && $field instanceof TabSet) {
265
				return true;
266
			}
267
		}
268
269
		return false;
270
	}
271
272
	/**
273
	 * Returns the specified tab object, creating it if necessary.
274
	 *
275
	 * @todo Support recursive creation of TabSets
276
	 *
277
	 * @param string $tabName The tab to return, in the form "Tab.Subtab.Subsubtab".
278
	 *   Caution: Does not recursively create TabSet instances, you need to make sure everything
279
	 *   up until the last tab in the chain exists.
280
	 * @param string $title Natural language title of the tab. If {@link $tabName} is passed in dot notation,
281
	 *   the title parameter will only apply to the innermost referenced tab.
282
	 *   The title is only changed if the tab doesn't exist already.
283
	 * @return Tab The found or newly created Tab instance
284
	 */
285
	public function findOrMakeTab($tabName, $title = null) {
286
		// Backwards compatibility measure: Allow rewriting of outdated tab paths
287
		$tabName = $this->rewriteTabPath($tabName);
288
289
		$parts = explode('.',$tabName);
290
		$last_idx = count($parts) - 1;
291
		// We could have made this recursive, but I've chosen to keep all the logic code within FieldList rather than
292
		// add it to TabSet and Tab too.
293
		$currentPointer = $this;
294
		foreach($parts as $k => $part) {
295
			$parentPointer = $currentPointer;
296
			$currentPointer = $currentPointer->fieldByName($part);
297
			// Create any missing tabs
298
			if(!$currentPointer) {
299
				if(is_a($parentPointer, 'TabSet')) {
300
					// use $title on the innermost tab only
301
					if ($k == $last_idx) {
302
						$currentPointer = isset($title) ? new Tab($part, $title) : new Tab($part);
303
					}
304
					else {
305
						$currentPointer = new TabSet($part);
306
					}
307
					$parentPointer->push($currentPointer);
308
				}
309
				else {
310
					$withName = ($parentPointer->hasMethod('Name')) ? " named '{$parentPointer->getName()}'" : null;
311
					user_error("FieldList::addFieldToTab() Tried to add a tab to object"
312
						. " '{$parentPointer->class}'{$withName} - '$part' didn't exist.", E_USER_ERROR);
313
				}
314
			}
315
		}
316
317
		return $currentPointer;
318
	}
319
320
	/**
321
	 * Returns a named field.
322
	 * You can use dot syntax to get fields from child composite fields
323
	 *
324
	 * @todo Implement similarly to dataFieldByName() to support nested sets - or merge with dataFields()
325
	 *
326
	 * @param string $name
327
	 * @return FormField
328
	 */
329
	public function fieldByName($name) {
330
		$name = $this->rewriteTabPath($name);
331
		if(strpos($name,'.') !== false)	list($name, $remainder) = explode('.',$name,2);
332
		else $remainder = null;
333
334
		foreach($this->items as $child) {
335
			if(trim($name) == trim($child->getName()) || $name == $child->id) {
336
				if($remainder) {
337
					if($child->isComposite()) {
338
						return $child->fieldByName($remainder);
339
					} else {
340
						user_error("Trying to get field '$remainder' from non-composite field $child->class.$name",
341
							E_USER_WARNING);
342
						return null;
343
					}
344
				} else {
345
					return $child;
346
				}
347
			}
348
		}
349
	}
350
351
	/**
352
	 * Returns a named field in a sequential set.
353
	 * Use this if you're using nested FormFields.
354
	 *
355
	 * @param string $name The name of the field to return
356
	 * @return FormField instance
357
	 */
358
	public function dataFieldByName($name) {
359
		if($dataFields = $this->dataFields()) {
360
			foreach($dataFields as $child) {
361
				if(trim($name) == trim($child->getName()) || $name == $child->id) return $child;
362
			}
363
		}
364
	}
365
366
	/**
367
	 * Inserts a field before a particular field in a FieldList.
368
	 *
369
	 * @param string $name Name of the field to insert before
370
	 * @param FormField $item The form field to insert
371
	 * @return FormField|false
372
	 */
373
	public function insertBefore($name, $item) {
374
		// Backwards compatibility for order of arguments
375
		if($name instanceof FormField) {
376
			list($item, $name) = array($name, $item);
377
		}
378
		$this->onBeforeInsert($item);
379
		$item->setContainerFieldList($this);
380
381
		$i = 0;
382
		foreach($this->items as $child) {
383
			if($name == $child->getName() || $name == $child->id) {
384
				array_splice($this->items, $i, 0, array($item));
385
				return $item;
386
			} elseif($child->isComposite()) {
387
				$ret = $child->insertBefore($name, $item);
388
				if($ret) return $ret;
389
			}
390
			$i++;
391
		}
392
393
		return false;
394
	}
395
396
	/**
397
	 * Inserts a field after a particular field in a FieldList.
398
	 *
399
	 * @param string $name Name of the field to insert after
400
	 * @param FormField $item The form field to insert
401
	 * @return FormField|false
402
	 */
403
	public function insertAfter($name, $item) {
404
		// Backwards compatibility for order of arguments
405
		if($name instanceof FormField) {
406
			list($item, $name) = array($name, $item);
407
		}
408
		$this->onBeforeInsert($item);
409
		$item->setContainerFieldList($this);
410
411
		$i = 0;
412
		foreach($this->items as $child) {
413
			if($name == $child->getName() || $name == $child->id) {
414
				array_splice($this->items, $i+1, 0, array($item));
415
				return $item;
416
			} elseif($child->isComposite()) {
417
				$ret = $child->insertAfter($name, $item);
418
				if($ret) return $ret;
419
			}
420
			$i++;
421
		}
422
423
		return false;
424
	}
425
426
	/**
427
	 * Push a single field onto the end of this FieldList instance.
428
	 *
429
	 * @param FormField $item The FormField to add
430
	 */
431
	public function push($item) {
432
		$this->onBeforeInsert($item);
433
		$item->setContainerFieldList($this);
434
435
		return parent::push($item);
436
	}
437
438
	/**
439
	 * Push a single field onto the beginning of this FieldList instance.
440
	 *
441
	 * @param FormField $item The FormField to add
442
	 */
443
	public function unshift($item) {
444
		$this->onBeforeInsert($item);
445
		$item->setContainerFieldList($this);
446
447
		return parent::unshift($item);
448
	}
449
450
	/**
451
	 * Handler method called before the FieldList is going to be manipulated.
452
	 */
453
	protected function onBeforeInsert($item) {
454
		$this->flushFieldsCache();
455
456
		if($item->getName()) {
457
			$this->rootFieldList()->removeByName($item->getName(), true);
458
		}
459
	}
460
461
462
	/**
463
	 * Set the Form instance for this FieldList.
464
	 *
465
	 * @param Form $form The form to set this FieldList to
466
	 */
467
	public function setForm($form) {
468
		foreach($this as $field) {
469
			$field->setForm($form);
470
		}
471
472
		return $this;
473
	}
474
475
	/**
476
	 * Load the given data into this form.
477
	 *
478
	 * @param data An map of data to load into the FieldList
479
	 */
480
	public function setValues($data) {
481
		foreach($this->dataFields() as $field) {
482
			$fieldName = $field->getName();
483
			if(isset($data[$fieldName])) $field->setValue($data[$fieldName]);
484
		}
485
		return $this;
486
	}
487
488
	/**
489
	 * Return all <input type="hidden"> fields
490
	 * in a form - including fields nested in {@link CompositeFields}.
491
	 * Useful when doing custom field layouts.
492
	 *
493
	 * @return FieldList
494
	 */
495
	public function HiddenFields() {
496
		$hiddenFields = new FieldList();
497
		$dataFields = $this->dataFields();
498
499
		if($dataFields) foreach($dataFields as $field) {
500
			if($field instanceof HiddenField) $hiddenFields->push($field);
501
		}
502
503
		return $hiddenFields;
504
	}
505
506
	/**
507
	 * Return all fields except for the hidden fields.
508
	 * Useful when making your own simplified form layouts.
509
	 */
510
	public function VisibleFields() {
511
		$visibleFields = new FieldList();
512
513
		foreach($this as $field) {
514
			if(!($field instanceof HiddenField)) $visibleFields->push($field);
515
		}
516
517
		return $visibleFields;
518
	}
519
520
	/**
521
	 * Transform this FieldList with a given tranform method,
522
	 * e.g. $this->transform(new ReadonlyTransformation())
523
	 *
524
	 * @return FieldList
525
	 */
526
	public function transform($trans) {
527
		$this->flushFieldsCache();
528
		$newFields = new FieldList();
529
		foreach($this as $field) {
530
			$newFields->push($field->transform($trans));
531
		}
532
		return $newFields;
533
	}
534
535
	/**
536
	 * Returns the root field set that this belongs to
537
	 */
538
	public function rootFieldList() {
539
		if($this->containerField) return $this->containerField->rootFieldList();
540
		else return $this;
541
	}
542
543
	public function setContainerField($field) {
544
		$this->containerField = $field;
545
		return $this;
546
	}
547
548
	/**
549
	 * Transforms this FieldList instance to readonly.
550
	 *
551
	 * @return FieldList
552
	 */
553
	public function makeReadonly() {
554
		return $this->transform(new ReadonlyTransformation());
555
	}
556
557
	/**
558
	 * Transform the named field into a readonly feld.
559
	 *
560
	 * @param string|FormField
561
	 */
562
	public function makeFieldReadonly($field) {
563
		$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
564
		$srcField = $this->dataFieldByName($fieldName);
565
		$this->replaceField($fieldName, $srcField->performReadonlyTransformation());
566
	}
567
568
	/**
569
	 * Change the order of fields in this FieldList by specifying an ordered list of field names.
570
	 * This works well in conjunction with SilverStripe's scaffolding functions: take the scaffold, and
571
	 * shuffle the fields around to the order that you want.
572
	 *
573
	 * Please note that any tabs or other dataless fields will be clobbered by this operation.
574
	 *
575
	 * @param array $fieldNames Field names can be given as an array, or just as a list of arguments.
576
	 */
577
	public function changeFieldOrder($fieldNames) {
578
		// Field names can be given as an array, or just as a list of arguments.
579
		if(!is_array($fieldNames)) $fieldNames = func_get_args();
580
581
		// Build a map of fields indexed by their name.  This will make the 2nd step much easier.
582
		$fieldMap = array();
583
		foreach($this->dataFields() as $field) $fieldMap[$field->getName()] = $field;
584
585
		// Iterate through the ordered list	of names, building a new array to be put into $this->items.
586
		// While we're doing this, empty out $fieldMap so that we can keep track of leftovers.
587
		// Unrecognised field names are okay; just ignore them
588
		$fields = array();
589
		foreach($fieldNames as $fieldName) {
590
			if(isset($fieldMap[$fieldName])) {
591
				$fields[] = $fieldMap[$fieldName];
592
				unset($fieldMap[$fieldName]);
593
			}
594
		}
595
596
		// Add the leftover fields to the end of the list.
597
		$fields = array_values($fields + $fieldMap);
598
599
		// Update our internal $this->items parameter.
600
		$this->items = $fields;
601
602
		$this->flushFieldsCache();
603
	}
604
605
	/**
606
	 * Find the numerical position of a field within
607
	 * the children collection. Doesn't work recursively.
608
	 *
609
	 * @param string|FormField
610
	 * @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
611
	 *             be found.
612
	 */
613
	public function fieldPosition($field) {
614
		if(is_object($field)) $field = $field->getName();
615
616
		$i = 0;
617
		foreach($this->dataFields() as $child) {
618
			if($child->getName() == $field) return $i;
619
			$i++;
620
		}
621
622
		return false;
623
	}
624
625
	/**
626
	 * Ordered list of regular expressions
627
	 * matching a tab path, to their rewrite rule (through preg_replace()).
628
	 * Mainly used for backwards compatibility.
629
	 * Ensure that more specific rules are placed earlier in the list,
630
	 * and that tabs with children are accounted for in the rule sets.
631
	 *
632
	 * Example:
633
	 * $fields->setTabPathRewriting(array(
634
	 * 	// Rewrite specific innermost tab
635
	 * 	'/^Root\.Content\.Main$/' => 'Root.Content',
636
	 * 	// Rewrite all innermost tabs
637
	 * 	'/^Root\.Content\.([^.]+)$/' => 'Root.\\1',
638
	 * ));
639
	 *
640
	 * @param array $rewrites
641
	 */
642
	public function setTabPathRewrites($rewrites) {
643
		$this->tabPathRewrites = $rewrites;
644
	}
645
646
	/**
647
	 * @return array
648
	 */
649
	public function getTabPathRewrites() {
650
		return $this->tabPathRewrites;
651
	}
652
653
	/**
654
	 * Support function for backwards compatibility purposes.
655
	 * Caution: Volatile API, might be removed in 3.1 or later.
656
	 *
657
	 * @param  String $tabname Path to a tab, e.g. "Root.Content.Main"
0 ignored issues
show
Bug introduced by
There is no parameter named $tabname. 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...
658
	 * @return String Rewritten path, based on {@link tabPathRewrites}
659
	 */
660
	protected function rewriteTabPath($name) {
661
		foreach($this->getTabPathRewrites() as $regex => $replace) {
662
			if(preg_match($regex, $name)) {
663
				$newName = preg_replace($regex, $replace, $name);
664
				Deprecation::notice('3.0.0',
665
					sprintf(
666
						'Using outdated tab path "%s", please use the new location "%s" instead',
667
						$name,
668
						$newName
669
					),
670
					Deprecation::SCOPE_GLOBAL
671
				);
672
				return $newName;
673
			}
674
		}
675
676
		// No match found, return original path
677
		return $name;
678
	}
679
680
	/**
681
	 * Default template rendering of a FieldList will concatenate all FieldHolder values.
682
	 */
683
	public function forTemplate() {
684
		$output = "";
685
		foreach($this as $field) {
686
			$output .= $field->FieldHolder();
687
		}
688
		return $output;
689
	}
690
}
691