MediaWiki  master
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 ) { return "</$element>"; }
00119 
00129         public static function tags( $element, $attribs = null, $contents ) {
00130                 return self::openElement( $element, $attribs ) . $contents . "</$element>";
00131         }
00132 
00143         public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00144                 wfDeprecated( __METHOD__, '1.19' );
00145                 return Html::namespaceSelector( array(
00146                         'selected' => $selected,
00147                         'all'      => $all,
00148                         'label'    => $label,
00149                 ), array(
00150                         'name'  => $element_name,
00151                         'id'    => 'namespace',
00152                         'class' => 'namespaceselector',
00153                 ) );
00154         }
00155 
00164         public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00165                 global $wgLang;
00166                 $options = array();
00167                 if( is_null( $selected ) )
00168                         $selected = '';
00169                 if( !is_null( $allmonths ) )
00170                         $options[] = self::option( wfMessage( 'monthsall' )->text(), $allmonths, $selected === $allmonths );
00171                 for( $i = 1; $i < 13; $i++ )
00172                         $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00173                 return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
00174                         . implode( "\n", $options )
00175                         . self::closeElement( 'select' );
00176         }
00177 
00183         public static function dateMenu( $year, $month ) {
00184                 # Offset overrides year/month selection
00185                 if( $month && $month !== -1 ) {
00186                         $encMonth = intval( $month );
00187                 } else {
00188                         $encMonth = '';
00189                 }
00190                 if( $year ) {
00191                         $encYear = intval( $year );
00192                 } elseif( $encMonth ) {
00193                         $thisMonth = intval( gmdate( 'n' ) );
00194                         $thisYear = intval( gmdate( 'Y' ) );
00195                         if( intval($encMonth) > $thisMonth ) {
00196                                 $thisYear--;
00197                         }
00198                         $encYear = $thisYear;
00199                 } else {
00200                         $encYear = '';
00201                 }
00202                 $inputAttribs = array( 'id' => 'year', 'maxlength' => 4, 'size' => 7 );
00203                 return self::label( wfMessage( 'year' )->text(), 'year' ) . ' '.
00204                         Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' '.
00205                         self::label( wfMessage( 'month' )->text(), 'month' ) . ' '.
00206                         self::monthSelector( $encMonth, -1 );
00207         }
00208 
00219         public static function languageSelector( $selected, $customisedOnly = true, $inLanguage = null, $overrideAttrs = array(), Message $msg = null ) {
00220                 global $wgLanguageCode;
00221 
00222                 $include = $customisedOnly ? 'mwfile' : 'mw';
00223                 $languages = Language::fetchLanguageNames( $inLanguage, $include );
00224 
00225                 // Make sure the site language is in the list;
00226                 // a custom language code might not have a defined name...
00227                 if( !array_key_exists( $wgLanguageCode, $languages ) ) {
00228                         $languages[$wgLanguageCode] = $wgLanguageCode;
00229                 }
00230 
00231                 ksort( $languages );
00232 
00238                 $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
00239                 $options = "\n";
00240                 foreach( $languages as $code => $name ) {
00241                         $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
00242                 }
00243 
00244                 $attrs = array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' );
00245                 $attrs = array_merge( $attrs, $overrideAttrs );
00246 
00247                 if( $msg === null ) {
00248                         $msg = wfMessage( 'yourlanguage' );
00249                 }
00250                 return array(
00251                         Xml::label( $msg->text(), $attrs['id'] ),
00252                         Xml::tags( 'select', $attrs, $options )
00253                 );
00254 
00255         }
00256 
00264         public static function span( $text, $class, $attribs = array() ) {
00265                 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00266         }
00267 
00276         public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
00277                 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00278         }
00279 
00288         public static function input( $name, $size = false, $value = false, $attribs = array() ) {
00289                 $attributes = array( 'name' => $name );
00290 
00291                 if( $size ) {
00292                         $attributes['size'] = $size;
00293                 }
00294 
00295                 if( $value !== false ) { // maybe 0
00296                         $attributes['value'] = $value;
00297                 }
00298 
00299                 return self::element( 'input', $attributes + $attribs );
00300         }
00301 
00310         public static function password( $name, $size = false, $value = false, $attribs = array() ) {
00311                 return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
00312         }
00313 
00322         public static function attrib( $name, $present = true ) {
00323                 return $present ? array( $name => $name ) : array();
00324         }
00325 
00333         public static function check( $name, $checked = false, $attribs=array() ) {
00334                 return self::element( 'input', array_merge(
00335                         array(
00336                                 'name' => $name,
00337                                 'type' => 'checkbox',
00338                                 'value' => 1 ),
00339                         self::attrib( 'checked', $checked ),
00340                         $attribs ) );
00341         }
00342 
00351         public static function radio( $name, $value, $checked = false, $attribs = array() ) {
00352                 return self::element( 'input', array(
00353                         'name' => $name,
00354                         'type' => 'radio',
00355                         'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00356         }
00357 
00368         public static function label( $label, $id, $attribs = array() ) {
00369                 $a = array( 'for' => $id );
00370 
00371                 # FIXME avoid copy pasting below:
00372                 if( isset( $attribs['class'] ) ){
00373                                 $a['class'] = $attribs['class'];
00374                 }
00375                 if( isset( $attribs['title'] ) ){
00376                                 $a['title'] = $attribs['title'];
00377                 }
00378 
00379                 return self::element( 'label', $a, $label );
00380         }
00381 
00392         public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
00393                 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00394                 return $label . '&#160;' . $input;
00395         }
00396 
00409         public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
00410                 return array(
00411                         Xml::label( $label, $id, $attribs ),
00412                         self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00413                 );
00414         }
00415 
00427         public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
00428                 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00429                         '&#160;' .
00430                         self::label( $label, $id, $attribs );
00431         }
00432 
00445         public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
00446                 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00447                         '&#160;' .
00448                         self::label( $label, $id, $attribs );
00449         }
00450 
00457         public static function submitButton( $value, $attribs = array() ) {
00458                 return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00459         }
00460 
00469         public static function option( $text, $value=null, $selected = false,
00470                         $attribs = array() ) {
00471                 if( !is_null( $value ) ) {
00472                         $attribs['value'] = $value;
00473                 }
00474                 if( $selected ) {
00475                         $attribs['selected'] = 'selected';
00476                 }
00477                 return Html::element( 'option', $attribs, $text );
00478         }
00479 
00491         public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
00492                 $optgroup = false;
00493 
00494                 $options = self::option( $other, 'other', $selected === 'other' );
00495 
00496                 foreach ( explode( "\n", $list ) as $option) {
00497                                 $value = trim( $option );
00498                                 if ( $value == '' ) {
00499                                         continue;
00500                                 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
00501                                         // A new group is starting ...
00502                                         $value = trim( substr( $value, 1 ) );
00503                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00504                                         $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00505                                         $optgroup = true;
00506                                 } elseif ( substr( $value, 0, 2) == '**' ) {
00507                                         // groupmember
00508                                         $value = trim( substr( $value, 2 ) );
00509                                         $options .= self::option( $value, $value, $selected === $value );
00510                                 } else {
00511                                         // groupless reason list
00512                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00513                                         $options .= self::option( $value, $value, $selected === $value );
00514                                         $optgroup = false;
00515                                 }
00516                         }
00517 
00518                         if( $optgroup ) $options .= self::closeElement('optgroup');
00519 
00520                 $attribs = array();
00521 
00522                 if( $name ) {
00523                         $attribs['id'] = $name;
00524                         $attribs['name'] = $name;
00525                 }
00526 
00527                 if( $class ) {
00528                         $attribs['class'] = $class;
00529                 }
00530 
00531                 if( $tabindex ) {
00532                         $attribs['tabindex'] = $tabindex;
00533                 }
00534 
00535                 return Xml::openElement( 'select', $attribs )
00536                         . "\n"
00537                         . $options
00538                         . "\n"
00539                         . Xml::closeElement( 'select' );
00540         }
00541 
00551         public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00552                 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00553 
00554                 if ( $legend ) {
00555                         $s .= Xml::element( 'legend', null, $legend ) . "\n";
00556                 }
00557 
00558                 if ( $content !== false ) {
00559                         $s .= $content . "\n";
00560                         $s .= Xml::closeElement( 'fieldset' ) . "\n";
00561                 }
00562 
00563                 return $s;
00564         }
00565 
00577         public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00578                 return self::element( 'textarea',
00579                                         array(
00580                                                 'name' => $name,
00581                                                 'id' => $name,
00582                                                 'cols' => $cols,
00583                                                 'rows' => $rows
00584                                         ) + $attribs,
00585                                         $content, false );
00586         }
00587 
00596         public static function escapeJsString( $string ) {
00597                 // See ECMA 262 section 7.8.4 for string literal format
00598                 $pairs = array(
00599                         "\\" => "\\\\",
00600                         "\"" => "\\\"",
00601                         '\'' => '\\\'',
00602                         "\n" => "\\n",
00603                         "\r" => "\\r",
00604 
00605                         # To avoid closing the element or CDATA section
00606                         "<" => "\\x3c",
00607                         ">" => "\\x3e",
00608 
00609                         # To avoid any complaints about bad entity refs
00610                         "&" => "\\x26",
00611 
00612                         # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
00613                         # Encode certain Unicode formatting chars so affected
00614                         # versions of Gecko don't misinterpret our strings;
00615                         # this is a common problem with Farsi text.
00616                         "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00617                         "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00618                 );
00619 
00620                 return strtr( $string, $pairs );
00621         }
00622 
00633         public static function encodeJsVar( $value ) {
00634                 if ( is_bool( $value ) ) {
00635                         $s = $value ? 'true' : 'false';
00636                 } elseif ( is_null( $value ) ) {
00637                         $s = 'null';
00638                 } elseif ( is_int( $value ) || is_float( $value ) ) {
00639                         $s = strval($value);
00640                 } elseif ( is_array( $value ) && // Make sure it's not associative.
00641                                         array_keys($value) === range( 0, count($value) - 1 ) ||
00642                                         count($value) == 0
00643                                 ) {
00644                         $s = '[';
00645                         foreach ( $value as $elt ) {
00646                                 if ( $s != '[' ) {
00647                                         $s .= ',';
00648                                 }
00649                                 $s .= self::encodeJsVar( $elt );
00650                         }
00651                         $s .= ']';
00652                 } elseif ( $value instanceof XmlJsCode ) {
00653                         $s = $value->value;
00654                 } elseif ( is_object( $value ) || is_array( $value ) ) {
00655                         // Objects and associative arrays
00656                         $s = '{';
00657                         foreach ( (array)$value as $name => $elt ) {
00658                                 if ( $s != '{' ) {
00659                                         $s .= ',';
00660                                 }
00661 
00662                                 $s .= '"' . self::escapeJsString( $name ) . '":' .
00663                                         self::encodeJsVar( $elt );
00664                         }
00665                         $s .= '}';
00666                 } else {
00667                         $s = '"' . self::escapeJsString( $value ) . '"';
00668                 }
00669                 return $s;
00670         }
00671 
00684         public static function encodeJsCall( $name, $args ) {
00685                 $s = "$name(";
00686                 $first = true;
00687 
00688                 foreach ( $args as $arg ) {
00689                         if ( $first ) {
00690                                 $first = false;
00691                         } else {
00692                                 $s .= ', ';
00693                         }
00694 
00695                         $s .= Xml::encodeJsVar( $arg );
00696                 }
00697 
00698                 $s .= ");\n";
00699 
00700                 return $s;
00701         }
00702 
00712         public static function isWellFormed( $text ) {
00713                 $parser = xml_parser_create( "UTF-8" );
00714 
00715                 # case folding violates XML standard, turn it off
00716                 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00717 
00718                 if( !xml_parse( $parser, $text, true ) ) {
00719                         //$err = xml_error_string( xml_get_error_code( $parser ) );
00720                         //$position = xml_get_current_byte_index( $parser );
00721                         //$fragment = $this->extractFragment( $html, $position );
00722                         //$this->mXmlError = "$err at byte $position:\n$fragment";
00723                         xml_parser_free( $parser );
00724                         return false;
00725                 }
00726 
00727                 xml_parser_free( $parser );
00728 
00729                 return true;
00730         }
00731 
00740         public static function isWellFormedXmlFragment( $text ) {
00741                 $html =
00742                         Sanitizer::hackDocType() .
00743                         '<html>' .
00744                         $text .
00745                         '</html>';
00746 
00747                 return Xml::isWellFormed( $html );
00748         }
00749 
00757         public static function escapeTagsOnly( $in ) {
00758                 return str_replace(
00759                         array( '"', '>', '<' ),
00760                         array( '&quot;', '&gt;', '&lt;' ),
00761                         $in );
00762         }
00763 
00771         public static function buildForm( $fields, $submitLabel = null ) {
00772                 $form = '';
00773                 $form .= "<table><tbody>";
00774 
00775                 foreach( $fields as $labelmsg => $input ) {
00776                         $id = "mw-$labelmsg";
00777                         $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00778                         $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMessage( $labelmsg )->parse() );
00779                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00780                         $form .= Xml::closeElement( 'tr' );
00781                 }
00782 
00783                 if( $submitLabel ) {
00784                         $form .= Xml::openElement( 'tr' );
00785                         $form .= Xml::tags( 'td', array(), '' );
00786                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMessage( $submitLabel )->text() ) . Xml::closeElement( 'td' );
00787                         $form .= Xml::closeElement( 'tr' );
00788                 }
00789 
00790                 $form .= "</tbody></table>";
00791 
00792                 return $form;
00793         }
00794 
00802         public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00803                 $s = Xml::openElement( 'table', $attribs );
00804 
00805                 if ( is_array( $headers ) ) {
00806                         $s .= Xml::openElement( 'thead', $attribs );
00807 
00808                         foreach( $headers as $id => $header ) {
00809                                 $attribs = array();
00810 
00811                                 if ( is_string( $id ) ) {
00812                                         $attribs['id'] = $id;
00813                                 }
00814 
00815                                 $s .= Xml::element( 'th', $attribs, $header );
00816                         }
00817                         $s .= Xml::closeElement( 'thead' );
00818                 }
00819 
00820                 foreach( $rows as $id => $row ) {
00821                         $attribs = array();
00822 
00823                         if ( is_string( $id ) ) {
00824                                 $attribs['id'] = $id;
00825                         }
00826 
00827                         $s .= Xml::buildTableRow( $attribs, $row );
00828                 }
00829 
00830                 $s .= Xml::closeElement( 'table' );
00831 
00832                 return $s;
00833         }
00834 
00841         public static function buildTableRow( $attribs, $cells ) {
00842                 $s = Xml::openElement( 'tr', $attribs );
00843 
00844                 foreach( $cells as $id => $cell ) {
00845 
00846                         $attribs = array();
00847 
00848                         if ( is_string( $id ) ) {
00849                                 $attribs['id'] = $id;
00850                         }
00851 
00852                         $s .= Xml::element( 'td', $attribs, $cell );
00853                 }
00854 
00855                 $s .= Xml::closeElement( 'tr' );
00856 
00857                 return $s;
00858         }
00859 }
00860 
00861 class XmlSelect {
00862         protected $options = array();
00863         protected $default = false;
00864         protected $attributes = array();
00865 
00866         public function __construct( $name = false, $id = false, $default = false ) {
00867                 if ( $name ) {
00868                         $this->setAttribute( 'name', $name );
00869                 }
00870 
00871                 if ( $id ) {
00872                         $this->setAttribute( 'id', $id );
00873                 }
00874 
00875                 if ( $default !== false ) {
00876                         $this->default = $default;
00877                 }
00878         }
00879 
00883         public function setDefault( $default ) {
00884                 $this->default = $default;
00885         }
00886 
00891         public function setAttribute( $name, $value ) {
00892                 $this->attributes[$name] = $value;
00893         }
00894 
00899         public function getAttribute( $name ) {
00900                 if ( isset( $this->attributes[$name] ) ) {
00901                         return $this->attributes[$name];
00902                 } else {
00903                         return null;
00904                 }
00905         }
00906 
00911         public function addOption( $name, $value = false ) {
00912                 // Stab stab stab
00913                 $value = ($value !== false) ? $value : $name;
00914 
00915                 $this->options[] = array( $name => $value );
00916         }
00917 
00925         public function addOptions( $options ) {
00926                 $this->options[] = $options;
00927         }
00928 
00938         static function formatOptions( $options, $default = false ) {
00939                 $data = '';
00940 
00941                 foreach( $options as $label => $value ) {
00942                         if ( is_array( $value ) ) {
00943                                 $contents = self::formatOptions( $value, $default );
00944                                 $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00945                         } else {
00946                                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00947                         }
00948                 }
00949 
00950                 return $data;
00951         }
00952 
00956         public function getHTML() {
00957                 $contents = '';
00958 
00959                 foreach ( $this->options as $options ) {
00960                         $contents .= self::formatOptions( $options, $this->default );
00961                 }
00962 
00963                 return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
00964         }
00965 }
00966 
00979 class XmlJsCode {
00980         public $value;
00981 
00982         function __construct( $value ) {
00983                 $this->value = $value;
00984         }
00985 }