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