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 global $wgContLang;
00069 if( $attribs ) {
00070 $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00071 }
00072 if( $contents ) {
00073 wfProfileIn( __METHOD__ . '-norm' );
00074 $contents = $wgContLang->normalize( $contents );
00075 wfProfileOut( __METHOD__ . '-norm' );
00076 }
00077 return self::element( $element, $attribs, $contents );
00078 }
00079
00087 public static function openElement( $element, $attribs = null ) {
00088 return '<' . $element . self::expandAttributes( $attribs ) . '>';
00089 }
00090
00096 public static function closeElement( $element ) { return "</$element>"; }
00097
00107 public static function tags( $element, $attribs = null, $contents ) {
00108 return self::openElement( $element, $attribs ) . $contents . "</$element>";
00109 }
00110
00120 public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00121 global $wgContLang;
00122 $namespaces = $wgContLang->getFormattedNamespaces();
00123 $options = array();
00124
00125
00126
00127
00128
00129 if( preg_match( '/^\d+$/', $selected ) ) {
00130 $selected = intval( $selected );
00131 }
00132
00133 if( !is_null( $all ) )
00134 $namespaces = array( $all => wfMsg( 'namespacesall' ) ) + $namespaces;
00135 foreach( $namespaces as $index => $name ) {
00136 if( $index < NS_MAIN )
00137 continue;
00138 if( $index === 0 )
00139 $name = wfMsg( 'blanknamespace' );
00140 $options[] = self::option( $name, $index, $index === $selected );
00141 }
00142
00143 $ret = Xml::openElement( 'select', array( 'id' => 'namespace', 'name' => $element_name,
00144 'class' => 'namespaceselector' ) )
00145 . "\n"
00146 . implode( "\n", $options )
00147 . "\n"
00148 . Xml::closeElement( 'select' );
00149 if ( !is_null( $label ) ) {
00150 $ret = Xml::label( $label, $element_name ) . ' ' . $ret;
00151 }
00152 return $ret;
00153 }
00154
00163 public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00164 global $wgLang;
00165 $options = array();
00166 if( is_null( $selected ) )
00167 $selected = '';
00168 if( !is_null( $allmonths ) )
00169 $options[] = self::option( wfMsg( 'monthsall' ), $allmonths, $selected === $allmonths );
00170 for( $i = 1; $i < 13; $i++ )
00171 $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00172 return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
00173 . implode( "\n", $options )
00174 . self::closeElement( 'select' );
00175 }
00176
00182 public static function dateMenu( $year, $month ) {
00183 # Offset overrides year/month selection
00184 if( $month && $month !== -1 ) {
00185 $encMonth = intval( $month );
00186 } else {
00187 $encMonth = '';
00188 }
00189 if( $year ) {
00190 $encYear = intval( $year );
00191 } else if( $encMonth ) {
00192 $thisMonth = intval( gmdate( 'n' ) );
00193 $thisYear = intval( gmdate( 'Y' ) );
00194 if( intval($encMonth) > $thisMonth ) {
00195 $thisYear--;
00196 }
00197 $encYear = $thisYear;
00198 } else {
00199 $encYear = '';
00200 }
00201 return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
00202 Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
00203 Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
00204 Xml::monthSelector( $encMonth, -1 );
00205 }
00206
00213 public static function languageSelector( $selected, $customisedOnly = true ) {
00214 global $wgContLanguageCode;
00219 $languages = Language::getLanguageNames( $customisedOnly );
00220 if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
00221 $languages[$wgContLanguageCode] = $wgContLanguageCode;
00222 }
00223 ksort( $languages );
00224
00230 $selected = isset( $languages[$selected] ) ? $selected : $wgContLanguageCode;
00231 $options = "\n";
00232 foreach( $languages as $code => $name ) {
00233 $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
00234 }
00235
00236 return array(
00237 Xml::label( wfMsg('yourlanguage'), 'wpUserLanguage' ),
00238 Xml::tags( 'select',
00239 array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ),
00240 $options
00241 )
00242 );
00243
00244 }
00245
00253 public static function span( $text, $class, $attribs=array() ) {
00254 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00255 }
00256
00265 public static function wrapClass( $text, $class, $tag='span', $attribs=array() ) {
00266 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00267 }
00268
00277 public static function input( $name, $size=false, $value=false, $attribs=array() ) {
00278 return self::element( 'input', array(
00279 'name' => $name,
00280 'size' => $size,
00281 'value' => $value ) + $attribs );
00282 }
00283
00292 public static function password( $name, $size=false, $value=false, $attribs=array() ) {
00293 return self::input( $name, $size, $value, array_merge($attribs, array('type' => 'password')));
00294 }
00295
00300 public static function attrib( $name, $present = true ) {
00301 return $present ? array( $name => $name ) : array();
00302 }
00303
00311 public static function check( $name, $checked=false, $attribs=array() ) {
00312 return self::element( 'input', array_merge(
00313 array(
00314 'name' => $name,
00315 'type' => 'checkbox',
00316 'value' => 1 ),
00317 self::attrib( 'checked', $checked ),
00318 $attribs ) );
00319 }
00320
00329 public static function radio( $name, $value, $checked=false, $attribs=array() ) {
00330 return self::element( 'input', array(
00331 'name' => $name,
00332 'type' => 'radio',
00333 'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00334 }
00335
00342 public static function label( $label, $id ) {
00343 return self::element( 'label', array( 'for' => $id ), $label );
00344 }
00345
00356 public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
00357 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00358 return $label . ' ' . $input;
00359 }
00360
00364 public static function inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
00365 return array(
00366 Xml::label( $label, $id ),
00367 self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00368 );
00369 }
00370
00375 public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
00376 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00377 ' ' .
00378 self::label( $label, $id );
00379 }
00380
00385 public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
00386 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00387 ' ' .
00388 self::label( $label, $id );
00389 }
00390
00397 public static function submitButton( $value, $attribs=array() ) {
00398 return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00399 }
00400
00404 public static function hidden( $name, $value, $attribs = array() ) {
00405 return Html::hidden( $name, $value, $attribs );
00406 }
00407
00416 public static function option( $text, $value=null, $selected=false,
00417 $attribs=array() ) {
00418 if( !is_null( $value ) ) {
00419 $attribs['value'] = $value;
00420 }
00421 if( $selected ) {
00422 $attribs['selected'] = 'selected';
00423 }
00424 return self::element( 'option', $attribs, $text );
00425 }
00426
00438 public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = Null ) {
00439 $options = '';
00440 $optgroup = false;
00441
00442 $options = self::option( $other, 'other', $selected === 'other' );
00443
00444 foreach ( explode( "\n", $list ) as $option) {
00445 $value = trim( $option );
00446 if ( $value == '' ) {
00447 continue;
00448 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
00449
00450 $value = trim( substr( $value, 1 ) );
00451 if( $optgroup ) $options .= self::closeElement('optgroup');
00452 $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00453 $optgroup = true;
00454 } elseif ( substr( $value, 0, 2) == '**' ) {
00455
00456 $value = trim( substr( $value, 2 ) );
00457 $options .= self::option( $value, $value, $selected === $value );
00458 } else {
00459
00460 if( $optgroup ) $options .= self::closeElement('optgroup');
00461 $options .= self::option( $value, $value, $selected === $value );
00462 $optgroup = false;
00463 }
00464 }
00465 if( $optgroup ) $options .= self::closeElement('optgroup');
00466
00467 $attribs = array();
00468 if( $name ) {
00469 $attribs['id'] = $name;
00470 $attribs['name'] = $name;
00471 }
00472 if( $class ) {
00473 $attribs['class'] = $class;
00474 }
00475 if( $tabindex ) {
00476 $attribs['tabindex'] = $tabindex;
00477 }
00478 return Xml::openElement( 'select', $attribs )
00479 . "\n"
00480 . $options
00481 . "\n"
00482 . Xml::closeElement( 'select' );
00483 }
00484
00492 public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00493 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00494 if ( $legend ) {
00495 $s .= Xml::element( 'legend', null, $legend ) . "\n";
00496 }
00497 if ( $content !== false ) {
00498 $s .= $content . "\n";
00499 $s .= Xml::closeElement( 'fieldset' ) . "\n";
00500 }
00501
00502 return $s;
00503 }
00504
00514 public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00515 return self::element( 'textarea',
00516 array( 'name' => $name,
00517 'id' => $name,
00518 'cols' => $cols,
00519 'rows' => $rows
00520 ) + $attribs,
00521 $content, false );
00522 }
00523
00532 public static function escapeJsString( $string ) {
00533
00534 $pairs = array(
00535 "\\" => "\\\\",
00536 "\"" => "\\\"",
00537 '\'' => '\\\'',
00538 "\n" => "\\n",
00539 "\r" => "\\r",
00540
00541 # To avoid closing the element or CDATA section
00542 "<" => "\\x3c",
00543 ">" => "\\x3e",
00544
00545 # To avoid any complaints about bad entity refs
00546 "&" => "\\x26",
00547
00548 # Work around https:
00549 # Encode certain Unicode formatting chars so affected
00550 # versions of Gecko don't misinterpret our strings;
00551 # this is a common problem with Farsi text.
00552 "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00553 "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00554 );
00555 return strtr( $string, $pairs );
00556 }
00557
00564 public static function encodeJsVar( $value ) {
00565 if ( is_bool( $value ) ) {
00566 $s = $value ? 'true' : 'false';
00567 } elseif ( is_null( $value ) ) {
00568 $s = 'null';
00569 } elseif ( is_int( $value ) ) {
00570 $s = $value;
00571 } elseif ( is_array( $value ) && // Make sure it's not associative.
00572 array_keys($value) === range( 0, count($value) - 1 ) ||
00573 count($value) == 0
00574 ) {
00575 $s = '[';
00576 foreach ( $value as $elt ) {
00577 if ( $s != '[' ) {
00578 $s .= ', ';
00579 }
00580 $s .= self::encodeJsVar( $elt );
00581 }
00582 $s .= ']';
00583 } elseif ( is_object( $value ) || is_array( $value ) ) {
00584
00585 $s = '{';
00586 foreach ( (array)$value as $name => $elt ) {
00587 if ( $s != '{' ) {
00588 $s .= ', ';
00589 }
00590 $s .= '"' . self::escapeJsString( $name ) . '": ' .
00591 self::encodeJsVar( $elt );
00592 }
00593 $s .= '}';
00594 } else {
00595 $s = '"' . self::escapeJsString( $value ) . '"';
00596 }
00597 return $s;
00598 }
00599
00600
00610 public static function isWellFormed( $text ) {
00611 $parser = xml_parser_create( "UTF-8" );
00612
00613 # case folding violates XML standard, turn it off
00614 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00615
00616 if( !xml_parse( $parser, $text, true ) ) {
00617
00618
00619
00620
00621 xml_parser_free( $parser );
00622 return false;
00623 }
00624 xml_parser_free( $parser );
00625 return true;
00626 }
00627
00636 public static function isWellFormedXmlFragment( $text ) {
00637 $html =
00638 Sanitizer::hackDocType() .
00639 '<html>' .
00640 $text .
00641 '</html>';
00642 return Xml::isWellFormed( $html );
00643 }
00644
00652 public static function escapeTagsOnly( $in ) {
00653 return str_replace(
00654 array( '"', '>', '<' ),
00655 array( '"', '>', '<' ),
00656 $in );
00657 }
00658
00666 public static function buildForm( $fields, $submitLabel = null ) {
00667 $form = '';
00668 $form .= "<table><tbody>";
00669
00670 foreach( $fields as $labelmsg => $input ) {
00671 $id = "mw-$labelmsg";
00672 $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00673 $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMsgExt( $labelmsg, array('parseinline') ) );
00674 $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00675 $form .= Xml::closeElement( 'tr' );
00676 }
00677
00678 if( $submitLabel ) {
00679 $form .= Xml::openElement( 'tr' );
00680 $form .= Xml::tags( 'td', array(), '' );
00681 $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' );
00682 $form .= Xml::closeElement( 'tr' );
00683 }
00684
00685 $form .= "</tbody></table>";
00686
00687
00688 return $form;
00689 }
00690
00698 public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00699 $s = Xml::openElement( 'table', $attribs );
00700 if ( is_array( $headers ) ) {
00701 foreach( $headers as $id => $header ) {
00702 $attribs = array();
00703 if ( is_string( $id ) ) $attribs['id'] = $id;
00704 $s .= Xml::element( 'th', $attribs, $header );
00705 }
00706 }
00707 foreach( $rows as $id => $row ) {
00708 $attribs = array();
00709 if ( is_string( $id ) ) $attribs['id'] = $id;
00710 $s .= Xml::buildTableRow( $attribs, $row );
00711 }
00712 $s .= Xml::closeElement( 'table' );
00713 return $s;
00714 }
00715
00722 public static function buildTableRow( $attribs, $cells ) {
00723 $s = Xml::openElement( 'tr', $attribs );
00724 foreach( $cells as $id => $cell ) {
00725 $attribs = array();
00726 if ( is_string( $id ) ) $attribs['id'] = $id;
00727 $s .= Xml::element( 'td', $attribs, $cell );
00728 }
00729 $s .= Xml::closeElement( 'tr' );
00730 return $s;
00731 }
00732 }
00733
00734 class XmlSelect {
00735 protected $options = array();
00736 protected $default = false;
00737 protected $attributes = array();
00738
00739 public function __construct( $name = false, $id = false, $default = false ) {
00740 if ( $name ) $this->setAttribute( 'name', $name );
00741 if ( $id ) $this->setAttribute( 'id', $id );
00742 if ( $default ) $this->default = $default;
00743 }
00744
00745 public function setDefault( $default ) {
00746 $this->default = $default;
00747 }
00748
00749 public function setAttribute( $name, $value ) {
00750 $this->attributes[$name] = $value;
00751 }
00752
00753 public function getAttribute( $name ) {
00754 if ( isset($this->attributes[$name]) ) {
00755 return $this->attributes[$name];
00756 } else {
00757 return null;
00758 }
00759 }
00760
00761 public function addOption( $name, $value = false ) {
00762
00763 $value = ($value !== false) ? $value : $name;
00764 $this->options[] = Xml::option( $name, $value, $value === $this->default );
00765 }
00766
00767
00768
00769
00770 public function addOptions( $options ) {
00771 $this->options[] = trim(self::formatOptions( $options, $this->default ));
00772 }
00773
00774
00775
00776
00777 static function formatOptions( $options, $default = false ) {
00778 $data = '';
00779 foreach( $options as $label => $value ) {
00780 if ( is_array( $value ) ) {
00781 $contents = self::formatOptions( $value, $default );
00782 $data .= Xml::tags( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00783 } else {
00784 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00785 }
00786 }
00787
00788 return $data;
00789 }
00790
00791 public function getHTML() {
00792 return Xml::tags( 'select', $this->attributes, implode( "\n", $this->options ) );
00793 }
00794
00795 }