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