MediaWiki  REL1_22
Xml.php
Go to the documentation of this file.
00001 <?php
00026 class Xml {
00039     public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
00040         $out = '<' . $element;
00041         if ( !is_null( $attribs ) ) {
00042             $out .= self::expandAttributes( $attribs );
00043         }
00044         if ( is_null( $contents ) ) {
00045             $out .= '>';
00046         } else {
00047             if ( $allowShortTag && $contents === '' ) {
00048                 $out .= ' />';
00049             } else {
00050                 $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
00051             }
00052         }
00053         return $out;
00054     }
00055 
00065     public static function expandAttributes( $attribs ) {
00066         $out = '';
00067         if ( is_null( $attribs ) ) {
00068             return null;
00069         } elseif ( is_array( $attribs ) ) {
00070             foreach ( $attribs as $name => $val ) {
00071                 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
00072             }
00073             return $out;
00074         } else {
00075             throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
00076         }
00077     }
00078 
00089     public static function elementClean( $element, $attribs = array(), $contents = '' ) {
00090         global $wgContLang;
00091         if ( $attribs ) {
00092             $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00093         }
00094         if ( $contents ) {
00095             wfProfileIn( __METHOD__ . '-norm' );
00096             $contents = $wgContLang->normalize( $contents );
00097             wfProfileOut( __METHOD__ . '-norm' );
00098         }
00099         return self::element( $element, $attribs, $contents );
00100     }
00101 
00109     public static function openElement( $element, $attribs = null ) {
00110         return '<' . $element . self::expandAttributes( $attribs ) . '>';
00111     }
00112 
00118     public static function closeElement( $element ) {
00119         return "</$element>";
00120     }
00121 
00131     public static function tags( $element, $attribs = null, $contents ) {
00132         return self::openElement( $element, $attribs ) . $contents . "</$element>";
00133     }
00134 
00145     public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00146         wfDeprecated( __METHOD__, '1.19' );
00147         return Html::namespaceSelector( array(
00148             'selected' => $selected,
00149             'all' => $all,
00150             'label' => $label,
00151         ), array(
00152             'name' => $element_name,
00153             'id' => 'namespace',
00154             'class' => 'namespaceselector',
00155         ) );
00156     }
00157 
00166     public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00167         global $wgLang;
00168         $options = array();
00169         if ( is_null( $selected ) ) {
00170             $selected = '';
00171         }
00172         if ( !is_null( $allmonths ) ) {
00173             $options[] = self::option( wfMessage( 'monthsall' )->text(), $allmonths, $selected === $allmonths );
00174         }
00175         for ( $i = 1; $i < 13; $i++ ) {
00176             $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00177         }
00178         return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
00179             . implode( "\n", $options )
00180             . self::closeElement( 'select' );
00181     }
00182 
00188     public static function dateMenu( $year, $month ) {
00189         # Offset overrides year/month selection
00190         if ( $month && $month !== -1 ) {
00191             $encMonth = intval( $month );
00192         } else {
00193             $encMonth = '';
00194         }
00195         if ( $year ) {
00196             $encYear = intval( $year );
00197         } elseif ( $encMonth ) {
00198             $timestamp = MWTimestamp::getInstance();
00199             $thisMonth = intval( $timestamp->format( 'n' ) );
00200             $thisYear = intval( $timestamp->format( 'Y' ) );
00201             if ( intval( $encMonth ) > $thisMonth ) {
00202                 $thisYear--;
00203             }
00204             $encYear = $thisYear;
00205         } else {
00206             $encYear = '';
00207         }
00208         $inputAttribs = array( 'id' => 'year', 'maxlength' => 4, 'size' => 7 );
00209         return self::label( wfMessage( 'year' )->text(), 'year' ) . ' ' .
00210             Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' ' .
00211             self::label( wfMessage( 'month' )->text(), 'month' ) . ' ' .
00212             self::monthSelector( $encMonth, -1 );
00213     }
00214 
00225     public static function languageSelector( $selected, $customisedOnly = true, $inLanguage = null, $overrideAttrs = array(), Message $msg = null ) {
00226         global $wgLanguageCode;
00227 
00228         $include = $customisedOnly ? 'mwfile' : 'mw';
00229         $languages = Language::fetchLanguageNames( $inLanguage, $include );
00230 
00231         // Make sure the site language is in the list;
00232         // a custom language code might not have a defined name...
00233         if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
00234             $languages[$wgLanguageCode] = $wgLanguageCode;
00235         }
00236 
00237         ksort( $languages );
00238 
00244         $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
00245         $options = "\n";
00246         foreach ( $languages as $code => $name ) {
00247             $options .= Xml::option( "$code - $name", $code, $code == $selected ) . "\n";
00248         }
00249 
00250         $attrs = array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' );
00251         $attrs = array_merge( $attrs, $overrideAttrs );
00252 
00253         if ( $msg === null ) {
00254             $msg = wfMessage( 'yourlanguage' );
00255         }
00256         return array(
00257             Xml::label( $msg->text(), $attrs['id'] ),
00258             Xml::tags( 'select', $attrs, $options )
00259         );
00260 
00261     }
00262 
00270     public static function span( $text, $class, $attribs = array() ) {
00271         return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00272     }
00273 
00282     public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
00283         return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00284     }
00285 
00294     public static function input( $name, $size = false, $value = false, $attribs = array() ) {
00295         $attributes = array( 'name' => $name );
00296 
00297         if ( $size ) {
00298             $attributes['size'] = $size;
00299         }
00300 
00301         if ( $value !== false ) { // maybe 0
00302             $attributes['value'] = $value;
00303         }
00304 
00305         return self::element( 'input', $attributes + $attribs );
00306     }
00307 
00316     public static function password( $name, $size = false, $value = false, $attribs = array() ) {
00317         return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
00318     }
00319 
00328     public static function attrib( $name, $present = true ) {
00329         return $present ? array( $name => $name ) : array();
00330     }
00331 
00339     public static function check( $name, $checked = false, $attribs = array() ) {
00340         return self::element( 'input', array_merge(
00341             array(
00342                 'name' => $name,
00343                 'type' => 'checkbox',
00344                 'value' => 1 ),
00345             self::attrib( 'checked', $checked ),
00346             $attribs ) );
00347     }
00348 
00357     public static function radio( $name, $value, $checked = false, $attribs = array() ) {
00358         return self::element( 'input', array(
00359             'name' => $name,
00360             'type' => 'radio',
00361             'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00362     }
00363 
00374     public static function label( $label, $id, $attribs = array() ) {
00375         $a = array( 'for' => $id );
00376 
00377         # FIXME avoid copy pasting below:
00378         if ( isset( $attribs['class'] ) ) {
00379                 $a['class'] = $attribs['class'];
00380         }
00381         if ( isset( $attribs['title'] ) ) {
00382                 $a['title'] = $attribs['title'];
00383         }
00384 
00385         return self::element( 'label', $a, $label );
00386     }
00387 
00398     public static function inputLabel( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
00399         list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00400         return $label . '&#160;' . $input;
00401     }
00402 
00415     public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
00416         return array(
00417             Xml::label( $label, $id, $attribs ),
00418             self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00419         );
00420     }
00421 
00433     public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
00434         return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00435             '&#160;' .
00436             self::label( $label, $id, $attribs );
00437     }
00438 
00451     public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
00452         return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00453             '&#160;' .
00454             self::label( $label, $id, $attribs );
00455     }
00456 
00463     public static function submitButton( $value, $attribs = array() ) {
00464         return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00465     }
00466 
00475     public static function option( $text, $value = null, $selected = false,
00476             $attribs = array() ) {
00477         if ( !is_null( $value ) ) {
00478             $attribs['value'] = $value;
00479         }
00480         if ( $selected ) {
00481             $attribs['selected'] = 'selected';
00482         }
00483         return Html::element( 'option', $attribs, $text );
00484     }
00485 
00497     public static function listDropDown( $name = '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
00498         $optgroup = false;
00499 
00500         $options = self::option( $other, 'other', $selected === 'other' );
00501 
00502         foreach ( explode( "\n", $list ) as $option ) {
00503                 $value = trim( $option );
00504                 if ( $value == '' ) {
00505                     continue;
00506                 } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
00507                     // A new group is starting ...
00508                     $value = trim( substr( $value, 1 ) );
00509                     if ( $optgroup ) {
00510                         $options .= self::closeElement( 'optgroup' );
00511                     }
00512                     $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00513                     $optgroup = true;
00514                 } elseif ( substr( $value, 0, 2 ) == '**' ) {
00515                     // groupmember
00516                     $value = trim( substr( $value, 2 ) );
00517                     $options .= self::option( $value, $value, $selected === $value );
00518                 } else {
00519                     // groupless reason list
00520                     if ( $optgroup ) {
00521                         $options .= self::closeElement( 'optgroup' );
00522                     }
00523                     $options .= self::option( $value, $value, $selected === $value );
00524                     $optgroup = false;
00525                 }
00526             }
00527 
00528             if ( $optgroup ) {
00529                 $options .= self::closeElement( 'optgroup' );
00530             }
00531 
00532         $attribs = array();
00533 
00534         if ( $name ) {
00535             $attribs['id'] = $name;
00536             $attribs['name'] = $name;
00537         }
00538 
00539         if ( $class ) {
00540             $attribs['class'] = $class;
00541         }
00542 
00543         if ( $tabindex ) {
00544             $attribs['tabindex'] = $tabindex;
00545         }
00546 
00547         return Xml::openElement( 'select', $attribs )
00548             . "\n"
00549             . $options
00550             . "\n"
00551             . Xml::closeElement( 'select' );
00552     }
00553 
00563     public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00564         $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00565 
00566         if ( $legend ) {
00567             $s .= Xml::element( 'legend', null, $legend ) . "\n";
00568         }
00569 
00570         if ( $content !== false ) {
00571             $s .= $content . "\n";
00572             $s .= Xml::closeElement( 'fieldset' ) . "\n";
00573         }
00574 
00575         return $s;
00576     }
00577 
00589     public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00590         return self::element( 'textarea',
00591                     array(
00592                         'name' => $name,
00593                         'id' => $name,
00594                         'cols' => $cols,
00595                         'rows' => $rows
00596                     ) + $attribs,
00597                     $content, false );
00598     }
00599 
00609     public static function escapeJsString( $string ) {
00610         // See ECMA 262 section 7.8.4 for string literal format
00611         $pairs = array(
00612             "\\" => "\\\\",
00613             "\"" => "\\\"",
00614             '\'' => '\\\'',
00615             "\n" => "\\n",
00616             "\r" => "\\r",
00617 
00618             # To avoid closing the element or CDATA section
00619             "<" => "\\x3c",
00620             ">" => "\\x3e",
00621 
00622             # To avoid any complaints about bad entity refs
00623             "&" => "\\x26",
00624 
00625             # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
00626             # Encode certain Unicode formatting chars so affected
00627             # versions of Gecko don't misinterpret our strings;
00628             # this is a common problem with Farsi text.
00629             "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00630             "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00631         );
00632 
00633         return strtr( $string, $pairs );
00634     }
00635 
00647     public static function encodeJsVar( $value, $pretty = false ) {
00648         if ( $value instanceof XmlJsCode ) {
00649             return $value->value;
00650         }
00651         return FormatJson::encode( $value, $pretty, FormatJson::UTF8_OK );
00652     }
00653 
00665     public static function encodeJsCall( $name, $args, $pretty = false ) {
00666         foreach ( $args as &$arg ) {
00667             $arg = Xml::encodeJsVar( $arg, $pretty );
00668             if ( $arg === false ) {
00669                 return false;
00670             }
00671         }
00672 
00673         return "$name(" . ( $pretty
00674             ? ( ' ' . implode( ', ', $args ) . ' ' )
00675             : implode( ',', $args )
00676         ) . ");";
00677     }
00678 
00688     public static function isWellFormed( $text ) {
00689         $parser = xml_parser_create( "UTF-8" );
00690 
00691         # case folding violates XML standard, turn it off
00692         xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00693 
00694         if ( !xml_parse( $parser, $text, true ) ) {
00695             //$err = xml_error_string( xml_get_error_code( $parser ) );
00696             //$position = xml_get_current_byte_index( $parser );
00697             //$fragment = $this->extractFragment( $html, $position );
00698             //$this->mXmlError = "$err at byte $position:\n$fragment";
00699             xml_parser_free( $parser );
00700             return false;
00701         }
00702 
00703         xml_parser_free( $parser );
00704 
00705         return true;
00706     }
00707 
00716     public static function isWellFormedXmlFragment( $text ) {
00717         $html =
00718             Sanitizer::hackDocType() .
00719             '<html>' .
00720             $text .
00721             '</html>';
00722 
00723         return Xml::isWellFormed( $html );
00724     }
00725 
00733     public static function escapeTagsOnly( $in ) {
00734         return str_replace(
00735             array( '"', '>', '<' ),
00736             array( '&quot;', '&gt;', '&lt;' ),
00737             $in );
00738     }
00739 
00748     public static function buildForm( $fields, $submitLabel = null, $submitAttribs = array() ) {
00749         $form = '';
00750         $form .= "<table><tbody>";
00751 
00752         foreach ( $fields as $labelmsg => $input ) {
00753             $id = "mw-$labelmsg";
00754             $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00755 
00756             // TODO use a <label> here for accessibility purposes - will need
00757             // to either not use a table to build the form, or find the ID of
00758             // the input somehow.
00759 
00760             $form .= Xml::tags( 'td', array( 'class' => 'mw-label' ), wfMessage( $labelmsg )->parse() );
00761             $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00762             $form .= Xml::closeElement( 'tr' );
00763         }
00764 
00765         if ( $submitLabel ) {
00766             $form .= Xml::openElement( 'tr' );
00767             $form .= Xml::tags( 'td', array(), '' );
00768             $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs ) . Xml::closeElement( 'td' );
00769             $form .= Xml::closeElement( 'tr' );
00770         }
00771 
00772         $form .= "</tbody></table>";
00773 
00774         return $form;
00775     }
00776 
00784     public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00785         $s = Xml::openElement( 'table', $attribs );
00786 
00787         if ( is_array( $headers ) ) {
00788             $s .= Xml::openElement( 'thead', $attribs );
00789 
00790             foreach ( $headers as $id => $header ) {
00791                 $attribs = array();
00792 
00793                 if ( is_string( $id ) ) {
00794                     $attribs['id'] = $id;
00795                 }
00796 
00797                 $s .= Xml::element( 'th', $attribs, $header );
00798             }
00799             $s .= Xml::closeElement( 'thead' );
00800         }
00801 
00802         foreach ( $rows as $id => $row ) {
00803             $attribs = array();
00804 
00805             if ( is_string( $id ) ) {
00806                 $attribs['id'] = $id;
00807             }
00808 
00809             $s .= Xml::buildTableRow( $attribs, $row );
00810         }
00811 
00812         $s .= Xml::closeElement( 'table' );
00813 
00814         return $s;
00815     }
00816 
00823     public static function buildTableRow( $attribs, $cells ) {
00824         $s = Xml::openElement( 'tr', $attribs );
00825 
00826         foreach ( $cells as $id => $cell ) {
00827             $attribs = array();
00828 
00829             if ( is_string( $id ) ) {
00830                 $attribs['id'] = $id;
00831             }
00832 
00833             $s .= Xml::element( 'td', $attribs, $cell );
00834         }
00835 
00836         $s .= Xml::closeElement( 'tr' );
00837 
00838         return $s;
00839     }
00840 }
00841 
00842 class XmlSelect {
00843     protected $options = array();
00844     protected $default = false;
00845     protected $attributes = array();
00846 
00847     public function __construct( $name = false, $id = false, $default = false ) {
00848         if ( $name ) {
00849             $this->setAttribute( 'name', $name );
00850         }
00851 
00852         if ( $id ) {
00853             $this->setAttribute( 'id', $id );
00854         }
00855 
00856         if ( $default !== false ) {
00857             $this->default = $default;
00858         }
00859     }
00860 
00864     public function setDefault( $default ) {
00865         $this->default = $default;
00866     }
00867 
00872     public function setAttribute( $name, $value ) {
00873         $this->attributes[$name] = $value;
00874     }
00875 
00880     public function getAttribute( $name ) {
00881         if ( isset( $this->attributes[$name] ) ) {
00882             return $this->attributes[$name];
00883         } else {
00884             return null;
00885         }
00886     }
00887 
00892     public function addOption( $name, $value = false ) {
00893         // Stab stab stab
00894         $value = $value !== false ? $value : $name;
00895 
00896         $this->options[] = array( $name => $value );
00897     }
00898 
00906     public function addOptions( $options ) {
00907         $this->options[] = $options;
00908     }
00909 
00919     static function formatOptions( $options, $default = false ) {
00920         $data = '';
00921 
00922         foreach ( $options as $label => $value ) {
00923             if ( is_array( $value ) ) {
00924                 $contents = self::formatOptions( $value, $default );
00925                 $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00926             } else {
00927                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00928             }
00929         }
00930 
00931         return $data;
00932     }
00933 
00937     public function getHTML() {
00938         $contents = '';
00939 
00940         foreach ( $this->options as $options ) {
00941             $contents .= self::formatOptions( $options, $this->default );
00942         }
00943 
00944         return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
00945     }
00946 }
00947 
00965 class XmlJsCode {
00966     public $value;
00967 
00968     function __construct( $value ) {
00969         $this->value = $value;
00970     }
00971 }