Completed
Push — namespace-model ( 372fd8...16b22c )
by Sam
11:22 queued 02:27
created

CompositeField::removeByName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 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
		// Skipping FormField::__construct(), but we have to make sure this
61
		// doesn't count as a broken constructor
62
		$this->brokenOnConstruct = false;
63
		Object::__construct();
64
	}
65
66
	/**
67
	 * Returns all the sub-fields, suitable for <% loop FieldList %>
68
	 *
69
	 * @return FieldList
70
	 */
71
	public function FieldList() {
72
		return $this->children;
73
	}
74
75
	public function setID($id) {
76
		$this->id = $id;
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<CompositeField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
77
		return $this;
78
	}
79
80
	/**
81
	 * Accessor method for $this->children
82
	 *
83
	 * @return FieldList
84
	 */
85
	public function getChildren() {
86
		return $this->children;
87
	}
88
89
	/**
90
	 * @param FieldList $children
91
	 */
92
	public function setChildren($children) {
93
		$this->children = $children;
94
		return $this;
95
	}
96
97
	/**
98
	 * @param string
99
	 */
100
	public function setTag($tag) {
101
		$this->tag = $tag;
102
103
		return $this;
104
	}
105
106
	/**
107
	 * @return string
108
	 */
109
	public function getTag() {
110
		return $this->tag;
111
	}
112
113
	/**
114
	 * @param string
115
	 */
116
	public function setLegend($legend) {
117
		$this->legend = $legend;
118
		return $this;
119
	}
120
121
	/**
122
	 * @return string
123
	 */
124
	public function getLegend() {
125
		return $this->legend;
126
	}
127
128
	/**
129
	 * @deprecated
130
	 */
131
	public function extraClasses() {
132
		Deprecation::notice('4.0', 'Use extraClass() instead');
133
		return $this->extraClass();
134
	}
135
136
	public function extraClass() {
137
		$classes = array('field', 'CompositeField', parent::extraClass());
138
		if($this->columnCount) $classes[] = 'multicolumn';
139
140
		return implode(' ', $classes);
141
	}
142
143
	public function getAttributes() {
144
		return array_merge(
145
			parent::getAttributes(),
146
			array(
147
				'tabindex' => null,
148
				'type' => null,
149
				'value' => null,
150
				'type' => null,
151
				'title' => ($this->tag == 'fieldset') ? null : $this->legend
152
			)
153
		);
154
	}
155
156
	/**
157
	 * Add all of the non-composite fields contained within this field to the
158
	 * list.
159
	 *
160
	 * Sequentialisation is used when connecting the form to its data source
161
	 */
162
	public function collateDataFields(&$list, $saveableOnly = false) {
163
		foreach($this->children as $field) {
164
			if(is_object($field)) {
165
				if($field->isComposite()) $field->collateDataFields($list, $saveableOnly);
166 View Code Duplication
				if($saveableOnly) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
167
					$isIncluded =  ($field->hasData() && !$field->isReadonly() && !$field->isDisabled());
168
				} else {
169
					$isIncluded =  ($field->hasData());
170
				}
171
				if($isIncluded) {
172
					$name = $field->getName();
173
					if($name) {
174
						$formName = (isset($this->form)) ? $this->form->FormName() : '(unknown form)';
175
						if(isset($list[$name])) {
176
							user_error("collateDataFields() I noticed that a field called '$name' appears twice in"
177
								. " your form: '{$formName}'.  One is a '{$field->class}' and the other is a"
178
								. " '{$list[$name]->class}'", E_USER_ERROR);
179
						}
180
						$list[$name] = $field;
181
					}
182
				}
183
			}
184
		}
185
	}
186
187
	public function setForm($form) {
188
		foreach($this->children as $f)
189
			if(is_object($f)) $f->setForm($form);
190
191
		parent::setForm($form);
192
193
		return $this;
194
	}
195
196
	public function setColumnCount($columnCount) {
197
		$this->columnCount = $columnCount;
198
		return $this;
199
	}
200
201
	public function getColumnCount() {
202
		return $this->columnCount;
203
	}
204
205
	public function isComposite() {
206
		return true;
207
	}
208
209
	public function hasData() {
210
		return false;
211
	}
212
213
	public function fieldByName($name) {
214
		return $this->children->fieldByName($name);
215
	}
216
217
	/**
218
	 * Add a new child field to the end of the set.
219
	 *
220
	 * @param FormField
221
	 */
222
	public function push(FormField $field) {
223
		$this->children->push($field);
224
	}
225
226
	/**
227
	 * Add a new child field to the beginning of the set.
228
	 *
229
	 * @param FormField
230
	 */
231
	public function unshift(FormField $field) {
232
		$this->children->unshift($field);
233
	}
234
235
	/**
236
	 * @uses FieldList->insertBefore()
237
	 */
238
	public function insertBefore($insertBefore, $field) {
239
		$ret = $this->children->insertBefore($insertBefore, $field);
240
		$this->sequentialSet = null;
0 ignored issues
show
Documentation introduced by
The property sequentialSet does not exist on object<CompositeField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
241
		return $ret;
242
	}
243
244
	/**
245
	 * @uses FieldList->insertAfter()
246
	 */
