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