Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

DropdownField::isSelected()   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 0
1
<?php
2
/**
3
 * Dropdown field, created from a <select> tag.
4
 *
5
 * <b>Setting a $has_one relation</b>
6
 *
7
 * Using here an example of an art gallery, with Exhibition pages,
8
 * each of which has a Gallery they belong to.  The Gallery class is also user-defined.
9
 * <code>
10
 * 	static $has_one = array(
11
 * 		'Gallery' => 'Gallery',
12
 * 	);
13
 *
14
 * 	public function getCMSFields() {
15
 * 		$fields = parent::getCMSFields();
16
 * 		$field = DropdownField::create('GalleryID', 'Gallery', Gallery::get()->map('ID', 'Title'))
17
 * 			->setEmptyString('(Select one)');
18
 * 		$fields->addFieldToTab('Root.Content', $field, 'Content');
19
 * </code>
20
 *
21
 * As you see, you need to put "GalleryID", rather than "Gallery" here.
22
 *
23
 * <b>Populate with Array</b>
24
 *
25
 * Example model defintion:
26
 * <code>
27
 * class MyObject extends DataObject {
28
 *   static $db = array(
29
 *     'Country' => "Varchar(100)"
30
 *   );
31
 * }
32
 * </code>
33
 *
34
 * Example instantiation:
35
 * <code>
36
 * DropdownField::create(
37
 *   'Country',
38
 *   'Country',
39
 *   array(
40
 *     'NZ' => 'New Zealand',
41
 *     'US' => 'United States',
42
 *     'GEM'=> 'Germany'
43
 *   )
44
 * );
45
 * </code>
46
 *
47
 * <b>Populate with Enum-Values</b>
48
 *
49
 * You can automatically create a map of possible values from an {@link Enum} database column.
50
 *
51
 * Example model definition:
52
 * <code>
53
 * class MyObject extends DataObject {
54
 *   static $db = array(
55
 *     'Country' => "Enum('New Zealand,United States,Germany','New Zealand')"
56
 *   );
57
 * }
58
 * </code>
59
 *
60
 * Field construction:
61
 * <code>
62
 * DropdownField::create(
63
 *   'Country',
64
 *   'Country',
65
 *   singleton('MyObject')->dbObject('Country')->enumValues()
66
 * );
67
 * </code>
68
 *
69
 * <b>Disabling individual items</b>
70
 *
71
 * Individual items can be disabled by feeding their array keys to setDisabledItems.
72
 *
73
 * <code>
74
 * $DrDownField->setDisabledItems( array( 'US', 'GEM' ) );
75
 * </code>
76
 *
77
 * @see CheckboxSetField for multiple selections through checkboxes instead.
78
 * @see ListboxField for a single <select> box (with single or multiple selections).
79
 * @see TreeDropdownField for a rich and customizeable UI that can visualize a tree of selectable elements
80
 *
81
 * @package forms
82
 * @subpackage fields-basic
83
 */
