Completed
Push — master ( d19955...af891e )
by Sam
22s
created

CompositeField::getSchemaDataDefaults()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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