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