MediaWiki  REL1_20
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 
00064         public static function expandAttributes( $attribs ) {
00065                 $out = '';
00066                 if( is_null( $attribs ) ) {
00067                         return null;
00068                 } elseif( is_array( $attribs ) ) {
00069                         foreach( $attribs as $name => $val ) {
00070                                 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
00071                         }
00072                         return $out;
00073                 } else {
00074                         throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
00075                 }
00076         }
00077 
00088         public static function elementClean( $element, $attribs = array(), $contents = '') {
00089                 global $wgContLang;
00090                 if( $attribs ) {
00091                         $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00092                 }
00093                 if( $contents ) {
00094                         wfProfileIn( __METHOD__ . '-norm' );
00095                         $contents = $wgContLang->normalize( $contents );
00096                         wfProfileOut( __METHOD__ . '-norm' );
00097                 }
00098                 return self::element( $element, $attribs, $contents );
00099         }
00100 
00108         public static function openElement( $element, $attribs = null ) {
00109                 return '<' . $element . self::expandAttributes( $attribs ) . '>';
00110         }
00111 
00117         public static function closeElement( $element ) { return "</$element>"; }
00118 
00128         public static function tags( $element, $attribs = null, $contents ) {
00129                 return self::openElement( $element, $attribs ) . $contents . "</$element>";
00130         }
00131 
00142         public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00143                 wfDeprecated( __METHOD__, '1.19' );
00144                 return Html::namespaceSelector( array(
00145                         'selected' => $selected,
00146                         'all'      => $all,
00147                         'label'    => $label,
00148                 ), array(
00149                         'name'  => $element_name,
00150                         'id'    => 'namespace',
00151                         'class' => 'namespaceselector',
00152                 ) );
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( wfMessage( 'monthsall' )->text(), $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                 } elseif( $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( wfMessage( 'year' )->text(), 'year' ) . ' '.
00202                         Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
00203                         Xml::label( wfMessage( 'month' )->text(), 'month' ) . ' '.
00204                         Xml::monthSelector( $encMonth, -1 );
00205         }
00206 
00217         public static function languageSelector( $selected, $customisedOnly = true, $inLanguage = null, $overrideAttrs = array(), Message $msg = null ) {
00218                 global $wgLanguageCode;
00219 
00220                 $include = $customisedOnly ? 'mwfile' : 'mw';
00221                 $languages = Language::fetchLanguageNames( $inLanguage, $include );
00222 
00223                 // Make sure the site language is in the list;
00224                 // a custom language code might not have a defined name...
00225                 if( !array_key_exists( $wgLanguageCode, $languages ) ) {
00226                         $languages[$wgLanguageCode] = $wgLanguageCode;
00227                 }
00228 
00229                 ksort( $languages );
00230 
00236                 $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
00237                 $options = "\n";
00238                 foreach( $languages as $code => $name ) {
00239                         $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
00240                 }
00241 
00242                 $attrs = array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' );
00243                 $attrs = array_merge( $attrs, $overrideAttrs );
00244 
00245                 if( $msg === null ) {
00246                         $msg = wfMessage( 'yourlanguage' );
00247                 }
00248                 return array(
00249                         Xml::label( $msg->text(), $attrs['id'] ),
00250                         Xml::tags( 'select', $attrs, $options )
00251                 );
00252 
00253         }
00254 
00262         public static function span( $text, $class, $attribs = array() ) {
00263                 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00264         }
00265 
00274         public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
00275                 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00276         }
00277 
00286         public static function input( $name, $size = false, $value = false, $attribs = array() ) {
00287                 $attributes = array( 'name' => $name );
00288 
00289                 if( $size ) {
00290                         $attributes['size'] = $size;
00291                 }
00292 
00293                 if( $value !== false ) { // maybe 0
00294                         $attributes['value'] = $value;
00295                 }
00296 
00297                 return self::element( 'input', $attributes + $attribs );
00298         }
00299 
00308         public static function password( $name, $size = false, $value = false, $attribs = array() ) {
00309                 return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
00310         }
00311 
00320         public static function attrib( $name, $present = true ) {
00321                 return $present ? array( $name => $name ) : array();
00322         }
00323 
00331         public static function check( $name, $checked = false, $attribs=array() ) {
00332                 return self::element( 'input', array_merge(
00333                         array(
00334                                 'name' => $name,
00335                                 'type' => 'checkbox',
00336                                 'value' => 1 ),
00337                         self::attrib( 'checked', $checked ),
00338                         $attribs ) );
00339         }
00340 
00349         public static function radio( $name, $value, $checked = false, $attribs = array() ) {
00350                 return self::element( 'input', array(
00351                         'name' => $name,
00352                         'type' => 'radio',
00353                         'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00354         }
00355 
00366         public static function label( $label, $id, $attribs = array() ) {
00367                 $a = array( 'for' => $id );
00368 
00369                 # FIXME avoid copy pasting below:
00370                 if( isset( $attribs['class'] ) ){
00371                                 $a['class'] = $attribs['class'];
00372                 }
00373                 if( isset( $attribs['title'] ) ){
00374                                 $a['title'] = $attribs['title'];
00375                 }
00376 
00377                 return self::element( 'label', $a, $label );
00378         }
00379 
00390         public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
00391                 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00392                 return $label . '&#160;' . $input;
00393         }
00394 
00407         public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
00408                 return array(
00409                         Xml::label( $label, $id, $attribs ),
00410                         self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00411                 );
00412         }
00413 
00425         public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
00426                 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00427                         '&#160;' .
00428                         self::label( $label, $id, $attribs );
00429         }
00430 
00443         public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
00444                 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00445                         '&#160;' .
00446                         self::label( $label, $id, $attribs );
00447         }
00448 
00455         public static function submitButton( $value, $attribs = array() ) {
00456                 return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00457         }
00458 
00467         public static function option( $text, $value=null, $selected = false,
00468                         $attribs = array() ) {
00469                 if( !is_null( $value ) ) {
00470                         $attribs['value'] = $value;
00471                 }
00472                 if( $selected ) {
00473                         $attribs['selected'] = 'selected';
00474                 }
00475                 return Html::element( 'option', $attribs, $text );
00476         }
00477 
00489         public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
00490                 $optgroup = false;
00491 
00492                 $options = self::option( $other, 'other', $selected === 'other' );
00493 
00494                 foreach ( explode( "\n", $list ) as $option) {
00495                                 $value = trim( $option );
00496                                 if ( $value == '' ) {
00497                                         continue;
00498                                 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
00499                                         // A new group is starting ...
00500                                         $value = trim( substr( $value, 1 ) );
00501                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00502                                         $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00503                                         $optgroup = true;
00504                                 } elseif ( substr( $value, 0, 2) == '**' ) {
00505                                         // groupmember
00506                                         $value = trim( substr( $value, 2 ) );
00507                                         $options .= self::option( $value, $value, $selected === $value );
00508                                 } else {
00509                                         // groupless reason list
00510                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00511                                         $options .= self::option( $value, $value, $selected === $value );
00512                                         $optgroup = false;
00513                                 }
00514                         }
00515 
00516                         if( $optgroup ) $options .= self::closeElement('optgroup');
00517 
00518                 $attribs = array();
00519 
00520                 if( $name ) {
00521                         $attribs['id'] = $name;
00522                         $attribs['name'] = $name;
00523                 }
00524 
00525                 if( $class ) {
00526                         $attribs['class'] = $class;
00527                 }
00528 
00529                 if( $tabindex ) {
00530                         $attribs['tabindex'] = $tabindex;
00531                 }
00532 
00533                 return Xml::openElement( 'select', $attribs )
00534                         . "\n"
00535                         . $options
00536                         . "\n"
00537                         . Xml::closeElement( 'select' );
00538         }
00539 
00549         public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00550                 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00551 
00552                 if ( $legend ) {
00553                         $s .= Xml::element( 'legend', null, $legend ) . "\n";
00554                 }
00555 
00556                 if ( $content !== false ) {
00557                         $s .= $content . "\n";
00558                         $s .= Xml::closeElement( 'fieldset' ) . "\n";
00559                 }
00560 
00561                 return $s;
00562         }
00563 
00575         public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00576                 return self::element( 'textarea',
00577                                         array(  'name' => $name,
00578                                                 'id' => $name,
00579                                                 'cols' => $cols,
00580                                                 'rows' => $rows
00581                                         ) + $attribs,
00582                                         $content, false );
00583         }
00584 
00593         public static function escapeJsString( $string ) {
00594                 // See ECMA 262 section 7.8.4 for string literal format
00595                 $pairs = array(
00596                         "\\" => "\\\\",
00597                         "\"" => "\\\"",
00598                         '\'' => '\\\'',
00599                         "\n" => "\\n",
00600                         "\r" => "\\r",
00601 
00602                         # To avoid closing the element or CDATA section
00603                         "<" => "\\x3c",
00604                         ">" => "\\x3e",
00605 
00606                         # To avoid any complaints about bad entity refs
00607                         "&" => "\\x26",
00608 
00609                         # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
00610                         # Encode certain Unicode formatting chars so affected
00611                         # versions of Gecko don't misinterpret our strings;
00612                         # this is a common problem with Farsi text.
00613                         "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00614                         "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00615                 );
00616 
00617                 return strtr( $string, $pairs );
00618         }
00619 
00630         public static function encodeJsVar( $value ) {
00631                 if ( is_bool( $value ) ) {
00632                         $s = $value ? 'true' : 'false';
00633                 } elseif ( is_null( $value ) ) {
00634                         $s = 'null';
00635                 } elseif ( is_int( $value ) || is_float( $value ) ) {
00636                         $s = strval($value);
00637                 } elseif ( is_array( $value ) && // Make sure it's not associative.
00638                                         array_keys($value) === range( 0, count($value) - 1 ) ||
00639                                         count($value) == 0
00640                                 ) {
00641                         $s = '[';
00642                         foreach ( $value as $elt ) {
00643                                 if ( $s != '[' ) {
00644                                         $s .= ',';
00645                                 }
00646                                 $s .= self::encodeJsVar( $elt );
00647                         }
00648                         $s .= ']';
00649                 } elseif ( $value instanceof XmlJsCode ) {
00650                         $s = $value->value;
00651                 } elseif ( is_object( $value ) || is_array( $value ) ) {
00652                         // Objects and associative arrays
00653                         $s = '{';
00654                         foreach ( (array)$value as $name => $elt ) {
00655                                 if ( $s != '{' ) {
00656                                         $s .= ',';
00657                                 }
00658 
00659                                 $s .= '"' . self::escapeJsString( $name ) . '":' .
00660                                         self::encodeJsVar( $elt );
00661                         }
00662                         $s .= '}';
00663                 } else {
00664                         $s = '"' . self::escapeJsString( $value ) . '"';
00665                 }
00666                 return $s;
00667         }
00668 
00681         public static function encodeJsCall( $name, $args ) {
00682                 $s = "$name(";
00683                 $first = true;
00684 
00685                 foreach ( $args as $arg ) {
00686                         if ( $first ) {
00687                                 $first = false;
00688                         } else {
00689                                 $s .= ', ';
00690                         }
00691 
00692                         $s .= Xml::encodeJsVar( $arg );
00693                 }
00694 
00695                 $s .= ");\n";
00696 
00697                 return $s;
00698         }
00699 
00709         public static function isWellFormed( $text ) {
00710                 $parser = xml_parser_create( "UTF-8" );
00711 
00712                 # case folding violates XML standard, turn it off
00713                 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00714 
00715                 if( !xml_parse( $parser, $text, true ) ) {
00716                         //$err = xml_error_string( xml_get_error_code( $parser ) );
00717                         //$position = xml_get_current_byte_index( $parser );
00718                         //$fragment = $this->extractFragment( $html, $position );
00719                         //$this->mXmlError = "$err at byte $position:\n$fragment";
00720                         xml_parser_free( $parser );
00721                         return false;
00722                 }
00723 
00724                 xml_parser_free( $parser );
00725 
00726                 return true;
00727         }
00728 
00737         public static function isWellFormedXmlFragment( $text ) {
00738                 $html =
00739                         Sanitizer::hackDocType() .
00740                         '<html>' .
00741                         $text .
00742                         '</html>';
00743 
00744                 return Xml::isWellFormed( $html );
00745         }
00746 
00754         public static function escapeTagsOnly( $in ) {
00755                 return str_replace(
00756                         array( '"', '>', '<' ),
00757                         array( '&quot;', '&gt;', '&lt;' ),
00758                         $in );
00759         }
00760 
00768         public static function buildForm( $fields, $submitLabel = null ) {
00769                 $form = '';
00770                 $form .= "<table><tbody>";
00771 
00772                 foreach( $fields as $labelmsg => $input ) {
00773                         $id = "mw-$labelmsg";
00774                         $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00775                         $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMessage( $labelmsg )->parse() );
00776                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00777                         $form .= Xml::closeElement( 'tr' );
00778                 }
00779 
00780                 if( $submitLabel ) {
00781                         $form .= Xml::openElement( 'tr' );
00782                         $form .= Xml::tags( 'td', array(), '' );
00783                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text() ) . Xml::closeElement( 'td' );
00784                         $form .= Xml::closeElement( 'tr' );
00785                 }
00786 
00787                 $form .= "</tbody></table>";
00788 
00789                 return $form;
00790         }
00791 
00799         public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00800                 $s = Xml::openElement( 'table', $attribs );
00801 
00802                 if ( is_array( $headers ) ) {
00803                         $s .= Xml::openElement( 'thead', $attribs );
00804 
00805                         foreach( $headers as $id => $header ) {
00806                                 $attribs = array();
00807 
00808                                 if ( is_string( $id ) ) {
00809                                         $attribs['id'] = $id;
00810                                 }
00811 
00812                                 $s .= Xml::element( 'th', $attribs, $header );
00813                         }
00814                         $s .= Xml::closeElement( 'thead' );
00815                 }
00816 
00817                 foreach( $rows as $id => $row ) {
00818                         $attribs = array();
00819 
00820                         if ( is_string( $id ) ) {
00821                                 $attribs['id'] = $id;
00822                         }
00823 
00824                         $s .= Xml::buildTableRow( $attribs, $row );
00825                 }
00826 
00827                 $s .= Xml::closeElement( 'table' );
00828 
00829                 return $s;
00830         }
00831 
00838         public static function buildTableRow( $attribs, $cells ) {
00839                 $s = Xml::openElement( 'tr', $attribs );
00840 
00841                 foreach( $cells as $id => $cell ) {
00842 
00843                         $attribs = array();
00844 
00845                         if ( is_string( $id ) ) {
00846                                 $attribs['id'] = $id;
00847                         }
00848 
00849                         $s .= Xml::element( 'td', $attribs, $cell );
00850                 }
00851 
00852                 $s .= Xml::closeElement( 'tr' );
00853 
00854                 return $s;
00855         }
00856 }
00857 
00858 class XmlSelect {
00859         protected $options = array();
00860         protected $default = false;
00861         protected $attributes = array();
00862 
00863         public function __construct( $name = false, $id = false, $default = false ) {
00864                 if ( $name ) {
00865                         $this->setAttribute( 'name', $name );
00866                 }
00867 
00868                 if ( $id ) {
00869                         $this->setAttribute( 'id', $id );
00870                 }
00871 
00872                 if ( $default !== false ) {
00873                         $this->default = $default;
00874                 }
00875         }
00876 
00880         public function setDefault( $default ) {
00881                 $this->default = $default;
00882         }
00883 
00888         public function setAttribute( $name, $value ) {
00889                 $this->attributes[$name] = $value;
00890         }
00891 
00896         public function getAttribute( $name ) {
00897                 if ( isset( $this->attributes[$name] ) ) {
00898                         return $this->attributes[$name];
00899                 } else {
00900                         return null;
00901                 }
00902         }
00903 
00908         public function addOption( $name, $value = false ) {
00909                 // Stab stab stab
00910                 $value = ($value !== false) ? $value : $name;
00911 
00912                 $this->options[] = array( $name => $value );
00913         }
00914 
00922         public function addOptions( $options ) {
00923                 $this->options[] = $options;
00924         }
00925 
00935         static function formatOptions( $options, $default = false ) {
00936                 $data = '';
00937 
00938                 foreach( $options as $label => $value ) {
00939                         if ( is_array( $value ) ) {
00940                                 $contents = self::formatOptions( $value, $default );
00941                                 $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00942                         } else {
00943                                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00944                         }
00945                 }
00946 
00947                 return $data;
00948         }
00949 
00953         public function getHTML() {
00954                 $contents = '';
00955 
00956                 foreach ( $this->options as $options ) {
00957                         $contents .= self::formatOptions( $options, $this->default );
00958                 }
00959 
00960                 return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
00961         }
00962 }
00963 
00976 class XmlJsCode {
00977         public $value;
00978 
00979         function __construct( $value ) {
00980                 $this->value = $value;
00981         }
00982 }