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