Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.69% covered (warning)
65.69%
157 / 239
41.67% covered (danger)
41.67%
15 / 36
CRAP
0.00% covered (danger)
0.00%
0 / 1
Xml
65.69% covered (warning)
65.69%
157 / 239
41.67% covered (danger)
41.67%
15 / 36
417.14
0.00% covered (danger)
0.00%
0 / 1
 element
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 expandAttributes
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 elementClean
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 openElement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 closeElement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 monthSelector
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 dateMenu
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 languageSelector
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
6.09
 span
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wrapClass
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 input
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 password
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 attrib
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 check
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 radio
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 label
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 inputLabel
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 inputLabelSep
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 checkLabel
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 radioLabel
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 submitButton
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 option
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 listDropdown
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 listDropdownOptions
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
11.31
 listDropdownOptionsOoui
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 fieldset
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 textarea
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 encodeJsVar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 encodeJsCall
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWellFormed
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isWellFormedXmlFragment
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 escapeTagsOnly
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 buildForm
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 buildTable
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
6.07
 buildTableRow
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
1<?php
2/**
3 * Methods to generate XML.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\Html\Html;
24use MediaWiki\Languages\LanguageNameUtils;
25use MediaWiki\MainConfigNames;
26use MediaWiki\MediaWikiServices;
27use MediaWiki\Parser\Sanitizer;
28use MediaWiki\Utils\MWTimestamp;
29
30/**
31 * Module of static functions for generating XML
32 */
33class Xml {
34    /**
35     * Format an XML element with given attributes and, optionally, text content.
36     * Element and attribute names are assumed to be ready for literal inclusion.
37     * Strings are assumed to not contain XML-illegal characters; special
38     * characters (<, >, &) are escaped but illegals are not touched.
39     *
40     * @param string $element Element name
41     * @param-taint $element tainted
42     * @param array|null $attribs Name=>value pairs. Values will be escaped.
43     * @param-taint $attribs escapes_html
44     * @param string|null $contents Null to make an open tag only; '' for a contentless closed tag (default)
45     * @param-taint $contents escapes_html
46     * @param bool $allowShortTag Whether '' in $contents will result in a contentless closed tag
47     * @return string
48     * @return-taint escaped
49     */
50    public static function element( $element, $attribs = null, $contents = '',
51        $allowShortTag = true
52    ) {
53        $out = '<' . $element;
54        if ( $attribs !== null ) {
55            $out .= self::expandAttributes( $attribs );
56        }
57        if ( $contents === null ) {
58            $out .= '>';
59        } elseif ( $allowShortTag && $contents === '' ) {
60            $out .= ' />';
61        } else {
62            $out .= '>' . htmlspecialchars( $contents, ENT_NOQUOTES ) . "</$element>";
63        }
64        return $out;
65    }
66
67    /**
68     * Given an array of ('attributename' => 'value'), it generates the code
69     * to set the XML attributes : attributename="value".
70     * The values are passed to Sanitizer::encodeAttribute.
71     * Returns null or empty string if no attributes given.
72     * @param array|null $attribs Array of attributes for an XML element
73     * @return null|string
74     */
75    public static function expandAttributes( ?array $attribs ) {
76        if ( $attribs === null ) {
77            return null;
78        }
79        $out = '';
80        foreach ( $attribs as $name => $val ) {
81            $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
82        }
83        return $out;
84    }
85
86    /**
87     * Format an XML element as with self::element(), but run text through the content language's
88     * normalize() validator first to ensure that no invalid UTF-8 is passed.
89     *
90     * @param string $element
91     * @param array|null $attribs Name=>value pairs. Values will be escaped.
92     * @param string|null $contents Null to make an open tag only; '' for a contentless closed tag (default)
93     * @return string
94     * @param-taint $attribs escapes_html
95     * @param-taint $contents escapes_html
96     */
97    public static function elementClean( $element, $attribs = [], $contents = '' ) {
98        if ( $attribs ) {
99            $attribs = array_map( [ UtfNormal\Validator::class, 'cleanUp' ], $attribs );
100        }
101        if ( $contents ) {
102            $contents =
103                MediaWikiServices::getInstance()->getContentLanguage()->normalize( $contents );
104        }
105        return self::element( $element, $attribs, $contents );
106    }
107
108    /**
109     * This opens an XML element
110     *
111     * @param string $element Name of the element
112     * @param array|null $attribs Array of attributes, see Xml::expandAttributes()
113     * @return string
114     */
115    public static function openElement( $element, $attribs = null ) {
116        return '<' . $element . self::expandAttributes( $attribs ) . '>';
117    }
118
119    /**
120     * Shortcut to close an XML element
121     * @param string $element Element name
122     * @return string
123     */
124    public static function closeElement( $element ) {
125        return "</$element>";
126    }
127
128    /**
129     * Same as Xml::element(), but does not escape contents. Handy when the
130     * content you have is already valid xml.
131     *
132     * @param string $element Element name
133     * @param-taint $element tainted
134     * @param array|null $attribs Array of attributes
135     * @param-taint $attribs escapes_html
136     * @param string $contents Content of the element
137     * @param-taint $contents tainted
138     * @return string
139     * @return-taint escaped
140     */
141    public static function tags( $element, $attribs, $contents ) {
142        return self::openElement( $element, $attribs ) . $contents . "</$element>";
143    }
144
145    /**
146     * Create a date selector
147     *
148     * @param string|null $selected The month which should be selected, default ''.
149     * @param string|null $allmonths Value of a special item denoting all month.
150     *   Null to not include (default).
151     * @param string $id Element identifier
152     * @return string Html string containing the month selector
153     *
154     * @deprecated since 1.42
155     */
156    public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
157        wfDeprecated( __METHOD__, '1.42' );
158
159        global $wgLang;
160        $options = [];
161
162        $data = new XmlSelect( 'month', $id, $selected ?? '' );
163
164        if ( $allmonths !== null ) {
165            $options[wfMessage( 'monthsall' )->text()] = $allmonths;
166        }
167        for ( $i = 1; $i < 13; $i++ ) {
168            $options[$wgLang->getMonthName( $i )] = $i;
169        }
170        $data->addOptions( $options );
171        $data->setAttribute( 'class', 'mw-month-selector' );
172        return $data->getHTML();
173    }
174
175    /**
176     * @param int|string $year Use '' or 0 to start with no year preselected.
177     * @param int|string $month A month in the 1..12 range. Use '', 0 or -1 to start with no month
178     *  preselected.
179     * @return string Formatted HTML
180     *
181     * @deprecated since 1.42
182     */
183    public static function dateMenu( $year, $month ) {
184        wfDeprecated( __METHOD__, '1.42' );
185        # Offset overrides year/month selection
186        if ( $month && $month !== -1 ) {
187            $encMonth = intval( $month );
188        } else {
189            $encMonth = '';
190        }
191        if ( $year ) {
192            $encYear = intval( $year );
193        } elseif ( $encMonth ) {
194            $timestamp = MWTimestamp::getInstance();
195            $thisMonth = intval( $timestamp->format( 'n' ) );
196            $thisYear = intval( $timestamp->format( 'Y' ) );
197            if ( $encMonth > $thisMonth ) {
198                $thisYear--;
199            }
200            $encYear = $thisYear;
201        } else {
202            $encYear = '';
203        }
204        $inputAttribs = [ 'id' => 'year', 'maxlength' => 4, 'size' => 7 ];
205        return self::label( wfMessage( 'year' )->text(), 'year' ) . ' ' .
206            Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' ' .
207            self::label( wfMessage( 'month' )->text(), 'month' ) . ' ' .
208            self::monthSelector( $encMonth, '-1' );
209    }
210
211    /**
212     * Construct a language selector appropriate for use in a form or preferences
213     *
214     * @param string $selected The language code of the selected language
215     * @param bool $customisedOnly If true only languages which have some content are listed
216     * @param string|null $inLanguage The ISO code of the language to display the select list in
217     * @param array $overrideAttrs Override the attributes of the select tag (since 1.20)
218     * @param Message|null $msg Label message key (since 1.20)
219     * @return array Array containing 2 items: label HTML and select list HTML
220     *
221     * @deprecated since 1.42
222     */
223    public static function languageSelector( $selected, $customisedOnly = true,
224        $inLanguage = null, $overrideAttrs = [], Message $msg = null
225    ) {
226        wfDeprecated( __METHOD__, '1.42' );
227        $languageCode = MediaWikiServices::getInstance()->getMainConfig()
228            ->get( MainConfigNames::LanguageCode );
229
230        $include = $customisedOnly ? LanguageNameUtils::SUPPORTED : LanguageNameUtils::DEFINED;
231        $languages = MediaWikiServices::getInstance()
232            ->getLanguageNameUtils()
233            ->getLanguageNames( $inLanguage, $include );
234
235        // Make sure the site language is in the list;
236        // a custom language code might not have a defined name...
237        if ( !array_key_exists( $languageCode, $languages ) ) {
238            $languages[$languageCode] = $languageCode;
239            // Sort the array again
240            ksort( $languages );
241        }
242
243        /**
244         * If a bogus value is set, default to the content language.
245         * Otherwise, no default is selected and the user ends up
246         * with Afrikaans since it's first in the list.
247         */
248        $selected = isset( $languages[$selected] ) ? $selected : $languageCode;
249        $options = "\n";
250        foreach ( $languages as $code => $name ) {
251            $options .= self::option( "$code - $name", $code, $code == $selected ) . "\n";
252        }
253
254        $attrs = [ 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ];
255        $attrs = array_merge( $attrs, $overrideAttrs );
256
257        if ( $msg === null ) {
258            $msg = wfMessage( 'yourlanguage' );
259        }
260        return [
261            self::label( $msg->text(), $attrs['id'] ),
262            self::tags( 'select', $attrs, $options )
263        ];
264    }
265
266    /**
267     * Shortcut to make a span element
268     * @param string $text Content of the element, will be escaped
269     * @param string $class Class name of the span element
270     * @param array $attribs Other attributes
271     * @return string
272     *
273     * @deprecated since 1.42, use {@see Html::element} instead
274     */
275    public static function span( $text, $class, $attribs = [] ) {
276        return self::element( 'span', [ 'class' => $class ] + $attribs, $text );
277    }
278
279    /**
280     * Shortcut to make a specific element with a class attribute
281     *
282     * @param string $text Content of the element, will be escaped
283     * @param string $class Class name of the span element
284     * @param string $tag Element name
285     * @param array $attribs Other attributes
286     * @return string
287     *
288     * @deprecated since 1.42, use {@see Xml::tags} instead
289     */
290    public static function wrapClass( $text, $class, $tag = 'span', $attribs = [] ) {
291        wfDeprecated( __METHOD__, '1.42' );
292        return self::tags( $tag, [ 'class' => $class ] + $attribs, $text );
293    }
294
295    /**
296     * Convenience function to build an HTML text input field
297     * @param string $name Value of the name attribute
298     * @param int|false $size Value of the size attribute
299     * @param string|false $value Value of the value attribute
300     * @param array $attribs Other attributes
301     * @return string HTML
302     *
303     * @deprecated since 1.42, use {@see Html::input} instead
304     */
305    public static function input( $name, $size = false, $value = false, $attribs = [] ) {
306        $attributes = [ 'name' => $name ];
307
308        if ( $size ) {
309            $attributes['size'] = $size;
310        }
311
312        if ( $value !== false ) { // maybe 0
313            $attributes['value'] = $value;
314        }
315
316        return self::element( 'input', $attributes + $attribs );
317    }
318
319    /**
320     * Convenience function to build an HTML password input field
321     * @param string $name Value of the name attribute
322     * @param int|false $size Value of the size attribute
323     * @param string|false $value Value of the value attribute
324     * @param array $attribs Other attributes
325     * @return string HTML
326     *
327     * @deprecated since 1.42, use {@see Html::input} instead
328     */
329    public static function password( $name, $size = false, $value = false,
330        $attribs = []
331    ) {
332        return self::input( $name, $size, $value,
333            array_merge( $attribs, [ 'type' => 'password' ] ) );
334    }
335
336    /**
337     * Internal function for use in checkboxes and radio buttons and such.
338     *
339     * @param string $name
340     * @param bool $present
341     *
342     * @return array
343     *
344     * @deprecated since 1.42; only for use in methods being deprecated
345     */
346    public static function attrib( $name, $present = true ) {
347        return $present ? [ $name => $name ] : [];
348    }
349
350    /**
351     * Convenience function to build an HTML checkbox
352     * @param string $name Value of the name attribute
353     * @param bool $checked Whether the checkbox is checked or not
354     * @param array $attribs Array other attributes
355     * @return string HTML
356     *
357     * @deprecated since 1.42, use {@see Html::check} instead
358     */
359    public static function check( $name, $checked = false, $attribs = [] ) {
360        return self::element( 'input', array_merge(
361            [
362                'name' => $name,
363                'type' => 'checkbox',
364                'value' => 1 ],
365            self::attrib( 'checked', $checked ),
366            $attribs ) );
367    }
368
369    /**
370     * Convenience function to build an HTML radio button
371     * @param string $name Value of the name attribute
372     * @param string $value Value of the value attribute
373     * @param bool $checked Whether the checkbox is checked or not
374     * @param array $attribs Other attributes
375     * @return string HTML
376     *
377     * @deprecated since 1.42, use {@see Html::radio} instead
378     */
379    public static function radio( $name, $value, $checked = false, $attribs = [] ) {
380        return self::element( 'input', [
381            'name' => $name,
382            'type' => 'radio',
383            'value' => $value ] + self::attrib( 'checked', $checked ) + $attribs );
384    }
385
386    /**
387     * Convenience function to build an HTML form label
388     * @param string $label Text of the label
389     * @param string $id
390     * @param array $attribs An attribute array.  This will usually be
391     *     the same array as is passed to the corresponding input element,
392     *     so this function will cherry-pick appropriate attributes to
393     *     apply to the label as well; only class and title are applied.
394     * @return string HTML
395     *
396     * @deprecated since 1.42, use {@see Html::label} instead
397     */
398    public static function label( $label, $id, $attribs = [] ) {
399        $a = [ 'for' => $id ];
400
401        foreach ( [ 'class', 'title' ] as $attr ) {
402            if ( isset( $attribs[$attr] ) ) {
403                $a[$attr] = $attribs[$attr];
404            }
405        }
406
407        return self::element( 'label', $a, $label );
408    }
409
410    /**
411     * Convenience function to build an HTML text input field with a label
412     * @param string $label Text of the label
413     * @param string $name Value of the name attribute
414     * @param string $id Id of the input
415     * @param int|false $size Value of the size attribute
416     * @param string|false $value Value of the value attribute
417     * @param array $attribs Other attributes
418     * @return string HTML
419     *
420     * @deprecated since 1.42, use {@see Html::input} and {@see Html::label} instead
421     */
422    public static function inputLabel( $label, $name, $id, $size = false,
423        $value = false, $attribs = []
424    ) {
425        [ $label, $input ] = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
426        return $label . "\u{00A0}" . $input;
427    }
428
429    /**
430     * Same as Xml::inputLabel() but return input and label in an array
431     *
432     * @param string $label
433     * @param string $name
434     * @param string $id
435     * @param int|false $size
436     * @param string|false $value
437     * @param array $attribs
438     * @return array
439     *
440     * @deprecated since 1.42, use {@see Html::input} and {@see Html::label} instead
441     */
442    public static function inputLabelSep( $label, $name, $id, $size = false,
443        $value = false, $attribs = []
444    ) {
445        return [
446            self::label( $label, $id, $attribs ),
447            self::input( $name, $size, $value, [ 'id' => $id ] + $attribs )
448        ];
449    }
450
451    /**
452     * Convenience function to build an HTML checkbox with a label
453     *
454     * @param string $label
455     * @param string $name
456     * @param string $id
457     * @param bool $checked
458     * @param array $attribs
459     * @return string HTML
460     *
461     * @deprecated since 1.42, use {@see Html::check} and {@see Html::label} instead
462     */
463    public static function checkLabel( $label, $name, $id, $checked = false, $attribs = [] ) {
464        return self::check( $name, $checked, [ 'id' => $id ] + $attribs ) .
465            "\u{00A0}" .
466            self::label( $label, $id, $attribs );
467    }
468
469    /**
470     * Convenience function to build an HTML radio button with a label
471     *
472     * @param string $label
473     * @param string $name
474     * @param string $value
475     * @param string $id
476     * @param bool $checked
477     * @param array $attribs
478     * @return string HTML
479     *
480     * @deprecated since 1.42, use {@see Html::radio} and {@see Html::label} instead
481     */
482    public static function radioLabel( $label, $name, $value, $id,
483        $checked = false, $attribs = []
484    ) {
485        return self::radio( $name, $value, $checked, [ 'id' => $id ] + $attribs ) .
486            "\u{00A0}" .
487            self::label( $label, $id, $attribs );
488    }
489
490    /**
491     * Convenience function to build an HTML submit button.
492     *
493     * @param string $value Label text for the button (unescaped)
494     * @param array $attribs Optional custom attributes
495     * @return string HTML
496     *
497     * @deprecated since 1.42, use {@see Html::submitButton} instead
498     */
499    public static function submitButton( $value, $attribs = [] ) {
500        $attribs += [
501            'type' => 'submit',
502            'value' => $value,
503        ];
504        return Html::element( 'input', $attribs );
505    }
506
507    /**
508     * Convenience function to build an HTML drop-down list item.
509     * @param string $text Text for this item. Will be HTML escaped
510     * @param string|null $value Form submission value; if empty, use text
511     * @param bool $selected If true, will be the default selected item
512     * @param array $attribs Optional additional HTML attributes
513     * @return string HTML
514     *
515     * @deprecated since 1.42, use {@see Html::element} instead
516     */
517    public static function option( $text, $value = null, $selected = false,
518            $attribs = [] ) {
519        if ( $value !== null ) {
520            $attribs['value'] = $value;
521        }
522        if ( $selected ) {
523            $attribs['selected'] = 'selected';
524        }
525        return Html::element( 'option', $attribs, $text );
526    }
527
528    /**
529     * Build a drop-down box from a textual list. This is a wrapper
530     * for Xml::listDropdownOptions() plus the XmlSelect class.
531     *
532     * @param string $name Name and id for the drop-down
533     * @param string $list Correctly formatted text (newline delimited) to be
534     *   used to generate the options.
535     * @param string $other Text for the "Other reasons" option
536     * @param string $selected Option which should be pre-selected
537     * @param string $class CSS classes for the drop-down
538     * @param int|null $tabindex Value of the tabindex attribute
539     * @return string
540     *
541     * @deprecated since 1.42; use the equivalent methods in Html without a wrapper
542     */
543    public static function listDropdown( $name = '', $list = '', $other = '',
544        $selected = '', $class = '', $tabindex = null
545    ) {
546        $options = self::listDropdownOptions( $list, [ 'other' => $other ] );
547
548        $xmlSelect = new XmlSelect( $name, $name, $selected );
549        $xmlSelect->addOptions( $options );
550
551        if ( $class ) {
552            $xmlSelect->setAttribute( 'class', $class );
553        }
554        if ( $tabindex ) {
555            $xmlSelect->setAttribute( 'tabindex', $tabindex );
556        }
557
558        return $xmlSelect->getHTML();
559    }
560
561    /**
562     * Build options for a drop-down box from a textual list.
563     *
564     * The result of this function can be passed to XmlSelect::addOptions()
565     * (to render a plain `<select>` dropdown box) or to Xml::listDropdownOptionsOoui()
566     * and then OOUI\DropdownInputWidget() (to render a pretty one).
567     *
568     * @param string $list Correctly formatted text (newline delimited) to be
569     *   used to generate the options.
570     * @param array $params Extra parameters:
571     *   - string $params['other'] If set, add an option with this as text and a value of 'other'
572     * @return array Array keys are textual labels, values are internal values
573     *
574     * @deprecated since 1.42; use the equivalent method in Html
575     */
576    public static function listDropdownOptions( $list, $params = [] ) {
577        $options = [];
578
579        if ( isset( $params['other'] ) ) {
580            $options[ $params['other'] ] = 'other';
581        }
582
583        $optgroup = false;
584        foreach ( explode( "\n", $list ) as $option ) {
585            $value = trim( $option );
586            if ( $value == '' ) {
587                continue;
588            }
589            if ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
590                # A new group is starting...
591                $value = trim( substr( $value, 1 ) );
592                if ( $value !== '' &&
593                    // Do not use the value for 'other' as option group - T251351
594                    ( !isset( $params['other'] ) || $value !== $params['other'] )
595                ) {
596                    $optgroup = $value;
597                } else {
598                    $optgroup = false;
599                }
600            } elseif ( substr( $value, 0, 2 ) == '**' ) {
601                # groupmember
602                $opt = trim( substr( $value, 2 ) );
603                if ( $optgroup === false ) {
604                    $options[$opt] = $opt;
605                } else {
606                    $options[$optgroup][$opt] = $opt;
607                }
608            } else {
609                # groupless reason list
610                $optgroup = false;
611                $options[$option] = $option;
612            }
613        }
614
615        return $options;
616    }
617
618    /**
619     * Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
620     *
621     * TODO Find a better home for this function.
622     *
623     * @param array $options Options, as returned e.g. by Xml::listDropdownOptions()
624     * @return array
625     *
626     * @deprecated since 1.42; use the equivalent method in Html
627     */
628    public static function listDropdownOptionsOoui( $options ) {
629        $optionsOoui = [];
630
631        foreach ( $options as $text => $value ) {
632            if ( is_array( $value ) ) {
633                $optionsOoui[] = [ 'optgroup' => (string)$text ];
634                foreach ( $value as $text2 => $value2 ) {
635                    $optionsOoui[] = [ 'data' => (string)$value2, 'label' => (string)$text2 ];
636                }
637            } else {
638                $optionsOoui[] = [ 'data' => (string)$value, 'label' => (string)$text ];
639            }
640        }
641
642        return $optionsOoui;
643    }
644
645    /**
646     * Shortcut for creating fieldsets.
647     *
648     * @param string|false $legend Legend of the fieldset. If evaluates to false,
649     *   legend is not added.
650     * @param string|false $content Pre-escaped content for the fieldset. If false,
651     *   only open fieldset is returned.
652     * @param array $attribs Any attributes to fieldset-element.
653     * @return string
654     *
655     * @deprecated since 1.42, use {@see Html::element} instead
656     */
657    public static function fieldset( $legend = false, $content = false, $attribs = [] ) {
658        $s = self::openElement( 'fieldset', $attribs ) . "\n";
659
660        if ( $legend ) {
661            $s .= self::element( 'legend', null, $legend ) . "\n";
662        }
663
664        if ( $content !== false ) {
665            $s .= $content . "\n";
666            $s .= self::closeElement( 'fieldset' ) . "\n";
667        }
668
669        return $s;
670    }
671
672    /**
673     * Shortcut for creating textareas.
674     *
675     * @param string $name The 'name' for the textarea
676     * @param string $content Content for the textarea
677     * @param int $cols The number of columns for the textarea
678     * @param int $rows The number of rows for the textarea
679     * @param array $attribs Any other attributes for the textarea
680     * @return string
681     *
682     * @deprecated since 1.42, use {@see Html::textarea} instead
683     */
684    public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = [] ) {
685        return self::element( 'textarea',
686                    [
687                        'name' => $name,
688                        'id' => $name,
689                        'cols' => $cols,
690                        'rows' => $rows
691                    ] + $attribs,
692                    $content, false );
693    }
694
695    /**
696     * Encode a variable of arbitrary type to JavaScript.
697     * If the value is an HtmlJsCode object, pass through the object's value verbatim.
698     *
699     * @note Only use this function for generating JavaScript code. If generating output
700     *       for a proper JSON parser, just call FormatJson::encode() directly.
701     *
702     * @param mixed $value The value being encoded. Can be any type except a resource.
703     * @param-taint $value escapes_html
704     * @param bool $pretty If true, add non-significant whitespace to improve readability.
705     * @return string|false String if successful; false upon failure
706     * @return-taint none
707     *
708     * @deprecated since 1.41, use {@see Html::encodeJsVar} instead
709     */
710    public static function encodeJsVar( $value, $pretty = false ) {
711        return Html::encodeJsVar( $value, $pretty );
712    }
713
714    /**
715     * Create a call to a JavaScript function. The supplied arguments will be
716     * encoded using Xml::encodeJsVar().
717     *
718     * @since 1.17
719     * @param string $name The name of the function to call, or a JavaScript expression
720     *    which evaluates to a function object which is called.
721     * @param-taint $name tainted
722     * @param array $args The arguments to pass to the function.
723     * @param-taint $args escapes_html
724     * @param bool $pretty If true, add non-significant whitespace to improve readability.
725     * @return string|false String if successful; false upon failure
726     * @return-taint none
727     *
728     * @deprecated since 1.41, use {@see Html::encodeJsCall} instead
729     */
730    public static function encodeJsCall( $name, $args, $pretty = false ) {
731        return Html::encodeJsCall( $name, $args, $pretty );
732    }
733
734    /**
735     * Check if a string is well-formed XML.
736     * Must include the surrounding tag.
737     * This function is a DoS vector if an attacker can define
738     * entities in $text.
739     *
740     * @param string $text String to test.
741     * @return bool
742     *
743     * @todo Error position reporting return
744     */
745    private static function isWellFormed( $text ) {
746        $parser = xml_parser_create( "UTF-8" );
747
748        # case folding violates XML standard, turn it off
749        xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
750
751        if ( !xml_parse( $parser, $text, true ) ) {
752            // $err = xml_error_string( xml_get_error_code( $parser ) );
753            // $position = xml_get_current_byte_index( $parser );
754            // $fragment = $this->extractFragment( $html, $position );
755            // $this->mXmlError = "$err at byte $position:\n$fragment";
756            xml_parser_free( $parser );
757            return false;
758        }
759
760        xml_parser_free( $parser );
761
762        return true;
763    }
764
765    /**
766     * Check if a string is a well-formed XML fragment.
767     * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
768     * and can use HTML named entities.
769     *
770     * @param string $text
771     * @return bool
772     */
773    public static function isWellFormedXmlFragment( $text ) {
774        $html =
775            Sanitizer::hackDocType() .
776            '<html>' .
777            $text .
778            '</html>';
779
780        return self::isWellFormed( $html );
781    }
782
783    /**
784     * Replace " > and < with their respective HTML entities ( &quot;,
785     * &gt;, &lt;)
786     *
787     * @param string $in Text that might contain HTML tags.
788     * @return string Escaped string
789     */
790    public static function escapeTagsOnly( $in ) {
791        return str_replace(
792            [ '"', '>', '<' ],
793            [ '&quot;', '&gt;', '&lt;' ],
794            $in );
795    }
796
797    /**
798     * Generate a form (without the opening form element).
799     * Output optionally includes a submit button.
800     * @param array $fields Associative array, key is the name of a message that
801     *   contains a description for the field, value is an HTML string
802     *   containing the appropriate input.
803     * @param string|null $submitLabel The name of a message containing a label for
804     *   the submit button.
805     * @param array $submitAttribs The attributes to add to the submit button
806     * @return string HTML form.
807     *
808     * @deprecated since 1.42, use OOUI or Codex widgets instead
809     */
810    public static function buildForm( $fields, $submitLabel = null, $submitAttribs = [] ) {
811        $form = '';
812        $form .= "<table><tbody>";
813
814        foreach ( $fields as $labelmsg => $input ) {
815            $id = "mw-$labelmsg";
816            $form .= self::openElement( 'tr', [ 'id' => $id ] );
817
818            // TODO use a <label> here for accessibility purposes - will need
819            // to either not use a table to build the form, or find the ID of
820            // the input somehow.
821
822            $form .= self::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
823            $form .= self::openElement( 'td', [ 'class' => 'mw-input' ] )
824                . $input . self::closeElement( 'td' );
825            $form .= self::closeElement( 'tr' );
826        }
827
828        if ( $submitLabel ) {
829            $form .= self::openElement( 'tr' );
830            $form .= self::tags( 'td', [], '' );
831            $form .= self::openElement( 'td', [ 'class' => 'mw-submit' ] )
832                . self::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
833                . self::closeElement( 'td' );
834            $form .= self::closeElement( 'tr' );
835        }
836
837        $form .= "</tbody></table>";
838
839        return $form;
840    }
841
842    /**
843     * @param string[][] $rows
844     * @param array|null $attribs An array of attributes to apply to the table tag
845     * @param array|null $headers An array of strings to use as table headers
846     * @return string
847     *
848     * @deprecated since 1.42; use OOUI or Codex widgets instead
849     */
850    public static function buildTable( $rows, $attribs = [], $headers = null ) {
851        $s = self::openElement( 'table', $attribs );
852
853        if ( is_array( $headers ) ) {
854            $s .= self::openElement( 'thead', $attribs );
855
856            foreach ( $headers as $id => $header ) {
857                $attribs = [];
858
859                if ( is_string( $id ) ) {
860                    $attribs['id'] = $id;
861                }
862
863                $s .= self::element( 'th', $attribs, $header );
864            }
865            $s .= self::closeElement( 'thead' );
866        }
867
868        foreach ( $rows as $id => $row ) {
869            $attribs = [];
870
871            if ( is_string( $id ) ) {
872                $attribs['id'] = $id;
873            }
874
875            $s .= self::buildTableRow( $attribs, $row );
876        }
877
878        $s .= self::closeElement( 'table' );
879
880        return $s;
881    }
882
883    /**
884     * Build a row for a table
885     * @param array|null $attribs An array of attributes to apply to the tr tag
886     * @param string[] $cells An array of strings to put in <td>
887     * @return string
888     *
889     * @deprecated since 1.42; use OOUI or Codex widgets instead
890     */
891    public static function buildTableRow( $attribs, $cells ) {
892        $s = self::openElement( 'tr', $attribs );
893
894        foreach ( $cells as $id => $cell ) {
895            $attribs = [];
896
897            if ( is_string( $id ) ) {
898                $attribs['id'] = $id;
899            }
900
901            $s .= self::element( 'td', $attribs, $cell );
902        }
903
904        $s .= self::closeElement( 'tr' );
905
906        return $s;
907    }
908}