FieldList   F
last analyzed

Complexity

Total Complexity 142

Size/Duplication

Total Lines 911
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 142
eloc 314
dl 0
loc 911
rs 2
c 0
b 0
f 0

40 Methods

Rating   Name   Duplication   Size   Complexity  
A flushFieldsCache() 0 4 1
A dataFieldNames() 0 3 1
A __construct() 0 11 5
A recursiveWalk() 0 9 3
B collateDataFields() 0 32 8
A addFieldsToTab() 0 21 5
A dataFields() 0 17 4
A flattenFields() 0 7 1
A saveableFields() 0 17 4
A fieldNameError() 0 20 2
A addFieldToTab() 0 16 2
A __clone() 0 5 2
B removeByName() 0 30 10
A removeFieldsFromTab() 0 14 3
A removeFieldFromTab() 0 11 2
A makeReadonly() 0 3 1
A renameField() 0 10 2
A onBeforeInsert() 0 6 2
A setForm() 0 7 2
B insertBefore() 0 31 8
B insertAfter() 0 31 8
A setContainerField() 0 4 1
A unshift() 0 6 1
B fieldByName() 0 27 7
A getContainerField() 0 3 1
A setValues() 0 9 3
A VisibleFields() 0 11 3
A rootFieldList() 0 7 2
B findOrMakeTab() 0 36 7
A HiddenFields() 0 14 4
A findTab() 0 14 2
B replaceField() 0 14 7
A push() 0 6 1
A dataFieldByName() 0 10 5
A hasTabSet() 0 9 4
A transform() 0 8 2
A changeFieldOrder() 0 31 5
A fieldPosition() 0 15 4
A makeFieldReadonly() 0 15 5
A forTemplate() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like FieldList often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FieldList, and based on these observations, apply Extract Interface, too.

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
     * @param boolean $dataFieldOnly If this is true, then a field will only be replaced if it's a data field.  Dataless
383
     *                               fields, such as tabs, will be not be considered for replacement.
384
     * @return boolean TRUE field was successfully replaced
385
     *                   FALSE field wasn't found, nothing changed
386
     */
387
    public function replaceField($fieldName, $newField, $dataFieldOnly = true)
388
    {
389
        $this->flushFieldsCache();
390
        foreach ($this as $i => $field) {
391
            if ($field->getName() == $fieldName && (!$dataFieldOnly || $field->hasData())) {
392
                $this->items[$i] = $newField;
393
                return true;
394
            } elseif ($field instanceof CompositeField) {
395
                if ($field->replaceField($fieldName, $newField)) {
396
                    return true;
397
                }
398
            }
399
        }
400
        return false;
401
    }
402
403
    /**
404
     * Rename the title of a particular field name in this set.
405
     *
406
     * @param string $fieldName Name of field to rename title of
407
     * @param string $newFieldTitle New title of field
408
     * @return boolean
409
     */
410
    public function renameField($fieldName, $newFieldTitle)
411
    {
412
        $field = $this->dataFieldByName($fieldName);
413
        if (!$field) {
0 ignored issues
show
introduced by
$field is of type SilverStripe\Forms\FormField, thus it always evaluated to true.
Loading history...
414
            return false;
415
        }
416
417
        $field->setTitle($newFieldTitle);
418
419
        return $field->Title() == $newFieldTitle;
420
    }
421
422
    /**
423
     * @return boolean
424
     */
425
    public function hasTabSet()
426
    {
427
        foreach ($this->items as $i => $field) {
428
            if (is_object($field) && $field instanceof TabSet) {
429
                return true;
430
            }
431
        }
432
433
        return false;
434
    }
435
436
    /**
437
     * Returns the specified tab object, if it exists
438
     *
439
     * @param string $tabName The tab to return, in the form "Tab.Subtab.Subsubtab".
440
     * @return Tab|null The found or null
441
     */
442
    public function findTab($tabName)
