Completed
Push — master ( c130e5...a809e8 )
by Sam
10:09
created

CompositeField::extraClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 6
rs 9.4285
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->getChildren() as $field) {
206
			if ($field instanceof FormField) {
207
				$field->setForm($form);
208
			}
209
		}
210
211
		parent::setForm($form);
212
		return $this;
213
	}
214
215
216
217
	public function setDisabled($disabled) {
218
		parent::setDisabled($disabled);
219
		foreach($this->getChildren() as $child) {
220
			$child->setDisabled($disabled);
221
		}
222
		return $this;
223
	}
224
225
	public function setReadonly($readonly)
226
	{
227
		parent::setReadonly($readonly);
228
		foreach($this->getChildren() as $child) {
229
			$child->setReadonly($readonly);
230
		}
231
		return $this;
232
	}
233
234
	public function setColumnCount($columnCount) {
235
		$this->columnCount = $columnCount;
236
		return $this;
237
	}
238
239
	public function getColumnCount() {
240
		return $this->columnCount;
241
	}
242
243
	public function isComposite() {
244
		return true;
245
	}
246
247
	public function hasData() {
248
		return false;
249
	}
250
251
	public function fieldByName($name) {
252
		return $this->children->fieldByName($name);
253
	}
254
255
	/**
256
	 * Add a new child field to the end of the set.
257
	 *
258
	 * @param FormField
259
	 */
260
	public function push(FormField $field) {
261
		$this->children->push($field);
262
	}
263
264
	/**
265
	 * Add a new child field to the beginning of the set.
266
	 *
267
	 * @param FormField
268
	 */
269
	public function unshift(FormField $field) {
270
		$this->children->unshift($field);
271
	}
272
273
	/**
274
	 * @uses FieldList->insertBefore()
275
	 *
276
	 * @param string $insertBefore
277
	 * @param FormField $field
278
	 * @return false|FormField
279
	 */
280
	public function insertBefore($insertBefore, $field) {
281
		return $this->children->insertBefore($insertBefore, $field);
282
	}
283
284
	/**
285
	 * @uses FieldList->insertAfter()
286
	 * @param string $insertAfter
287
	 * @param FormField $field
288
	 * @return false|FormField
289
	 */
290
	public function insertAfter($insertAfter, $field) {
291
		return $this->children->insertAfter($insertAfter, $field);
292
	}
293
294
	/**
295
	 * Remove a field from this CompositeField by Name.
296
	 * The field could also be inside a CompositeField.
297
	 *
298
	 * @param string $fieldName The name of the field
299
	 * @param boolean $dataFieldOnly If this is true, then a field will only
300
	 * be removed if it's a data field.  Dataless fields, such as tabs, will
301
	 * be left as-is.
302
	 */
303
	public function removeByName($fieldName, $dataFieldOnly = false) {
304
		$this->children->removeByName($fieldName, $dataFieldOnly);
305
	}
306
307
	public function replaceField($fieldName, $newField) {
308
		return $this->children->replaceField($fieldName, $newField);
309
	}
310
311
	public function rootFieldList() {
312
		if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
313
		else return $this->children;
314
	}
315
316
	public function __clone() {
317
		/** {@see FieldList::__clone(}} */
318
		$this->setChildren(clone $this->children);
319
	}
320
321
	/**
322
	 * Return a readonly version of this field. Keeps the composition but returns readonly
323
	 * versions of all the child {@link FormField} objects.
324
	 *
325
	 * @return CompositeField
326
	 */
327
	public function performReadonlyTransformation() {
328
		$newChildren = new FieldList();
329
		$clone = clone $this;
330
		if($clone->getChildren()) foreach($clone->getChildren() as $child) {
331
			/** @var FormField $child */
332
			$child = $child->transform(new ReadonlyTransformation());
333
			$newChildren->push($child);
334
		}
335
336
		$clone->setChildren($newChildren);
337
		$clone->setReadonly(true);
338
		$clone->addExtraClass($this->extraClass());
339
		$clone->setDescription($this->getDescription());
340
341
		return $clone;
342
	}
343
344
	/**
345
	 * Return a disabled version of this field. Keeps the composition but returns disabled
346
	 * versions of all the child {@link FormField} objects.
347
	 *
348
	 * @return CompositeField
349
	 */
350
	public function performDisabledTransformation() {
351
		$newChildren = new FieldList();
352
		$clone = clone $this;
353
		if($clone->getChildren()) foreach($clone->getChildren() as $child) {
354
			/** @var FormField $child */
355
			$child = $child->transform(new DisabledTransformation());
356
			$newChildren->push($child);
357
		}
358
359
		$clone->setChildren($newChildren);
360
		$clone->setDisabled(true);
361
		$clone->addExtraClass($this->extraClass());
362
		$clone->setDescription($this->getDescription());
363
		foreach($this->attributes as $k => $v) {
364
			$clone->setAttribute($k, $v);
365
		}
366
367
		return $clone;
368
	}
369
370
	public function IsReadonly() {
371
		return $this->readonly;
372
	}
373
374
	/**
375
	 * Find the numerical position of a field within
376
	 * the children collection. Doesn't work recursively.
377
	 *
378
	 * @param string|FormField
379
	 * @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
380
	 *             be found.
381
	 */
382
	public function fieldPosition($field) {
383
		if(is_string($field)) {
384
			$field = $this->fieldByName($field);
385
		}
386
		if(!$field) {
387
			return false;
388
		}
389
390
		$i = 0;
391
		foreach($this->children as $child) {
392
			/** @var FormField $child */
393
			if($child->getName() == $field->getName()) {
394
				return $i;
395
			}
396
			$i++;
397
		}
398
399
		return false;
400
	}
401
402
	/**
403
	 * Transform the named field into a readonly feld.
404
	 *
405
	 * @param string|FormField
406
	 * @return bool
407
	 */
408
	public function makeFieldReadonly($field) {
409
		$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
410
411
		// Iterate on items, looking for the applicable field
412
		foreach($this->children as $i => $item) {
413
			if($item instanceof CompositeField) {
414
				if($item->makeFieldReadonly($fieldName)) {
415
					return true;
416
				};
417
			} elseif($item instanceof FormField && $item->getName() == $fieldName) {
418
				// Once it's found, use FormField::transform to turn the field into a readonly version of itself.
419
				$this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
420
421
				// A true results indicates that the field was found
422
				return true;
423
			}
424
		}
425
		return false;
426
	}
427
428
	public function debug() {
429
		$result = "$this->class ($this->name) <ul>";
430
		foreach($this->children as $child) {
431
			$result .= "<li>" . Debug::text($child) . "&nbsp;</li>";
432
		}
433
		$result .= "</ul>";
434
		return $result;
435
	}
436
437
	/**
438
	 * Validate this field
439
	 *
440
	 * @param Validator $validator
441
	 * @return bool
442
	 */
443
	public function validate($validator) {
444
		$valid = true;
445
		foreach($this->children as $child){
446
			/** @var FormField $child */
447
			$valid = ($child && $child->validate($validator) && $valid);
448
		}
449
		return $valid;
450
	}
451
452
}
453