MediaWiki master
Xml.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Xml;
24
26use MediaWiki\Languages\LanguageNameUtils;
32use UtfNormal\Validator;
33
37class Xml {
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 // @phan-suppress-next-line PhanTypeMismatchArgumentNullableInternal $contents is non-nullable
67 $out .= '>' . htmlspecialchars( $contents, ENT_NOQUOTES ) . "</$element>";
68 }
69 return $out;
70 }
71
80 public static function expandAttributes( ?array $attribs ) {
81 if ( $attribs === null ) {
82 return null;
83 }
84 $out = '';
85 foreach ( $attribs as $name => $val ) {
86 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
87 }
88 return $out;
89 }
90
102 public static function elementClean( $element, $attribs = [], $contents = '' ) {
103 if ( $attribs ) {
104 $attribs = array_map( [ Validator::class, 'cleanUp' ], $attribs );
105 }
106 if ( $contents ) {
107 $contents =
108 MediaWikiServices::getInstance()->getContentLanguage()->normalize( $contents );
109 }
110 return self::element( $element, $attribs, $contents );
111 }
112
120 public static function openElement( $element, $attribs = null ) {
121 return '<' . $element . self::expandAttributes( $attribs ) . '>';
122 }
123
129 public static function closeElement( $element ) {
130 return "</$element>";
131 }
132
146 public static function tags( $element, $attribs, $contents ) {
147 return self::openElement( $element, $attribs ) . $contents . "</$element>";
148 }
149
161 public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
162 wfDeprecated( __METHOD__, '1.42' );
163
164 global $wgLang;
165 $options = [];
166
167 $data = new XmlSelect( 'month', $id, $selected ?? '' );
168
169 if ( $allmonths !== null ) {
170 $options[wfMessage( 'monthsall' )->text()] = $allmonths;
171 }
172 for ( $i = 1; $i < 13; $i++ ) {
173 $options[$wgLang->getMonthName( $i )] = $i;
174 }
175 $data->addOptions( $options );
176 $data->setAttribute( 'class', 'mw-month-selector' );
177 return $data->getHTML();
178 }
179
188 public static function dateMenu( $year, $month ) {
189 wfDeprecated( __METHOD__, '1.42' );
190 # Offset overrides year/month selection
191 if ( $month && $month !== -1 ) {
192 $encMonth = intval( $month );
193 } else {
194 $encMonth = '';
195 }
196 if ( $year ) {
197 $encYear = intval( $year );
198 } elseif ( $encMonth ) {
199 $timestamp = MWTimestamp::getInstance();
200 $thisMonth = intval( $timestamp->format( 'n' ) );
201 $thisYear = intval( $timestamp->format( 'Y' ) );
202 if ( $encMonth > $thisMonth ) {
203 $thisYear--;
204 }
205 $encYear = $thisYear;
206 } else {
207 $encYear = '';
208 }
209 $inputAttribs = [ 'id' => 'year', 'maxlength' => 4, 'size' => 7 ];
210 return self::label( wfMessage( 'year' )->text(), 'year' ) . ' ' .
211 Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' ' .
212 self::label( wfMessage( 'month' )->text(), 'month' ) . ' ' .
213 self::monthSelector( $encMonth, '-1' );
214 }
215
228 public static function languageSelector( $selected, $customisedOnly = true,
229 $inLanguage = null, $overrideAttrs = [], ?Message $msg = null
230 ) {
231 wfDeprecated( __METHOD__, '1.42' );
232 $languageCode = MediaWikiServices::getInstance()->getMainConfig()
234
235 $include = $customisedOnly ? LanguageNameUtils::SUPPORTED : LanguageNameUtils::DEFINED;
236 $languages = MediaWikiServices::getInstance()
237 ->getLanguageNameUtils()
238 ->getLanguageNames( $inLanguage, $include );
239
240 // Make sure the site language is in the list;
241 // a custom language code might not have a defined name...
242 if ( !array_key_exists( $languageCode, $languages ) ) {
243 $languages[$languageCode] = $languageCode;
244 // Sort the array again
245 ksort( $languages );
246 }
247
253 $selected = isset( $languages[$selected] ) ? $selected : $languageCode;
254 $options = "\n";
255 foreach ( $languages as $code => $name ) {
256 $options .= self::option( "$code - $name", $code, $code == $selected ) . "\n";
257 }
258
259 $attrs = [ 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ];
260 $attrs = array_merge( $attrs, $overrideAttrs );
261
262 $msg ??= wfMessage( 'yourlanguage' );
263 return [
264 self::label( $msg->text(), $attrs['id'] ),
265 self::tags( 'select', $attrs, $options )
266 ];
267 }
268
278 public static function span( $text, $class, $attribs = [] ) {
279 wfDeprecated( __METHOD__, '1.42' );
280
281 return self::element( 'span', [ 'class' => $class ] + $attribs, $text );
282 }
283
295 public static function wrapClass( $text, $class, $tag = 'span', $attribs = [] ) {
296 wfDeprecated( __METHOD__, '1.42' );
297 return self::tags( $tag, [ 'class' => $class ] + $attribs, $text );
298 }
299
310 public static function input( $name, $size = false, $value = false, $attribs = [] ) {
311 wfDeprecated( __METHOD__, '1.42' );
312
313 $attributes = [ 'name' => $name ];
314
315 if ( $size ) {
316 $attributes['size'] = $size;
317 }
318
319 if ( $value !== false ) { // maybe 0
320 $attributes['value'] = $value;
321 }
322
323 return self::element( 'input', $attributes + $attribs );
324 }
325
336 public static function password( $name, $size = false, $value = false,
337 $attribs = []
338 ) {
339 wfDeprecated( __METHOD__, '1.42' );
340
341 return self::input( $name, $size, $value,
342 array_merge( $attribs, [ 'type' => 'password' ] ) );
343 }
344
355 public static function attrib( $name, $present = true ) {
356 return $present ? [ $name => $name ] : [];
357 }
358
368 public static function check( $name, $checked = false, $attribs = [] ) {
369 wfDeprecated( __METHOD__, '1.42' );
370
371 return self::element( 'input', array_merge(
372 [
373 'name' => $name,
374 'type' => 'checkbox',
375 'value' => 1 ],
376 self::attrib( 'checked', $checked ),
377 $attribs ) );
378 }
379
390 public static function radio( $name, $value, $checked = false, $attribs = [] ) {
391 wfDeprecated( __METHOD__, '1.42' );
392
393 return self::element( 'input', [
394 'name' => $name,
395 'type' => 'radio',
396 'value' => $value ] + self::attrib( 'checked', $checked ) + $attribs );
397 }
398
411 public static function label( $label, $id, $attribs = [] ) {
412 $a = [ 'for' => $id ];
413
414 foreach ( [ 'class', 'title' ] as $attr ) {
415 if ( isset( $attribs[$attr] ) ) {
416 $a[$attr] = $attribs[$attr];
417 }
418 }
419
420 return self::element( 'label', $a, $label );
421 }
422
435 public static function inputLabel( $label, $name, $id, $size = false,
436 $value = false, $attribs = []
437 ) {
438 wfDeprecated( __METHOD__, '1.42' );
439
440 [ $label, $input ] = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
441 return $label . "\u{00A0}" . $input;
442 }
443
457 public static function inputLabelSep( $label, $name, $id, $size = false,
458 $value = false, $attribs = []
459 ) {
460 wfDeprecated( __METHOD__, '1.42' );
461
462 return [
463 self::label( $label, $id, $attribs ),
464 self::input( $name, $size, $value, [ 'id' => $id ] + $attribs )
465 ];
466 }
467
480 public static function checkLabel( $label, $name, $id, $checked = false, $attribs = [] ) {
481 wfDeprecated( __METHOD__, '1.42' );
482
483 return self::check( $name, $checked, [ 'id' => $id ] + $attribs ) .
484 "\u{00A0}" .
485 self::label( $label, $id, $attribs );
486 }
487
501 public static function radioLabel( $label, $name, $value, $id,
502 $checked = false, $attribs = []
503 ) {
504 wfDeprecated( __METHOD__, '1.42' );
505
506 return self::radio( $name, $value, $checked, [ 'id' => $id ] + $attribs ) .
507 "\u{00A0}" .
508 self::label( $label, $id, $attribs );
509 }
510
520 public static function submitButton( $value, $attribs = [] ) {
521 wfDeprecated( __METHOD__, '1.42' );
522
523 $attribs += [
524 'type' => 'submit',
525 'value' => $value,
526 ];
527 return Html::element( 'input', $attribs );
528 }
529
540 public static function option( $text, $value = null, $selected = false,
541 $attribs = [] ) {
542 if ( $value !== null ) {
543 $attribs['value'] = $value;
544 }
545 if ( $selected ) {
546 $attribs['selected'] = 'selected';
547 }
548 return Html::element( 'option', $attribs, $text );
549 }
550
566 public static function listDropdown( $name = '', $list = '', $other = '',
567 $selected = '', $class = '', $tabindex = null
568 ) {
569 $options = self::listDropdownOptions( $list, [ 'other' => $other ] );
570
571 $xmlSelect = new XmlSelect( $name, $name, $selected );
572 $xmlSelect->addOptions( $options );
573
574 if ( $class ) {
575 $xmlSelect->setAttribute( 'class', $class );
576 }
577 if ( $tabindex ) {
578 $xmlSelect->setAttribute( 'tabindex', $tabindex );
579 }
580
581 return $xmlSelect->getHTML();
582 }
583
599 public static function listDropdownOptions( $list, $params = [] ) {
600 $options = [];
601
602 if ( isset( $params['other'] ) ) {
603 $options[ $params['other'] ] = 'other';
604 }
605
606 $optgroup = false;
607 foreach ( explode( "\n", $list ) as $option ) {
608 $value = trim( $option );
609 if ( $value == '' ) {
610 continue;
611 }
612 if ( str_starts_with( $value, '*' ) && substr( $value, 1, 1 ) != '*' ) {
613 # A new group is starting...
614 $value = trim( substr( $value, 1 ) );
615 if ( $value !== '' &&
616 // Do not use the value for 'other' as option group - T251351
617 ( !isset( $params['other'] ) || $value !== $params['other'] )
618 ) {
619 $optgroup = $value;
620 } else {
621 $optgroup = false;
622 }
623 } elseif ( str_starts_with( $value, '**' ) ) {
624 # groupmember
625 $opt = trim( substr( $value, 2 ) );
626 if ( $optgroup === false ) {
627 $options[$opt] = $opt;
628 } else {
629 $options[$optgroup][$opt] = $opt;
630 }
631 } else {
632 # groupless reason list
633 $optgroup = false;
634 $options[$option] = $option;
635 }
636 }
637
638 return $options;
639 }
640
651 public static function listDropdownOptionsOoui( $options ) {
652 $optionsOoui = [];
653
654 foreach ( $options as $text => $value ) {
655 if ( is_array( $value ) ) {
656 $optionsOoui[] = [ 'optgroup' => (string)$text ];
657 foreach ( $value as $text2 => $value2 ) {
658 $optionsOoui[] = [ 'data' => (string)$value2, 'label' => (string)$text2 ];
659 }
660 } else {
661 $optionsOoui[] = [ 'data' => (string)$value, 'label' => (string)$text ];
662 }
663 }
664
665 return $optionsOoui;
666 }
667
680 public static function fieldset( $legend = false, $content = false, $attribs = [] ) {
681 $s = self::openElement( 'fieldset', $attribs ) . "\n";
682
683 if ( $legend ) {
684 $s .= self::element( 'legend', null, $legend ) . "\n";
685 }
686
687 if ( $content !== false ) {
688 $s .= $content . "\n";
689 $s .= self::closeElement( 'fieldset' ) . "\n";
690 }
691
692 return $s;
693 }
694
707 public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = [] ) {
708 wfDeprecated( __METHOD__, '1.42' );
709
710 return self::element( 'textarea',
711 [
712 'name' => $name,
713 'id' => $name,
714 'cols' => $cols,
715 'rows' => $rows
716 ] + $attribs,
717 $content, false );
718 }
719
735 public static function encodeJsVar( $value, $pretty = false ) {
736 wfDeprecated( __METHOD__, '1.41' );
737
738 return Html::encodeJsVar( $value, $pretty );
739 }
740
757 public static function encodeJsCall( $name, $args, $pretty = false ) {
758 wfDeprecated( __METHOD__, '1.41' );
759
760 return Html::encodeJsCall( $name, $args, $pretty );
761 }
762
774 private static function isWellFormed( $text ) {
775 $parser = xml_parser_create( "UTF-8" );
776
777 # case folding violates XML standard, turn it off
778 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
779
780 if ( !xml_parse( $parser, $text, true ) ) {
781 // $err = xml_error_string( xml_get_error_code( $parser ) );
782 // $position = xml_get_current_byte_index( $parser );
783 // $fragment = $this->extractFragment( $html, $position );
784 // $this->mXmlError = "$err at byte $position:\n$fragment";
785 xml_parser_free( $parser );
786 return false;
787 }
788
789 xml_parser_free( $parser );
790
791 return true;
792 }
793
802 public static function isWellFormedXmlFragment( $text ) {
803 $html =
804 Sanitizer::hackDocType() .
805 '<html>' .
806 $text .
807 '</html>';
808
809 return self::isWellFormed( $html );
810 }
811
819 public static function escapeTagsOnly( $in ) {
820 return str_replace(
821 [ '"', '>', '<' ],
822 [ '&quot;', '&gt;', '&lt;' ],
823 $in );
824 }
825
839 public static function buildForm( $fields, $submitLabel = null, $submitAttribs = [] ) {
840 $form = '';
841 $form .= "<table><tbody>";
842
843 foreach ( $fields as $labelmsg => $input ) {
844 $id = "mw-$labelmsg";
845 $form .= self::openElement( 'tr', [ 'id' => $id ] );
846
847 // TODO use a <label> here for accessibility purposes - will need
848 // to either not use a table to build the form, or find the ID of
849 // the input somehow.
850
851 $form .= self::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
852 $form .= self::openElement( 'td', [ 'class' => 'mw-input' ] )
853 . $input . self::closeElement( 'td' );
854 $form .= self::closeElement( 'tr' );
855 }
856
857 if ( $submitLabel ) {
858 $form .= self::openElement( 'tr' );
859 $form .= self::tags( 'td', [], '' );
860 $form .= self::openElement( 'td', [ 'class' => 'mw-submit' ] )
862 'input',
863 $submitAttribs + [
864 'type' => 'submit',
865 'value' => wfMessage( $submitLabel )->text(),
866 ]
867 )
868 . self::closeElement( 'td' );
869 $form .= self::closeElement( 'tr' );
870 }
871
872 $form .= "</tbody></table>";
873
874 return $form;
875 }
876
885 public static function buildTable( $rows, $attribs = [], $headers = null ) {
886 wfDeprecated( __METHOD__, '1.42' );
887
888 $s = self::openElement( 'table', $attribs );
889
890 if ( is_array( $headers ) ) {
891 $s .= self::openElement( 'thead', $attribs );
892
893 foreach ( $headers as $id => $header ) {
894 $attribs = [];
895
896 if ( is_string( $id ) ) {
897 $attribs['id'] = $id;
898 }
899
900 $s .= self::element( 'th', $attribs, $header );
901 }
902 $s .= self::closeElement( 'thead' );
903 }
904
905 foreach ( $rows as $id => $row ) {
906 $attribs = [];
907
908 if ( is_string( $id ) ) {
909 $attribs['id'] = $id;
910 }
911
912 $s .= self::buildTableRow( $attribs, $row );
913 }
914
915 $s .= self::closeElement( 'table' );
916
917 return $s;
918 }
919
928 public static function buildTableRow( $attribs, $cells ) {
929 wfDeprecated( __METHOD__, '1.42' );
930
931 $s = self::openElement( 'tr', $attribs );
932
933 foreach ( $cells as $id => $cell ) {
934 $attribs = [];
935
936 if ( is_string( $id ) ) {
937 $attribs['id'] = $id;
938 }
939
940 $s .= self::element( 'td', $attribs, $cell );
941 }
942
943 $s .= self::closeElement( 'tr' );
944
945 return $s;
946 }
947}
949class_alias( Xml::class, 'Xml' );
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:559
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
A class containing constants representing the names of configuration variables.
const LanguageCode
Name constant for the LanguageCode setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
Library for creating and parsing MW-style timestamps.
Class for generating HTML <select> or <datalist> elements.
Definition XmlSelect.php:30
Module of static functions for generating XML.
Definition Xml.php:37
static escapeTagsOnly( $in)
Replace " > and < with their respective HTML entities ( ", >, <)
Definition Xml.php:819
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition Xml.php:707
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition Xml.php:480
static elementClean( $element, $attribs=[], $contents='')
Format an XML element as with self::element(), but run text through the content language's normalize(...
Definition Xml.php:102
static inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field with a label.
Definition Xml.php:435
static attrib( $name, $present=true)
Internal function for use in checkboxes and radio buttons and such.
Definition Xml.php:355
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition Xml.php:802
static span( $text, $class, $attribs=[])
Shortcut to make a span element.
Definition Xml.php:278
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:54
static radio( $name, $value, $checked=false, $attribs=[])
Convenience function to build an HTML radio button.
Definition Xml.php:390
static monthSelector( $selected='', $allmonths=null, $id='month')
Create a date selector.
Definition Xml.php:161
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition Xml.php:411
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition Xml.php:146
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition Xml.php:368
static expandAttributes(?array $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition Xml.php:80
static buildForm( $fields, $submitLabel=null, $submitAttribs=[])
Generate a form (without the opening form element).
Definition Xml.php:839
static radioLabel( $label, $name, $value, $id, $checked=false, $attribs=[])
Convenience function to build an HTML radio button with a label.
Definition Xml.php:501
static buildTable( $rows, $attribs=[], $headers=null)
Definition Xml.php:885
static inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=[])
Same as Xml::inputLabel() but return input and label in an array.
Definition Xml.php:457
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:757
static openElement( $element, $attribs=null)
This opens an XML element.
Definition Xml.php:120
static option( $text, $value=null, $selected=false, $attribs=[])
Convenience function to build an HTML drop-down list item.
Definition Xml.php:540
static languageSelector( $selected, $customisedOnly=true, $inLanguage=null, $overrideAttrs=[], ?Message $msg=null)
Construct a language selector appropriate for use in a form or preferences.
Definition Xml.php:228
static listDropdown( $name='', $list='', $other='', $selected='', $class='', $tabindex=null)
Build a drop-down box from a textual list.
Definition Xml.php:566
static listDropdownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition Xml.php:651
static dateMenu( $year, $month)
Definition Xml.php:188
static closeElement( $element)
Shortcut to close an XML element.
Definition Xml.php:129
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition Xml.php:310
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition Xml.php:680
static password( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML password input field.
Definition Xml.php:336
static listDropdownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition Xml.php:599
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button.
Definition Xml.php:520
static encodeJsVar( $value, $pretty=false)
Encode a variable of arbitrary type to JavaScript.
Definition Xml.php:735
static buildTableRow( $attribs, $cells)
Build a row for a table.
Definition Xml.php:928
static wrapClass( $text, $class, $tag='span', $attribs=[])
Shortcut to make a specific element with a class attribute.
Definition Xml.php:295
element(SerializerNode $parent, SerializerNode $node, $contents)