Completed
Push — master ( daaff8...44ccfa )
by Ingo
22:24 queued 11:43
created

SelectField::getAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use SilverStripe\ORM\SS_List;
4
use SilverStripe\ORM\SS_Map;
5
6
/**
7
 * Represents a field that allows users to select one or more items from a list
8
 */
9
abstract class SelectField extends FormField {
10
11
	/**
12
	 * Associative or numeric array of all dropdown items,
13
	 * with array key as the submitted field value, and the array value as a
14
	 * natural language description shown in the interface element.
15
	 *
16
	 * @var array|ArrayAccess
17
	 */
18
	protected $source;
19
20
	/**
21
	 * The values for items that should be disabled (greyed out) in the dropdown.
22
	 * This is a non-associative array
23
	 *
24
	 * @var array
25
	 */
26
	protected $disabledItems = array();
27
28
	/**
29
	 * @param string $name The field name
30
	 * @param string $title The field title
31
	 * @param array|ArrayAccess $source A map of the dropdown items
32
	 * @param mixed $value The current value
33
	 */
34
	public function __construct($name, $title = null, $source = array(), $value = null) {
35
		$this->setSource($source);
36
		if(!isset($title)) {
37
			$title = $name;
38
		}
39
		parent::__construct($name, $title, $value);
40
	}
41
42
	public function getSchemaStateDefaults() {
43
		$data = parent::getSchemaStateDefaults();
44
		$disabled = $this->getDisabledItems();
45
46
		// Add options to 'data'
47
		$source = $this->getSource();
48
		$data['source'] = (is_array($source))
49
			? array_map(function ($value, $title) use ($disabled) {
50
				return [
51
					'value' => $value,
52
					'title' => $title,
53
					'disabled' => in_array($value, $disabled),
54
				];
55
			}, array_keys($source), $source)
56
			: [];
57
58
		return $data;
59
	}
60
61
	/**
62
	 * Mark certain elements as disabled,
63
	 * regardless of the {@link setDisabled()} settings.
64
	 *
65
	 * These should be items that appear in the source list, not in addition to them.
66
	 *
67
	 * @param array|SS_List $items Collection of values or items
68
	 * @return $this
69
	 */
70
	public function setDisabledItems($items){
71
		$this->disabledItems = $this->getListValues($items);
72
		return $this;
73
	}
74
75
	/**
76
	 * Non-associative list of disabled item values
77
	 *
78
	 * @return array
79
	 */
80
	public function getDisabledItems(){
81
		return $this->disabledItems;
82
	}
83
84
	/**
85
	 * Check if the given value is disabled
86
	 *
87
	 * @param string $value
88
	 * @return bool
89
	 */
90
	protected function isDisabledValue($value) {
91
		if($this->isDisabled()) {
92
			return true;
93
		}
94
		return in_array($value, $this->getDisabledItems());
95
	}
96
97
	public function getAttributes() {
98
		return array_merge(
99
			parent::getAttributes(),
100
			array('type' => null, 'value' => null)
101
		);
102
	}
103
104
	/**
105
	 * Retrieve all values in the source array
106
	 *
107
	 * @return array
108
	 */
109
	protected function getSourceValues() {
110
		return array_keys($this->getSource());
111
	}
112
113
	/**
114
	 * Gets all valid values for this field.
115
	 *
116
	 * Does not include "empty" value if specified
117
	 *
118
	 * @return array
119
	 */
120
	public function getValidValues() {
121
		$valid = array_diff($this->getSourceValues(), $this->getDisabledItems());
122
		// Renumber indexes from 0
123
		return array_values($valid);
124
	}
125
126
	/**
127
	 * Gets the source array not including any empty default values.
128
	 *
129
	 * @return array|ArrayAccess
130
	 */
131
	public function getSource() {
132
		return $this->source;
133
	}
134
135
	/**
136
	 * Set the source for this list
137
	 *
138
	 * @param mixed $source
139
	 * @return $this
140
	 */
141
	public function setSource($source) {
142
		$this->source = $this->getListMap($source);
143
		return $this;
144
	}
145
146
	/**
147
	 * Given a list of values, extract the associative map of id => title
148
	 *
149
	 * @param mixed $source
150
	 * @return array Associative array of ids and titles
151
	 */
152
	protected function getListMap($source) {
153
		// Extract source as an array
154
		if($source instanceof SS_List) {
155
			$source = $source->map();
156
		}
157
		if($source instanceof SS_Map) {
158
			$source = $source->toArray();
159
		}
160
		if(!is_array($source) && !($source instanceof ArrayAccess)) {
161
			user_error('$source passed in as invalid type', E_USER_ERROR);
162
		}
163
164
		return $source;
165
	}
166
167
	/**
168
	 * Given a non-array collection, extract the non-associative list of ids
169
	 * If passing as array, treat the array values (not the keys) as the ids
170
	 *
171
	 * @param mixed $values
172
	 * @return array Non-associative list of values
173
	 */
174
	protected function getListValues($values) {
175
		// Empty values
176
		if(empty($values)) {
177
			return array();
178
		}
179
180
		// Direct array
181
		if(is_array($values)) {
182
			return array_values($values);
183
		}
184
185
		// Extract lists
186
		if($values instanceof SS_List) {
187
			return $values->column('ID');
188
		}
189
190
		return array(trim($values));
191
	}
192
193
	/**
194
	 * Determine if the current value of this field matches the given option value
195
	 *
196
	 * @param mixed $dataValue The value as extracted from the source of this field (or empty value if available)
197
	 * @param mixed $userValue The value as submitted by the user
198
	 * @return boolean True if the selected value matches the given option value
199
	 */
200
	public function isSelectedValue($dataValue, $userValue) {
201
		if($dataValue === $userValue) {
202
			return true;
203
		}
204
205
		// Allow null to match empty strings
206
		if($dataValue === '' && $userValue === null) {
207
			return true;
208
		}
209
210
		// Safety check against casting arrays as strings in PHP>5.4
211
 		if(is_array($dataValue) || is_array($userValue)) {
212
			return false;
213
		}
214
215
		// For non-falsey values do loose comparison
216
		if($dataValue) {
217
			return $dataValue == $userValue;
218
		}
219
220
		// For empty values, use string comparison to perform visible value match
221
		return ((string) $dataValue) === ((string) $userValue);
222
	}
223
224
	public function performReadonlyTransformation() {
225
		/** @var LookupField $field */
226
		$field = $this->castedCopy('LookupField');
227
		$field->setSource($this->getSource());
228
		$field->setReadonly(true);
229
230
		return $field;
231
	}
232
233
	public function performDisabledTransformation() {
234
		$clone = clone $this;
235
		$clone->setDisabled(true);
236
		return $clone;
237
	}
238
239
	/**
240
	 * Returns another instance of this field, but "cast" to a different class.
241
	 *
242
	 * @see FormField::castedCopy()
243
	 *
244
	 * @param String $classOrCopy
245
	 * @return FormField
246
	 */
247
	public function castedCopy($classOrCopy) {
248
		$field = parent::castedCopy($classOrCopy);
249
		if($field instanceof SelectField) {
250
			$field->setSource($this->getSource());
251
		}
252
		return $field;
253
	}
254
}
255