443
    {
444
        $parts = explode('.', $tabName);
445
        $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...
446
447
        $currentPointer = $this;
448
449
        foreach ($parts as $k => $part) {
450
            $parentPointer = $currentPointer;
0 ignored issues
show
Unused Code introduced by
The assignment to $parentPointer is dead and can be removed.
Loading history...
451
            /** @var FormField $currentPointer */
452
            $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

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

494
                        ? " named '{$parentPointer->/** @scrutinizer ignore-call */ getName()}'"
Loading history...
495
                        : null;
496
                    $parentPointerClass = get_class($parentPointer);
497
                    user_error(
498
                        "FieldList::addFieldToTab() Tried to add a tab to object"
499
                        . " '{$parentPointerClass}'{$withName} - '{$part}' didn't exist.",
500
                        E_USER_ERROR
501
                    );
502
                }
503
            }
504
        }
505
506
        return $currentPointer;
507
    }
508
509
    /**
510
     * Returns a named field.
511
     * You can use dot syntax to get fields from child composite fields
512
     *
513
     * @todo Implement similarly to dataFieldByName() to support nested sets - or merge with dataFields()
514
     *
515
     * @param string $name
516
     * @return FormField
517
     */
518
    public function fieldByName($name)
519
    {
520
        if (strpos($name, '.') !== false) {
521
            list($name, $remainder) = explode('.', $name, 2);
522
        } else {
523
            $remainder = null;
524
        }
525
526
        foreach ($this as $child) {
527
            if (trim($name) == trim($child->getName()) || $name == $child->id) {
528
                if ($remainder) {
529
                    if ($child instanceof CompositeField) {
530
                        return $child->fieldByName($remainder);
531
                    } else {
532
                        $childClass = get_class($child);
533
                        user_error(
534
                            "Trying to get field '{$remainder}' from non-composite field {$childClass}.{$name}",
535
                            E_USER_WARNING
536
                        );
537
                        return null;
538
                    }
539
                } else {
540
                    return $child;
541
                }
542
            }
543
        }
544
        return null;
545
    }
546
547
    /**
548
     * Returns a named field in a sequential set.
549
     * Use this if you're using nested FormFields.
550
     *
551
     * @param string $name The name of the field to return
552
     * @return FormField instance
553
     */
554
    public function dataFieldByName($name)
555
    {
556
        if ($dataFields = $this->dataFields()) {
557
            foreach ($dataFields as $child) {
558
                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...
559
                    return $child;
560
                }
561
            }
562
        }
563
        return null;
564
    }
565
566
    /**
567
     * Inserts a field before a particular field in a FieldList.
568
     * Will traverse CompositeFields depth-first to find the maching $name, and insert before the first match
569
     *
570
     * @param string $name Name of the field to insert before
571
     * @param FormField $item The form field to insert
572
     * @param bool $appendIfMissing Append to the end of the list if $name isn't found
573
     * @return FormField|false Field if it was successfully inserted, false if not inserted
574
     */
575
    public function insertBefore($name, $item, $appendIfMissing = true)
576
    {
577
        // Backwards compatibility for order of arguments
578
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
579
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertBefore');
580
            list($item, $name) = array($name, $item);
581
        }
582
        $this->onBeforeInsert($item);
583
        $item->setContainerFieldList($this);
584
585
        $i = 0;
586
        foreach ($this as $child) {
587
            if ($name == $child->getName() || $name == $child->id) {
588
                array_splice($this->items, $i, 0, array($item));
589
                return $item;
590
            } elseif ($child instanceof CompositeField) {
591
                $ret = $child->insertBefore($name, $item, false);
592
                if ($ret) {
593
                    return $ret;
594
                }
595
            }
596
            $i++;
597
        }
598
599
        // $name not found, append if needed
600
        if ($appendIfMissing) {
601
            $this->push($item);
602
            return $item;
603
        }
604
605
        return false;
606
    }
607
608
    /**
609
     * Inserts a field after a particular field in a FieldList.
610
     * Will traverse CompositeFields depth-first to find the maching $name, and insert after the first match
611
     *
612
     * @param string $name Name of the field to insert after
613
     * @param FormField $item The form field to insert
614
     * @param bool $appendIfMissing Append to the end of the list if $name isn't found
615
     * @return FormField|false Field if it was successfully inserted, false if not inserted
616
     */
617
    public function insertAfter($name, $item, $appendIfMissing = true)
