MediaWiki master
Xml.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Xml;
24
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 $out .= '>' . htmlspecialchars( $contents, ENT_NOQUOTES ) . "</$element>";
67 }
68 return $out;
69 }
70
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
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
119 public static function openElement( $element, $attribs = null ) {
120 return '<' . $element . self::expandAttributes( $attribs ) . '>';
121 }
122
128 public static function closeElement( $element ) {
129 return "</$element>";
130 }
131
145 public static function tags( $element, $attribs, $contents ) {
146 return self::openElement( $element, $attribs ) . $contents . "</$element>";
147 }
148
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
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
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()
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
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
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
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
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
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
352 public static function attrib( $name, $present = true ) {
353 return $present ? [ $name => $name ] : [];
354 }
355
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
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
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
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
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
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
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
505 public static function submitButton( $value, $attribs = [] ) {
506 $attribs += [
507 'type' => 'submit',
508 'value' => $value,
509 ];
510 return Html::element( 'input', $attribs );
511 }
512
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
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
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
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
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
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
716 public static function encodeJsVar( $value, $pretty = false ) {
717 wfDeprecated( __METHOD__, '1.41' );
718
719 return Html::encodeJsVar( $value, $pretty );
720 }
721
738 public static function encodeJsCall( $name, $args, $pretty = false ) {
739 wfDeprecated( __METHOD__, '1.41' );
740
741 return Html::encodeJsCall( $name, $args, $pretty );
742 }
743
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
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
800 public static function escapeTagsOnly( $in ) {
801 return str_replace(
802 [ '"', '>', '<' ],
803 [ '&quot;', '&gt;', '&lt;' ],
804 $in );
805 }
806
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
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
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}
924class_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:562
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
A service that provides utilities to do with language names and codes.
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:800
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition Xml.php:690
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition Xml.php:469
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:101
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:428
static attrib( $name, $present=true)
Internal function for use in checkboxes and radio buttons and such.
Definition Xml.php:352
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition Xml.php:783
static span( $text, $class, $attribs=[])
Shortcut to make a span element.
Definition Xml.php:277
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:385
static monthSelector( $selected='', $allmonths=null, $id='month')
Create a date selector.
Definition Xml.php:160
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition Xml.php:404
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition Xml.php:145
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition Xml.php:365
static expandAttributes(?array $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition Xml.php:79
static buildForm( $fields, $submitLabel=null, $submitAttribs=[])
Generate a form (without the opening form element).
Definition Xml.php:820
static radioLabel( $label, $name, $value, $id, $checked=false, $attribs=[])
Convenience function to build an HTML radio button with a label.
Definition Xml.php:488
static buildTable( $rows, $attribs=[], $headers=null)
Definition Xml.php:860
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:448
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:738
static openElement( $element, $attribs=null)
This opens an XML element.
Definition Xml.php:119
static option( $text, $value=null, $selected=false, $attribs=[])
Convenience function to build an HTML drop-down list item.
Definition Xml.php:523
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:227
static listDropdown( $name='', $list='', $other='', $selected='', $class='', $tabindex=null)
Build a drop-down box from a textual list.
Definition Xml.php:549
static listDropdownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition Xml.php:634
static dateMenu( $year, $month)
Definition Xml.php:187
static closeElement( $element)
Shortcut to close an XML element.
Definition Xml.php:128
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition Xml.php:309
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition Xml.php:663
static password( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML password input field.
Definition Xml.php:333
static listDropdownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition Xml.php:582
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button.
Definition Xml.php:505
static encodeJsVar( $value, $pretty=false)
Encode a variable of arbitrary type to JavaScript.
Definition Xml.php:716
static buildTableRow( $attribs, $cells)
Build a row for a table.
Definition Xml.php:903
static wrapClass( $text, $class, $tag='span', $attribs=[])
Shortcut to make a specific element with a class attribute.
Definition Xml.php:294
element(SerializerNode $parent, SerializerNode $node, $contents)
$header