Completed
Push — master ( 955d75...4d8fb1 )
by Ingo
36:25 queued 24:15
created

CompositeField::performDisabledTransformation()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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