Completed
Pull Request — 3.4 (#6168)
by Damian
10:25
created

DropdownField::getSourceAsArray()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 12
nop 0
dl 0
loc 22
rs 8.6737
c 0
b 0
f 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;
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
						// Safety check against casting arrays as strings in PHP>5.4
176
						if(!is_array($value) && !is_array($this->value)) {
177
							$selected = ($value === $this->value) || (((string) $value) === ((string) $this->value));
178
						} else {
179
							$selected = ($value === $this->value);
180
						}
181
					}
182
183
					$this->isSelected = $selected;
184
				}
185
186
				$disabled = false;
187
				if(in_array($value, $this->disabledItems) && $title != $this->emptyString ){
188
					$disabled = 'disabled';
189
				}
190
191
				$options[] = new ArrayData(array(
192
					'Title' => $title,
193
					'Value' => $value,
194
					'Selected' => $selected,
195
					'Disabled' => $disabled,
196
				));
197
			}
198
		}
199
200
		$properties = array_merge($properties, array(
201
			'Options' => new ArrayList($options)
202
		));
203
204
		return parent::Field($properties);
205
	}
206
207
	/**
208
	 * Mark certain elements as disabled, regardless of the
209
	 * {@link setDisabled()} settings.
210
	 *
211
	 * @param array $items Collection of array keys, as defined in the $source array
212
	 */
213
	public function setDisabledItems($items) {
214
		$this->disabledItems = $items;
215
216
		return $this;
217
	}
218
219
	/**
220
	 * @return array
221
	 */
222
	public function getDisabledItems() {
223
		return $this->disabledItems;
224
	}
225
226
	/**
227
	 * @return array
228
	 */
229
	public function getAttributes() {
230
		return array_merge(
231
			parent::getAttributes(),
232
			array(
233
				'type' => null,
234
				'value' => null
235
			)
236
		);
237
	}
238
239
	/**
240
	 * @return boolean
241
	 */
242
	public function isSelected() {
243
		return $this->isSelected;
244
	}
245
246
	/**
247
	 * Gets the source array including any empty default values.
248
	 *
249
	 * @return array|ArrayAccess
250
	 */
251
	public function getSource() {
252
		return $this->source;
253
	}
254
255
	/**
256
	 * @param array|ArrayAccess $source
257
	 */
258
	public function setSource($source) {
259
		$this->source = $source;
260
261
		return $this;
262
	}
263
264
	/**
265
	 * @param boolean $bool
266
	 */
267
	public function setHasEmptyDefault($bool) {
268
		$this->hasEmptyDefault = $bool;
269
270
		return $this;
271
	}
272
273
	/**
274
	 * @return boolean
275
	 */
276
	public function getHasEmptyDefault() {
277
		return $this->hasEmptyDefault;
278
	}
279
280
	/**
281
	 * Set the default selection label, e.g. "select...".
282
	 *
283
	 * Defaults to an empty string. Automatically sets {@link $hasEmptyDefault}
284
	 * to true.
285
	 *
286
	 * @param string $str
287
	 */
288
	public function setEmptyString($str) {
289
		$this->setHasEmptyDefault(true);
290
		$this->emptyString = $str;
291
292
		return $this;
293
	}
294
295
	/**
296
	 * @return string
297
	 */
298
	public function getEmptyString() {
299
		return $this->emptyString;
300
	}
301
302
	/**
303
	 * @return LookupField
304
	 */
305
	public function performReadonlyTransformation() {
306
		$field = $this->castedCopy('LookupField');
307
		$field->setSource($this->getSource());
308
		$field->setReadonly(true);
309
310
		return $field;
311
	}
312
313
	/**
314
	 * Get the source of this field as an array
315
	 *
316
	 * @return array
317
	 */
318
	public function getSourceAsArray()
319
	{
320
		$source = $this->getSource();
321
322
		// Simplify source if presented as dataobject list
323
		if ($source instanceof SS_List) {
324
			$source = $source->map();
325
		}
326
		if ($source instanceof SS_Map) {
327
			$source = $source->toArray();
328
		}
329
330
		if (is_array($source)) {
331
			return $source;
332
		}
333
334
		$sourceArray = array();
335
		foreach ($source as $key => $value) {
336
			$sourceArray[$key] = $value;
337
		}
338
		return $sourceArray;
339
	}
340
341
	/**
342
	 * Validate this field
343
	 *
344
	 * @param Validator $validator
345
	 * @return bool
346
	 */
347
	public function validate($validator) {
348
		$source = $this->getSourceAsArray();
349
		$disabled = $this->getDisabledItems();
350
351
		if (!array_key_exists($this->value, $source) || in_array($this->value, $disabled)) {
352
			if ($this->getHasEmptyDefault() && !$this->value) {
353
				return true;
354
			}
355
			$validator->validationError(
356
				$this->name,
357
				_t(
358
					'DropdownField.SOURCE_VALIDATION',
359
					"Please select a value within the list provided. {value} is not a valid option",
360
					array('value' => $this->value)
361
				),
362
				"validation"
363
			);
364
			return false;
365
		}
366
		return true;
367
	}
368
369
	/**
370
	 * Returns another instance of this field, but "cast" to a different class.
371
	 *
372
	 * @see FormField::castedCopy()
373
	 *
374
	 * @param String $classOrCopy
375
	 * @return FormField
376
	 */
377
	public function castedCopy($classOrCopy) {
378
		$field = parent::castedCopy($classOrCopy);
379
		if($field->hasMethod('setHasEmptyDefault')) {
380
			$field->setHasEmptyDefault($this->getHasEmptyDefault());
381
		}
382
		return $field;
383
	}
384
}
385