Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 157 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
HTMLSelectAndOtherField | |
0.00% |
0 / 156 |
|
0.00% |
0 / 10 |
1056 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
getInputHTML | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
12 | |||
getOOUIModules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInputOOUI | |
0.00% |
0 / 46 |
|
0.00% |
0 / 1 |
20 | |||
getInputWidget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInputCodex | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
12 | |||
getDefault | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
loadDataFromRequest | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
getSize | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | namespace MediaWiki\HTMLForm\Field; |
4 | |
5 | use HTMLTextField; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Html\Html; |
8 | use MediaWiki\Request\WebRequest; |
9 | use MediaWiki\Widget\SelectWithInputWidget; |
10 | |
11 | /** |
12 | * Double field with a dropdown list constructed from a system message in the format |
13 | * * Optgroup header |
14 | * ** <option value> |
15 | * * New Optgroup header |
16 | * Plus a text field underneath for an additional reason. The 'value' of the field is |
17 | * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the |
18 | * select dropdown. |
19 | * |
20 | * @stable to extend |
21 | * @todo FIXME: If made 'required', only the text field should be compulsory. |
22 | */ |
23 | class HTMLSelectAndOtherField extends HTMLSelectField { |
24 | private const FIELD_CLASS = 'mw-htmlform-select-and-other-field'; |
25 | /** @var string[] */ |
26 | private $mFlatOptions; |
27 | |
28 | /** |
29 | * @stable to call |
30 | * @inheritDoc |
31 | */ |
32 | public function __construct( $params ) { |
33 | if ( array_key_exists( 'other', $params ) ) { |
34 | // Do nothing |
35 | } elseif ( array_key_exists( 'other-message', $params ) ) { |
36 | $params['other'] = $this->getMessage( $params['other-message'] )->plain(); |
37 | } else { |
38 | $params['other'] = $this->msg( 'htmlform-selectorother-other' )->plain(); |
39 | } |
40 | |
41 | parent::__construct( $params ); |
42 | |
43 | if ( $this->getOptions() === null ) { |
44 | throw new InvalidArgumentException( 'HTMLSelectAndOtherField called without any options' ); |
45 | } |
46 | if ( !in_array( 'other', $this->mOptions, true ) ) { |
47 | // Have 'other' always as first element |
48 | $this->mOptions = [ $params['other'] => 'other' ] + $this->mOptions; |
49 | } |
50 | $this->mFlatOptions = self::flattenOptions( $this->getOptions() ); |
51 | } |
52 | |
53 | public function getInputHTML( $value ) { |
54 | $select = parent::getInputHTML( $value[1] ); |
55 | |
56 | $textAttribs = [ |
57 | 'size' => $this->getSize(), |
58 | ]; |
59 | |
60 | if ( isset( $this->mParams['maxlength-unit'] ) ) { |
61 | $textAttribs['data-mw-maxlength-unit'] = $this->mParams['maxlength-unit']; |
62 | } |
63 | |
64 | $allowedParams = [ |
65 | 'required', |
66 | 'autofocus', |
67 | 'multiple', |
68 | 'disabled', |
69 | 'tabindex', |
70 | 'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js |
71 | 'maxlength-unit', // 'bytes' or 'codepoints', see mediawiki.htmlform.js |
72 | ]; |
73 | |
74 | $textAttribs += $this->getAttributes( $allowedParams ); |
75 | |
76 | $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs ); |
77 | |
78 | $wrapperAttribs = [ |
79 | 'id' => $this->mID, |
80 | 'class' => self::FIELD_CLASS |
81 | ]; |
82 | if ( $this->mClass !== '' ) { |
83 | $wrapperAttribs['class'] .= ' ' . $this->mClass; |
84 | } |
85 | return Html::rawElement( |
86 | 'div', |
87 | $wrapperAttribs, |
88 | "$select<br />\n$textbox" |
89 | ); |
90 | } |
91 | |
92 | protected function getOOUIModules() { |
93 | return [ 'mediawiki.widgets.SelectWithInputWidget' ]; |
94 | } |
95 | |
96 | public function getInputOOUI( $value ) { |
97 | $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' ); |
98 | |
99 | # TextInput |
100 | $textAttribs = [ |
101 | 'name' => $this->mName . '-other', |
102 | 'value' => $value[2], |
103 | ]; |
104 | |
105 | $allowedParams = [ |
106 | 'required', |
107 | 'autofocus', |
108 | 'multiple', |
109 | 'disabled', |
110 | 'tabindex', |
111 | 'maxlength', |
112 | ]; |
113 | |
114 | $textAttribs += \OOUI\Element::configFromHtmlAttributes( |
115 | $this->getAttributes( $allowedParams ) |
116 | ); |
117 | |
118 | # DropdownInput |
119 | $dropdownInputAttribs = [ |
120 | 'name' => $this->mName, |
121 | 'options' => $this->getOptionsOOUI(), |
122 | 'value' => $value[1], |
123 | ]; |
124 | |
125 | $allowedParams = [ |
126 | 'tabindex', |
127 | 'disabled', |
128 | ]; |
129 | |
130 | $dropdownInputAttribs += \OOUI\Element::configFromHtmlAttributes( |
131 | $this->getAttributes( $allowedParams ) |
132 | ); |
133 | |
134 | $disabled = false; |
135 | if ( isset( $this->mParams[ 'disabled' ] ) && $this->mParams[ 'disabled' ] ) { |
136 | $disabled = true; |
137 | } |
138 | |
139 | $inputClasses = [ self::FIELD_CLASS ]; |
140 | if ( $this->mClass !== '' ) { |
141 | $inputClasses = array_merge( $inputClasses, explode( ' ', $this->mClass ) ); |
142 | } |
143 | return $this->getInputWidget( [ |
144 | 'id' => $this->mID, |
145 | 'disabled' => $disabled, |
146 | 'textinput' => $textAttribs, |
147 | 'dropdowninput' => $dropdownInputAttribs, |
148 | 'or' => false, |
149 | 'required' => $this->mParams[ 'required' ] ?? false, |
150 | 'classes' => $inputClasses, |
151 | 'data' => [ |
152 | 'maxlengthUnit' => $this->mParams['maxlength-unit'] ?? 'bytes' |
153 | ], |
154 | ] ); |
155 | } |
156 | |
157 | /** |
158 | * @stable to override |
159 | * @param array $params |
160 | * @return \MediaWiki\Widget\SelectWithInputWidget |
161 | */ |
162 | public function getInputWidget( $params ) { |
163 | return new SelectWithInputWidget( $params ); |
164 | } |
165 | |
166 | public function getInputCodex( $value, $hasErrors ) { |
167 | $select = parent::getInputCodex( $value[1], $hasErrors ); |
168 | |
169 | // Set up attributes for the text input. |
170 | $textInputAttribs = [ |
171 | 'size' => $this->getSize(), |
172 | 'name' => $this->mName . '-other' |
173 | ]; |
174 | |
175 | if ( isset( $this->mParams['maxlength-unit'] ) ) { |
176 | $textInputAttribs['data-mw-maxlength-unit'] = $this->mParams['maxlength-unit']; |
177 | } |
178 | |
179 | $allowedParams = [ |
180 | 'required', |
181 | 'autofocus', |
182 | 'multiple', |
183 | 'disabled', |
184 | 'tabindex', |
185 | 'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js |
186 | 'maxlength-unit', // 'bytes' or 'codepoints', see mediawiki.htmlform.js |
187 | ]; |
188 | |
189 | $textInputAttribs += $this->getAttributes( $allowedParams ); |
190 | |
191 | // Get text input HTML. |
192 | $textInput = HTMLTextField::buildCodexComponent( |
193 | $value[2], |
194 | $hasErrors, |
195 | 'text', |
196 | $this->mName . '-other', |
197 | $textInputAttribs |
198 | ); |
199 | |
200 | // Set up the wrapper element and return the entire component. |
201 | $wrapperAttribs = [ |
202 | 'id' => $this->mID, |
203 | 'class' => [ self::FIELD_CLASS ] |
204 | ]; |
205 | if ( $this->mClass !== '' ) { |
206 | $wrapperAttribs['class'][] = $this->mClass; |
207 | } |
208 | return Html::rawElement( |
209 | 'div', |
210 | $wrapperAttribs, |
211 | "$select<br />\n$textInput" |
212 | ); |
213 | } |
214 | |
215 | /** |
216 | * @inheritDoc |
217 | */ |
218 | public function getDefault() { |
219 | $default = parent::getDefault(); |
220 | |
221 | // Default values of empty form |
222 | $final = ''; |
223 | $list = 'other'; |
224 | $text = ''; |
225 | |
226 | if ( $default !== null ) { |
227 | $final = $default; |
228 | // Assume the default is a text value, with the 'other' option selected. |
229 | // Then check if that assumption is correct, and update $list and $text if not. |
230 | $text = $final; |
231 | foreach ( $this->mFlatOptions as $option ) { |
232 | $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text(); |
233 | if ( str_starts_with( $final, $match ) ) { |
234 | $list = $option; |
235 | $text = substr( $final, strlen( $match ) ); |
236 | break; |
237 | } |
238 | } |
239 | } |
240 | |
241 | return [ $final, $list, $text ]; |
242 | } |
243 | |
244 | /** |
245 | * @param WebRequest $request |
246 | * |
247 | * @return array ["<overall message>","<select value>","<text field value>"] |
248 | */ |
249 | public function loadDataFromRequest( $request ) { |
250 | if ( $request->getCheck( $this->mName ) ) { |
251 | $list = $request->getText( $this->mName ); |
252 | $text = $request->getText( $this->mName . '-other' ); |
253 | |
254 | // Should be built the same as in mediawiki.htmlform.js |
255 | if ( $list == 'other' ) { |
256 | $final = $text; |
257 | } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) { |
258 | # User has spoofed the select form to give an option which wasn't |
259 | # in the original offer. Sulk... |
260 | $final = $text; |
261 | } elseif ( $text == '' ) { |
262 | $final = $list; |
263 | } else { |
264 | $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text; |
265 | } |
266 | return [ $final, $list, $text ]; |
267 | } |
268 | return $this->getDefault(); |
269 | } |
270 | |
271 | public function getSize() { |
272 | return $this->mParams['size'] ?? 45; |
273 | } |
274 | |
275 | public function validate( $value, $alldata ) { |
276 | # HTMLSelectField forces $value to be one of the options in the select |
277 | # field, which is not useful here. But we do want the validation further up |
278 | # the chain |
279 | $p = parent::validate( $value[1], $alldata ); |
280 | |
281 | if ( $p !== true ) { |
282 | return $p; |
283 | } |
284 | |
285 | if ( isset( $this->mParams['required'] ) |
286 | && $this->mParams['required'] !== false |
287 | && $value[0] === '' |
288 | ) { |
289 | return $this->msg( 'htmlform-required' ); |
290 | } |
291 | |
292 | return true; |
293 | } |
294 | } |
295 | |
296 | /** @deprecated class alias since 1.42 */ |
297 | class_alias( HTMLSelectAndOtherField::class, 'HTMLSelectAndOtherField' ); |