247
	public function insertAfter($insertAfter, $field) {
248
		$ret = $this->children->insertAfter($insertAfter, $field);
249
		$this->sequentialSet = null;
0 ignored issues
show
Documentation introduced by
The property sequentialSet does not exist on object<CompositeField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
250
		return $ret;
251
	}
252
253
	/**
254
	 * Remove a field from this CompositeField by Name.
255
	 * The field could also be inside a CompositeField.
256
	 *
257
	 * @param string $fieldName The name of the field
258
	 * @param boolean $dataFieldOnly If this is true, then a field will only
259
	 * be removed if it's a data field.  Dataless fields, such as tabs, will
260
	 * be left as-is.
261
	 */
262
	public function removeByName($fieldName, $dataFieldOnly = false) {
263
		$this->children->removeByName($fieldName, $dataFieldOnly);
264
	}
265
266
	public function replaceField($fieldName, $newField) {
267
		return $this->children->replaceField($fieldName, $newField);
268
	}
269
270
	public function rootFieldList() {
271
		if(is_object($this->containerFieldList)) return $this->containerFieldList->rootFieldList();
272
		else return $this->children;
273
	}
274
275
	/**
276
	 * Return a readonly version of this field. Keeps the composition but returns readonly
277
	 * versions of all the child {@link FormField} objects.
278
	 *
279
	 * @return CompositeField
280
	 */
281
	public function performReadonlyTransformation() {
282
		$newChildren = new FieldList();
283
		$clone = clone $this;
284 View Code Duplication
		if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
285
			if(is_object($child)) $child = $child->transform(new ReadonlyTransformation());
286
			$newChildren->push($child, $idx);
0 ignored issues
show
Unused Code introduced by
The call to FieldList::push() has too many arguments starting with $idx.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
287
		}
288
289
		$clone->children = $newChildren;
290
		$clone->readonly = true;
291
		$clone->addExtraClass($this->extraClass());
292
		$clone->setDescription($this->getDescription());
293
294
		return $clone;
295
	}
296
297
	/**
298
	 * Return a disabled version of this field. Keeps the composition but returns disabled
299
	 * versions of all the child {@link FormField} objects.
300
	 *
301
	 * @return CompositeField
302
	 */
303
	public function performDisabledTransformation() {
304
		$newChildren = new FieldList();
305
		$clone = clone $this;
306 View Code Duplication
		if($clone->getChildren()) foreach($clone->getChildren() as $idx => $child) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
			if(is_object($child)) $child = $child->transform(new DisabledTransformation());
308
			$newChildren->push($child, $idx);
0 ignored issues
show
Unused Code introduced by
The call to FieldList::push() has too many arguments starting with $idx.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
309
		}
310
311
		$clone->children = $newChildren;
312
		$clone->readonly = true;
313
		$clone->addExtraClass($this->extraClass());
314
		$clone->setDescription($this->getDescription());
315
		foreach($this->attributes as $k => $v) {
316
			$clone->setAttribute($k, $v);
317
		}
318
319
		return $clone;
320
	}
321
322
	public function IsReadonly() {
323
		return $this->readonly;
324
	}
325
326
	/**
327
	 * Find the numerical position of a field within
328
	 * the children collection. Doesn't work recursively.
329
	 *
330
	 * @param string|FormField
331
	 * @return int Position in children collection (first position starts with 0). Returns FALSE if the field can't
332
	 *             be found.
333
	 */
334 View Code Duplication
	public function fieldPosition($field) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
		if(is_string($field)) $field = $this->fieldByName($field);
336
		if(!$field) return false;
337
338
		$i = 0;
339
		foreach($this->children as $child) {
340
			if($child->getName() == $field->getName()) return $i;
341
			$i++;
342
		}
343
344
		return false;
345
	}
346
347
	/**
348
	 * Transform the named field into a readonly feld.
349
	 *
350
	 * @param string|FormField
351
	 */
352
	public function makeFieldReadonly($field) {
353
		$fieldName = ($field instanceof FormField) ? $field->getName() : $field;
354
355
		// Iterate on items, looking for the applicable field
356
		foreach($this->children as $i => $item) {
357
			if($item->isComposite()) {
358
				$item->makeFieldReadonly($fieldName);
359
			} else {
360
				// Once it's found, use FormField::transform to turn the field into a readonly version of itself.
361
				if($item->getName() == $fieldName) {
362
					$this->children->replaceField($fieldName, $item->transform(new ReadonlyTransformation()));
363
364
					// Clear an internal cache
365
					$this->sequentialSet = null;
0 ignored issues
show
Documentation introduced by
The property sequentialSet does not exist on object<CompositeField>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
366
367
					// A true results indicates that the field was found
368
					return true;
369
				}
370
			}
371
		}
372
		return false;
373
	}
374
375
	public function debug() {
376
		$result = "$this->class ($this->name) <ul>";
377
		foreach($this->children as $child) {
378
			$result .= "<li>" . Debug::text($child) . "&nbsp;</li>";
379
		}
380
		$result .= "</ul>";
381
		return $result;
382
	}
383
384
	/**
385
	 * Validate this field
386
	 *
387
	 * @param Validator $validator
388
	 * @return bool
389
	 */
390
	public function validate($validator) {
391
		$valid = true;
392
		foreach($this->children as $idx => $child){
393
			$valid = ($child && $child->validate($validator) && $valid);
394
		}
395
		return $valid;
396
	}
397
398
}
399