84
class DropdownField extends FormField {
85
86
	/**
87
	 * @var array|ArrayAccess $source Associative or numeric array of all dropdown items,
88
	 * with array key as the submitted field value, and the array value as a
89
	 * natural language description shown in the interface element.
90
	 */
91
	protected $source;
92
93
	/**
94
	 * @var boolean $isSelected Determines if the field was selected
95
	 * at the time it was rendered, so if {@link $value} matches on of the array
96
	 * values specified in {@link $source}
97
	 */
98
	protected $isSelected;
99
100
	/**
101
	 * @var boolean $hasEmptyDefault Show the first <option> element as
102
	 * empty (not having a value), with an optional label defined through
103
	 * {@link $emptyString}. By default, the <select> element will be
104
	 * rendered with the first option from {@link $source} selected.
105
	 */
106
	protected $hasEmptyDefault = false;
107
108
	/**
109
	 * @var string $emptyString The title shown for an empty default selection,
110
	 * e.g. "Select...".
111
	 */
112
	protected $emptyString = '';
113
114
	/**
115
	 * @var array $disabledItems The keys for items that should be disabled (greyed out) in the dropdown
116
	 */
117
	protected $disabledItems = array();
118
119
	/**
120
	 * @param string $name The field name
121
	 * @param string $title The field title
122
	 * @param array|ArrayAccess $source A map of the dropdown items
123
	 * @param string $value The current value
124
	 * @param Form $form The parent form
125
	 */
126
	public function __construct($name, $title=null, $source=array(), $value='', $form=null, $emptyString=null) {
127
		$this->setSource($source);
128
129
		if($emptyString === true) {
130
			Deprecation::notice('4.0',
131
				'Please use setHasEmptyDefault(true) instead of passing a boolean true $emptyString argument',
132
				Deprecation::SCOPE_GLOBAL);
133
		}
134
		if(is_string($emptyString)) {
135
			Deprecation::notice('4.0', 'Please use setEmptyString() instead of passing a string emptyString argument.',
136
				Deprecation::SCOPE_GLOBAL);
137
		}
138
139
		if($emptyString) $this->setHasEmptyDefault(true);
140
		if(is_string($emptyString)) $this->setEmptyString($emptyString);
141
142
		parent::__construct($name, ($title===null) ? $name : $title, $value, $form);
0 ignored issues
show
Unused Code introduced by
The call to FormField::__construct() has too many arguments starting with $form.

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...
143
	}
144
145
	/**
146
	 * @param array $properties
147
	 * @return HTMLText
148
	 */
149
	public function Field($properties = array()) {
150
		$source = $this->getSource();
151
		$options = array();
152
153
		if ($this->getHasEmptyDefault()) {
154
			$selected = ($this->value === '' || $this->value === null);
155
			$disabled = (in_array('', $this->disabledItems, true)) ? 'disabled' : false;
156
157
			$options[] = new ArrayData(array(
158
				'Value' => '',
159
				'Title' => $this->getEmptyString(),
160
				'Selected' => $selected,
161
				'Disabled' => $disabled
162
			));
163
		}
164
165
		if ($source) {
166
			foreach($source as $value => $title) {
167
				$selected = false;
0 ignored issues
show
Unused Code introduced by
$selected is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
168
				if($value === '' && ($this->value === '' || $this->value === null)) {
169
					$selected = true;
170
				} else {
171
					// check against value, fallback to a type check comparison when !value
172
					if($value) {
173
						$selected = ($value == $this->value);
174
					} else {
175
						$selected = ($value === $this->value) || (((string) $value) === ((string) $this->value));
176
					}
177
178
					$this->isSelected = $selected;
179
				}
180
181
				$disabled = false;
182
				if(in_array($value, $this->disabledItems) && $title != $this->emptyString ){
183
					$disabled = 'disabled';
184
				}
185
186
				$options[] = new ArrayData(array(
187
					'Title' => $title,
188
					'Value' => $value,
189
					'Selected' => $selected,
190
					'Disabled' => $disabled,
191
				));
192
			}
193
		}
194
195
		$properties = array_merge($properties, array(
196
			'Options' => new ArrayList($options)
197
		));
198
199
		return parent::Field($properties);
200
	}
201
202
	/**
203
	 * Mark certain elements as disabled, regardless of the
204
	 * {@link setDisabled()} settings.
205
	 *
206
	 * @param array $items Collection of array keys, as defined in the $source array
207
	 */
208
	public function setDisabledItems($items) {
209
		$this->disabledItems = $items;
210
211
		return $this;
212
	}
213
214
	/**
215
	 * @return array
216
	 */
217
	public function getDisabledItems() {
218
		return $this->disabledItems;
219
	}
220
221
	/**
222
	 * @return array
223
	 */
224
	public function getAttributes() {
225
		return array_merge(
226
			parent::getAttributes(),
227
			array(
228
				'type' => null,
229
				'value' => null
230
			)
231
		);
232
	}
233
234
	/**
235
	 * @return boolean
236
	 */
237
	public function isSelected() {
238
		return $this->isSelected;
239
	}
240
241
	/**
242
	 * Gets the source array including any empty default values.
243
	 *
244
	 * @return array|ArrayAccess
245
	 */
246
	public function getSource() {
247
		return $this->source;
248
	}
249
250
	/**
251
	 * @param array|ArrayAccess $source
252
	 */
253
	public function setSource($source) {
254
		$this->source = $source;
255
256
		return $this;
257
	}
258
259
	/**
260
	 * @param boolean $bool
261
	 */
262
	public function setHasEmptyDefault($bool) {
263
		$this->hasEmptyDefault = $bool;
264
265
		return $this;
266
	}
267
268
	/**
269
	 * @return boolean
270
	 */
271
	public function getHasEmptyDefault() {
272
		return $this->hasEmptyDefault;
273
	}
274
275
	/**
276
	 * Set the default selection label, e.g. "select...".
277
	 *
278
	 * Defaults to an empty string. Automatically sets {@link $hasEmptyDefault}
279
	 * to true.
280
	 *
281
	 * @param string $str
282
	 */
283
	public function setEmptyString($str) {
284
		$this->setHasEmptyDefault(true);
285
		$this->emptyString = $str;
286
287
		return $this;
288
	}
289
290
	/**
291
	 * @return string
292
	 */
293
	public function getEmptyString() {
294
		return $this->emptyString;
295
	}
296
297
	/**
298
	 * @return LookupField
299
	 */
300
	public function performReadonlyTransformation() {
301
		$field = $this->castedCopy('LookupField');
302
		$field->setSource($this->getSource());
303
		$field->setReadonly(true);
304
305
		return $field;
306
	}
307
308
	/**
309
	 * Get the source of this field as an array
310
	 *
311
	 * @return array
312
	 */
313
	public function getSourceAsArray()
314
	{
315
		$source = $this->getSource();
316
		if (is_array($source)) {
317
			return $source;
318
		} else {
319
			$sourceArray = array();
320
			foreach ($source as $key => $value) {
321
				$sourceArray[$key] = $value;
322
			}
323
		}
324
		return $sourceArray;
325
	}
326
327
	/**
328
	 * Validate this field
329
	 *
330
	 * @param Validator $validator
331
	 * @return bool
332
	 */
333
	public function validate($validator) {
334
		$source = $this->getSourceAsArray();
335
		$disabled = $this->getDisabledItems();
336
337
		if (!array_key_exists($this->value, $source) || in_array($this->value, $disabled)) {
338
			if ($this->getHasEmptyDefault() && !$this->value) {
339
				return true;
340
			}
341
			$validator->validationError(
342
				$this->name,
343
				_t(
344
					'DropdownField.SOURCE_VALIDATION',
345
					"Please select a value within the list provided. {value} is not a valid option",
346
					array('value' => $this->value)
0 ignored issues
show
Documentation introduced by
array('value' => $this->value) is of type array<string,*,{"value":"*"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
347
				),
348
				"validation"
349
			);
350
			return false;
351
		}
352
		return true;
353
	}
354
355
	/**
356
	 * Returns another instance of this field, but "cast" to a different class.
357
	 *
358
	 * @see FormField::castedCopy()
359
	 *
360
	 * @param String $classOrCopy
361
	 * @return FormField
362
	 */
363
	public function castedCopy($classOrCopy) {
364
		$field = parent::castedCopy($classOrCopy);
365
		if($field->hasMethod('setHasEmptyDefault')) {
366
			$field->setHasEmptyDefault($this->getHasEmptyDefault());
367
		}
368
		return $field;
369
	}
370
}
371