Completed
Push — 4 ( 921b98...baddf7 )
by Daniel
38s queued 17s
created

FieldList::findTab()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 14
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 FieldList|FormField
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..5.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) {
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) {
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;
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;
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);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $child->fieldByName($remainder) targeting SilverStripe\Forms\CompositeField::fieldByName() 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...
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
     *
567
     * @param string $name Name of the field to insert before
568
     * @param FormField $item The form field to insert
569
     * @return FormField|false
570
     */
571
    public function insertBefore($name, $item)
572
    {
573
        // Backwards compatibility for order of arguments
574
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
575
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertBefore');
576
            list($item, $name) = array($name, $item);
577
        }
578
        $this->onBeforeInsert($item);
579
        $item->setContainerFieldList($this);
580
581
        $i = 0;
582
        foreach ($this as $child) {
583
            if ($name == $child->getName() || $name == $child->id) {
584
                array_splice($this->items, $i, 0, array($item));
585
                return $item;
586
            } elseif ($child instanceof CompositeField) {
587
                $ret = $child->insertBefore($name, $item);
588
                if ($ret) {
589
                    return $ret;
590
                }
591
            }
592
            $i++;
593
        }
594
595
        return false;
596
    }
597
598
    /**
599
     * Inserts a field after a particular field in a FieldList.
600
     *
601
     * @param string $name Name of the field to insert after
602
     * @param FormField $item The form field to insert
603
     * @return FormField|false
604
     */
605
    public function insertAfter($name, $item)
606
    {
607
        // Backwards compatibility for order of arguments
608
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
609
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertAfter');
610
            list($item, $name) = array($name, $item);
611
        }
612
        $this->onBeforeInsert($item);
613
        $item->setContainerFieldList($this);
614
615
        $i = 0;
616
        foreach ($this as $child) {
617
            if ($name == $child->getName() || $name == $child->id) {
618
                array_splice($this->items, $i+1, 0, array($item));
619
                return $item;
620
            } elseif ($child instanceof CompositeField) {
621
                $ret = $child->insertAfter($name, $item);
622
                if ($ret) {
623
                    return $ret;
624
                }
625
            }
626
            $i++;
627
        }
628
629
        return false;
630
    }
631
632
    /**
633
     * Push a single field onto the end of this FieldList instance.
634
     *
635
     * @param FormField $item The FormField to add
636
     */
637
    public function push($item)
638
    {
639
        $this->onBeforeInsert($item);
640
        $item->setContainerFieldList($this);
641
642
        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...
643
    }
644
645
    /**
646
     * Push a single field onto the beginning of this FieldList instance.
647
     *
648
     * @param FormField $item The FormField to add
649
     */
650
    public function unshift($item)
651
    {
652
        $this->onBeforeInsert($item);
653
        $item->setContainerFieldList($this);
654
655
        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...
656
    }
657
658
    /**
659
     * Handler method called before the FieldList is going to be manipulated.
660
     *
661
     * @param FormField $item
662
     */
663
    protected function onBeforeInsert($item)
664
    {
665
        $this->flushFieldsCache();
666
667
        if ($item->getName()) {
668
            $this->rootFieldList()->removeByName($item->getName(), true);
0 ignored issues
show
Bug introduced by
The method removeByName() 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

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