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); |
|
|
|
|
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
|
|
|
$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) |
|
|
|
|
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
|
|
|
|
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.