618
    {
619
        // Backwards compatibility for order of arguments
620
        if ($name instanceof FormField) {
0 ignored issues
show
introduced by
$name is never a sub-type of SilverStripe\Forms\FormField.
Loading history...
621
            Deprecation::notice('5.0', 'Incorrect order of arguments for insertAfter');
622
            list($item, $name) = array($name, $item);
623
        }
624
        $this->onBeforeInsert($item);
625
        $item->setContainerFieldList($this);
626
627
        $i = 0;
628
        foreach ($this as $child) {
629
            if ($name == $child->getName() || $name == $child->id) {
630
                array_splice($this->items, $i+1, 0, array($item));
631
                return $item;
632
            } elseif ($child instanceof CompositeField) {
633
                $ret = $child->insertAfter($name, $item, false);
634
                if ($ret) {
635
                    return $ret;
636
                }
637
            }
638
            $i++;
639
        }
640
641
        // $name not found, append if needed
642
        if ($appendIfMissing) {
643
            $this->push($item);
644
            return $item;
645
        }
646
647
        return false;
648
    }
649
650
    /**
651
     * Push a single field onto the end of this FieldList instance.
652
     *
653
     * @param FormField $item The FormField to add
654
     */
655
    public function push($item)
656
    {
657
        $this->onBeforeInsert($item);
658
        $item->setContainerFieldList($this);
659
660
        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...
661
    }
662
663
    /**
664
     * Push a single field onto the beginning of this FieldList instance.
665
     *
666
     * @param FormField $item The FormField to add
667
     */
668
    public function unshift($item)
669
    {
670
        $this->onBeforeInsert($item);
671
        $item->setContainerFieldList($this);
672
673
        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...
674
    }
675
676
    /**
677
     * Handler method called before the FieldList is going to be manipulated.
678
     *
679
     * @param FormField $item
680
     */
681
    protected function onBeforeInsert($item)
682
    {
683
        $this->flushFieldsCache();
684
685
        if ($item->getName()) {
686
            $this->rootFieldList()->removeByName($item->getName(), true);
687
        }
688
    }
689
690
691
    /**
692
     * Set the Form instance for this FieldList.
693
     *
694
     * @param Form $form The form to set this FieldList to
695
     * @return $this
696
     */
697
    public function setForm($form)
698
    {
699
        foreach ($this as $field) {
700
            $field->setForm($form);
701
        }
702
703
        return $this;
704
    }
705
706
    /**
707
     * Load the given data into this form.
708
     *
709
     * @param array $data An map of data to load into the FieldList
710
     * @return $this
711
     */
712
    public function setValues($data)
713
    {
714
        foreach ($this->dataFields() as $field) {
715
            $fieldName = $field->getName();
716
            if (isset($data[$fieldName])) {
717
                $field->setValue($data[$fieldName]);
718
            }
719
        }
720
        return $this;
721
    }
722
723
    /**
724
     * Return all <input type="hidden"> fields
725
     * in a form - including fields nested in {@link CompositeFields}.
726
     * Useful when doing custom field layouts.
727
     *
728
     * @return FieldList
729
     */
730
    public function HiddenFields()
731
    {
732
        $hiddenFields = new FieldList();
733
        $dataFields = $this->dataFields();
734
735
        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...
736
            foreach ($dataFields as $field) {
737
                if ($field instanceof HiddenField) {
738
                    $hiddenFields->push($field);
739
                }
740
            }
741
        }
742
743
        return $hiddenFields;
744
    }
745
746
    /**
747
     * Return all fields except for the hidden fields.
748
     * Useful when making your own simplified form layouts.
749
     */
750
    public function VisibleFields()
751
    {
752
        $visibleFields = new FieldList();
753
754
        foreach ($this as $field) {
755
            if (!($field instanceof HiddenField)) {
756
                $visibleFields->push($field);
757
            }
758
        }
759
760
        return $visibleFields;
761
    }
762
763
    /**
764
     * Transform this FieldList with a given tranform method,
765
     * e.g. $this->transform(new ReadonlyTransformation())
766
     *
767
     * @param FormTransformation $trans
768
     * @return FieldList
769
     */
770
    public function transform($trans)
771
    {
772
        $this->flushFieldsCache();
773
        $newFields = new FieldList();
774
        foreach ($this as $field) {
775
            $newFields->push($field->transform($trans));
776
        }
777
        return $newFields;
778
    }
