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 return self::element( 'span', [ 'class' => $class ] + $attribs, $text );
279 }
280
292 public static function wrapClass( $text, $class, $tag = 'span', $attribs = [] ) {
293 wfDeprecated( __METHOD__, '1.42' );
294 return self::tags( $tag, [ 'class' => $class ] + $attribs, $text );
295 }
296
307 public static function input( $name, $size = false, $value = false, $attribs = [] ) {
308 $attributes = [ 'name' => $name ];
309
310 if ( $size ) {
311 $attributes['size'] = $size;
312 }
313
314 if ( $value !== false ) { // maybe 0
315 $attributes['value'] = $value;
316 }
317
318 return self::element( 'input', $attributes + $attribs );
319 }
320
331 public static function password( $name, $size = false, $value = false,
332 $attribs = []
333 ) {
334 return self::input( $name, $size, $value,
335 array_merge( $attribs, [ 'type' => 'password' ] ) );
336 }
337
348 public static function attrib( $name, $present = true ) {
349 return $present ? [ $name => $name ] : [];
350 }
351
361 public static function check( $name, $checked = false, $attribs = [] ) {
362 return self::element( 'input', array_merge(
363 [
364 'name' => $name,
365 'type' => 'checkbox',
366 'value' => 1 ],
367 self::attrib( 'checked', $checked ),
368 $attribs ) );
369 }
370
381 public static function radio( $name, $value, $checked = false, $attribs = [] ) {
382 return self::element( 'input', [
383 'name' => $name,
384 'type' => 'radio',
385 'value' => $value ] + self::attrib( 'checked', $checked ) + $attribs );
386 }
387
400 public static function label( $label, $id, $attribs = [] ) {
401 $a = [ 'for' => $id ];
402
403 foreach ( [ 'class', 'title' ] as $attr ) {
404 if ( isset( $attribs[$attr] ) ) {
405 $a[$attr] = $attribs[$attr];
406 }
407 }
408
409 return self::element( 'label', $a, $label );
410 }
411
424 public static function inputLabel( $label, $name, $id, $size = false,
425 $value = false, $attribs = []
426 ) {
427 [ $label, $input ] = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
428 return $label . "\u{00A0}" . $input;
429 }
430
444 public static function inputLabelSep( $label, $name, $id, $size = false,
445 $value = false, $attribs = []
446 ) {
447 return [
448 self::label( $label, $id, $attribs ),
449 self::input( $name, $size, $value, [ 'id' => $id ] + $attribs )
450 ];
451 }
452
465 public static function checkLabel( $label, $name, $id, $checked = false, $attribs = [] ) {
466 return self::check( $name, $checked, [ 'id' => $id ] + $attribs ) .
467 "\u{00A0}" .
468 self::label( $label, $id, $attribs );
469 }
470
484 public static function radioLabel( $label, $name, $value, $id,
485 $checked = false, $attribs = []
486 ) {
487 return self::radio( $name, $value, $checked, [ 'id' => $id ] + $attribs ) .
488 "\u{00A0}" .
489 self::label( $label, $id, $attribs );
490 }
491
501 public static function submitButton( $value, $attribs = [] ) {
502 $attribs += [
503 'type' => 'submit',
504 'value' => $value,
505 ];
506 return Html::element( 'input', $attribs );
507 }
508
519 public static function option( $text, $value = null, $selected = false,
520 $attribs = [] ) {
521 if ( $value !== null ) {
522 $attribs['value'] = $value;
523 }
524 if ( $selected ) {
525 $attribs['selected'] = 'selected';
526 }
527 return Html::element( 'option', $attribs, $text );
528 }
529
545 public static function listDropdown( $name = '', $list = '', $other = '',
546 $selected = '', $class = '', $tabindex = null
547 ) {
548 $options = self::listDropdownOptions( $list, [ 'other' => $other ] );
549
550 $xmlSelect = new XmlSelect( $name, $name, $selected );
551 $xmlSelect->addOptions( $options );
552
553 if ( $class ) {
554 $xmlSelect->setAttribute( 'class', $class );
555 }
556 if ( $tabindex ) {
557 $xmlSelect->setAttribute( 'tabindex', $tabindex );
558 }
559
560 return $xmlSelect->getHTML();
561 }
562
578 public static function listDropdownOptions( $list, $params = [] ) {
579 $options = [];
580
581 if ( isset( $params['other'] ) ) {
582 $options[ $params['other'] ] = 'other';
583 }
584
585 $optgroup = false;
586 foreach ( explode( "\n", $list ) as $option ) {
587 $value = trim( $option );
588 if ( $value == '' ) {
589 continue;
590 }
591 if ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
592 # A new group is starting...
593 $value = trim( substr( $value, 1 ) );
594 if ( $value !== '' &&
595 // Do not use the value for 'other' as option group - T251351
596 ( !isset( $params['other'] ) || $value !== $params['other'] )
597 ) {
598 $optgroup = $value;
599 } else {
600 $optgroup = false;
601 }
602 } elseif ( substr( $value, 0, 2 ) == '**' ) {
603 # groupmember
604 $opt = trim( substr( $value, 2 ) );
605 if ( $optgroup === false ) {
606 $options[$opt] = $opt;
607 } else {
608 $options[$optgroup][$opt] = $opt;
609 }
610 } else {
611 # groupless reason list
612 $optgroup = false;
613 $options[$option] = $option;
614 }
615 }
616
617 return $options;
618 }
619
630 public static function listDropdownOptionsOoui( $options ) {
631 $optionsOoui = [];
632
633 foreach ( $options as $text => $value ) {
634 if ( is_array( $value ) ) {
635 $optionsOoui[] = [ 'optgroup' => (string)$text ];
636 foreach ( $value as $text2 => $value2 ) {
637 $optionsOoui[] = [ 'data' => (string)$value2, 'label' => (string)$text2 ];
638 }
639 } else {
640 $optionsOoui[] = [ 'data' => (string)$value, 'label' => (string)$text ];
641 }
642 }
643
644 return $optionsOoui;
645 }
646
659 public static function fieldset( $legend = false, $content = false, $attribs = [] ) {
660 $s = self::openElement( 'fieldset', $attribs ) . "\n";
661
662 if ( $legend ) {
663 $s .= self::element( 'legend', null, $legend ) . "\n";
664 }
665
666 if ( $content !== false ) {
667 $s .= $content . "\n";
668 $s .= self::closeElement( 'fieldset' ) . "\n";
669 }
670
671 return $s;
672 }
673
686 public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = [] ) {
687 return self::element( 'textarea',
688 [
689 'name' => $name,
690 'id' => $name,
691 'cols' => $cols,
692 'rows' => $rows
693 ] + $attribs,
694 $content, false );
695 }
696
712 public static function encodeJsVar( $value, $pretty = false ) {
713 return Html::encodeJsVar( $value, $pretty );
714 }
715
732 public static function encodeJsCall( $name, $args, $pretty = false ) {
733 return Html::encodeJsCall( $name, $args, $pretty );
734 }
735
747 private static function isWellFormed( $text ) {
748 $parser = xml_parser_create( "UTF-8" );
749
750 # case folding violates XML standard, turn it off
751 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
752
753 if ( !xml_parse( $parser, $text, true ) ) {
754 // $err = xml_error_string( xml_get_error_code( $parser ) );
755 // $position = xml_get_current_byte_index( $parser );
756 // $fragment = $this->extractFragment( $html, $position );
757 // $this->mXmlError = "$err at byte $position:\n$fragment";
758 xml_parser_free( $parser );
759 return false;
760 }
761
762 xml_parser_free( $parser );
763
764 return true;
765 }
766
775 public static function isWellFormedXmlFragment( $text ) {
776 $html =
777 Sanitizer::hackDocType() .
778 '<html>' .
779 $text .
780 '</html>';
781
782 return self::isWellFormed( $html );
783 }
784
792 public static function escapeTagsOnly( $in ) {
793 return str_replace(
794 [ '"', '>', '<' ],
795 [ '&quot;', '&gt;', '&lt;' ],
796 $in );
797 }
798
812 public static function buildForm( $fields, $submitLabel = null, $submitAttribs = [] ) {
813 $form = '';
814 $form .= "<table><tbody>";
815
816 foreach ( $fields as $labelmsg => $input ) {
817 $id = "mw-$labelmsg";
818 $form .= self::openElement( 'tr', [ 'id' => $id ] );
819
820 // TODO use a <label> here for accessibility purposes - will need
821 // to either not use a table to build the form, or find the ID of
822 // the input somehow.
823
824 $form .= self::tags( 'td', [ 'class' => 'mw-label' ], wfMessage( $labelmsg )->parse() );
825 $form .= self::openElement( 'td', [ 'class' => 'mw-input' ] )
826 . $input . self::closeElement( 'td' );
827 $form .= self::closeElement( 'tr' );
828 }
829
830 if ( $submitLabel ) {
831 $form .= self::openElement( 'tr' );
832 $form .= self::tags( 'td', [], '' );
833 $form .= self::openElement( 'td', [ 'class' => 'mw-submit' ] )
834 . self::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
835 . self::closeElement( 'td' );
836 $form .= self::closeElement( 'tr' );
837 }
838
839 $form .= "</tbody></table>";
840
841 return $form;
842 }
843
852 public static function buildTable( $rows, $attribs = [], $headers = null ) {
853 $s = self::openElement( 'table', $attribs );
854
855 if ( is_array( $headers ) ) {
856 $s .= self::openElement( 'thead', $attribs );
857
858 foreach ( $headers as $id => $header ) {
859 $attribs = [];
860
861 if ( is_string( $id ) ) {
862 $attribs['id'] = $id;
863 }
864
865 $s .= self::element( 'th', $attribs, $header );
866 }
867 $s .= self::closeElement( 'thead' );
868 }
869
870 foreach ( $rows as $id => $row ) {
871 $attribs = [];
872
873 if ( is_string( $id ) ) {
874 $attribs['id'] = $id;
875 }
876
877 $s .= self::buildTableRow( $attribs, $row );
878 }
879
880 $s .= self::closeElement( 'table' );
881
882 return $s;
883 }
884
893 public static function buildTableRow( $attribs, $cells ) {
894 $s = self::openElement( 'tr', $attribs );
895
896 foreach ( $cells as $id => $cell ) {
897 $attribs = [];
898
899 if ( is_string( $id ) ) {
900 $attribs['id'] = $id;
901 }
902
903 $s .= self::element( 'td', $attribs, $cell );
904 }
905
906 $s .= self::closeElement( 'tr' );
907
908 return $s;
909 }
910}
912class_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:572
array $params
The job parameters.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
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:155
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:792
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition Xml.php:686
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition Xml.php:465
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:424
static attrib( $name, $present=true)
Internal function for use in checkboxes and radio buttons and such.
Definition Xml.php:348
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition Xml.php:775
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:381
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:400
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:361
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:812
static radioLabel( $label, $name, $value, $id, $checked=false, $attribs=[])
Convenience function to build an HTML radio button with a label.
Definition Xml.php:484
static buildTable( $rows, $attribs=[], $headers=null)
Definition Xml.php:852
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:444
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:732
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:519
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:545
static listDropdownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition Xml.php:630
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:307
static fieldset( $legend=false, $content=false, $attribs=[])
Shortcut for creating fieldsets.
Definition Xml.php:659
static password( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML password input field.
Definition Xml.php:331
static listDropdownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition Xml.php:578
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button.
Definition Xml.php:501
static encodeJsVar( $value, $pretty=false)
Encode a variable of arbitrary type to JavaScript.
Definition Xml.php:712
static buildTableRow( $attribs, $cells)
Build a row for a table.
Definition Xml.php:893
static wrapClass( $text, $class, $tag='span', $attribs=[])
Shortcut to make a specific element with a class attribute.
Definition Xml.php:292
element(SerializerNode $parent, SerializerNode $node, $contents)
$header