Merge "FauxRequest: don’t override getValues()"
[lhc/web/wiklou.git] / includes / htmlform / fields / HTMLSelectAndOtherField.php
1 <?php
2
3 /**
4 * Double field with a dropdown list constructed from a system message in the format
5 * * Optgroup header
6 * ** <option value>
7 * * New Optgroup header
8 * Plus a text field underneath for an additional reason. The 'value' of the field is
9 * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
10 * select dropdown.
11 * @todo FIXME: If made 'required', only the text field should be compulsory.
12 */
13 class HTMLSelectAndOtherField extends HTMLSelectField {
14 /** @var string[] */
15 private $mFlatOptions;
16
17 public function __construct( $params ) {
18 if ( array_key_exists( 'other', $params ) ) {
19 // Do nothing
20 } elseif ( array_key_exists( 'other-message', $params ) ) {
21 $params['other'] = $this->getMessage( $params['other-message'] )->plain();
22 } else {
23 $params['other'] = $this->msg( 'htmlform-selectorother-other' )->plain();
24 }
25
26 parent::__construct( $params );
27
28 if ( $this->getOptions() === null ) {
29 // Sulk
30 throw new MWException( 'HTMLSelectAndOtherField called without any options' );
31 }
32 if ( !in_array( 'other', $this->mOptions, true ) ) {
33 // Have 'other' always as first element
34 $this->mOptions = [ $params['other'] => 'other' ] + $this->mOptions;
35 }
36 $this->mFlatOptions = self::flattenOptions( $this->getOptions() );
37 }
38
39 public function getInputHTML( $value ) {
40 $select = parent::getInputHTML( $value[1] );
41
42 $textAttribs = [
43 'id' => $this->mID . '-other',
44 'size' => $this->getSize(),
45 'class' => [ 'mw-htmlform-select-and-other-field' ],
46 'data-id-select' => $this->mID,
47 ];
48
49 if ( $this->mClass !== '' ) {
50 $textAttribs['class'][] = $this->mClass;
51 }
52
53 if ( isset( $this->mParams['maxlength-unit'] ) ) {
54 $textAttribs['data-mw-maxlength-unit'] = $this->mParams['maxlength-unit'];
55 }
56
57 $allowedParams = [
58 'required',
59 'autofocus',
60 'multiple',
61 'disabled',
62 'tabindex',
63 'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js
64 'maxlength-unit', // 'bytes' or 'codepoints', see mediawiki.htmlform.js
65 ];
66
67 $textAttribs += $this->getAttributes( $allowedParams );
68
69 $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs );
70
71 return "$select<br />\n$textbox";
72 }
73
74 protected function getOOUIModules() {
75 return [ 'mediawiki.widgets.SelectWithInputWidget' ];
76 }
77
78 public function getInputOOUI( $value ) {
79 $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' );
80
81 # TextInput
82 $textAttribs = [
83 'name' => $this->mName . '-other',
84 'value' => $value[2],
85 ];
86
87 $allowedParams = [
88 'required',
89 'autofocus',
90 'multiple',
91 'disabled',
92 'tabindex',
93 'maxlength',
94 ];
95
96 $textAttribs += OOUI\Element::configFromHtmlAttributes(
97 $this->getAttributes( $allowedParams )
98 );
99
100 if ( $this->mClass !== '' ) {
101 $textAttribs['classes'] = [ $this->mClass ];
102 }
103
104 # DropdownInput
105 $dropdownInputAttribs = [
106 'name' => $this->mName,
107 'id' => $this->mID . '-select',
108 'options' => $this->getOptionsOOUI(),
109 'value' => $value[1],
110 ];
111
112 $allowedParams = [
113 'tabindex',
114 'disabled',
115 ];
116
117 $dropdownInputAttribs += OOUI\Element::configFromHtmlAttributes(
118 $this->getAttributes( $allowedParams )
119 );
120
121 if ( $this->mClass !== '' ) {
122 $dropdownInputAttribs['classes'] = [ $this->mClass ];
123 }
124
125 $disabled = false;
126 if ( isset( $this->mParams[ 'disabled' ] ) && $this->mParams[ 'disabled' ] ) {
127 $disabled = true;
128 }
129
130 return $this->getInputWidget( [
131 'id' => $this->mID,
132 'disabled' => $disabled,
133 'textinput' => $textAttribs,
134 'dropdowninput' => $dropdownInputAttribs,
135 'or' => false,
136 'required' => $this->mParams[ 'required' ] ?? false,
137 'classes' => [ 'mw-htmlform-select-and-other-field' ],
138 'data' => [
139 'maxlengthUnit' => $this->mParams['maxlength-unit'] ?? 'bytes'
140 ],
141 ] );
142 }
143
144 public function getInputWidget( $params ) {
145 return new MediaWiki\Widget\SelectWithInputWidget( $params );
146 }
147
148 /**
149 * @inheritDoc
150 */
151 public function getDefault() {
152 $default = parent::getDefault();
153
154 // Default values of empty form
155 $final = '';
156 $list = 'other';
157 $text = '';
158
159 if ( $default !== null ) {
160 $final = $default;
161 // Assume the default is a text value, with the 'other' option selected.
162 // Then check if that assumption is correct, and update $list and $text if not.
163 $text = $final;
164 foreach ( $this->mFlatOptions as $option ) {
165 $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
166 if ( strpos( $final, $match ) === 0 ) {
167 $list = $option;
168 $text = substr( $final, strlen( $match ) );
169 break;
170 }
171 }
172 }
173
174 return [ $final, $list, $text ];
175 }
176
177 /**
178 * @param WebRequest $request
179 *
180 * @return array ["<overall message>","<select value>","<text field value>"]
181 */
182 public function loadDataFromRequest( $request ) {
183 if ( $request->getCheck( $this->mName ) ) {
184 $list = $request->getText( $this->mName );
185 $text = $request->getText( $this->mName . '-other' );
186
187 // Should be built the same as in mediawiki.htmlform.js
188 if ( $list == 'other' ) {
189 $final = $text;
190 } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
191 # User has spoofed the select form to give an option which wasn't
192 # in the original offer. Sulk...
193 $final = $text;
194 } elseif ( $text == '' ) {
195 $final = $list;
196 } else {
197 $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
198 }
199 return [ $final, $list, $text ];
200 }
201 return $this->getDefault();
202 }
203
204 public function getSize() {
205 return $this->mParams['size'] ?? 45;
206 }
207
208 public function validate( $value, $alldata ) {
209 # HTMLSelectField forces $value to be one of the options in the select
210 # field, which is not useful here. But we do want the validation further up
211 # the chain
212 $p = parent::validate( $value[1], $alldata );
213
214 if ( $p !== true ) {
215 return $p;
216 }
217
218 if ( isset( $this->mParams['required'] )
219 && $this->mParams['required'] !== false
220 && $value[0] === ''
221 ) {
222 return $this->msg( 'htmlform-required' );
223 }
224
225 return true;
226 }
227 }