Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.31% covered (warning)
65.31%
160 / 245
41.67% covered (danger)
41.67%
15 / 36
CRAP
0.00% covered (danger)
0.00%
0 / 1
Xml
65.57% covered (warning)
65.57%
160 / 244
41.67% covered (danger)
41.67%
15 / 36
412.18
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%
2 / 2
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 / 3
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 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 encodeJsCall
0.00% covered (danger)
0.00%
0 / 2
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
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
6.06
 buildTableRow
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
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; emiting deprecation warnings since 1.44
276     */
277    public static function span( $text, $class, $attribs = [] ) {
278        wfDeprecated( __METHOD__, '1.42' );
279
280        return self::element( 'span', [ 'class' => $class ] + $attribs, $text );
281    }
282
283    /**
284     * Shortcut to make a specific element with a class attribute
285     *
286     * @param string $text Content of the element, will be escaped
287     * @param string $class Class name of the span element
288     * @param string $tag Element name
289     * @param array $attribs Other attributes
290     * @return string
291     *
292     * @deprecated since 1.42, use {@see Xml::tags} instead
293     */
294    public static function wrapClass( $text, $class, $tag = 'span', $attribs = [] ) {
295        wfDeprecated( __METHOD__, '1.42' );
296        return self::tags( $tag, [ 'class' => $class ] + $attribs, $text );
297    }
298
299    /**
300     * Convenience function to build an HTML text input field
301     * @param string $name Value of the name attribute
302     * @param int|false $size Value of the size attribute
303     * @param string|false $value Value of the value attribute
304     * @param array $attribs Other attributes
305     * @return string HTML
306     *
307     * @deprecated since 1.42, use {@see Html::input} instead
308     */
309    public static function input( $name, $size = false, $value = false, $attribs = [] ) {
310        $attributes = [ 'name' => $name ];
311
312        if ( $size ) {
313            $attributes['size'] = $size;
314        }
315
316        if ( $value !== false ) { // maybe 0
317            $attributes['value'] = $value;
318        }
319
320        return self::element( 'input', $attributes + $attribs );
321    }
322
323    /**
324     * Convenience function to build an HTML password input field
325     * @param string $name Value of the name attribute
326     * @param int|false $size Value of the size attribute
327     * @param string|false $value Value of the value attribute
328     * @param array $attribs Other attributes
329     * @return string HTML
330     *
331     * @deprecated since 1.42, use {@see Html::input} instead; emiting deprecation warnings since 1.44
332     */
333    public static function password( $name, $size = false, $value = false,
334        $attribs = []
335    ) {
336        wfDeprecated( __METHOD__, '1.42' );
337
338        return self::input( $name, $size, $value,
339            array_merge( $attribs, [ 'type' => 'password' ] ) );
340    }
341
342    /**
343     * Internal function for use in checkboxes and radio buttons and such.
344     *
345     * @param string $name
346     * @param bool $present
347     *
348     * @return array
349     *
350     * @deprecated since 1.42; only for use in methods being deprecated
351     */
352    public static function attrib( $name, $present = true ) {
353        return $present ? [ $name => $name ] : [];
354    }
355
356    /**
357     * Convenience function to build an HTML checkbox
358     * @param string $name Value of the name attribute
359     * @param bool $checked Whether the checkbox is checked or not
360     * @param array $attribs Array other attributes
361     * @return string HTML
362     *
363     * @deprecated since 1.42, use {@see Html::check} instead
364     */
365    public static function check( $name, $checked = false, $attribs = [] ) {
366        return self::element( 'input', array_merge(
367            [
368                'name' => $name,
369                'type' => 'checkbox',
370                'value' => 1 ],
371            self::attrib( 'checked', $checked ),
372            $attribs ) );
373    }
374
375    /**
376     * Convenience function to build an HTML radio button
377     * @param string $name Value of the name attribute
378     * @param string $value Value of the value attribute
379     * @param bool $checked Whether the checkbox is checked or not
380     * @param array $attribs Other attributes
381     * @return string HTML
382     *
383     * @deprecated since 1.42, use {@see Html::radio} instead
384     */
385    public static function radio( $name, $value, $checked = false, $attribs = [] ) {
386        return self::element( 'input', [
387            'name' => $name,
388            'type' => 'radio',
389            'value' => $value ] + self::attrib( 'checked', $checked ) + $attribs );
390    }
391
392    /**
393     * Convenience function to build an HTML form label
394     * @param string $label Text of the label
395     * @param string $id
396     * @param array $attribs An attribute array.  This will usually be
397     *     the same array as is passed to the corresponding input element,
398     *     so this function will cherry-pick appropriate attributes to
399     *     apply to the label as well; only class and title are applied.
400     * @return string HTML
401     *
402     * @deprecated since 1.42, use {@see Html::label} instead
403     */
404    public static function label( $label, $id, $attribs = [] ) {
405        $a = [ 'for' => $id ];
406
407        foreach ( [ 'class', 'title' ] as $attr ) {
408            if ( isset( $attribs[$attr] ) ) {
409                $a[$attr] = $attribs[$attr];
410            }
411        }
412
413        return self::element( 'label', $a, $label );
414    }
415
416    /**
417     * Convenience function to build an HTML text input field with a label
418     * @param string $label Text of the label
419     * @param string $name Value of the name attribute
420     * @param string $id Id of the input
421     * @param int|false $size Value of the size attribute
422     * @param string|false $value Value of the value attribute
423     * @param array $attribs Other attributes
424     * @return string HTML
425     *
426     * @deprecated since 1.42, use {@see Html::input} and {@see Html::label} instead
427     */
428    public static function inputLabel( $label, $name, $id, $size = false,
429        $value = false, $attribs = []
430    ) {
431        [ $label, $input ] = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
432        return $label . "\u{00A0}" . $input;
433    }
434
435    /**
436     * Same as Xml::inputLabel() but return input and label in an array
437     *
438     * @param string $label
439     * @param string $name
440     * @param string $id
441     * @param int|false $size
442     * @param string|false $value
443     * @param array $attribs
444     * @return array
445     *
446     * @deprecated since 1.42, use {@see Html::input} and {@see Html::label} instead
447     */
448    public static function inputLabelSep( $label, $name, $id, $size = false,
449        $value = false, $attribs = []
450    ) {
451        return [
452            self::label( $label, $id, $attribs ),
453            self::input( $name, $size, $value, [ 'id' => $id ] + $attribs )
454        ];
455    }
456
457    /**
458     * Convenience function to build an HTML checkbox with a label
459     *
460     * @param string $label
461     * @param string $name
462     * @param string $id
463     * @param bool $checked
464     * @param array $attribs
465     * @return string HTML
466     *
467     * @deprecated since 1.42, use {@see Html::check} and {@see Html::label} instead
468     */
469    public static function checkLabel( $label, $name, $id, $checked = false, $attribs = [] ) {
470        return self::check( $name, $checked, [ 'id' => $id ] + $attribs ) .
471            "\u{00A0}" .
472            self::label( $label, $id, $attribs );
473    }
474
475    /**
476     * Convenience function to build an HTML radio button with a label
477     *
478     * @param string $label
479     * @param string $name
480     * @param string $value
481     * @param string $id
482     * @param bool $checked
483     * @param array $attribs
484     * @return string HTML
485     *
486     * @deprecated since 1.42, use {@see Html::radio} and {@see Html::label} instead
487     */
488    public static function radioLabel( $label, $name, $value, $id,
489        $checked = false, $attribs = []
490    ) {
491        return self::radio( $name, $value, $checked, [ 'id' => $id ] + $attribs ) .
492            "\u{00A0}" .
493            self::label( $label, $id, $attribs );
494    }
495
496    /**
497     * Convenience function to build an HTML submit button.
498     *
499     * @param string $value Label text for the button (unescaped)
500     * @param array $attribs Optional custom attributes
501     * @return string HTML
502     *
503     * @deprecated since 1.42, use {@see Html::submitButton} instead
504     */
505    public static function submitButton( $value, $attribs = [] ) {
506        $attribs += [
507            'type' => 'submit',
508            'value' => $value,
509        ];
510        return Html::element( 'input', $attribs );
511    }
512
513    /**
514     * Convenience function to build an HTML drop-down list item.
515     * @param string $text Text for this item. Will be HTML escaped
516     * @param string|null $value Form submission value; if empty, use text
517     * @param bool $selected If true, will be the default selected item
518     * @param array $attribs Optional additional HTML attributes
519     * @return string HTML
520     *
521     * @deprecated since 1.42, use {@see Html::element} instead
522     */
523    public static function option( $text, $value = null, $selected = false,
524            $attribs = [] ) {
525        if ( $value !== null ) {
526            $attribs['value'] = $value;
527        }
528        if ( $selected ) {
529            $attribs['selected'] = 'selected';
530        }
531        return Html::element( 'option', $attribs, $text );
532    }
533
534    /**
535     * Build a drop-down box from a textual list. This is a wrapper
536     * for Xml::listDropdownOptions() plus the XmlSelect class.
537     *
538     * @param string $name Name and id for the drop-down
539     * @param string $list Correctly formatted text (newline delimited) to be
540     *   used to generate the options.
541     * @param string $other Text for the "Other reasons" option
542     * @param string $selected Option which should be pre-selected
543     * @param string $class CSS classes for the drop-down
544     * @param int|null $tabindex Value of the tabindex attribute
545     * @return string
546     *
547     * @deprecated since 1.42; use the equivalent methods in Html without a wrapper
548     */
549    public static function listDropdown( $name = '', $list = '', $other = '',
550        $selected = '', $class = '', $tabindex = null
551    ) {
552        $options = self::listDropdownOptions( $list, [ 'other' => $other ] );
553
554        $xmlSelect = new XmlSelect( $name, $name, $selected );
555        $xmlSelect->addOptions( $options );
556
557        if ( $class ) {
558            $xmlSelect->setAttribute( 'class', $class );
559        }
560        if ( $tabindex ) {
561            $xmlSelect->setAttribute( 'tabindex', $tabindex );
562        }
563
564        return $xmlSelect->getHTML();
565    }
566
567    /**
568     * Build options for a drop-down box from a textual list.
569     *
570     * The result of this function can be passed to XmlSelect::addOptions()
571     * (to render a plain `<select>` dropdown box) or to Xml::listDropdownOptionsOoui()
572     * and then OOUI\DropdownInputWidget() (to render a pretty one).
573     *
574     * @param string $list Correctly formatted text (newline delimited) to be
575     *   used to generate the options.
576     * @param array $params Extra parameters:
577     *   - string $params['other'] If set, add an option with this as text and a value of 'other'
578     * @return array Array keys are textual labels, values are internal values
579     *
580     * @deprecated since 1.42; use the equivalent method in Html
581     */
582    public static function listDropdownOptions( $list, $params = [] ) {
583        $options = [];
584
585        if ( isset( $params['other'] ) ) {
586            $options[ $params['other'] ] = 'other';
587        }
588
589        $optgroup = false;
590        foreach ( explode( "\n", $list ) as $option ) {
591            $value = trim( $option );
592            if ( $value == '' ) {
593                continue;
594            }
595            if ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
596                # A new group is starting...
597                $value = trim( substr( $value, 1 ) );
598                if ( $value !== '' &&
599                    // Do not use the value for 'other' as option group - T251351
600                    ( !isset( $params['other'] ) || $value !== $params['other'] )
601                ) {
602                    $optgroup = $value;
603                } else {
604                    $optgroup = false;
605                }
606            } elseif ( substr( $value, 0, 2 ) == '**' ) {
607                # groupmember
608                $opt = trim( substr( $value, 2 ) );
609                if ( $optgroup === false ) {
610                    $options[$opt] = $opt;
611                } else {
612                    $options[$optgroup][$opt] = $opt;
613                }
614            } else {
615                # groupless reason list
616                $optgroup = false;
617                $options[$option] = $option;
618            }
619        }
620
621        return $options;
622    }
623
624    /**
625     * Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
626     *
627     * TODO Find a better home for this function.
628     *
629     * @param array $options Options, as returned e.g. by Xml::listDropdownOptions()
630     * @return array
631     *
632     * @deprecated since 1.42; use the equivalent method in Html
633     */
634    public static function listDropdownOptionsOoui( $options ) {
635        $optionsOoui = [];
636
637        foreach ( $options as $text => $value ) {
638            if ( is_array( $value ) ) {
639                $optionsOoui[] = [ 'optgroup' => (string)$text ];
640                foreach ( $value as $text2 => $value2 ) {
641                    $optionsOoui[] = [ 'data' => (string)$value2, 'label' => (string)$text2 ];
642                }
643            } else {
644                $optionsOoui[] = [ 'data' => (string)$value, 'label' => (string)$text ];
645            }
646        }
647
648        return $optionsOoui;
649    }
650
651    /**
652     * Shortcut for creating fieldsets.
653     *
654     * @param string|false $legend Legend of the fieldset. If evaluates to false,
655     *   legend is not added.
656     * @param string|false $content Pre-escaped content for the fieldset. If false,
657     *   only open fieldset is returned.
658     * @param array $attribs Any attributes to fieldset-element.
659     * @return string
660     *
661     * @deprecated since 1.42, use {@see Html::element} instead
662     */
663    public static function fieldset( $legend = false, $content = false, $attribs = [] ) {
664        $s = self::openElement( 'fieldset', $attribs ) . "\n";
665
666        if ( $legend ) {
667            $s .= self::element( 'legend', null, $legend ) . "\n";
668        }
669
670        if ( $content !== false ) {
671            $s .= $content . "\n";
672            $s .= self::closeElement( 'fieldset' ) . "\n";
673        }
674
675        return $s;
676    }
677
678    /**
679     * Shortcut for creating textareas.
680     *
681     * @param string $name The 'name' for the textarea
682     * @param string $content Content for the textarea
683     * @param int $cols The number of columns for the textarea
684     * @param int $rows The number of rows for the textarea
685     * @param array $attribs Any other attributes for the textarea
686     * @return string
687     *
688     * @deprecated since 1.42, use {@see Html::textarea} instead
689     */
690    public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = [] ) {
691        return self::element( 'textarea',
692                    [
693                        'name' => $name,
694                        'id' => $name,
695                        'cols' => $cols,
696                        'rows' => $rows
697                    ] + $attribs,
698                    $content, false );
699    }
700
701    /**
702     * Encode a variable of arbitrary type to JavaScript.
703     * If the value is an HtmlJsCode object, pass through the object's value verbatim.
704     *
705     * @note Only use this function for generating JavaScript code. If generating output
706     *       for a proper JSON parser, just call FormatJson::encode() directly.
707     *
708     * @param mixed $value The value being encoded. Can be any type except a resource.
709     * @param-taint $value escapes_html
710     * @param bool $pretty If true, add non-significant whitespace to improve readability.
711     * @return string|false String if successful; false upon failure
712     * @return-taint none
713     *
714     * @deprecated since 1.41, use {@see Html::encodeJsVar} instead; emiting deprecation warnings since 1.44
715     */
716    public static function encodeJsVar( $value, $pretty = false ) {
717        wfDeprecated( __METHOD__, '1.41' );
718
719        return Html::encodeJsVar( $value, $pretty );
720    }
721
722    /**
723     * Create a call to a JavaScript function. The supplied arguments will be
724     * encoded using Xml::encodeJsVar().
725     *
726     * @since 1.17
727     * @param string $name The name of the function to call, or a JavaScript expression
728     *    which evaluates to a function object which is called.
729     * @param-taint $name tainted
730     * @param array $args The arguments to pass to the function.
731     * @param-taint $args escapes_html
732     * @param bool $pretty If true, add non-significant whitespace to improve readability.
733     * @return string|false String if successful; false upon failure
734     * @return-taint none
735     *
736     * @deprecated since 1.41, use {@see Html::encodeJsCall} instead; emiting deprecation warnings since 1.44
737     */
738    public static function encodeJsCall( $name, $args, $pretty = false ) {
739        wfDeprecated( __METHOD__, '1.41' );
740
741        return Html::encodeJsCall( $name, $args, $pretty );
742    }
743
744    /**
745     * Check if a string is well-formed XML.
746     * Must include the surrounding tag.
747     * This function is a DoS vector if an attacker can define
748     * entities in $text.
749     *
750     * @param string $text String to test.
751     * @return bool
752     *
753     * @todo Error position reporting return
754     */
755    private static function isWellFormed( $text ) {
756        $parser = xml_parser_create( "UTF-8" );
757
758        # case folding violates XML standard, turn it off
759        xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
760
761        if ( !xml_parse( $parser, $text, true ) ) {
762            // $err = xml_error_string( xml_get_error_code( $parser ) );
763            // $position = xml_get_current_byte_index( $parser );
764            // $fragment = $this->extractFragment( $html, $position );
765            // $this->mXmlError = "$err at byte $position:\n$fragment";
766            xml_parser_free( $parser );
767            return false;
768        }
769
770        xml_parser_free( $parser );
771
772        return true;
773    }
774
775    /**
776     * Check if a string is a well-formed XML fragment.
777     * Wraps fragment in an \<html\> bit and doctype, so it can be a fragment
778     * and can use HTML named entities.
779     *
780     * @param string $text
781     * @return bool
782     */
783    public static function isWellFormedXmlFragment( $text ) {
784        $html =
785            Sanitizer::hackDocType() .
786            '<html>' .
787            $text .
788            '</html>';
789
790        return self::isWellFormed( $html );
791    }
792
793    /**
794     * Replace " > and < with their respective HTML entities ( &quot;,
795     * &gt;, &lt;)
796     *
797     * @param string $in Text that might contain HTML tags.
798     * @return string Escaped string
799     */
800    public static function escapeTagsOnly( $in ) {
801        return str_replace(
802            [ '"', '>', '<' ],
803            [ '&quot;', '&gt;', '&lt;' ],
804            $in );
805    }
806
807    /**
808     * Generate a form (without the opening form element).
809     * Output optionally includes a submit button.
810     * @param array $fields Associative array, key is the name of a message that
811     *   contains a description for the field, value is an HTML string
812     *   containing the appropriate input.
813     * @param string|null $submitLabel The name of a message containing a label for
814     *   the submit button.
815     * @param array $submitAttribs The attributes to add to the submit button
816     * @return string HTML form.
817     *
818     * @deprecated since 1.42, use OOUI or Codex widgets instead
819     */
820    public static function buildForm( $fields, $submitLabel = null, $submitAttribs = [] ) {
821        $form = '';
822        $form .= "<table><tbody>";
823
824        foreach ( $fields as $labelmsg => $input ) {
825            $id = "mw-$labelmsg";
826            $form .= self::openElement( 'tr', [ 'id' => $id ] );
827
828            // TODO use a <label> here for accessibility purposes - will need
829            // to either not use a table to build the form, or find the ID of
830            // the input somehow.
831
832            $form .= self::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
833            $form .= self::openElement( 'td', [ 'class' => 'mw-input' ] )
834                . $input . self::closeElement( 'td' );
835            $form .= self::closeElement( 'tr' );
836        }
837
838        if ( $submitLabel ) {
839            $form .= self::openElement( 'tr' );
840            $form .= self::tags( 'td', [], '' );
841            $form .= self::openElement( 'td', [ 'class' => 'mw-submit' ] )
842                . self::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
843                . self::closeElement( 'td' );
844            $form .= self::closeElement( 'tr' );
845        }
846
847        $form .= "</tbody></table>";
848
849        return $form;
850    }
851
852    /**
853     * @param string[][] $rows
854     * @param array|null $attribs An array of attributes to apply to the table tag
855     * @param array|null $headers An array of strings to use as table headers
856     * @return string
857     *
858     * @deprecated since 1.42; use OOUI or Codex widgets instead; emiting deprecation warnings since 1.44
859     */
860    public static function buildTable( $rows, $attribs = [], $headers = null ) {
861        wfDeprecated( __METHOD__, '1.42' );
862
863        $s = self::openElement( 'table', $attribs );
864
865        if ( is_array( $headers ) ) {
866            $s .= self::openElement( 'thead', $attribs );
867
868            foreach ( $headers as $id => $header ) {
869                $attribs = [];
870
871                if ( is_string( $id ) ) {
872                    $attribs['id'] = $id;
873                }
874
875                $s .= self::element( 'th', $attribs, $header );
876            }
877            $s .= self::closeElement( 'thead' );
878        }
879
880        foreach ( $rows as $id => $row ) {
881            $attribs = [];
882
883            if ( is_string( $id ) ) {
884                $attribs['id'] = $id;
885            }
886
887            $s .= self::buildTableRow( $attribs, $row );
888        }
889
890        $s .= self::closeElement( 'table' );
891
892        return $s;
893    }
894
895    /**
896     * Build a row for a table
897     * @param array|null $attribs An array of attributes to apply to the tr tag
898     * @param string[] $cells An array of strings to put in <td>
899     * @return string
900     *
901     * @deprecated since 1.42; use OOUI or Codex widgets instead; emiting deprecation warnings since 1.44
902     */
903    public static function buildTableRow( $attribs, $cells ) {
904        wfDeprecated( __METHOD__, '1.42' );
905
906        $s = self::openElement( 'tr', $attribs );
907
908        foreach ( $cells as $id => $cell ) {
909            $attribs = [];
910
911            if ( is_string( $id ) ) {
912                $attribs['id'] = $id;
913            }
914
915            $s .= self::element( 'td', $attribs, $cell );
916        }
917
918        $s .= self::closeElement( 'tr' );
919
920        return $s;
921    }
922}
923/** @deprecated class alias since 1.43 */
924class_alias( Xml::class, 'Xml' );