CompositeField::makeFieldReadonly()   B
last analyzed

Complexity

Conditions 7
Paths 10

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 9
nc 10
nop 1
dl 0
loc 19
rs 8.8333
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
     * @var bool
26
     */
27
    protected $readonly;
28
29
    /**
30
     * @var int Toggle different css-rendering for multiple columns
31
     * ("onecolumn", "twocolumns", "threecolumns"). The content is determined
32
     * by the $children-array, so wrap all items you want to have grouped in a
33
     * column inside a CompositeField.
34
     * Caution: Please make sure that this variable actually matches the
35
     * count of your $children.
36
     */
37
    protected $columnCount = null;
38
39
    /**
40
     * @var string custom HTML tag to render with, e.g. to produce a <fieldset>.
41
     */
42
    protected $tag = 'div';
43
44
    /**
45
     * @var string Optional description for this set of fields.
46
     * If the {@link $tag} property is set to use a 'fieldset', this will be
47
     * rendered as a <legend> tag, otherwise its a 'title' attribute.
48
     */
49
    protected $legend;
50
51
    protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_STRUCTURAL;
52
53
    /** @skipUpgrade */
54
    protected $schemaComponent = 'CompositeField';
55
56
    public function __construct($children = null)
57
    {
58
        // Normalise $children to a FieldList
59
        if (!$children instanceof FieldList) {
60
            if (!is_array($children)) {
61
                // Fields are provided as a list of arguments
62
                $children = array_filter(func_get_args());
63
            }
64
            $children = new FieldList($children);
65
        }
66
        $this->setChildren($children);
67
68
        parent::__construct(null, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type SilverStripe\Forms\Silve...iewableData|null|string expected by parameter $title of SilverStripe\Forms\FormField::__construct(). ( Ignorable by Annotation )

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

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