Passed
Push — nondestructive-enum ( eef0c9...88175c )
by Sam
07:14
created

FieldList::getContainerField()   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 SilverStripe\Dev\Deprecation;
6
use SilverStripe\ORM\ArrayList;
7
8
/**
9
 * A list designed to hold form field instances.
10
 *
11
 * @method FormField[] getIterator()
12
 */
13
class FieldList extends ArrayList
14
{
15
16
    /**
17
     * Cached flat representation of all fields in this set,
18
     * including fields nested in {@link CompositeFields}.
19
     *
20
     * @uses self::collateDataFields()
21
     * @var FormField[]
22
     */
23
    protected $sequentialSet;
24
25
    /**
26
     * @var FormField[]
27
     */
28
    protected $sequentialSaveableSet;
29
30
    /**
31
     * If this fieldlist is owned by a parent field (e.g. CompositeField)
32
     * this is the parent field.
33
     *
34
     * @var CompositeField
35
     */
36
    protected $containerField;
37
38
    public function __construct($items = array())
39
    {
40
        if (!is_array($items) || func_num_args() > 1) {
41
            $items = func_get_args();
42
        }
43
44
        parent::__construct($items);
45
46
        foreach ($items as $item) {
47
            if ($item instanceof FormField) {
48
                $item->setContainerFieldList($this);
49
            }
50
        }
51
    }
52
53
    public function __clone()
54
    {
55
        // Clone all fields in this list
56
        foreach ($this->items as $key => $field) {
57
            $this->items[$key] = clone $field;
58
        }
59
    }
60
61
    /**
62
     * Iterate over each field in the current list recursively
63
     *
64
     * @param callable $callback
65
     */
66
    public function recursiveWalk(callable $callback)
67
    {
68
        $stack = $this->toArray();
69
        while (!empty($stack)) {
70
            /** @var FormField $field */
71
            $field = array_shift($stack);
72
            $callback($field);
73
            if ($field instanceof CompositeField) {
74
                $stack = array_merge($field->getChildren()->toArray(), $stack);
75
            }
76
        }
77
    }
78
79
    /**
80
     * Return a flattened list of all fields
81
     *
82
     * @return static
83
     */
84
    public function flattenFields()
85
    {
86
        $fields = [];
87
        $this->recursiveWalk(function (FormField $field) use (&$fields) {
88
            $fields[] = $field;
89
        });
90
        return static::create($fields);
91
    }
92
93
    /**
94
     * Return a sequential set of all fields that have data.  This excludes wrapper composite fields
95
     * as well as heading / help text fields.
96
     *
97
     * @return FormField[]
98
     */
99
    public function dataFields()
100
    {
101
        if (empty($this->sequentialSet)) {
102
            $fields = [];
103
            $this->recursiveWalk(function (FormField $field) use (&$fields) {
104
                if (!$field->hasData()) {
105
                    return;
106
                }
107
                $name = $field->getName();
108
                if (isset($fields[$name])) {
109
                    $this->fieldNameError($field, __FUNCTION__);
110
                }
111
                $fields[$name] = $field;
112
            });
113
            $this->sequentialSet = $fields;
114
        }
115
        return $this->sequentialSet;
116
    }
117
118
    /**
119
     * @return FormField[]
120
     */
121
    public function saveableFields()
122
    {
123
        if (empty($this->sequentialSaveableSet)) {
124
            $fields = [];
125
            $this->recursiveWalk(function (FormField $field) use (&$fields) {
126
                if (!$field->canSubmitValue()) {
127
                    return;
128
                }
129
                $name = $field->getName();
130
                if (isset($fields[$name])) {
131
                    $this->fieldNameError($field, __FUNCTION__);
132
                }
133
                $fields[$name] = $field;
134
            });
135
            $this->sequentialSaveableSet = $fields;
136
        }
137
        return $this->sequentialSaveableSet;
138
    }
139
140
    /**
141
     * Return array of all field names
142
     *
143
     * @return array
144
     */
145
    public function dataFieldNames()
146
    {
147
        return array_keys($this->dataFields());
148
    }
149
150
    /**
151
     * Trigger an error for duplicate field names
152
     *
153
     * @param FormField $field
154
     * @param $functionName
155
     */
156
    protected function fieldNameError(FormField $field, $functionName)
157
    {
158
        if ($this->form) {
0 ignored issues
show
Bug Best Practice introduced by
The property form does not exist on SilverStripe\Forms\FieldList. Since you implemented __get, consider adding a @property annotation.
Loading history...
159
            $errorSuffix = sprintf(
160
                " in your '%s' form called '%s'",
161
                get_class($this->form),
162
                $this->form->getName()
163
            );
164
        } else {
165
            $errorSuffix = '';
166
        }
167
168
        user_error(
169
            sprintf(
170
                "%s() I noticed that a field called '%s' appears twice%s",
171
                $functionName,
172
                $field->getName(),
173
                $errorSuffix
174
            ),
175
            E_USER_ERROR
176
        );
177
    }
178
179
    protected function flushFieldsCache()
180
    {
181
        $this->sequentialSet = null;
182
        $this->sequentialSaveableSet = null;
183
    }
184
185
    /**
186
     * @deprecated 4.1.0:5.0.0 Please use dataFields or saveableFields
187
     * @param $list
188
     * @param bool $saveableOnly
189
     */
190
    protected function collateDataFields(&$list, $saveableOnly = false)
191
    {
192
        Deprecation::notice('5.0', 'Please use dataFields or SaveableFields');
193
        if (!isset($list)) {
194
            $list = array();
195
        }
196
        /** @var FormField $field */
197
        foreach ($this as $field) {
198
            if ($field instanceof CompositeField) {
199
                $field->collateDataFields($list, $saveableOnly);
200
            }
201
202
            if ($saveableOnly) {
203
                $isIncluded =  $field->canSubmitValue();
204
            } else {
205
                $isIncluded =  $field->hasData();
206
            }
207
            if ($isIncluded) {
208
                $name = $field->getName();
209
                if (isset($list[$name])) {
210
                    if ($this->form) {
0 ignored issues
show
Bug Best Practice introduced by
The property form does not exist on SilverStripe\Forms\FieldList. Since you implemented __get, consider adding a @property annotation.
Loading history...
211
                        $formClass = get_class($this->form);
212
                        $errSuffix = " in your '{$formClass}' form called '" . $this->form->Name() . "'";
213
                    } else {
214
                        $errSuffix = '';
215
                    }
216
                    user_error(
217
                        "collateDataFields() I noticed that a field called '$name' appears twice$errSuffix.",
218
                        E_USER_ERROR
219
                    );
220
                }
221
                $list[$name] = $field;
222
            }
223
        }
224
    }
225
226
    /**
227
     * Add an extra field to a tab within this FieldList.
228
     * This is most commonly used when overloading getCMSFields()
229
     *
230
     * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab
231
     *                        or TabSet.Tab.Subtab. This function will create any missing tabs.
232
     * @param FormField $field The {@link FormField} object to add to the end of that tab.
233
     * @param string $insertBefore The name of the field to insert before.  Optional.
234
     *
235
     * @return $this
236
     */
237
    public function addFieldToTab($tabName, $field, $insertBefore = null)
238
    {
239
        // This is a cache that must be flushed
240
        $this->flushFieldsCache();
241
242
        // Find the tab
243
        $tab = $this->findOrMakeTab($tabName);
244
245
        // Add the field to the end of this set
246
        if ($insertBefore) {
247
            $tab->insertBefore($insertBefore, $field);
248
        } else {
249
            $tab->push($field);
250
        }
251
252
        return $this;
253
    }
254
255
    /**
256
     * Add a number of extra fields to a tab within this FieldList.
257
     * This is most commonly used when overloading getCMSFields()
258
     *
259
     * @param string $tabName The name of the tab or tabset.  Subtabs can be referred to as TabSet.Tab
260
     *                        or TabSet.Tab.Subtab. This function will create any missing tabs.
261
     * @param array $fields An array of {@link FormField} objects.
262
     * @param string $insertBefore Name of field to insert before
263
     *
264
     * @return $this
265
     */
266
    public function addFieldsToTab($tabName, $fields, $insertBefore = null)
267
    {
268
        $this->flushFieldsCache();
269
270
        // Find the tab
271
        $tab = $this->findOrMakeTab($tabName);
272
273
        // Add the fields to the end of this set
274
        foreach ($fields as $field) {
275
            // Check if a field by the same name exists in this tab
276
            if ($insertBefore) {
277
                $tab->insertBefore($insertBefore, $field);
278
            } elseif (($name = $field->getName()) && $tab->fieldByName($name)) {
279
                // It exists, so we need to replace the old one
280
                $this->replaceField($field->getName(), $field);
281
            } else {
282
                $tab->push($field);
283
            }
284
        }
285
286
        return $this;
287
    }
288
289
    /**
290
     * Remove the given field from the given tab in the field.
291
     *
292
     * @param string $tabName The name of the tab
293
     * @param string $fieldName The name of the field
294
     *
295
     * @return $this
296
     */
297
    public function removeFieldFromTab($tabName, $fieldName)
298
    {
299
        $this->flushFieldsCache();
300
301
        // Find the tab
302
        $tab = $this->findTab($tabName);
303
        if ($tab) {
0 ignored issues
show
introduced by
$tab is of type SilverStripe\Forms\Tab, thus it always evaluated to true.
Loading history...
304
            $tab->removeByName($fieldName);
305
        }
306
307
        return $this;
308
    }
309
310
    /**
311
     * Removes a number of fields from a Tab/TabSet within this FieldList.
312
     *
313
     * @param string $tabName The name of the Tab or TabSet field
314
     * @param array $fields A list of fields, e.g. array('Name', 'Email')
315
     *
316
     * @return $this
317
     */
318
    public function removeFieldsFromTab($tabName, $fields)
319
    {
320
        $this->flushFieldsCache();
321
322
        // Find the tab
323
        $tab = $this->findTab($tabName);
324
        if ($tab) {
0 ignored issues
show
introduced by
$tab is of type SilverStripe\Forms\Tab, thus it always evaluated to true.
Loading history...
325
            // Add the fields to the end of this set
326
            foreach ($fields as $field) {
327
                $tab->removeByName($field);
328
            }
329
        }
330
331
        return $this;
332
    }
333
334
    /**
335
     * Remove a field or fields from this FieldList by Name.
336
     * The field could also be inside a CompositeField.
337
     *
338
     * @param string|array $fieldName The name of, or an array with the field(s) or tab(s)
339
     * @param boolean $dataFieldOnly If this is true, then a field will only
340
     * be removed if it's a data field.  Dataless fields, such as tabs, will
341
     * be left as-is.
342
     *
343
     * @return $this
344
     */
345
    public function removeByName($fieldName, $dataFieldOnly = false)
346
    {
347
        if (!$fieldName) {
348
            user_error('FieldList::removeByName() was called with a blank field name.', E_USER_WARNING);
349
        }
350
351
        // Handle array syntax
352
        if (is_array($fieldName)) {
353
            foreach ($fieldName as $field) {
354
                $this->removeByName($field, $dataFieldOnly);
355
            }
356
            return $this;
357
        }
358
359
        $this->flushFieldsCache();
360
        foreach ($this as $i => $child) {
361
            $childName = $child->getName();
362
            if (!$childName) {
363
                $childName = $child->Title();
364
            }
365
366
            if (($childName == $fieldName) && (!$dataFieldOnly || $child->hasData())) {
367
                array_splice($this->items, $i, 1);
368
                break;
369
            } elseif ($child instanceof CompositeField) {
370
                $child->removeByName($fieldName, $dataFieldOnly);
371
            }
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * Replace a single field with another.  Ignores dataless fields such as Tabs and TabSets
379
     *
380
     * @param string $fieldName The name of the field to replace
381
     * @param FormField $newField The field object to replace with
382
     * @return boolean TRUE field was successfully replaced
383
     *                   FALSE field wasn't found, nothing changed
384
     */
385
    public function replaceField($fieldName, $newField)
386
    {
387
        $this->flushFieldsCache();
388
        foreach ($this as $i => $field) {
389
            if ($field->getName() == $fieldName && $field->hasData()) {
390
                $this->items[$i] = $newField;
391
                return true;
392
            } elseif ($field instanceof CompositeField) {
393
                if ($field->replaceField($fieldName, $newField)) {
394
                    return true;
395
                }
396
            }
397
        }
398
        return false;
399
    }
400
401
    /**
402
     * Rename the title of a particular field name in this set.
403
     *
404
     * @param string $fieldName Name of field to rename title of
405
     * @param string $newFieldTitle New title of field
406
     * @return boolean
407
     */
408
    public function renameField($fieldName, $newFieldTitle)
409
    {
410
        $field = $this->dataFieldByName($fieldName);
411
        if (!$field) {
0 ignored issues
show
introduced by
$field is of type SilverStripe\Forms\FormField, thus it always evaluated to true.
Loading history...
412
            return false;
413
        }
414
415
        $field->setTitle($newFieldTitle);
416
417
        return $field->Title() == $newFieldTitle;
418
    }
419
420
    /**
421
     * @return boolean
422
     */
423
    public function hasTabSet()
424
    {
425
        foreach ($this->items as $i => $field) {
426
            if (is_object($field) && $field instanceof TabSet) {
427
                return true;
428
            }
429
        }
430
431
        return false;
432
    }
433
434
    /**
435
     * Returns the specified tab object, if it exists
436
     *
437
     * @param string $tabName The tab to return, in the form "Tab.Subtab.Subsubtab".
438
     * @return Tab|null The found or null
439
     */
440
    public function findTab($tabName)
441
    {
442
        $parts = explode('.', $tabName);
443
        $last_idx = count($parts) - 1;
0 ignored issues
show
Unused Code introduced by
The assignment to $last_idx is dead and can be removed.
Loading history...
444
445
        $currentPointer = $this;
446
447
        foreach ($parts as $k => $part) {
448
            $parentPointer = $currentPointer;
0 ignored issues
show
Unused Code introduced by
The assignment to $parentPointer is dead and can be removed.
Loading history...
449
            /** @var FormField $currentPointer */
450
            $currentPointer = $currentPointer->fieldByName($part);
0 ignored issues
show
Bug introduced by
The method fieldByName() does not exist on SilverStripe\Forms\FormField. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

450
            /** @scrutinizer ignore-call */ 
451
            $currentPointer = $currentPointer->fieldByName($part);
Loading history...
451
        }
452
453
        return $currentPointer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $currentPointer returns the type SilverStripe\Forms\FieldList which is incompatible with the documented return type SilverStripe\Forms\Tab|null.
Loading history...
454
    }
455
456
    /**
457
     * Returns the specified tab object, creating it if necessary.
458
     *
459
     * @todo Support recursive creation of TabSets
460
     *
461
     * @param string $tabName The tab to return, in the form "Tab.Subtab.Subsubtab".
462
     *   Caution: Does not recursively create TabSet instances, you need to make sure everything
463
     *   up until the last tab in the chain exists.
464
     * @param string $title Natural language title of the tab. If {@link $tabName} is passed in dot notation,
465
     *   the title parameter will only apply to the innermost referenced tab.
466
     *   The title is only changed if the tab doesn't exist already.
467
     * @return Tab The found or newly created Tab instance
468
     */
469
    public function findOrMakeTab($tabName, $title = null)
470
    {
471
        $parts = explode('.', $tabName);
472
        $last_idx = count($parts) - 1;
473
        // We could have made this recursive, but I've chosen to keep all the logic code within FieldList rather than
474
        // add it to TabSet and Tab too.
475
        $currentPointer = $this;
476
        foreach ($parts as $k => $part) {
477
            $parentPointer = $currentPointer;
478
            /** @var FormField $currentPointer */
479
            $currentPointer = $currentPointer->fieldByName($part);
480
            // Create any missing tabs
481
            if (!$currentPointer) {
482
                if ($parentPointer instanceof TabSet) {
483
                    // use $title on the innermost tab only
484
                    if ($k == $last_idx) {
485
                        $currentPointer = isset($title) ? new Tab($part, $title) : new Tab($part);
486
                    } else {
487
                        $currentPointer = new TabSet($part);
488
                    }
489
                    $parentPointer->push($currentPointer);
490
                } else {
491
                    $withName = $parentPointer instanceof FormField
492
                        ? " named '{$parentPointer->getName()}'"
0 ignored issues
show
Bug introduced by
The method getName() does not exist on SilverStripe\Forms\FieldList. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

492
                        ? " named '{$parentPointer->/** @scrutinizer ignore-call */ getName()}'"
Loading history...
493
                        : null;
494
                    $parentPointerClass = get_class($parentPointer);
495
                    user_error(
496
                        "FieldList::addFieldToTab() Tried to add a tab to object"
497
                        . " '{$parentPointerClass}'{$withName} - '{$part}' didn't exist.",
498
                        E_USER_ERROR
499
                    );
500
                }
501
            }
502
        }
503
504
        return $currentPointer;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $currentPointer also could return the type SilverStripe\Forms\Field...lverStripe\Forms\TabSet which is incompatible with the documented return type SilverStripe\Forms\Tab.
Loading history...
505
    }
506
507
    /**
508
     * Returns a named field.
509
     * You can use dot syntax to get fields from child composite fields
510
     *
511
     * @todo Implement similarly to dataFieldByName() to support nested sets - or merge with dataFields()
512
     *
513
     * @param string $name
514
     * @return FormField
515
     */
516
    public function fieldByName($name)
517
    {
518
        if (strpos($name, '.') !== false) {
519
            list($name, $remainder) = explode('.', $name, 2);
520
        } else {
521
            $remainder = null;
522
        }
523
524
        foreach ($this as $child) {
525
            if (trim($name) == trim($child->getName()) || $name == $child->id) {
526
                if ($remainder) {
527
                    if ($child instanceof CompositeField) {
528
                        return $child->fieldByName($remainder);
529
                    } else {
530
                        $childClass = get_class($child);
531
                        user_error(
532
                            "Trying to get field '{$remainder}' from non-composite field {$childClass}.{$name}",
533
                            E_USER_WARNING
534
                        );
535
                        return null;
536
                    }
537
                } else {
538
                    return $child;
539
                }
540
            }
541
        }
542
        return null;
543
    }
544
545
    /**
546
     * Returns a named field in a sequential set.
547
     * Use this if you're using nested FormFields.
548
     *
549
     * @param string $name The name of the field to return
550
     * @return FormField instance
551
     */
552
    public function dataFieldByName($name)
553
    {
554
        if ($dataFields = $this->dataFields()) {
555
            foreach ($dataFields as $child) {
556
                if (trim($name) == trim($child->getName()) || $name == $child->id) {
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on SilverStripe\Forms\FormField. Since you implemented __get, consider adding a @property annotation.
Loading history...
557
                    return $child;
558
                }
559
            }
560
        }
561
        return null;
562
    }
563
564
    /**
565
     * Inserts a field before a particular field in a FieldList.
566
     * Will traverse CompositeFields depth-first to find the maching $name, and insert before the first match
567
     *
568
     * @param string $name Name of the field to insert before
569
     * @param FormField $item The form field to insert
570
     * @param bool $appendIfMissing Append to the end of the list if $name isn't found
571
     * @return FormField|false Field if it was successfully inserted, false if not inserted
572
     */
573
    public function insertBefore($name, $item, $appendIfMissing = true)
574
    {
575
        // Backwards compatibility for order of arguments
576
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
577
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertBefore');
578
            list($item, $name) = array($name, $item);
579
        }
580
        $this->onBeforeInsert($item);
581
        $item->setContainerFieldList($this);
582
583
        $i = 0;
584
        foreach ($this as $child) {
585
            if ($name == $child->getName() || $name == $child->id) {
586
                array_splice($this->items, $i, 0, array($item));
587
                return $item;
588
            } elseif ($child instanceof CompositeField) {
589
                $ret = $child->insertBefore($name, $item, false);
590
                if ($ret) {
591
                    return $ret;
592
                }
593
            }
594
            $i++;
595
        }
596
597
        // $name not found, append if needed
598
        if ($appendIfMissing) {
599
            $this->push($item);
600
            return $item;
601
        }
602
603
        return false;
604
    }
605
606
    /**
607
     * Inserts a field after a particular field in a FieldList.
608
     * Will traverse CompositeFields depth-first to find the maching $name, and insert after the first match
609
     *
610
     * @param string $name Name of the field to insert after
611
     * @param FormField $item The form field to insert
612
     * @param bool $appendIfMissing Append to the end of the list if $name isn't found
613
     * @return FormField|false Field if it was successfully inserted, false if not inserted
614
     */
615
    public function insertAfter($name, $item, $appendIfMissing = true)
616
    {
617
        // Backwards compatibility for order of arguments
618
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
619
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertAfter');
620
            list($item, $name) = array($name, $item);
621
        }
622
        $this->onBeforeInsert($item);
623
        $item->setContainerFieldList($this);
624
625
        $i = 0;
626
        foreach ($this as $child) {
627
            if ($name == $child->getName() || $name == $child->id) {
628
                array_splice($this->items, $i+1, 0, array($item));
629
                return $item;
630
            } elseif ($child instanceof CompositeField) {
631
                $ret = $child->insertAfter($name, $item, false);
632
                if ($ret) {
633
                    return $ret;
634
                }
635
            }
636
            $i++;
637
        }
638
639
        // $name not found, append if needed
640
        if ($appendIfMissing) {
641
            $this->push($item);
642
            return $item;
643
        }
644
645
        return false;
646
    }
647
648
    /**
649
     * Push a single field onto the end of this FieldList instance.
650
     *
651
     * @param FormField $item The FormField to add
652
     */
653
    public function push($item)
654
    {
655
        $this->onBeforeInsert($item);
656
        $item->setContainerFieldList($this);
657
658
        return parent::push($item);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::push($item) targeting SilverStripe\ORM\ArrayList::push() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
659
    }
660
661
    /**
662
     * Push a single field onto the beginning of this FieldList instance.
663
     *
664
     * @param FormField $item The FormField to add
665
     */
666
    public function unshift($item)
667
    {
668
        $this->onBeforeInsert($item);
669
        $item->setContainerFieldList($this);
670
671
        return parent::unshift($item);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::unshift($item) targeting SilverStripe\ORM\ArrayList::unshift() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
672
    }
673
674
    /**
675
     * Handler method called before the FieldList is going to be manipulated.
676
     *
677
     * @param FormField $item
678
     */
679
    protected function onBeforeInsert($item)
680
    {
681
        $this->flushFieldsCache();
682
683
        if ($item->getName()) {
684
            $this->rootFieldList()->removeByName($item->getName(), true);
685
        }
686
    }
687
688
689
    /**
690
     * Set the Form instance for this FieldList.
691
     *
692
     * @param Form $form The form to set this FieldList to
693
     * @return $this
694
     */
695
    public function setForm($form)
696
    {
697
        foreach ($this as $field) {
698
            $field->setForm($form);
699
        }
700
701
        return $this;
702
    }
703
704
    /**
705
     * Load the given data into this form.
706
     *
707
     * @param array $data An map of data to load into the FieldList
708
     * @return $this
709
     */
710
    public function setValues($data)
711
    {
712
        foreach ($this->dataFields() as $field) {
713
            $fieldName = $field->getName();
714
            if (isset($data[$fieldName])) {
715
                $field->setValue($data[$fieldName]);
716
            }
717
        }
718
        return $this;
719
    }
720
721
    /**
722
     * Return all <input type="hidden"> fields
723
     * in a form - including fields nested in {@link CompositeFields}.
724
     * Useful when doing custom field layouts.
725
     *
726
     * @return FieldList
727
     */
728
    public function HiddenFields()
729
    {
730
        $hiddenFields = new FieldList();
731
        $dataFields = $this->dataFields();
732
733
        if ($dataFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dataFields of type SilverStripe\Forms\FormField[] 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...
734
            foreach ($dataFields as $field) {
735
                if ($field instanceof HiddenField) {
736
                    $hiddenFields->push($field);
737
                }
738
            }
739
        }
740
741
        return $hiddenFields;
742
    }
743
744
    /**
745
     * Return all fields except for the hidden fields.
746
     * Useful when making your own simplified form layouts.
747
     */
748
    public function VisibleFields()
749
    {
750
        $visibleFields = new FieldList();
751
752
        foreach ($this as $field) {
753
            if (!($field instanceof HiddenField)) {
754
                $visibleFields->push($field);
755
            }
756
        }
757
758
        return $visibleFields;
759
    }
760
761
    /**
762
     * Transform this FieldList with a given tranform method,
763
     * e.g. $this->transform(new ReadonlyTransformation())
764
     *
765
     * @param FormTransformation $trans
766
     * @return FieldList
767
     */
768
    public function transform($trans)
769
    {
770
        $this->flushFieldsCache();
771
        $newFields = new FieldList();
772
        foreach ($this as $field) {
773
            $newFields->push($field->transform($trans));
774
        }
775
        return $newFields;
776
    }
777
778
    /**
779
     * Returns the root field set that this belongs to
780
     *
781
     * @return FieldList|FormField
782
     */
783
    public function rootFieldList()
784
    {
785
        if ($this->containerField) {
786
            return $this->containerField->rootFieldList();
787
        }
788
789
        return $this;
790
    }
791
792
    /**
793
     * @return CompositeField|null
794
     */
795
    public function getContainerField()
796
    {
797
        return $this->containerField;
798
    }
799
800
    /**
801
     * @param CompositeField|null $field
802
     * @return $this
803
     */
804
    public function setContainerField($field)
805
    {
806
        $this->containerField = $field;
807
        return $this;
808
    }
809
810
    /**
811
     * Transforms this FieldList instance to readonly.
812
     *
813
     * @return FieldList
814
     */
815
    public function makeReadonly()
816
    {
817
        return $this->transform(new ReadonlyTransformation());
818
    }
819
820
    /**
821
     * Transform the named field into a readonly field.
822
     *
823
     * @param string|array|FormField $field
824
     */
825
    public function makeFieldReadonly($field)
826
    {
827
        if (!is_array($field)) {
828
            $field = [$field];
829
        }
830
831
        foreach ($field as $item) {
832
            $fieldName = ($item instanceof FormField) ? $item->getName() : $item;
833
            $srcField = $this->dataFieldByName($fieldName);
834
            if ($srcField) {
835
                $this->replaceField($fieldName, $srcField->performReadonlyTransformation());
836
            } else {
837
                user_error("Trying to make field '$fieldName' readonly, but it does not exist in the list", E_USER_WARNING);
838
            }
839
        }
840
    }
841
842
    /**
843
     * Change the order of fields in this FieldList by specifying an ordered list of field names.
844
     * This works well in conjunction with SilverStripe's scaffolding functions: take the scaffold, and
845
     * shuffle the fields around to the order that you want.
846
     *
847
     * Please note that any tabs or other dataless fields will be clobbered by this operation.
848
     *
849
     * @param array $fieldNames Field names can be given as an array, or just as a list of arguments.
850
     */
851
    public function changeFieldOrder($fieldNames)
852
    {
853
        // Field names can be given as an array, or just as a list of arguments.
854
        if (!is_array($fieldNames)) {
855
            $fieldNames = func_get_args();
856
        }
857
858
        // Build a map of fields indexed by their name.  This will make the 2nd step much easier.
859
        $fieldMap = array();
860
        foreach ($this->dataFields() as $field) {
861
            $fieldMap[$field->getName()] = $field;
862
        }
863
864
        // Iterate through the ordered list of names, building a new array to be put into $this->items.
865
        // While we're doing this, empty out $fieldMap so that we can keep track of leftovers.
866
        // Unrecognised field names are okay; just ignore them
867
        $fields = array();
868
        foreach ($fieldNames as $fieldName) {
869
            if (isset($fieldMap[$fieldName])) {
870
                $fields[] = $fieldMap[$fieldName];
871
                unset($fieldMap[$fieldName]);
872
            }
873
        }
874
875
        // Add the leftover fields to the end of the list.
876
        $fields = array_values($fields + $fieldMap);
877
878
        // Update our internal $this->items parameter.
879
        $this->items = $fields;
880
881
        $this->flushFieldsCache();
882
    }
883
884
    /**
885
     * Find the numerical position of a field within
886
     * the children collection. Doesn't work recursively.
887
     *
888
     * @param string|FormField
889
     * @return int Position in children collection (first position starts with 0).
890
     * Returns FALSE if the field can't be found.
891
     */
892
    public function fieldPosition($field)
893
    {
894
        if ($field instanceof FormField) {
895
            $field = $field->getName();
896
        }
897
898
        $i = 0;
899
        foreach ($this->dataFields() as $child) {
900
            if ($child->getName() == $field) {
901
                return $i;
902
            }
903
            $i++;
904
        }
905
906
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
907
    }
908
909
    /**
910
     * Default template rendering of a FieldList will concatenate all FieldHolder values.
911
     */
912
    public function forTemplate()
913
    {
914
        $output = "";
915
        foreach ($this as $field) {
916
            $output .= $field->FieldHolder();
917
        }
918
        return $output;
919
    }
920
}
921