Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 151
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
HTMLTextField
0.00% covered (danger)
0.00%
0 / 150
0.00% covered (danger)
0.00%
0 / 11
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 getSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSpellCheck
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 isPersistent
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getInputHTML
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
20
 getType
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 getInputOOUI
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
42
 getInputCodex
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
20
 buildCodexComponent
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getInputWidget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDataAttribs
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 MediaWiki\Html\Html;
6use MediaWiki\HTMLForm\HTMLFormField;
7use OOUI\Widget;
8
9/**
10 * <input> field.
11 *
12 * Besides the parameters recognized by HTMLFormField, the following are
13 * recognized:
14 *   autocomplete - HTML autocomplete value (a boolean for on/off or a string according to
15 *     https://html.spec.whatwg.org/multipage/forms.html#autofill )
16 *
17 * @stable to extend
18 */
19class HTMLTextField extends HTMLFormField {
20    protected $mPlaceholder = '';
21
22    /** @var bool HTML autocomplete attribute */
23    protected $autocomplete;
24
25    /**
26     * @stable to call
27     *
28     * @param array $params
29     *   - type: HTML textfield type
30     *   - size: field size in characters (defaults to 45)
31     *   - placeholder/placeholder-message: set HTML placeholder attribute
32     *   - spellcheck: set HTML spellcheck attribute
33     *   - persistent: upon unsuccessful requests, retain the value (defaults to true, except
34     *     for password fields)
35     */
36    public function __construct( $params ) {
37        if ( isset( $params['autocomplete'] ) && is_bool( $params['autocomplete'] ) ) {
38            $params['autocomplete'] = $params['autocomplete'] ? 'on' : 'off';
39        }
40
41        parent::__construct( $params );
42
43        if ( isset( $params['placeholder-message'] ) ) {
44            $this->mPlaceholder = $this->getMessage( $params['placeholder-message'] )->text();
45        } elseif ( isset( $params['placeholder'] ) ) {
46            $this->mPlaceholder = $params['placeholder'];
47        }
48    }
49
50    /**
51     * @stable to override
52     * @return int
53     */
54    public function getSize() {
55        return $this->mParams['size'] ?? 45;
56    }
57
58    public function getSpellCheck() {
59        $val = $this->mParams['spellcheck'] ?? null;
60        if ( is_bool( $val ) ) {
61            // "spellcheck" attribute literally requires "true" or "false" to work.
62            return $val ? 'true' : 'false';
63        }
64        return null;
65    }
66
67    public function isPersistent() {
68        if ( isset( $this->mParams['persistent'] ) ) {
69            return $this->mParams['persistent'];
70        }
71        // don't put passwords into the HTML body, they could get cached or otherwise leaked
72        return !( isset( $this->mParams['type'] ) && $this->mParams['type'] === 'password' );
73    }
74
75    /**
76     * @inheritDoc
77     * @stable to override
78     */
79    public function getInputHTML( $value ) {
80        if ( !$this->isPersistent() ) {
81            $value = '';
82        }
83
84        $attribs = [
85                'id' => $this->mID,
86                'name' => $this->mName,
87                'size' => $this->getSize(),
88                'value' => $value,
89                'dir' => $this->mDir,
90                'spellcheck' => $this->getSpellCheck(),
91            ] + $this->getTooltipAndAccessKey() + $this->getDataAttribs();
92
93        if ( $this->mClass !== '' ) {
94            $attribs['class'] = $this->mClass;
95        }
96        if ( $this->mPlaceholder !== '' ) {
97            $attribs['placeholder'] = $this->mPlaceholder;
98        }
99
100        # @todo Enforce pattern, step, required, readonly on the server side as
101        # well
102        $allowedParams = [
103            'type',
104            'min',
105            'max',
106            'step',
107            'title',
108            'maxlength',
109            'minlength',
110            'tabindex',
111            'disabled',
112            'required',
113            'autofocus',
114            'readonly',
115            'autocomplete',
116            // Only used in HTML mode:
117            'pattern',
118            'list',
119        ];
120
121        $attribs += $this->getAttributes( $allowedParams );
122
123        # Extract 'type'
124        $type = $this->getType( $attribs );
125
126        $inputHtml = Html::input( $this->mName, $value, $type, $attribs );
127        return $inputHtml;
128    }
129
130    protected function getType( &$attribs ) {
131        $type = $attribs['type'] ?? 'text';
132        unset( $attribs['type'] );
133
134        # Implement tiny differences between some field variants
135        # here, rather than creating a new class for each one which
136        # is essentially just a clone of this one.
137        if ( isset( $this->mParams['type'] ) ) {
138            switch ( $this->mParams['type'] ) {
139                case 'int':
140                    $type = 'number';
141                    $attribs['step'] = 1;
142                    break;
143                case 'float':
144                    $type = 'number';
145                    $attribs['step'] = 'any';
146                    break;
147                # Pass through
148                case 'email':
149                case 'password':
150                case 'url':
151                    $type = $this->mParams['type'];
152                    break;
153                case 'textwithbutton':
154                    $type = $this->mParams['inputtype'] ?? 'text';
155                    break;
156            }
157        }
158
159        return $type;
160    }
161
162    /**
163     * @inheritDoc
164     * @stable to override
165     */
166    public function getInputOOUI( $value ) {
167        if ( !$this->isPersistent() ) {
168            $value = '';
169        }
170
171        $attribs = $this->getTooltipAndAccessKeyOOUI();
172
173        if ( $this->mClass !== '' ) {
174            $attribs['classes'] = [ $this->mClass ];
175        }
176        if ( $this->mPlaceholder !== '' ) {
177            $attribs['placeholder'] = $this->mPlaceholder;
178        }
179
180        # @todo Enforce pattern, step, required, readonly on the server side as
181        # well
182        $allowedParams = [
183            'type',
184            'min',
185            'max',
186            'step',
187            'title',
188            'maxlength',
189            'minlength',
190            'tabindex',
191            'disabled',
192            'required',
193            'autofocus',
194            'readonly',
195            'autocomplete',
196            // Only used in OOUI mode:
197            'autosize',
198            'flags',
199            'indicator',
200        ];
201
202        $attribs += \OOUI\Element::configFromHtmlAttributes(
203            $this->getAttributes( $allowedParams )
204        );
205
206        $type = $this->getType( $attribs );
207        if ( isset( $attribs['step'] ) && $attribs['step'] === 'any' ) {
208            $attribs['step'] = null;
209        }
210
211        return $this->getInputWidget( [
212            'id' => $this->mID,
213            'name' => $this->mName,
214            'value' => $value,
215            'type' => $type,
216            'dir' => $this->mDir,
217        ] + $attribs );
218    }
219
220    public function getInputCodex( $value, $hasErrors ) {
221        if ( !$this->isPersistent() ) {
222            $value = '';
223        }
224
225        $attribs = [
226                'id' => $this->mID,
227                'name' => $this->mName,
228                'size' => $this->getSize(),
229                'value' => $value,
230                'dir' => $this->mDir,
231                'spellcheck' => $this->getSpellCheck(),
232            ] + $this->getTooltipAndAccessKey() + $this->getDataAttribs();
233
234        if ( $this->mPlaceholder !== '' ) {
235            $attribs['placeholder'] = $this->mPlaceholder;
236        }
237        $attribs['class'] = $this->mClass ? [ $this->mClass ] : [];
238
239        $allowedParams = [
240            'type',
241            'min',
242            'max',
243            'step',
244            'title',
245            'maxlength',
246            'minlength',
247            'tabindex',
248            'disabled',
249            'required',
250            'autofocus',
251            'readonly',
252            'autocomplete',
253            'pattern',
254            'list',
255        ];
256
257        $attribs += $this->getAttributes( $allowedParams );
258
259        // Extract 'type'.
260        $type = $this->getType( $attribs );
261
262        return static::buildCodexComponent( $value, $hasErrors, $type, $this->mName, $attribs );
263    }
264
265    /**
266     * Build the markup of the Codex component
267     *
268     * @param string $value The value to set the input to
269     * @param bool $hasErrors Whether there are validation errors.
270     * @param string $type The input's type attribute
271     * @param string $name The input's name attribute
272     * @param array $inputAttribs Other input attributes
273     * @return string Raw HTML
274     */
275    public static function buildCodexComponent( $value, $hasErrors, $type, $name, $inputAttribs ) {
276        // Set up classes for the outer <div>.
277        $wrapperClass = [ 'cdx-text-input' ];
278        if ( $hasErrors ) {
279            $wrapperClass[] = 'cdx-text-input--status-error';
280        }
281
282        $inputAttribs['class'][] = 'cdx-text-input__input';
283        $inputHtml = Html::input( $name, $value, $type, $inputAttribs );
284
285        return Html::rawElement( 'div', [ 'class' => $wrapperClass ], $inputHtml );
286    }
287
288    /**
289     * @stable to override
290     *
291     * @param array $params
292     *
293     * @return Widget
294     */
295    protected function getInputWidget( $params ) {
296        return new \OOUI\TextInputWidget( $params );
297    }
298
299    /**
300     * Returns an array of data-* attributes to add to the field.
301     * @stable to override
302     *
303     * @return array
304     */
305    protected function getDataAttribs() {
306        return [];
307    }
308}
309
310/** @deprecated class alias since 1.42 */
311class_alias( HTMLTextField::class, 'HTMLTextField' );