Completed
Pull Request — master (#6389)
by Damian
08:59
created

CompositeField::getSchemaDataDefaults()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 2
nop 0
dl 0
loc 25
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms;
4
5
use SilverStripe\Dev\Debug;
6
7
/**
8
 * Base class for all fields that contain other fields.
9
 *
10
 * Implements sequentialisation - so that when we're saving / loading data, we
11
 * can populate a tabbed form properly. All of the children are stored in
12
 * $this->children
13
 */
14
class CompositeField extends FormField
15
{
16
17
    /**
18
     * @var FieldList
19
     */
20
    protected $children;
21
22
    /**
23
     * Set to true when this field is a readonly field
24
     */
25
    protected $readonly;
26
27
    /**
28
     * @var $columnCount int Toggle different css-rendering for multiple columns
29
     * ("onecolumn", "twocolumns", "threecolumns"). The content is determined
30
     * by the $children-array, so wrap all items you want to have grouped in a
31
     * column inside a CompositeField.
32
     * Caution: Please make sure that this variable actually matches the
33
     * count of your $children.
34
     */
35
    protected $columnCount = null;
36
37
    /**
38
     * @var String custom HTML tag to render with, e.g. to produce a <fieldset>.
39
     */
40
    protected $tag = 'div';
41
42
    /**
43
     * @var String Optional description for this set of fields.
44
     * If the {@link $tag} property is set to use a 'fieldset', this will be
45
     * rendered as a <legend> tag, otherwise its a 'title' attribute.
46
     */
47
    protected $legend;
48
49
    protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_STRUCTURAL;
50
51
    protected $schemaComponent = 'CompositeField';
52
53
    public function __construct($children = null)
54
    {
55
        if ($children instanceof FieldList) {
56
            $this->children = $children;
57
        } elseif (is_array($children)) {
58
            $this->children = new FieldList($children);
59
        } else {
60
            //filter out null/empty items
61
            $children = array_filter(func_get_args());
62
            $this->children = new FieldList($children);
63
        }
64
        $this->children->setContainerField($this);
65
66
        parent::__construct(null, false);
67
    }
68
69
    /**
70
     * Merge child field data into this form
71
     */
72
    public function getSchemaDataDefaults()
73
    {
74
        $defaults = parent::getSchemaDataDefaults();
75
        $children = $this->getChildren();
76
        if ($children && $children->count()) {
77
            $childSchema = [];
78
            /** @var FormField $child */
79
            foreach ($children as $child) {
80
                $childSchema[] = $child->getSchemaData();
81
            }
82
            $defaults['children'] = $childSchema;
83
        }
84
85
        $defaults['data']['tag'] = $this->getTag();
86
        $defaults['data']['legend'] = $this->getLegend();
87
88
        // Scaffolded children will inherit this data
89
        $defaults['data']['inherited'] = [
90
            'data' => [
91
                'fieldholder' => 'small'
92
            ],
93
        ];
94
95
        return $defaults;
96
    }
97
98
    /**
99
     * Returns all the sub-fields, suitable for <% loop FieldList %>
100
     *
101
     * @return FieldList
102
     */
103
    public function FieldList()
104
    {
105
        return $this->children;
106
    }
107
108
    /**
109
     * Accessor method for $this->children
110
     *
111
     * @return FieldList
112
     */
113
    public function getChildren()
114
    {
115
        return $this->children;
116
    }
117
118
    /**
119
     * Returns the name (ID) for the element.
120
     * If the CompositeField doesn't have a name, but we still want the ID/name to be set.
121
     * This code generates the ID from the nested children.
122
     *
123
     * @todo this is temporary, and should be removed when FormTemplateHelper is updated to handle ID for CompositeFields with no name
124
     *
125
     * @return String $name
126
     */
127
    public function getName()
128
    {
129
        if ($this->name) {
130
            return $this->name;
131
        }
132
133
        $fieldList = $this->FieldList();
134
        $compositeTitle = '';
135
        $count = 0;
136
        /** @var FormField $subfield */
137
        foreach ($fieldList as $subfield) {
138
            $compositeTitle .= $subfield->getName();
139
            if ($subfield->getName()) {
140
                $count++;
141
            }
142
        }
143
        /** @skipUpgrade */
144
        if ($count === 1) {
145
            $compositeTitle .= 'Group';
146
        }
147
        return preg_replace("/[^a-zA-Z0-9]+/", "", $compositeTitle);
148
    }
149
150
    /**
151
     * @param FieldList $children
152
     * @return $this
153
     */
154
    public function setChildren($children)
155
    {
156
        $this->children = $children;
157
        return $this;
158
    }
159
160
    /**
161
     * @param string $tag
162
     * @return $this
163
     */
164
    public function setTag($tag)
165
    {
166
        $this->tag = $tag;
167
168
        return $this;
169
    }
170
171
    /**
172
     * @return string
173
     */
174
    public function getTag()
175
    {
176
        return $this->tag;
177
    }
178
179
    /**
180
     * @param string $legend
181
     * @return $this
182
     */
183
    public function setLegend($legend)
184
    {
185
        $this->legend = $legend;
186
        return $this;
187
    }
188
189
    /**
190
     * @return string
191
     */
192
    public function getLegend()
193
    {
194
        return $this->legend;
195
    }
196
197
    public function extraClass()
198
    {
199
        /** @skipUpgrade */
200
        $classes = array('field', 'CompositeField', parent::extraClass());
201
        if ($this->columnCount) {
202
            $classes[] = 'multicolumn';
203
        }
204
205
        return implode(' ', $classes);
206
    }
207
208
    public function getAttributes()
209
    {
210
        return array_merge(
211
            parent::getAttributes(),
212
            array(
213
                'tabindex' => null,
214
                'type' => null,
215
                'value' => null,
216
                'title' => ($this->tag == 'fieldset') ? null : $this->legend
217
            )
218
        );
219
    }
220
221
    /**
222
     * Add all of the non-composite fields contained within this field to the
223
     * list.
224
     *
225
     * Sequentialisation is used when connecting the form to its data source
226
     *
227
     * @param array $list
228
     * @param bool $saveableOnly
229
     */
230
    public function collateDataFields(&$list, $saveableOnly = false)
231
    {
232
        foreach ($this->children as $field) {
233
            if (! $field instanceof FormField) {
234
                continue;
235
            }
236
            if ($field instanceof CompositeField) {
237
                $field->collateDataFields($list, $saveableOnly);
238
            }
239
            if ($saveableOnly) {
240
                $isIncluded =  ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
241
            } else {
242
                $isIncluded =  ($field->hasData());
243
            }
244
            if ($isIncluded) {
245
                $name = $field->getName();
246
                if ($name) {
247
                    $formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)';
248
                    if (isset($list[$name])) {
249
                        user_error("collateDataFields() I noticed that a field called '$name' appears twice in"
250
                            . " your form: '{$formName}'.  One is a '{$field->class}' and the other is a"
251
                            . " '{$list[$name]->class}'", E_USER_ERROR);
252
                    }
253
                    $list[$name] = $field;
254
                }
255
            }
256
        }
257
    }
258
259
    public function setForm($form)
260
    {
261
        foreach ($this->getChildren() as $field) {
262
            if ($field instanceof FormField) {
263
                $field->setForm($form);
264
            }
265
        }
266
267
        parent::setForm($form);
268
        return $this;
269
    }
270
271
272
273
    public function setDisabled($disabled)
274
    {
275
        parent::setDisabled($disabled);
276
        foreach ($this->getChildren() as $child) {
277
            $child->setDisabled($disabled);
278
        }
279
        return $this;
280
    }
281
282
    public function setReadonly($readonly)
283
    {
284
        parent::setReadonly($readonly);
285
        foreach ($this->getChildren() as $child) {
286
            $child->setReadonly($readonly);
287
        }
288
        return $this;
289
    }
290
291
    public function setColumnCount($columnCount)
292
    {
293
        $this->columnCount = $columnCount;
294
        return $this;
295
    }
296
297
    public function getColumnCount()
298
    {
299
        return $this->columnCount;
300
    }
301
302
    public function isComposite()
303
    {
304
        return true;
305
    }
306
307
    public function hasData()
308
    {
309
        return false;
310
    }
311
312
    public function fieldByName($name)
313
    {
314
        return $this->children->fieldByName($name);
315
    }
316
317
    /**
318
     * Add a new child field to the end of the set.
319
     *
320
     * @param FormField
321
     */
322
    public function push(FormField $field)
323
    {
324
        $this->children->push($field);
325
    }
326
327
    /**
328
     * Add a new child field to the beginning of the set.
329
     *
330
     * @param FormField
331
     */
332
    public function unshift(FormField $field)
333
    {
334
        $this->children->unshift($field);
335
    }
336
337
    /**
338
     * @uses FieldList->insertBefore()
339
     *
340
     * @param string $insertBefore
341
     * @param FormField $field
342
     * @return false|FormField
343
     */
344
    public function insertBefore($insertBefore, $field)
345
    {
346
        return $this->children->insertBefore($insertBefore, $field);
347
    }
348
349
    /**
350
     * @uses FieldList->insertAfter()
351
     * @param string $insertAfter
352
     * @param FormField $field
353
     * @return false|FormField
354
     */
355
    public function insertAfter($insertAfter, $field)
356
    {
357
        return $this->children->insertAfter($insertAfter, $field);
358
    }
359
360
    /**
361
     * Remove a field from this CompositeField by Name.
362
     * The field could also be inside a CompositeField.
363
     *
364
     * @param string $fieldName The name of the field
365
     * @param boolean $dataFieldOnly If this is true, then a field will only
366
     * be removed if it's a data field.  Dataless fields, such as tabs, will
367
     * be left as-is.
368
     */
369
    public function removeByName($fieldName, $dataFieldOnly = false)
370
    {
371
        $this->children->removeByName($fieldName, $dataFieldOnly);
372
    }
373
374
    public function replaceField($fieldName, $newField)
375
    {
376
        return $this->children->replaceField($fieldName, $newField);
377
    }
378
379
    public function rootFieldList()
380
    {
381
        if (is_object($this->containerFieldList)) {
382
            return $this->containerFieldList->rootFieldList();
383
        } else {
384
            return $this->children;
385
        }
386
    }
387
388
    public function __clone()
389
    {
390
        /** {@see FieldList::__clone(}} */
391
        $this->setChildren(clone $this->children);
392
    }
393
394
    /**
395
     * Return a readonly version of this field. Keeps the composition but returns readonly
396
     * versions of all the child {@link FormField} objects.
397
     *
398
     * @return CompositeField
399
     */
400
    public function performReadonlyTransformation()
401
    {
402
        $newChildren = new FieldList();
403
        $clone = clone $this;
404
        if ($clone->getChildren()) {
405
            foreach ($clone->getChildren() as $child) {
406
                        /** @var FormField $child */
407
                $child = $child->transform(new ReadonlyTransformation());
408
                $newChildren->push($child);
409
            }
410
        }
411
412
        $clone->setChildren($newChildren);
413
        $clone->setReadonly(true);
414
        $clone->addExtraClass($this->extraClass());
415
        $clone->setDescription($this->getDescription());
416
417
        return $clone;
418
    }
419
420
    /**
421
     * Return a disabled version of this field. Keeps the composition but returns disabled
422
     * versions of all the child {@link FormField} objects.
423
     *
424
     * @return CompositeField
425
     */
426
    public function performDisabledTransformation()
427
    {
428
        $newChildren = new FieldList();
429
        $clone = clone $this;
430
        if ($clone->getChildren()) {
431
            foreach ($clone->getChildren() as $child) {
432
                        /** @var FormField $child */
433
                $child = $child->transform(new DisabledTransformation());
434
                $newChildren->push($child);
435
            }
436
        }
437
438
        $clone->setChildren($newChildren);
439
        $clone->setDisabled(true);
440
        $clone->addExtraClass($this->extraClass());
441
        $clone->setDescription($this->getDescription());
442
        foreach ($this->attributes as $k => $v) {
443
            $clone->setAttribute($k, $v);
444
        }
445
446
        return $clone;
447
    }
448
449
    public function IsReadonly()
450
    {
451
        return $this->readonly;
452
    }
453
454
    /**
455
     * Find the numerical position of a field within
456
     * the children collection. Doesn't work recursively.
457
     *
458
     * @param string|FormField
459
     * @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
460
     *             be found.
461
     */
462
    public function fieldPosition($field)
463
    {
464
        if (is_string($field)) {
465
            $field = $this->fieldByName($field);
466
        }
467
        if (!$field) {
468
            return false;
469
        }
470
471
        $i = 0;
472
        foreach ($this->children as $child) {
473
            /** @var FormField $child */
474
            if ($child->getName() == $field->getName()) {
475
                return $i;
476
            }
477
            $i++;
478
        }
479
480
        return false;
481
    }
482
483
    /**
484
     * Transform the named field into a readonly feld.
485
     *
486
     * @param string|FormField
487
     * @return bool
488
     */
489
    public function makeFieldReadonly($field)
490
    {
491
        $fieldName = ($field instanceof FormField) ? $field->getName() : $field;
492
493
        // Iterate on items, looking for the applicable field
494
        foreach ($this->children as $i => $item) {
495
            if ($item instanceof CompositeField) {
496
                if ($item->makeFieldReadonly($fieldName)) {
497
                    return true;
498
                };
499
            } elseif ($item instanceof FormField && $item->getName() == $fieldName) {
500
                // Once it's found, use FormField::transform to turn the field into a readonly version of itself.
501
                $this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
502
503
                // A true results indicates that the field was found
504
                return true;
505
            }
506
        }
507
        return false;
508
    }
509
510
    public function debug()
511
    {
512
        $result = "$this->class ($this->name) <ul>";
513
        foreach ($this->children as $child) {
514
            $result .= "<li>" . Debug::text($child) . "&nbsp;</li>";
515
        }
516
        $result .= "</ul>";
517
        return $result;
518
    }
519
520
    /**
521
     * Validate this field
522
     *
523
     * @param Validator $validator
524
     * @return bool
525
     */
526
    public function validate($validator)
527
    {
528
        $valid = true;
529
        foreach ($this->children as $child) {
530
            /** @var FormField $child */
531
            $valid = ($child && $child->validate($validator) && $valid);
532
        }
533
        return $valid;
534
    }
535
}
536