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