00001 <?php
00002
00007 class Xml {
00020 public static function element( $element, $attribs = null, $contents = '', $allowShortTag = true ) {
00021 $out = '<' . $element;
00022 if( !is_null( $attribs ) ) {
00023 $out .= self::expandAttributes( $attribs );
00024 }
00025 if( is_null( $contents ) ) {
00026 $out .= '>';
00027 } else {
00028 if( $allowShortTag && $contents === '' ) {
00029 $out .= ' />';
00030 } else {
00031 $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
00032 }
00033 }
00034 return $out;
00035 }
00036
00044 public static function expandAttributes( $attribs ) {
00045 $out = '';
00046 if( is_null( $attribs ) ) {
00047 return null;
00048 } elseif( is_array( $attribs ) ) {
00049 foreach( $attribs as $name => $val )
00050 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
00051 return $out;
00052 } else {
00053 throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
00054 }
00055 }
00056
00067 public static function elementClean( $element, $attribs = array(), $contents = '') {
00068 if( $attribs ) {
00069 $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00070 }
00071 if( $contents ) {
00072 wfProfileIn( __METHOD__ . '-norm' );
00073 $contents = UtfNormal::cleanUp( $contents );
00074 wfProfileOut( __METHOD__ . '-norm' );
00075 }
00076 return self::element( $element, $attribs, $contents );
00077 }
00078
00086 public static function openElement( $element, $attribs = null ) {
00087 return '<' . $element . self::expandAttributes( $attribs ) . '>';
00088 }
00089
00095 public static function closeElement( $element ) { return "</$element>"; }
00096
00106 public static function tags( $element, $attribs = null, $contents ) {
00107 return self::openElement( $element, $attribs ) . $contents . "</$element>";
00108 }
00109
00119 public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00120 global $wgContLang;
00121 $namespaces = $wgContLang->getFormattedNamespaces();
00122 $options = array();
00123
00124
00125
00126
00127
00128 if( preg_match( '/^\d+$/', $selected ) ) {
00129 $selected = intval( $selected );
00130 }
00131
00132 if( !is_null( $all ) )
00133 $namespaces = array( $all => wfMsg( 'namespacesall' ) ) + $namespaces;
00134 foreach( $namespaces as $index => $name ) {
00135 if( $index < NS_MAIN )
00136 continue;
00137 if( $index === 0 )
00138 $name = wfMsg( 'blanknamespace' );
00139 $options[] = self::option( $name, $index, $index === $selected );
00140 }
00141
00142 $ret = Xml::openElement( 'select', array( 'id' => 'namespace', 'name' => $element_name,
00143 'class' => 'namespaceselector' ) )
00144 . "\n"
00145 . implode( "\n", $options )
00146 . "\n"
00147 . Xml::closeElement( 'select' );
00148 if ( !is_null( $label ) ) {
00149 $ret = Xml::label( $label, $element_name ) . ' ' . $ret;
00150 }
00151 return $ret;
00152 }
00153
00162 public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00163 global $wgLang;
00164 $options = array();
00165 if( is_null( $selected ) )
00166 $selected = '';
00167 if( !is_null( $allmonths ) )
00168 $options[] = self::option( wfMsg( 'monthsall' ), $allmonths, $selected === $allmonths );
00169 for( $i = 1; $i < 13; $i++ )
00170 $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00171 return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
00172 . implode( "\n", $options )
00173 . self::closeElement( 'select' );
00174 }
00175
00181 public static function dateMenu( $year, $month ) {
00182 # Offset overrides year/month selection
00183 if( $month && $month !== -1 ) {
00184 $encMonth = intval( $month );
00185 } else {
00186 $encMonth = '';
00187 }
00188 if( $year ) {
00189 $encYear = intval( $year );
00190 } else if( $encMonth ) {
00191 $thisMonth = intval( gmdate( 'n' ) );
00192 $thisYear = intval( gmdate( 'Y' ) );
00193 if( intval($encMonth) > $thisMonth ) {
00194 $thisYear--;
00195 }
00196 $encYear = $thisYear;
00197 } else {
00198 $encYear = '';
00199 }
00200 return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
00201 Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
00202 Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
00203 Xml::monthSelector( $encMonth, -1 );
00204 }
00205
00212 public static function languageSelector( $selected, $customisedOnly = true ) {
00213 global $wgContLanguageCode;
00218 $languages = Language::getLanguageNames( $customisedOnly );
00219 if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
00220 $languages[$wgContLanguageCode] = $wgContLanguageCode;
00221 }
00222 ksort( $languages );
00223
00229 $selected = isset( $languages[$selected] ) ? $selected : $wgContLanguageCode;
00230 $options = "\n";
00231 foreach( $languages as $code => $name ) {
00232 $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
00233 }
00234
00235 return array(
00236 Xml::label( wfMsg('yourlanguage'), 'wpUserLanguage' ),
00237 Xml::tags( 'select',
00238 array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ),
00239 $options
00240 )
00241 );
00242
00243 }
00244
00252 public static function span( $text, $class, $attribs=array() ) {
00253 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00254 }
00255
00264 public static function wrapClass( $text, $class, $tag='span', $attribs=array() ) {
00265 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00266 }
00267
00276 public static function input( $name, $size=false, $value=false, $attribs=array() ) {
00277 return self::element( 'input', array(
00278 'name' => $name,
00279 'size' => $size,
00280 'value' => $value ) + $attribs );
00281 }
00282
00291 public static function password( $name, $size=false, $value=false, $attribs=array() ) {
00292 return self::input( $name, $size, $value, array_merge($attribs, array('type' => 'password')));
00293 }
00294
00299 public static function attrib( $name, $present = true ) {
00300 return $present ? array( $name => $name ) : array();
00301 }
00302
00310 public static function check( $name, $checked=false, $attribs=array() ) {
00311 return self::element( 'input', array_merge(
00312 array(
00313 'name' => $name,
00314 'type' => 'checkbox',
00315 'value' => 1 ),
00316 self::attrib( 'checked', $checked ),
00317 $attribs ) );
00318 }
00319
00328 public static function radio( $name, $value, $checked=false, $attribs=array() ) {
00329 return self::element( 'input', array(
00330 'name' => $name,
00331 'type' => 'radio',
00332 'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00333 }
00334
00341 public static function label( $label, $id ) {
00342 return self::element( 'label', array( 'for' => $id ), $label );
00343 }
00344
00355 public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
00356 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00357 return $label . ' ' . $input;
00358 }
00359
00363 public static function inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
00364 return array(
00365 Xml::label( $label, $id ),
00366 self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00367 );
00368 }
00369
00374 public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
00375 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00376 ' ' .
00377 self::label( $label, $id );
00378 }
00379
00384 public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
00385 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00386 ' ' .
00387 self::label( $label, $id );
00388 }
00389
00396 public static function submitButton( $value, $attribs=array() ) {
00397 return self::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00398 }
00399
00407 public static function hidden( $name, $value, $attribs=array() ) {
00408 return self::element( 'input', array(
00409 'name' => $name,
00410 'type' => 'hidden',
00411 'value' => $value ) + $attribs );
00412 }
00413
00422 public static function option( $text, $value=null, $selected=false,
00423 $attribs=array() ) {
00424 if( !is_null( $value ) ) {
00425 $attribs['value'] = $value;
00426 }
00427 if( $selected ) {
00428 $attribs['selected'] = 'selected';
00429 }
00430 return self::element( 'option', $attribs, $text );
00431 }
00432
00444 public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = Null ) {
00445 $options = '';
00446 $optgroup = false;
00447
00448 $options = self::option( $other, 'other', $selected === 'other' );
00449
00450 foreach ( explode( "\n", $list ) as $option) {
00451 $value = trim( $option );
00452 if ( $value == '' ) {
00453 continue;
00454 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
00455
00456 $value = trim( substr( $value, 1 ) );
00457 if( $optgroup ) $options .= self::closeElement('optgroup');
00458 $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00459 $optgroup = true;
00460 } elseif ( substr( $value, 0, 2) == '**' ) {
00461
00462 $value = trim( substr( $value, 2 ) );
00463 $options .= self::option( $value, $value, $selected === $value );
00464 } else {
00465
00466 if( $optgroup ) $options .= self::closeElement('optgroup');
00467 $options .= self::option( $value, $value, $selected === $value );
00468 $optgroup = false;
00469 }
00470 }
00471 if( $optgroup ) $options .= self::closeElement('optgroup');
00472
00473 $attribs = array();
00474 if( $name ) {
00475 $attribs['id'] = $name;
00476 $attribs['name'] = $name;
00477 }
00478 if( $class ) {
00479 $attribs['class'] = $class;
00480 }
00481 if( $tabindex ) {
00482 $attribs['tabindex'] = $tabindex;
00483 }
00484 return Xml::openElement( 'select', $attribs )
00485 . "\n"
00486 . $options
00487 . "\n"
00488 . Xml::closeElement( 'select' );
00489 }
00490
00498 public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00499 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00500 if ( $legend ) {
00501 $s .= Xml::element( 'legend', null, $legend ) . "\n";
00502 }
00503 if ( $content !== false ) {
00504 $s .= $content . "\n";
00505 $s .= Xml::closeElement( 'fieldset' ) . "\n";
00506 }
00507
00508 return $s;
00509 }
00510
00520 public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00521 return self::element( 'textarea',
00522 array( 'name' => $name,
00523 'id' => $name,
00524 'cols' => $cols,
00525 'rows' => $rows
00526 ) + $attribs,
00527 $content, false );
00528 }
00529
00538 public static function escapeJsString( $string ) {
00539
00540 $pairs = array(
00541 "\\" => "\\\\",
00542 "\"" => "\\\"",
00543 '\'' => '\\\'',
00544 "\n" => "\\n",
00545 "\r" => "\\r",
00546
00547 # To avoid closing the element or CDATA section
00548 "<" => "\\x3c",
00549 ">" => "\\x3e",
00550
00551 # To avoid any complaints about bad entity refs
00552 "&" => "\\x26",
00553
00554 # Work around https:
00555 # Encode certain Unicode formatting chars so affected
00556 # versions of Gecko don't misinterpret our strings;
00557 # this is a common problem with Farsi text.
00558 "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00559 "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00560 );
00561 return strtr( $string, $pairs );
00562 }
00563
00570 public static function encodeJsVar( $value ) {
00571 if ( is_bool( $value ) ) {
00572 $s = $value ? 'true' : 'false';
00573 } elseif ( is_null( $value ) ) {
00574 $s = 'null';
00575 } elseif ( is_int( $value ) ) {
00576 $s = $value;
00577 } elseif ( is_array( $value ) ) {
00578 $s = '[';
00579 foreach ( $value as $elt ) {
00580 if ( $s != '[' ) {
00581 $s .= ', ';
00582 }
00583 $s .= self::encodeJsVar( $elt );
00584 }
00585 $s .= ']';
00586 } elseif ( is_object( $value ) ) {
00587 $s = '{';
00588 foreach ( (array)$value as $name => $elt ) {
00589 if ( $s != '{' ) {
00590 $s .= ', ';
00591 }
00592 $s .= '"' . self::escapeJsString( $name ) . '": ' .
00593 self::encodeJsVar( $elt );
00594 }
00595 $s .= '}';
00596 } else {
00597 $s = '"' . self::escapeJsString( $value ) . '"';
00598 }
00599 return $s;
00600 }
00601
00602
00612 public static function isWellFormed( $text ) {
00613 $parser = xml_parser_create( "UTF-8" );
00614
00615 # case folding violates XML standard, turn it off
00616 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00617
00618 if( !xml_parse( $parser, $text, true ) ) {
00619 //$err = xml_error_string( xml_get_error_code( $parser ) );
00620 //$position = xml_get_current_byte_index( $parser );
00621 //$fragment = $this->extractFragment( $html, $position );
00622 //$this->mXmlError = "$err at byte $position:\n$fragment";
00623 xml_parser_free( $parser );
00624 return false;
00625 }
00626 xml_parser_free( $parser );
00627 return true;
00628 }
00629
00638 public static function isWellFormedXmlFragment( $text ) {
00639 $html =
00640 Sanitizer::hackDocType() .
00641 '<html>' .
00642 $text .
00643 '</html>';
00644 return Xml::isWellFormed( $html );
00645 }
00646
00654 public static function escapeTagsOnly( $in ) {
00655 return str_replace(
00656 array( '"', '>', '<' ),
00657 array( '"', '>', '<' ),
00658 $in );
00659 }
00660
00668 public static function buildForm( $fields, $submitLabel = null ) {
00669 $form = '';
00670 $form .= "<table><tbody>";
00671
00672 foreach( $fields as $labelmsg => $input ) {
00673 $id = "mw-$labelmsg";
00674 $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00675 $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMsgExt( $labelmsg, array('parseinline') ) );
00676 $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00677 $form .= Xml::closeElement( 'tr' );
00678 }
00679
00680 if( $submitLabel ) {
00681 $form .= Xml::openElement( 'tr' );
00682 $form .= Xml::tags( 'td', array(), '' );
00683 $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' );
00684 $form .= Xml::closeElement( 'tr' );
00685 }
00686
00687 $form .= "</tbody></table>";
00688
00689
00690 return $form;
00691 }
00692
00700 public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00701 $s = Xml::openElement( 'table', $attribs );
00702 if ( is_array( $headers ) ) {
00703 foreach( $headers as $id => $header ) {
00704 $attribs = array();
00705 if ( is_string( $id ) ) $attribs['id'] = $id;
00706 $s .= Xml::element( 'th', $attribs, $header );
00707 }
00708 }
00709 foreach( $rows as $id => $row ) {
00710 $attribs = array();
00711 if ( is_string( $id ) ) $attribs['id'] = $id;
00712 $s .= Xml::buildTableRow( $attribs, $row );
00713 }
00714 $s .= Xml::closeElement( 'table' );
00715 return $s;
00716 }
00717
00723 public static function buildTableRow( $attribs, $cells ) {
00724 $s = Xml::openElement( 'tr', $attribs );
00725 foreach( $cells as $id => $cell ) {
00726 $attribs = array();
00727 if ( is_string( $id ) ) $attribs['id'] = $id;
00728 $s .= Xml::element( 'td', $attribs, $cell );
00729 }
00730 $s .= Xml::closeElement( 'tr' );
00731 return $s;
00732 }
00733 }
00734
00735 class XmlSelect {
00736 protected $options = array();
00737 protected $default = false;
00738 protected $attributes = array();
00739
00740 public function __construct( $name = false, $id = false, $default = false ) {
00741 if ( $name ) $this->setAttribute( 'name', $name );
00742 if ( $id ) $this->setAttribute( 'id', $id );
00743 if ( $default ) $this->default = $default;
00744 }
00745
00746 public function setDefault( $default ) {
00747 $this->default = $default;
00748 }
00749
00750 public function setAttribute( $name, $value ) {
00751 $this->attributes[$name] = $value;
00752 }
00753
00754 public function addOption( $name, $value = false ) {
00755 // Stab stab stab
00756 $value = ($value !== false) ? $value : $name;
00757 $this->options[] = Xml::option( $name, $value, $value === $this->default );
00758 }
00759
00760 public function getHTML() {
00761 return Xml::tags( 'select', $this->attributes, implode( "\n", $this->options ) );
00762 }
00763
00764 }