Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 448
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
InputBox
0.00% covered (danger)
0.00%
0 / 448
0.00% covered (danger)
0.00%
0 / 17
11130
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
110
 getEditActionArgs
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getLinebreakClasses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getSearchForm
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 1
600
 getSearchForm2
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 1
72
 getCreateForm
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
272
 getMoveForm
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
20
 getCommentForm
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
90
 extractOptions
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
272
 isValidColor
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 buildTextBox
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 buildCheckboxInput
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 buildSubmitInput
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 bgColorStyle
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 shouldUseVE
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 languageConvert
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Classes for InputBox extension
4 *
5 * @file
6 * @ingroup Extensions
7 */
8
9namespace MediaWiki\Extension\InputBox;
10
11use ExtensionRegistry;
12use MediaWiki\Config\Config;
13use MediaWiki\Html\Html;
14use MediaWiki\MainConfigNames;
15use MediaWiki\Parser\Sanitizer;
16use MediaWiki\SpecialPage\SpecialPage;
17use Parser;
18use Xml;
19
20/**
21 * InputBox class
22 */
23class InputBox {
24
25    /* Fields */
26
27    /** @var Config */
28    private $config;
29    /** @var Parser */
30    private $mParser;
31    /** @var string */
32    private $mType = '';
33    /** @var int */
34    private $mWidth = 50;
35    /** @var ?string */
36    private $mPreload = null;
37    /** @var ?array */
38    private $mPreloadparams = null;
39    /** @var ?string */
40    private $mEditIntro = null;
41    /** @var ?string */
42    private $mUseVE = null;
43    /** @var ?string */
44    private $mUseDT = null;
45    /** @var ?string */
46    private $mSummary = null;
47    /** @var ?string */
48    private $mNosummary = null;
49    /** @var ?string */
50    private $mMinor = null;
51    /** @var string */
52    private $mPage = '';
53    /** @var string */
54    private $mBR = 'yes';
55    /** @var string */
56    private $mDefaultText = '';
57    /** @var string */
58    private $mPlaceholderText = '';
59    /** @var string */
60    private $mBGColor = 'transparent';
61    /** @var string */
62    private $mButtonLabel = '';
63    /** @var string */
64    private $mSearchButtonLabel = '';
65    /** @var string */
66    private $mFullTextButton = '';
67    /** @var string */
68    private $mLabelText = '';
69    /** @var ?string */
70    private $mHidden = '';
71    /** @var string */
72    private $mNamespaces = '';
73    /** @var string */
74    private $mID = '';
75    /** @var ?string */
76    private $mInline = null;
77    /** @var string */
78    private $mPrefix = '';
79    /** @var string */
80    private $mDir = '';
81    /** @var string */
82    private $mSearchFilter = '';
83    /** @var string */
84    private $mTour = '';
85    /** @var string */
86    private $mTextBoxAriaLabel = '';
87
88    /* Functions */
89
90    /**
91     * @param Config $config
92     * @param Parser $parser
93     */
94    public function __construct(
95        Config $config,
96        $parser
97    ) {
98        $this->config = $config;
99        $this->mParser = $parser;
100        // Default value for dir taken from the page language (bug 37018)
101        $this->mDir = $this->mParser->getTargetLanguage()->getDir();
102        // Split caches by language, to make sure visitors do not see a cached
103        // version in a random language (since labels are in the user language)
104        $this->mParser->getOptions()->getUserLangObj();
105        $this->mParser->getOutput()->addModuleStyles( [
106            'ext.inputBox.styles',
107            'mediawiki.ui.input',
108            'mediawiki.ui.checkbox',
109            'mediawiki.ui.button',
110        ] );
111    }
112
113    public function render() {
114        // Handle various types
115        switch ( $this->mType ) {
116            case 'create':
117            case 'comment':
118                return $this->getCreateForm();
119            case 'move':
120                return $this->getMoveForm();
121            case 'commenttitle':
122                return $this->getCommentForm();
123            case 'search':
124                return $this->getSearchForm( 'search' );
125            case 'fulltext':
126                return $this->getSearchForm( 'fulltext' );
127            case 'search2':
128                return $this->getSearchForm2();
129            default:
130                $key = $this->mType === '' ? 'inputbox-error-no-type' : 'inputbox-error-bad-type';
131                return Xml::tags( 'div', null,
132                    Xml::element( 'strong',
133                        [ 'class' => 'error' ],
134                        wfMessage( $key, $this->mType )->text()
135                    )
136                );
137        }
138    }
139
140    /**
141     * Returns the action name and value to use in inputboxes which redirects to edit pages.
142     * Decides, if the link should redirect to VE edit page (veaction=edit) or to wikitext editor
143     * (action=edit).
144     *
145     * @return array Array with name and value data
146     */
147    private function getEditActionArgs() {
148        // default is wikitext editor
149        $args = [
150            'name' => 'action',
151            'value' => 'edit',
152        ];
153        // check, if VE is installed and VE editor is requested
154        if ( $this->shouldUseVE() ) {
155            $args = [
156                'name' => 'veaction',
157                'value' => 'edit',
158            ];
159        }
160        return $args;
161    }
162
163    /**
164     * Get common classes, that could be added and depend on, if
165     * a line break between a button and an input field is added or not.
166     *
167     * @return string
168     */
169    private function getLinebreakClasses() {
170        return strtolower( $this->mBR ) === '<br />' ? 'mw-inputbox-input ' : '';
171    }
172
173    /**
174     * Generate search form
175     * @param string $type
176     * @return string HTML
177     */
178    public function getSearchForm( $type ) {
179        // Use button label fallbacks
180        if ( !$this->mButtonLabel ) {
181            $this->mButtonLabel = wfMessage( 'inputbox-tryexact' )->text();
182        }
183        if ( !$this->mSearchButtonLabel ) {
184            $this->mSearchButtonLabel = wfMessage( 'inputbox-searchfulltext' )->text();
185        }
186        if ( $this->mID !== '' ) {
187            $idArray = [ 'id' => Sanitizer::escapeIdForAttribute( $this->mID ) ];
188        } else {
189            $idArray = [];
190        }
191        // We need a unqiue id to link <label> to checkboxes, but also
192        // want multiple <inputbox>'s to not be invalid html
193        $idRandStr = Sanitizer::escapeIdForAttribute( '-' . $this->mID . wfRandom() );
194
195        // Build HTML
196        $htmlOut = Xml::openElement( 'div',
197            [
198                'class' => 'mw-inputbox-centered',
199                'style' => $this->bgColorStyle(),
200            ]
201        );
202        $htmlOut .= Xml::openElement( 'form',
203            [
204                'name' => 'searchbox',
205                'class' => 'searchbox',
206                'action' => SpecialPage::getTitleFor( 'Search' )->getLocalUrl(),
207            ] + $idArray
208        );
209
210        $htmlOut .= $this->buildTextBox( [
211            // enable SearchSuggest with mw-searchInput class
212            'class' => $this->getLinebreakClasses() . 'mw-searchInput searchboxInput',
213            'name' => 'search',
214            'type' => $this->mHidden ? 'hidden' : 'text',
215            'value' => $this->mDefaultText,
216            'placeholder' => $this->mPlaceholderText,
217            'size' => $this->mWidth,
218            'dir' => $this->mDir
219        ] );
220
221        if ( $this->mPrefix !== '' ) {
222            $htmlOut .= Html::hidden( 'prefix', $this->mPrefix );
223        }
224
225        if ( $this->mSearchFilter !== '' ) {
226            $htmlOut .= Html::hidden( 'searchfilter', $this->mSearchFilter );
227        }
228
229        if ( $this->mTour !== '' ) {
230            $htmlOut .= Html::hidden( 'tour', $this->mTour );
231        }
232
233        $htmlOut .= $this->mBR;
234
235        // Determine namespace checkboxes
236        $namespacesArray = explode( ',', $this->mNamespaces );
237        if ( $this->mNamespaces ) {
238            $contLang = $this->mParser->getContentLanguage();
239            $namespaces = $contLang->getNamespaces();
240            $nsAliases = array_merge(
241                $contLang->getNamespaceAliases(),
242                $this->config->get( MainConfigNames::NamespaceAliases )
243            );
244            $showNamespaces = [];
245            $checkedNS = [];
246            // Check for valid namespaces
247            foreach ( $namespacesArray as $userNS ) {
248                // no whitespace
249                $userNS = trim( $userNS );
250
251                // Namespace needs to be checked if flagged with "**"
252                if ( strpos( $userNS, '**' ) ) {
253                    $userNS = str_replace( '**', '', $userNS );
254                    $checkedNS[$userNS] = true;
255                }
256
257                $mainMsg = wfMessage( 'inputbox-ns-main' )->inContentLanguage()->text();
258                if ( $userNS === 'Main' || $userNS === $mainMsg ) {
259                    $i = 0;
260                } elseif ( array_search( $userNS, $namespaces ) ) {
261                    $i = array_search( $userNS, $namespaces );
262                } elseif ( isset( $nsAliases[$userNS] ) ) {
263                    $i = $nsAliases[$userNS];
264                } else {
265                    // Namespace not recognized, skip
266                    continue;
267                }
268                $showNamespaces[$i] = $userNS;
269                if ( isset( $checkedNS[$userNS] ) && $checkedNS[$userNS] ) {
270                    $checkedNS[$i] = true;
271                }
272            }
273
274            // Show valid namespaces
275            foreach ( $showNamespaces as $i => $name ) {
276                $checked = [];
277                // Namespace flagged with "**" or if it's the only one
278                if ( ( isset( $checkedNS[$i] ) && $checkedNS[$i] ) || count( $showNamespaces ) === 1 ) {
279                    $checked = [ 'checked' => 'checked' ];
280                }
281
282                if ( count( $showNamespaces ) === 1 ) {
283                    // Checkbox
284                    $htmlOut .= Xml::element( 'input',
285                        [
286                            'type' => 'hidden',
287                            'name' => 'ns' . $i,
288                            'value' => 1,
289                            'id' => 'mw-inputbox-ns' . $i . $idRandStr
290                        ] + $checked
291                    );
292                } else {
293                    // Checkbox
294                    $htmlOut .= $this->buildCheckboxInput(
295                        'ns' . $i, 'mw-inputbox-ns' . $i . $idRandStr, "1", $checked
296                    );
297                }
298            }
299
300            // Line break
301            $htmlOut .= $this->mBR;
302        } elseif ( $type === 'search' ) {
303            // Go button
304            $htmlOut .= $this->buildSubmitInput(
305                [
306                    'type' => 'submit',
307                    'name' => 'go',
308                    'value' => $this->mButtonLabel
309                ]
310            );
311            $htmlOut .= "\u{00A0}";
312        }
313
314        // Search button
315        $htmlOut .= $this->buildSubmitInput(
316            [
317                'type' => 'submit',
318                'name' => 'fulltext',
319                'value' => $this->mSearchButtonLabel
320            ]
321        );
322
323        // Hidden fulltext param for IE (bug 17161)
324        if ( $type === 'fulltext' ) {
325            $htmlOut .= Html::hidden( 'fulltext', 'Search' );
326        }
327
328        $htmlOut .= Xml::closeElement( 'form' );
329        $htmlOut .= Xml::closeElement( 'div' );
330
331        // Return HTML
332        return $htmlOut;
333    }
334
335    /**
336     * Generate search form version 2
337     * @return string
338     */
339    public function getSearchForm2() {
340        // Use button label fallbacks
341        if ( !$this->mButtonLabel ) {
342            $this->mButtonLabel = wfMessage( 'inputbox-tryexact' )->text();
343        }
344
345        if ( $this->mID !== '' ) {
346            $unescapedID = $this->mID;
347        } else {
348            // The label element needs a unique id, use
349            // random number to avoid multiple input boxes
350            // having conflicts.
351            $unescapedID = wfRandom();
352        }
353        $id = Sanitizer::escapeIdForAttribute( $unescapedID );
354        $htmlLabel = '';
355        if ( strlen( trim( $this->mLabelText ) ) ) {
356            $htmlLabel = Xml::openElement( 'label', [ 'for' => 'bodySearchInput' . $id ] );
357            $htmlLabel .= $this->mParser->recursiveTagParse( $this->mLabelText );
358            $htmlLabel .= Xml::closeElement( 'label' );
359        }
360        $htmlOut = Xml::openElement( 'form',
361            [
362                'name' => 'bodySearch' . $id,
363                'id' => 'bodySearch' . $id,
364                'class' => 'bodySearch' . ( $this->mInline ? ' mw-inputbox-inline' : '' ),
365                'action' => SpecialPage::getTitleFor( 'Search' )->getLocalUrl(),
366            ]
367        );
368        $htmlOut .= Xml::openElement( 'div',
369            [
370                'class' => 'bodySearchWrap' . ( $this->mInline ? ' mw-inputbox-inline' : '' ),
371                'style' => $this->bgColorStyle(),
372            ]
373        );
374        $htmlOut .= $htmlLabel;
375
376        $htmlOut .= $this->buildTextBox( [
377            'type' => $this->mHidden ? 'hidden' : 'text',
378            'name' => 'search',
379            // enable SearchSuggest with mw-searchInput class
380            'class' => 'mw-searchInput',
381            'size' => $this->mWidth,
382            'id' => 'bodySearchInput' . $id,
383            'dir' => $this->mDir,
384            'placeholder' => $this->mPlaceholderText
385        ] );
386
387        $htmlOut .= "\u{00A0}" . $this->buildSubmitInput(
388            [
389                'type' => 'submit',
390                'name' => 'go',
391                'value' => $this->mButtonLabel,
392            ]
393        );
394
395        // Better testing needed here!
396        if ( $this->mFullTextButton !== '' ) {
397            $htmlOut .= $this->buildSubmitInput(
398                [
399                    'type' => 'submit',
400                    'name' => 'fulltext',
401                    'value' => $this->mSearchButtonLabel
402                ]
403            );
404        }
405
406        $htmlOut .= Xml::closeElement( 'div' );
407        $htmlOut .= Xml::closeElement( 'form' );
408
409        // Return HTML
410        return $htmlOut;
411    }
412
413    /**
414     * Generate create page form
415     * @return string
416     */
417    public function getCreateForm() {
418        if ( $this->mType === 'comment' ) {
419            if ( !$this->mButtonLabel ) {
420                $this->mButtonLabel = wfMessage( 'inputbox-postcomment' )->text();
421            }
422        } else {
423            if ( !$this->mButtonLabel ) {
424                $this->mButtonLabel = wfMessage( 'inputbox-createarticle' )->text();
425            }
426        }
427
428        $htmlOut = Xml::openElement( 'div',
429            [
430                'class' => 'mw-inputbox-centered',
431                'style' => $this->bgColorStyle(),
432            ]
433        );
434        $createBoxParams = [
435            'name' => 'createbox',
436            'class' => 'createbox',
437            'action' => $this->config->get( MainConfigNames::Script ),
438            'method' => 'get'
439        ];
440        if ( $this->mID !== '' ) {
441            $createBoxParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID );
442        }
443        $htmlOut .= Xml::openElement( 'form', $createBoxParams );
444        $editArgs = $this->getEditActionArgs();
445        $htmlOut .= Html::hidden( $editArgs['name'], $editArgs['value'] );
446        if ( $this->mPreload !== null ) {
447            $htmlOut .= Html::hidden( 'preload', $this->mPreload );
448        }
449        if ( is_array( $this->mPreloadparams ) ) {
450            foreach ( $this->mPreloadparams as $preloadparams ) {
451                $htmlOut .= Html::hidden( 'preloadparams[]', $preloadparams );
452            }
453        }
454        if ( $this->mEditIntro !== null ) {
455            $htmlOut .= Html::hidden( 'editintro', $this->mEditIntro );
456        }
457        if ( $this->mSummary !== null ) {
458            $htmlOut .= Html::hidden( 'summary', $this->mSummary );
459        }
460        if ( $this->mNosummary !== null ) {
461            $htmlOut .= Html::hidden( 'nosummary', $this->mNosummary );
462        }
463        if ( $this->mPrefix !== '' ) {
464            $htmlOut .= Html::hidden( 'prefix', $this->mPrefix );
465        }
466        if ( $this->mMinor !== null ) {
467            $htmlOut .= Html::hidden( 'minor', $this->mMinor );
468        }
469        // @phan-suppress-next-line PhanSuspiciousValueComparison False positive
470        if ( $this->mType === 'comment' ) {
471            $htmlOut .= Html::hidden( 'section', 'new' );
472            if ( $this->mUseDT ) {
473                $htmlOut .= Html::hidden( 'dtpreload', '1' );
474            }
475        }
476
477        $htmlOut .= $this->buildTextBox( [
478            'type' => $this->mHidden ? 'hidden' : 'text',
479            'name' => 'title',
480            'class' => $this->getLinebreakClasses() .
481                'mw-inputbox-createbox',
482            'value' => $this->mDefaultText,
483            'placeholder' => $this->mPlaceholderText,
484            // For visible input fields, use required so that the form will not
485            // submit without a value
486            'required' => !$this->mHidden,
487            'size' => $this->mWidth,
488            'dir' => $this->mDir
489        ] );
490
491        $htmlOut .= $this->mBR;
492        $htmlOut .= $this->buildSubmitInput(
493            [
494                'type' => 'submit',
495                'name' => 'create',
496                'value' => $this->mButtonLabel
497            ],
498            true
499        );
500        $htmlOut .= Xml::closeElement( 'form' );
501        $htmlOut .= Xml::closeElement( 'div' );
502
503        // Return HTML
504        return $htmlOut;
505    }
506
507    /**
508     * Generate move page form
509     * @return string
510     */
511    public function getMoveForm() {
512        if ( !$this->mButtonLabel ) {
513            $this->mButtonLabel = wfMessage( 'inputbox-movearticle' )->text();
514        }
515
516        $htmlOut = Xml::openElement( 'div',
517            [
518                'class' => 'mw-inputbox-centered',
519                'style' => $this->bgColorStyle(),
520            ]
521        );
522        $moveBoxParams = [
523            'name' => 'movebox',
524            'class' => 'mw-movebox',
525            'action' => $this->config->get( MainConfigNames::Script ),
526            'method' => 'get'
527        ];
528        if ( $this->mID !== '' ) {
529            $moveBoxParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID );
530        }
531        $htmlOut .= Xml::openElement( 'form', $moveBoxParams );
532        $htmlOut .= Html::hidden( 'title',
533            SpecialPage::getTitleFor( 'Movepage', $this->mPage )->getPrefixedText() );
534        $htmlOut .= Html::hidden( 'wpReason', $this->mSummary );
535        $htmlOut .= Html::hidden( 'prefix', $this->mPrefix );
536
537        $htmlOut .= $this->buildTextBox( [
538            'type' => $this->mHidden ? 'hidden' : 'text',
539            'name' => 'wpNewTitle',
540            'class' => $this->getLinebreakClasses() . 'mw-moveboxInput',
541            'value' => $this->mDefaultText,
542            'placeholder' => $this->mPlaceholderText,
543            'size' => $this->mWidth,
544            'dir' => $this->mDir
545        ] );
546
547        $htmlOut .= $this->mBR;
548        $htmlOut .= $this->buildSubmitInput(
549            [
550                'type' => 'submit',
551                'value' => $this->mButtonLabel
552            ],
553            true
554        );
555        $htmlOut .= Xml::closeElement( 'form' );
556        $htmlOut .= Xml::closeElement( 'div' );
557
558        // Return HTML
559        return $htmlOut;
560    }
561
562    /**
563     * Generate new section form
564     * @return string
565     */
566    public function getCommentForm() {
567        if ( !$this->mButtonLabel ) {
568                $this->mButtonLabel = wfMessage( 'inputbox-postcommenttitle' )->text();
569        }
570
571        $htmlOut = Xml::openElement( 'div',
572            [
573                'class' => 'mw-inputbox-centered',
574                'style' => $this->bgColorStyle(),
575            ]
576        );
577        $commentFormParams = [
578            'name' => 'commentbox',
579            'class' => 'commentbox',
580            'action' => $this->config->get( MainConfigNames::Script ),
581            'method' => 'get'
582        ];
583        if ( $this->mID !== '' ) {
584            $commentFormParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID );
585        }
586        $htmlOut .= Xml::openElement( 'form', $commentFormParams );
587        $editArgs = $this->getEditActionArgs();
588        $htmlOut .= Html::hidden( $editArgs['name'], $editArgs['value'] );
589        if ( $this->mPreload !== null ) {
590            $htmlOut .= Html::hidden( 'preload', $this->mPreload );
591        }
592        if ( is_array( $this->mPreloadparams ) ) {
593            foreach ( $this->mPreloadparams as $preloadparams ) {
594                $htmlOut .= Html::hidden( 'preloadparams[]', $preloadparams );
595            }
596        }
597        if ( $this->mEditIntro !== null ) {
598            $htmlOut .= Html::hidden( 'editintro', $this->mEditIntro );
599        }
600
601        $htmlOut .= $this->buildTextBox( [
602            'type' => $this->mHidden ? 'hidden' : 'text',
603            'name' => 'preloadtitle',
604            'class' => $this->getLinebreakClasses() . 'commentboxInput',
605            'value' => $this->mDefaultText,
606            'placeholder' => $this->mPlaceholderText,
607            'size' => $this->mWidth,
608            'dir' => $this->mDir
609        ] );
610
611        $htmlOut .= Html::hidden( 'section', 'new' );
612        if ( $this->mUseDT ) {
613            $htmlOut .= Html::hidden( 'dtpreload', '1' );
614        }
615        $htmlOut .= Html::hidden( 'title', $this->mPage );
616        $htmlOut .= $this->mBR;
617        $htmlOut .= $this->buildSubmitInput(
618            [
619                'type' => 'submit',
620                'name' => 'create',
621                'value' => $this->mButtonLabel
622            ],
623            true
624        );
625        $htmlOut .= Xml::closeElement( 'form' );
626        $htmlOut .= Xml::closeElement( 'div' );
627
628        // Return HTML
629        return $htmlOut;
630    }
631
632    /**
633     * Extract options from a blob of text
634     *
635     * @param string $text Tag contents
636     */
637    public function extractOptions( $text ) {
638        // Parse all possible options
639        $values = [];
640        foreach ( explode( "\n", $text ) as $line ) {
641            if ( strpos( $line, '=' ) === false ) {
642                continue;
643            }
644            [ $name, $value ] = explode( '=', $line, 2 );
645            $name = strtolower( trim( $name ) );
646            $value = Sanitizer::decodeCharReferences( trim( $value ) );
647            if ( $name === 'preloadparams[]' ) {
648                // We have to special-case this one because it's valid for it to appear more than once.
649                $this->mPreloadparams[] = $value;
650            } else {
651                $values[ $name ] = $value;
652            }
653        }
654
655        // Validate the dir value.
656        if ( isset( $values['dir'] ) && !in_array( $values['dir'], [ 'ltr', 'rtl' ] ) ) {
657            unset( $values['dir'] );
658        }
659
660        // Build list of options, with local member names
661        $options = [
662            'type' => 'mType',
663            'width' => 'mWidth',
664            'preload' => 'mPreload',
665            'page' => 'mPage',
666            'editintro' => 'mEditIntro',
667            'useve' => 'mUseVE',
668            'usedt' => 'mUseDT',
669            'summary' => 'mSummary',
670            'nosummary' => 'mNosummary',
671            'minor' => 'mMinor',
672            'break' => 'mBR',
673            'default' => 'mDefaultText',
674            'placeholder' => 'mPlaceholderText',
675            'bgcolor' => 'mBGColor',
676            'buttonlabel' => 'mButtonLabel',
677            'searchbuttonlabel' => 'mSearchButtonLabel',
678            'fulltextbutton' => 'mFullTextButton',
679            'namespaces' => 'mNamespaces',
680            'labeltext' => 'mLabelText',
681            'hidden' => 'mHidden',
682            'id' => 'mID',
683            'inline' => 'mInline',
684            'prefix' => 'mPrefix',
685            'dir' => 'mDir',
686            'searchfilter' => 'mSearchFilter',
687            'tour' => 'mTour',
688            'arialabel' => 'mTextBoxAriaLabel'
689        ];
690        // Options we should maybe run through lang converter.
691        $convertOptions = [
692            'default' => true,
693            'buttonlabel' => true,
694            'searchbuttonlabel' => true,
695            'placeholder' => true,
696            'arialabel' => true
697        ];
698        foreach ( $options as $name => $var ) {
699            if ( isset( $values[$name] ) ) {
700                $this->$var = $values[$name];
701                if ( isset( $convertOptions[$name] ) ) {
702                    $this->$var = $this->languageConvert( $this->$var );
703                }
704            }
705        }
706
707        // Insert a line break if configured to do so
708        $this->mBR = ( strtolower( $this->mBR ) === 'no' ) ? ' ' : '<br />';
709
710        // Validate the width; make sure it's a valid, positive integer
711        $this->mWidth = intval( $this->mWidth <= 0 ? 50 : $this->mWidth );
712
713        // Validate background color
714        if ( !$this->isValidColor( $this->mBGColor ) ) {
715            $this->mBGColor = 'transparent';
716        }
717
718        // T297725: De-obfuscate attempts to trick people into making edits to .js pages
719        $target = $this->mType === 'commenttitle' ? $this->mPage : $this->mDefaultText;
720        if ( $this->mHidden && $this->mPreload && substr( $target, -3 ) === '.js' ) {
721            $this->mHidden = null;
722        }
723    }
724
725    /**
726     * Do a security check on the bgcolor parameter
727     * @param string $color
728     * @return bool
729     */
730    public function isValidColor( $color ) {
731        $regex = <<<REGEX
732            /^ (
733                [a-zA-Z]* |       # color names
734                \# [0-9a-f]{3} |  # short hexadecimal
735                \# [0-9a-f]{6} |  # long hexadecimal
736                rgb \s* \( \s* (
737                    \d+ \s* , \s* \d+ \s* , \s* \d+ |    # rgb integer
738                    [0-9.]+% \s* , \s* [0-9.]+% \s* , \s* [0-9.]+%   # rgb percent
739                ) \s* \)
740            ) $ /xi
741REGEX;
742        return (bool)preg_match( $regex, $color );
743    }
744
745    /**
746     * Factory method to help build the textbox widget.
747     *
748     * @param array $defaultAttr
749     * @return string
750     */
751    private function buildTextBox( $defaultAttr ) {
752        if ( $this->mTextBoxAriaLabel ) {
753            $defaultAttr[ 'aria-label' ] = $this->mTextBoxAriaLabel;
754        }
755
756        $class = $defaultAttr[ 'class' ] ?? '';
757        $class .= '  mw-ui-input mw-ui-input-inline';
758        $defaultAttr[ 'class' ] = $class;
759        return Html::element( 'input', $defaultAttr );
760    }
761
762    /**
763     * Factory method to help build checkbox input.
764     *
765     * @param string $name name of input
766     * @param string $id id of input
767     * @param string $value value of input
768     * @param array $defaultAttr (optional)
769     * @return string
770     */
771    private function buildCheckboxInput( $name, $id, $value, $defaultAttr = [] ) {
772        $htmlOut = ' <div class="mw-inputbox-element mw-ui-checkbox">';
773        $htmlOut .= Xml::element( 'input',
774            [
775                'type' => 'checkbox',
776                'name' => $name,
777                'value' => $value,
778                'id' => $id,
779            ] + $defaultAttr
780        );
781        // Label
782        $htmlOut .= Xml::label( $name, $id );
783        $htmlOut .= '</div> ';
784        return $htmlOut;
785    }
786
787    /**
788     * Factory method to help build submit button.
789     *
790     * @param array $defaultAttr
791     * @param bool $isProgressive (optional)
792     * @return string
793     */
794    private function buildSubmitInput( $defaultAttr, $isProgressive = false ) {
795        $defaultAttr[ 'class' ] ??= '';
796        $defaultAttr[ 'class' ] .= ' mw-ui-button';
797        if ( $isProgressive ) {
798            $defaultAttr[ 'class' ] .= ' mw-ui-progressive';
799        }
800        $defaultAttr[ 'class' ] = trim( $defaultAttr[ 'class' ] );
801        return Xml::element( 'input', $defaultAttr );
802    }
803
804    private function bgColorStyle() {
805        if ( $this->mBGColor !== 'transparent' ) {
806            return 'background-color: ' . $this->mBGColor . ';';
807        }
808        return '';
809    }
810
811    /**
812     * Returns true, if the VisualEditor is requested from the inputbox wikitext definition and
813     * if the VisualEditor extension is actually installed or not, false otherwise.
814     *
815     * @return bool
816     */
817    private function shouldUseVE() {
818        return ExtensionRegistry::getInstance()->isLoaded( 'VisualEditor' ) && $this->mUseVE !== null;
819    }
820
821    /**
822     * For compatability with pre T119158 behaviour
823     *
824     * If a field that is going to be used as an attribute
825     * and it contains "-{" in it, run it through language
826     * converter.
827     *
828     * Its not really clear if it would make more sense to
829     * always convert instead of only if -{ is present. This
830     * function just more or less restores the previous
831     * accidental behaviour.
832     *
833     * @see https://phabricator.wikimedia.org/T180485
834     * @param string $text
835     * @return string
836     */
837    private function languageConvert( $text ) {
838        $langConv = $this->mParser->getTargetLanguageConverter();
839        if ( $langConv->hasVariants() && strpos( $text, '-{' ) !== false ) {
840            $text = $langConv->convert( $text );
841        }
842        return $text;
843    }
844}