MediaWiki  REL1_19
Xml.php
Go to the documentation of this file.
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                         }
00052                         return $out;
00053                 } else {
00054                         throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
00055                 }
00056         }
00057 
00068         public static function elementClean( $element, $attribs = array(), $contents = '') {
00069                 global $wgContLang;
00070                 if( $attribs ) {
00071                         $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00072                 }
00073                 if( $contents ) {
00074                         wfProfileIn( __METHOD__ . '-norm' );
00075                         $contents = $wgContLang->normalize( $contents );
00076                         wfProfileOut( __METHOD__ . '-norm' );
00077                 }
00078                 return self::element( $element, $attribs, $contents );
00079         }
00080 
00088         public static function openElement( $element, $attribs = null ) {
00089                 return '<' . $element . self::expandAttributes( $attribs ) . '>';
00090         }
00091 
00097         public static function closeElement( $element ) { return "</$element>"; }
00098 
00108         public static function tags( $element, $attribs = null, $contents ) {
00109                 return self::openElement( $element, $attribs ) . $contents . "</$element>";
00110         }
00111 
00122         public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
00123                 wfDeprecated( __METHOD__, '1.19' );
00124                 return Html::namespaceSelector( array(
00125                         'selected' => $selected,
00126                         'all'      => $all,
00127                         'label'    => $label,
00128                 ), array(
00129                         'name'  => $element_name,
00130                         'id'    => 'namespace',
00131                         'class' => 'namespaceselector',
00132                 ) );
00133         }
00134 
00143         public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00144                 global $wgLang;
00145                 $options = array();
00146                 if( is_null( $selected ) )
00147                         $selected = '';
00148                 if( !is_null( $allmonths ) )
00149                         $options[] = self::option( wfMsg( 'monthsall' ), $allmonths, $selected === $allmonths );
00150                 for( $i = 1; $i < 13; $i++ )
00151                         $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00152                 return self::openElement( 'select', array( 'id' => $id, 'name' => 'month', 'class' => 'mw-month-selector' ) )
00153                         . implode( "\n", $options )
00154                         . self::closeElement( 'select' );
00155         }
00156 
00162         public static function dateMenu( $year, $month ) {
00163                 # Offset overrides year/month selection
00164                 if( $month && $month !== -1 ) {
00165                         $encMonth = intval( $month );
00166                 } else {
00167                         $encMonth = '';
00168                 }
00169                 if( $year ) {
00170                         $encYear = intval( $year );
00171                 } elseif( $encMonth ) {
00172                         $thisMonth = intval( gmdate( 'n' ) );
00173                         $thisYear = intval( gmdate( 'Y' ) );
00174                         if( intval($encMonth) > $thisMonth ) {
00175                                 $thisYear--;
00176                         }
00177                         $encYear = $thisYear;
00178                 } else {
00179                         $encYear = '';
00180                 }
00181                 return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
00182                         Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '.
00183                         Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
00184                         Xml::monthSelector( $encMonth, -1 );
00185         }
00186 
00195         public static function languageSelector( $selected, $customisedOnly = true, $language = null ) {
00196                 global $wgLanguageCode;
00197 
00198                 // If a specific language was requested and CLDR is installed, use it
00199                 if ( $language && is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
00200                         if ( $customisedOnly ) {
00201                                 $listType = LanguageNames::LIST_MW_SUPPORTED; // Only pull names that have localisation in MediaWiki
00202                         } else {
00203                                 $listType = LanguageNames::LIST_MW; // Pull all languages that are in Names.php
00204                         }
00205                         // Retrieve the list of languages in the requested language (via CLDR)
00206                         $languages = LanguageNames::getNames(
00207                                 $language, // Code of the requested language
00208                                 LanguageNames::FALLBACK_NORMAL, // Use fallback chain
00209                                 $listType
00210                         );
00211                 } else {
00212                         $languages = Language::getLanguageNames( $customisedOnly );
00213                 }
00214                 
00215                 // Make sure the site language is in the list; a custom language code might not have a
00216                 // defined name...
00217                 if( !array_key_exists( $wgLanguageCode, $languages ) ) {
00218                         $languages[$wgLanguageCode] = $wgLanguageCode;
00219                 }
00220                 
00221                 ksort( $languages );
00222 
00228                 $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
00229                 $options = "\n";
00230                 foreach( $languages as $code => $name ) {
00231                         $options .= Xml::option( "$code - $name", $code, ($code == $selected) ) . "\n";
00232                 }
00233 
00234                 return array(
00235                         Xml::label( wfMsg('yourlanguage'), 'wpUserLanguage' ),
00236                         Xml::tags( 'select',
00237                                 array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' ),
00238                                 $options
00239                         )
00240                 );
00241 
00242         }
00243 
00251         public static function span( $text, $class, $attribs = array() ) {
00252                 return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00253         }
00254 
00263         public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
00264                 return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00265         }
00266 
00275         public static function input( $name, $size = false, $value = false, $attribs = array() ) {
00276                 $attributes = array( 'name' => $name );
00277 
00278                 if( $size ) {
00279                         $attributes['size'] = $size;
00280                 }
00281 
00282                 if( $value !== false ) { // maybe 0
00283                         $attributes['value'] = $value;
00284                 }
00285 
00286                 return self::element( 'input', $attributes + $attribs );
00287         }
00288 
00297         public static function password( $name, $size = false, $value = false, $attribs = array() ) {
00298                 return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
00299         }
00300 
00309         public static function attrib( $name, $present = true ) {
00310                 return $present ? array( $name => $name ) : array();
00311         }
00312 
00320         public static function check( $name, $checked = false, $attribs=array() ) {
00321                 return self::element( 'input', array_merge(
00322                         array(
00323                                 'name' => $name,
00324                                 'type' => 'checkbox',
00325                                 'value' => 1 ),
00326                         self::attrib( 'checked', $checked ),
00327                         $attribs ) );
00328         }
00329 
00338         public static function radio( $name, $value, $checked = false, $attribs = array() ) {
00339                 return self::element( 'input', array(
00340                         'name' => $name,
00341                         'type' => 'radio',
00342                         'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00343         }
00344 
00355         public static function label( $label, $id, $attribs = array() ) {
00356                 $a = array( 'for' => $id );
00357 
00358                 # FIXME avoid copy pasting below:
00359                 if( isset( $attribs['class'] ) ){
00360                                 $a['class'] = $attribs['class'];
00361                 }
00362                 if( isset( $attribs['title'] ) ){
00363                                 $a['title'] = $attribs['title'];
00364                 }
00365 
00366                 return self::element( 'label', $a, $label );
00367         }
00368 
00379         public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
00380                 list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00381                 return $label . '&#160;' . $input;
00382         }
00383 
00396         public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
00397                 return array(
00398                         Xml::label( $label, $id, $attribs ),
00399                         self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00400                 );
00401         }
00402 
00414         public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
00415                 return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00416                         '&#160;' .
00417                         self::label( $label, $id, $attribs );
00418         }
00419 
00432         public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
00433                 return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00434                         '&#160;' .
00435                         self::label( $label, $id, $attribs );
00436         }
00437 
00444         public static function submitButton( $value, $attribs = array() ) {
00445                 return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
00446         }
00447 
00456         public static function option( $text, $value=null, $selected = false,
00457                         $attribs = array() ) {
00458                 if( !is_null( $value ) ) {
00459                         $attribs['value'] = $value;
00460                 }
00461                 if( $selected ) {
00462                         $attribs['selected'] = 'selected';
00463                 }
00464                 return Html::element( 'option', $attribs, $text );
00465         }
00466 
00478         public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
00479                 $optgroup = false;
00480 
00481                 $options = self::option( $other, 'other', $selected === 'other' );
00482 
00483                 foreach ( explode( "\n", $list ) as $option) {
00484                                 $value = trim( $option );
00485                                 if ( $value == '' ) {
00486                                         continue;
00487                                 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
00488                                         // A new group is starting ...
00489                                         $value = trim( substr( $value, 1 ) );
00490                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00491                                         $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00492                                         $optgroup = true;
00493                                 } elseif ( substr( $value, 0, 2) == '**' ) {
00494                                         // groupmember
00495                                         $value = trim( substr( $value, 2 ) );
00496                                         $options .= self::option( $value, $value, $selected === $value );
00497                                 } else {
00498                                         // groupless reason list
00499                                         if( $optgroup ) $options .= self::closeElement('optgroup');
00500                                         $options .= self::option( $value, $value, $selected === $value );
00501                                         $optgroup = false;
00502                                 }
00503                         }
00504 
00505                         if( $optgroup ) $options .= self::closeElement('optgroup');
00506 
00507                 $attribs = array();
00508 
00509                 if( $name ) {
00510                         $attribs['id'] = $name;
00511                         $attribs['name'] = $name;
00512                 }
00513 
00514                 if( $class ) {
00515                         $attribs['class'] = $class;
00516                 }
00517 
00518                 if( $tabindex ) {
00519                         $attribs['tabindex'] = $tabindex;
00520                 }
00521 
00522                 return Xml::openElement( 'select', $attribs )
00523                         . "\n"
00524                         . $options
00525                         . "\n"
00526                         . Xml::closeElement( 'select' );
00527         }
00528 
00538         public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00539                 $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00540 
00541                 if ( $legend ) {
00542                         $s .= Xml::element( 'legend', null, $legend ) . "\n";
00543                 }
00544 
00545                 if ( $content !== false ) {
00546                         $s .= $content . "\n";
00547                         $s .= Xml::closeElement( 'fieldset' ) . "\n";
00548                 }
00549 
00550                 return $s;
00551         }
00552 
00564         public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00565                 return self::element( 'textarea',
00566                                         array(  'name' => $name,
00567                                                 'id' => $name,
00568                                                 'cols' => $cols,
00569                                                 'rows' => $rows
00570                                         ) + $attribs,
00571                                         $content, false );
00572         }
00573 
00582         public static function escapeJsString( $string ) {
00583                 // See ECMA 262 section 7.8.4 for string literal format
00584                 $pairs = array(
00585                         "\\" => "\\\\",
00586                         "\"" => "\\\"",
00587                         '\'' => '\\\'',
00588                         "\n" => "\\n",
00589                         "\r" => "\\r",
00590 
00591                         # To avoid closing the element or CDATA section
00592                         "<" => "\\x3c",
00593                         ">" => "\\x3e",
00594 
00595                         # To avoid any complaints about bad entity refs
00596                         "&" => "\\x26",
00597 
00598                         # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
00599                         # Encode certain Unicode formatting chars so affected
00600                         # versions of Gecko don't misinterpret our strings;
00601                         # this is a common problem with Farsi text.
00602                         "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00603                         "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00604                 );
00605 
00606                 return strtr( $string, $pairs );
00607         }
00608 
00619         public static function encodeJsVar( $value ) {
00620                 if ( is_bool( $value ) ) {
00621                         $s = $value ? 'true' : 'false';
00622                 } elseif ( is_null( $value ) ) {
00623                         $s = 'null';
00624                 } elseif ( is_int( $value ) || is_float( $value ) ) {
00625                         $s = strval($value);
00626                 } elseif ( is_array( $value ) && // Make sure it's not associative.
00627                                         array_keys($value) === range( 0, count($value) - 1 ) ||
00628                                         count($value) == 0
00629                                 ) {
00630                         $s = '[';
00631                         foreach ( $value as $elt ) {
00632                                 if ( $s != '[' ) {
00633                                         $s .= ',';
00634                                 }
00635                                 $s .= self::encodeJsVar( $elt );
00636                         }
00637                         $s .= ']';
00638                 } elseif ( $value instanceof XmlJsCode ) {
00639                         $s = $value->value;
00640                 } elseif ( is_object( $value ) || is_array( $value ) ) {
00641                         // Objects and associative arrays
00642                         $s = '{';
00643                         foreach ( (array)$value as $name => $elt ) {
00644                                 if ( $s != '{' ) {
00645                                         $s .= ',';
00646                                 }
00647 
00648                                 $s .= '"' . self::escapeJsString( $name ) . '":' .
00649                                         self::encodeJsVar( $elt );
00650                         }
00651                         $s .= '}';
00652                 } else {
00653                         $s = '"' . self::escapeJsString( $value ) . '"';
00654                 }
00655                 return $s;
00656         }
00657 
00670         public static function encodeJsCall( $name, $args ) {
00671                 $s = "$name(";
00672                 $first = true;
00673 
00674                 foreach ( $args as $arg ) {
00675                         if ( $first ) {
00676                                 $first = false;
00677                         } else {
00678                                 $s .= ', ';
00679                         }
00680 
00681                         $s .= Xml::encodeJsVar( $arg );
00682                 }
00683 
00684                 $s .= ");\n";
00685 
00686                 return $s;
00687         }
00688 
00698         public static function isWellFormed( $text ) {
00699                 $parser = xml_parser_create( "UTF-8" );
00700 
00701                 # case folding violates XML standard, turn it off
00702                 xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00703 
00704                 if( !xml_parse( $parser, $text, true ) ) {
00705                         //$err = xml_error_string( xml_get_error_code( $parser ) );
00706                         //$position = xml_get_current_byte_index( $parser );
00707                         //$fragment = $this->extractFragment( $html, $position );
00708                         //$this->mXmlError = "$err at byte $position:\n$fragment";
00709                         xml_parser_free( $parser );
00710                         return false;
00711                 }
00712 
00713                 xml_parser_free( $parser );
00714 
00715                 return true;
00716         }
00717 
00726         public static function isWellFormedXmlFragment( $text ) {
00727                 $html =
00728                         Sanitizer::hackDocType() .
00729                         '<html>' .
00730                         $text .
00731                         '</html>';
00732 
00733                 return Xml::isWellFormed( $html );
00734         }
00735 
00743         public static function escapeTagsOnly( $in ) {
00744                 return str_replace(
00745                         array( '"', '>', '<' ),
00746                         array( '&quot;', '&gt;', '&lt;' ),
00747                         $in );
00748         }
00749 
00757         public static function buildForm( $fields, $submitLabel = null ) {
00758                 $form = '';
00759                 $form .= "<table><tbody>";
00760 
00761                 foreach( $fields as $labelmsg => $input ) {
00762                         $id = "mw-$labelmsg";
00763                         $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00764                         $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMsgExt( $labelmsg, array('parseinline') ) );
00765                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' );
00766                         $form .= Xml::closeElement( 'tr' );
00767                 }
00768 
00769                 if( $submitLabel ) {
00770                         $form .= Xml::openElement( 'tr' );
00771                         $form .= Xml::tags( 'td', array(), '' );
00772                         $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' );
00773                         $form .= Xml::closeElement( 'tr' );
00774                 }
00775 
00776                 $form .= "</tbody></table>";
00777 
00778                 return $form;
00779         }
00780 
00788         public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00789                 $s = Xml::openElement( 'table', $attribs );
00790 
00791                 if ( is_array( $headers ) ) {
00792                         $s .= Xml::openElement( 'thead', $attribs );
00793 
00794                         foreach( $headers as $id => $header ) {
00795                                 $attribs = array();
00796 
00797                                 if ( is_string( $id ) ) {
00798                                         $attribs['id'] = $id;
00799                                 }
00800 
00801                                 $s .= Xml::element( 'th', $attribs, $header );
00802                         }
00803                         $s .= Xml::closeElement( 'thead' );
00804                 }
00805 
00806                 foreach( $rows as $id => $row ) {
00807                         $attribs = array();
00808 
00809                         if ( is_string( $id ) ) {
00810                                 $attribs['id'] = $id;
00811                         }
00812 
00813                         $s .= Xml::buildTableRow( $attribs, $row );
00814                 }
00815 
00816                 $s .= Xml::closeElement( 'table' );
00817 
00818                 return $s;
00819         }
00820 
00827         public static function buildTableRow( $attribs, $cells ) {
00828                 $s = Xml::openElement( 'tr', $attribs );
00829 
00830                 foreach( $cells as $id => $cell ) {
00831 
00832                         $attribs = array();
00833 
00834                         if ( is_string( $id ) ) {
00835                                 $attribs['id'] = $id;
00836                         }
00837 
00838                         $s .= Xml::element( 'td', $attribs, $cell );
00839                 }
00840 
00841                 $s .= Xml::closeElement( 'tr' );
00842 
00843                 return $s;
00844         }
00845 }
00846 
00847 class XmlSelect {
00848         protected $options = array();
00849         protected $default = false;
00850         protected $attributes = array();
00851 
00852         public function __construct( $name = false, $id = false, $default = false ) {
00853                 if ( $name ) {
00854                         $this->setAttribute( 'name', $name );
00855                 }
00856 
00857                 if ( $id ) {
00858                         $this->setAttribute( 'id', $id );
00859                 }
00860 
00861                 if ( $default !== false ) {
00862                         $this->default = $default;
00863                 }
00864         }
00865 
00869         public function setDefault( $default ) {
00870                 $this->default = $default;
00871         }
00872 
00877         public function setAttribute( $name, $value ) {
00878                 $this->attributes[$name] = $value;
00879         }
00880 
00885         public function getAttribute( $name ) {
00886                 if ( isset( $this->attributes[$name] ) ) {
00887                         return $this->attributes[$name];
00888                 } else {
00889                         return null;
00890                 }
00891         }
00892 
00897         public function addOption( $name, $value = false ) {
00898                 // Stab stab stab
00899                 $value = ($value !== false) ? $value : $name;
00900 
00901                 $this->options[] = array( $name => $value );
00902         }
00903 
00911         public function addOptions( $options ) {
00912                 $this->options[] = $options;
00913         }
00914 
00924         static function formatOptions( $options, $default = false ) {
00925                 $data = '';
00926 
00927                 foreach( $options as $label => $value ) {
00928                         if ( is_array( $value ) ) {
00929                                 $contents = self::formatOptions( $value, $default );
00930                                 $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00931                         } else {
00932                                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00933                         }
00934                 }
00935 
00936                 return $data;
00937         }
00938 
00942         public function getHTML() {
00943                 $contents = '';
00944 
00945                 foreach ( $this->options as $options ) {
00946                         $contents .= self::formatOptions( $options, $this->default );
00947                 }
00948 
00949                 return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
00950         }
00951 }
00952 
00965 class XmlJsCode {
00966         public $value;
00967 
00968         function __construct( $value ) {
00969                 $this->value = $value;
00970         }
00971 }