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 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
279 public static function span( $text, $class, $attribs = [] ) {
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 return self::input( $name, $size, $value,
337 array_merge( $attribs, [ 'type' => 'password' ] ) );
338 }
339
350 public static function attrib( $name, $present = true ) {
351 return $present ? [ $name => $name ] : [];
352 }
353
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
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
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
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
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
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
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
503 public static function submitButton( $value, $attribs = [] ) {
504 $attribs += [
505 'type' => 'submit',
506 'value' => $value,
507 ];
508 return Html::element( 'input', $attribs );
509 }
510
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
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
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
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
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
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
714 public static function encodeJsVar( $value, $pretty = false ) {
715 return Html::encodeJsVar( $value, $pretty );
716 }
717
734 public static function encodeJsCall( $name, $args, $pretty = false ) {
735 return Html::encodeJsCall( $name, $args, $pretty );
736 }
737
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
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
794 public static function escapeTagsOnly( $in ) {
795 return str_replace(
796 [ '"', '>', '<' ],
797 [ '&quot;', '&gt;', '&lt;' ],
798 $in );
799 }
800
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
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
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}
914class_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:538
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:158
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:794
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition Xml.php:688
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition Xml.php:467
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:426
static attrib( $name, $present=true)
Internal function for use in checkboxes and radio buttons and such.
Definition Xml.php:350
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition Xml.php:777
static span( $text, $class, $attribs=[])
Shortcut to make a span element.
Definition Xml.php:279
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:383
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:402
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:363
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:814
static radioLabel( $label, $name, $value, $id, $checked=false, $attribs=[])
Convenience function to build an HTML radio button with a label.
Definition Xml.php:486
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 buildTable( $rows, $attribs=[], $headers=null)
Definition Xml.php:854
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:446
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:734
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:521
static listDropdown( $name='', $list='', $other='', $selected='', $class='', $tabindex=null)
Build a drop-down box from a textual list.
Definition Xml.php:547
static listDropdownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition Xml.php:632
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:661
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:580
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button.
Definition Xml.php:503
static encodeJsVar( $value, $pretty=false)
Encode a variable of arbitrary type to JavaScript.
Definition Xml.php:714
static buildTableRow( $attribs, $cells)
Build a row for a table.
Definition Xml.php:895
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