779
780
    /**
781
     * Returns the root field set that this belongs to
782
     *
783
     * @return FieldList|FormField
784
     */
785
    public function rootFieldList()
786
    {
787
        if ($this->containerField) {
788
            return $this->containerField->rootFieldList();
789
        }
790
791
        return $this;
792
    }
793
794
    /**
795
     * @return CompositeField|null
796
     */
797
    public function getContainerField()
798
    {
799
        return $this->containerField;
800
    }
801
802
    /**
803
     * @param CompositeField|null $field
804
     * @return $this
805
     */
806
    public function setContainerField($field)
807
    {
808
        $this->containerField = $field;
809
        return $this;
810
    }
811
812
    /**
813
     * Transforms this FieldList instance to readonly.
814
     *
815
     * @return FieldList
816
     */
817
    public function makeReadonly()
818
    {
819
        return $this->transform(new ReadonlyTransformation());
820
    }
821
822
    /**
823
     * Transform the named field into a readonly field.
824
     *
825
     * @param string|array|FormField $field
826
     */
827
    public function makeFieldReadonly($field)
828
    {
829
        if (!is_array($field)) {
830
            $field = [$field];
831
        }
832
833
        foreach ($field as $item) {
834
            $fieldName = ($item instanceof FormField) ? $item->getName() : $item;
835
            $srcField = $this->dataFieldByName($fieldName);
836
            if ($srcField) {
837
                $this->replaceField($fieldName, $srcField->performReadonlyTransformation());
838
            } else {
839
                user_error(
840
                    "Trying to make field '$fieldName' readonly, but it does not exist in the list",
841
                    E_USER_WARNING
842
                );
843
            }
844
        }
845
    }
846
847
    /**
848
     * Change the order of fields in this FieldList by specifying an ordered list of field names.
849
     * This works well in conjunction with SilverStripe's scaffolding functions: take the scaffold, and
850
     * shuffle the fields around to the order that you want.
851
     *
852
     * Please note that any tabs or other dataless fields will be clobbered by this operation.
853
     *
854
     * @param array $fieldNames Field names can be given as an array, or just as a list of arguments.
855
     */
856
    public function changeFieldOrder($fieldNames)
857
    {
858
        // Field names can be given as an array, or just as a list of arguments.
859
        if (!is_array($fieldNames)) {
860
            $fieldNames = func_get_args();
861
        }
862
863
        // Build a map of fields indexed by their name.  This will make the 2nd step much easier.
864
        $fieldMap = array();
865
        foreach ($this->dataFields() as $field) {
866
            $fieldMap[$field->getName()] = $field;
867
        }
868
869
        // Iterate through the ordered list of names, building a new array to be put into $this->items.
870
        // While we're doing this, empty out $fieldMap so that we can keep track of leftovers.
871
        // Unrecognised field names are okay; just ignore them
872
        $fields = array();
873
        foreach ($fieldNames as $fieldName) {
874
            if (isset($fieldMap[$fieldName])) {
875
                $fields[] = $fieldMap[$fieldName];
876
                unset($fieldMap[$fieldName]);
877
            }
878
        }
879
880
        // Add the leftover fields to the end of the list.
881
        $fields = array_values($fields + $fieldMap);
882
883
        // Update our internal $this->items parameter.
884
        $this->items = $fields;
885
886
        $this->flushFieldsCache();
887
    }
888
889
    /**
890
     * Find the numerical position of a field within
891
     * the children collection. Doesn't work recursively.
892
     *
893
     * @param string|FormField
894
     * @return int Position in children collection (first position starts with 0).
895
     * Returns FALSE if the field can't be found.
896
     */
897
    public function fieldPosition($field)
898
    {
899
        if ($field instanceof FormField) {
900
            $field = $field->getName();
901
        }
902
903
        $i = 0;
904
        foreach ($this->dataFields() as $child) {
905
            if ($child->getName() == $field) {
906
                return $i;
907
            }
908
            $i++;
909
        }
910
911
        return false;
912
    }
913
914
    /**
915
     * Default template rendering of a FieldList will concatenate all FieldHolder values.
916
     */
917
    public function forTemplate()
918
    {
919
        $output = "";
920
        foreach ($this as $field) {
921
            $output .= $field->FieldHolder();
922
        }
923
        return $output;
924
    }
925
}
926