Merge "ApiBase: Always validate that 'limit' is numeric"
[lhc/web/wiklou.git] / includes / htmlform / fields / HTMLAutoCompleteSelectField.php
1 <?php
2
3 /**
4 * Text field for selecting a value from a large list of possible values, with
5 * auto-completion and optionally with a select dropdown for selecting common
6 * options.
7 *
8 * HTMLComboboxField implements most of the same functionality and should be
9 * used instead, if possible.
10 *
11 * If one of 'options-messages', 'options', or 'options-message' is provided
12 * and non-empty, the select dropdown will be shown. An 'other' key will be
13 * appended using message 'htmlform-selectorother-other' if not already
14 * present.
15 *
16 * Besides the parameters recognized by HTMLTextField, the following are
17 * recognized:
18 * options-messages - As for HTMLSelectField
19 * options - As for HTMLSelectField
20 * options-message - As for HTMLSelectField
21 * autocomplete-data - Associative array mapping display text to values.
22 * autocomplete-data-messages - Like autocomplete, but keys are message names.
23 * require-match - Boolean, if true the value must be in the options or the
24 * autocomplete.
25 * other-message - Message to use instead of htmlform-selectorother-other for
26 * the 'other' message.
27 * other - Raw text to use for the 'other' message
28 *
29 * The old name of autocomplete-data[-messages] was autocomplete[-messages] which is still
30 * recognized but deprecated since MediaWiki 1.29 since it conflicts with how autocomplete is
31 * used in HTMLTextField.
32 *
33 * @phan-file-suppress PhanTypeMismatchProperty This is doing weird things with mClass
34 */
35 class HTMLAutoCompleteSelectField extends HTMLTextField {
36 protected $autocompleteData = [];
37
38 public function __construct( $params ) {
39 $params += [
40 'require-match' => false,
41 ];
42
43 // FIXME B/C, remove in 1.30
44 if (
45 array_key_exists( 'autocomplete', $params )
46 && !array_key_exists( 'autocomplete-data', $params )
47 ) {
48 $params['autocomplete-data'] = $params['autocomplete'];
49 unset( $params['autocomplete'] );
50 }
51 if (
52 array_key_exists( 'autocomplete-messages', $params )
53 && !array_key_exists( 'autocomplete-data-messages', $params )
54 ) {
55 $params['autocomplete-data-messages'] = $params['autocomplete-messages'];
56 unset( $params['autocomplete-messages'] );
57 }
58
59 parent::__construct( $params );
60
61 if ( array_key_exists( 'autocomplete-data-messages', $this->mParams ) ) {
62 foreach ( $this->mParams['autocomplete-data-messages'] as $key => $value ) {
63 $key = $this->msg( $key )->plain();
64 $this->autocompleteData[$key] = strval( $value );
65 }
66 } elseif ( array_key_exists( 'autocomplete-data', $this->mParams ) ) {
67 foreach ( $this->mParams['autocomplete-data'] as $key => $value ) {
68 $this->autocompleteData[$key] = strval( $value );
69 }
70 }
71 if ( !is_array( $this->autocompleteData ) || !$this->autocompleteData ) {
72 throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' );
73 }
74
75 $this->getOptions();
76 if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) {
77 if ( isset( $params['other-message'] ) ) {
78 $msg = $this->getMessage( $params['other-message'] )->text();
79 } elseif ( isset( $params['other'] ) ) {
80 $msg = $params['other'];
81 } else {
82 $msg = wfMessage( 'htmlform-selectorother-other' )->text();
83 }
84 $this->mOptions[$msg] = 'other';
85 }
86 }
87
88 public function loadDataFromRequest( $request ) {
89 if ( $request->getCheck( $this->mName ) ) {
90 $val = $request->getText( $this->mName . '-select', 'other' );
91
92 if ( $val === 'other' ) {
93 $val = $request->getText( $this->mName );
94 if ( isset( $this->autocompleteData[$val] ) ) {
95 $val = $this->autocompleteData[$val];
96 }
97 }
98
99 return $val;
100 } else {
101 return $this->getDefault();
102 }
103 }
104
105 public function validate( $value, $alldata ) {
106 $p = parent::validate( $value, $alldata );
107
108 if ( $p !== true ) {
109 return $p;
110 }
111
112 $validOptions = HTMLFormField::flattenOptions( $this->getOptions() ?: [] );
113
114 if ( in_array( strval( $value ), $validOptions, true ) ) {
115 return true;
116 } elseif ( in_array( strval( $value ), $this->autocompleteData, true ) ) {
117 return true;
118 } elseif ( $this->mParams['require-match'] ) {
119 return $this->msg( 'htmlform-select-badoption' );
120 }
121
122 return true;
123 }
124
125 // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
126 public function getAttributes( array $list ) {
127 $attribs = [
128 'type' => 'text',
129 'data-autocomplete' => FormatJson::encode( array_keys( $this->autocompleteData ) ),
130 ] + parent::getAttributes( $list );
131
132 if ( $this->getOptions() ) {
133 $attribs['data-hide-if'] = FormatJson::encode(
134 [ '!==', $this->mName . '-select', 'other' ]
135 );
136 }
137
138 return $attribs;
139 }
140
141 public function getInputHTML( $value ) {
142 $oldClass = $this->mClass;
143 $this->mClass = (array)$this->mClass;
144
145 $valInSelect = false;
146 $ret = '';
147
148 if ( $this->getOptions() ) {
149 if ( $value !== false ) {
150 $value = strval( $value );
151 $valInSelect = in_array(
152 $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
153 );
154 }
155
156 $selected = $valInSelect ? $value : 'other';
157 $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected );
158 $select->addOptions( $this->getOptions() );
159 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
160
161 if ( !empty( $this->mParams['disabled'] ) ) {
162 $select->setAttribute( 'disabled', 'disabled' );
163 }
164
165 if ( isset( $this->mParams['tabindex'] ) ) {
166 $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
167 }
168
169 $ret = $select->getHTML() . "<br />\n";
170
171 // @phan-suppress-next-line PhanTypeMismatchDimEmpty
172 $this->mClass[] = 'mw-htmlform-hide-if';
173 }
174
175 if ( $valInSelect ) {
176 $value = '';
177 } else {
178 $key = array_search( strval( $value ), $this->autocompleteData, true );
179 if ( $key !== false ) {
180 $value = $key;
181 }
182 }
183
184 // @phan-suppress-next-line PhanTypeMismatchDimEmpty
185 $this->mClass[] = 'mw-htmlform-autocomplete';
186 $ret .= parent::getInputHTML( $valInSelect ? '' : $value );
187 $this->mClass = $oldClass;
188
189 return $ret;
190 }
191
192 /**
193 * Get the OOUI version of this input.
194 * @param string $value
195 * @return false
196 */
197 public function getInputOOUI( $value ) {
198 // To be implemented, for now override the function from HTMLTextField
199 return false;
200 }
201 }