MediaWiki  master
Html.php
Go to the documentation of this file.
00001 <?php
00050 class Html {
00051         // List of void elements from HTML5, section 8.1.2 as of 2011-08-12
00052         private static $voidElements = array(
00053                 'area',
00054                 'base',
00055                 'br',
00056                 'col',
00057                 'command',
00058                 'embed',
00059                 'hr',
00060                 'img',
00061                 'input',
00062                 'keygen',
00063                 'link',
00064                 'meta',
00065                 'param',
00066                 'source',
00067                 'track',
00068                 'wbr',
00069         );
00070 
00071         // Boolean attributes, which may have the value omitted entirely.  Manually
00072         // collected from the HTML5 spec as of 2011-08-12.
00073         private static $boolAttribs = array(
00074                 'async',
00075                 'autofocus',
00076                 'autoplay',
00077                 'checked',
00078                 'controls',
00079                 'default',
00080                 'defer',
00081                 'disabled',
00082                 'formnovalidate',
00083                 'hidden',
00084                 'ismap',
00085                 'itemscope',
00086                 'loop',
00087                 'multiple',
00088                 'muted',
00089                 'novalidate',
00090                 'open',
00091                 'pubdate',
00092                 'readonly',
00093                 'required',
00094                 'reversed',
00095                 'scoped',
00096                 'seamless',
00097                 'selected',
00098                 'truespeed',
00099                 'typemustmatch',
00100                 // HTML5 Microdata
00101                 'itemscope',
00102         );
00103 
00104         private static $HTMLFiveOnlyAttribs = array(
00105                 'autocomplete',
00106                 'autofocus',
00107                 'max',
00108                 'min',
00109                 'multiple',
00110                 'pattern',
00111                 'placeholder',
00112                 'required',
00113                 'step',
00114                 'spellcheck',
00115         );
00116 
00137         public static function rawElement( $element, $attribs = array(), $contents = '' ) {
00138                 global $wgWellFormedXml;
00139                 $start = self::openElement( $element, $attribs );
00140                 if ( in_array( $element, self::$voidElements ) ) {
00141                         if ( $wgWellFormedXml ) {
00142                                 // Silly XML.
00143                                 return substr( $start, 0, -1 ) . ' />';
00144                         }
00145                         return $start;
00146                 } else {
00147                         return "$start$contents" . self::closeElement( $element );
00148                 }
00149         }
00150 
00161         public static function element( $element, $attribs = array(), $contents = '' ) {
00162                 return self::rawElement( $element, $attribs, strtr( $contents, array(
00163                         // There's no point in escaping quotes, >, etc. in the contents of
00164                         // elements.
00165                         '&' => '&amp;',
00166                         '<' => '&lt;'
00167                 ) ) );
00168         }
00169 
00179         public static function openElement( $element, $attribs = array() ) {
00180                 global $wgHtml5, $wgWellFormedXml;
00181                 $attribs = (array)$attribs;
00182                 // This is not required in HTML5, but let's do it anyway, for
00183                 // consistency and better compression.
00184                 $element = strtolower( $element );
00185 
00186                 // In text/html, initial <html> and <head> tags can be omitted under
00187                 // pretty much any sane circumstances, if they have no attributes.  See:
00188                 // <http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags>
00189                 if ( !$wgWellFormedXml && !$attribs
00190                 && in_array( $element, array( 'html', 'head' ) ) ) {
00191                         return '';
00192                 }
00193 
00194                 // Remove invalid input types
00195                 if ( $element == 'input' ) {
00196                         $validTypes = array(
00197                                 'hidden',
00198                                 'text',
00199                                 'password',
00200                                 'checkbox',
00201                                 'radio',
00202                                 'file',
00203                                 'submit',
00204                                 'image',
00205                                 'reset',
00206                                 'button',
00207                         );
00208 
00209                         // Allow more input types in HTML5 mode
00210                         if( $wgHtml5 ) {
00211                                 $validTypes = array_merge( $validTypes, array(
00212                                         'datetime',
00213                                         'datetime-local',
00214                                         'date',
00215                                         'month',
00216                                         'time',
00217                                         'week',
00218                                         'number',
00219                                         'range',
00220                                         'email',
00221                                         'url',
00222                                         'search',
00223                                         'tel',
00224                                         'color',
00225                                 ) );
00226                         }
00227                         if ( isset( $attribs['type'] )
00228                         && !in_array( $attribs['type'], $validTypes ) ) {
00229                                 unset( $attribs['type'] );
00230                         }
00231                 }
00232 
00233                 if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) {
00234                         unset( $attribs['maxlength'] );
00235                 }
00236 
00237                 // According to standard the default type for <button> elements is "submit".
00238                 // Depending on compatibility mode IE might use "button", instead.
00239                 // We enforce the standard "submit".
00240                 if ( $element == 'button' && !isset( $attribs['type'] ) ) {
00241                         $attribs['type'] = 'submit';
00242                 }
00243 
00244                 return "<$element" . self::expandAttributes(
00245                         self::dropDefaults( $element, $attribs ) ) . '>';
00246         }
00247 
00256         public static function closeElement( $element ) {
00257                 global $wgWellFormedXml;
00258 
00259                 $element = strtolower( $element );
00260 
00261                 // Reference:
00262                 // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
00263                 if ( !$wgWellFormedXml && in_array( $element, array(
00264                         'html',
00265                         'head',
00266                         'body',
00267                         'li',
00268                         'dt',
00269                         'dd',
00270                         'tr',
00271                         'td',
00272                         'th',
00273                 ) ) ) {
00274                         return '';
00275                 }
00276                 return "</$element>";
00277         }
00278 
00296         private static function dropDefaults( $element, $attribs ) {
00297                 // Don't bother doing anything if we aren't outputting HTML5; it's too
00298                 // much of a pain to maintain two sets of defaults.
00299                 global $wgHtml5;
00300                 if ( !$wgHtml5 ) {
00301                         return $attribs;
00302                 }
00303 
00304                 // Whenever altering this array, please provide a covering test case
00305                 // in HtmlTest::provideElementsWithAttributesHavingDefaultValues
00306                 static $attribDefaults = array(
00307                         'area' => array( 'shape' => 'rect' ),
00308                         'button' => array(
00309                                 'formaction' => 'GET',
00310                                 'formenctype' => 'application/x-www-form-urlencoded',
00311                         ),
00312                         'canvas' => array(
00313                                 'height' => '150',
00314                                 'width' => '300',
00315                         ),
00316                         'command' => array( 'type' => 'command' ),
00317                         'form' => array(
00318                                 'action' => 'GET',
00319                                 'autocomplete' => 'on',
00320                                 'enctype' => 'application/x-www-form-urlencoded',
00321                         ),
00322                         'input' => array(
00323                                 'formaction' => 'GET',
00324                                 'type' => 'text',
00325                         ),
00326                         'keygen' => array( 'keytype' => 'rsa' ),
00327                         'link' => array( 'media' => 'all' ),
00328                         'menu' => array( 'type' => 'list' ),
00329                         // Note: the use of text/javascript here instead of other JavaScript
00330                         // MIME types follows the HTML5 spec.
00331                         'script' => array( 'type' => 'text/javascript' ),
00332                         'style' => array(
00333                                 'media' => 'all',
00334                                 'type' => 'text/css',
00335                         ),
00336                         'textarea' => array( 'wrap' => 'soft' ),
00337                 );
00338 
00339                 $element = strtolower( $element );
00340 
00341                 foreach ( $attribs as $attrib => $value ) {
00342                         $lcattrib = strtolower( $attrib );
00343                         if( is_array( $value ) ) {
00344                                 $value = implode( ' ', $value );
00345                         } else {
00346                                 $value = strval( $value );
00347                         }
00348 
00349                         // Simple checks using $attribDefaults
00350                         if ( isset( $attribDefaults[$element][$lcattrib] ) &&
00351                         $attribDefaults[$element][$lcattrib] == $value ) {
00352                                 unset( $attribs[$attrib] );
00353                         }
00354 
00355                         if ( $lcattrib == 'class' && $value == '' ) {
00356                                 unset( $attribs[$attrib] );
00357                         }
00358                 }
00359 
00360                 // More subtle checks
00361                 if ( $element === 'link' && isset( $attribs['type'] )
00362                 && strval( $attribs['type'] ) == 'text/css' ) {
00363                         unset( $attribs['type'] );
00364                 }
00365                 if ( $element === 'input' ) {
00366                         $type = isset( $attribs['type'] ) ? $attribs['type'] : null;
00367                         $value = isset( $attribs['value'] ) ? $attribs['value'] : null;
00368                         if ( $type === 'checkbox' || $type === 'radio' ) {
00369                                 // The default value for checkboxes and radio buttons is 'on'
00370                                 // not ''. By stripping value="" we break radio boxes that
00371                                 // actually wants empty values.
00372                                 if ( $value === 'on' ) {
00373                                         unset( $attribs['value'] );
00374                                 }
00375                         } elseif ( $type === 'submit' ) {
00376                                 // The default value for submit appears to be "Submit" but
00377                                 // let's not bother stripping out localized text that matches
00378                                 // that.
00379                         } else {
00380                                 // The default value for nearly every other field type is ''
00381                                 // The 'range' and 'color' types use different defaults but
00382                                 // stripping a value="" does not hurt them.
00383                                 if ( $value === '' ) {
00384                                         unset( $attribs['value'] );
00385                                 }
00386                         }
00387                 }
00388                 if ( $element === 'select' && isset( $attribs['size'] ) ) {
00389                         if ( in_array( 'multiple', $attribs )
00390                                 || ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false )
00391                         ) {
00392                                 // A multi-select
00393                                 if ( strval( $attribs['size'] ) == '4' ) {
00394                                         unset( $attribs['size'] );
00395                                 }
00396                         } else {
00397                                 // Single select
00398                                 if ( strval( $attribs['size'] ) == '1' ) {
00399                                         unset( $attribs['size'] );
00400                                 }
00401                         }
00402                 }
00403 
00404                 return $attribs;
00405         }
00406 
00446         public static function expandAttributes( $attribs ) {
00447                 global $wgHtml5, $wgWellFormedXml;
00448 
00449                 $ret = '';
00450                 $attribs = (array)$attribs;
00451                 foreach ( $attribs as $key => $value ) {
00452                         if ( $value === false || is_null( $value ) ) {
00453                                 continue;
00454                         }
00455 
00456                         // For boolean attributes, support array( 'foo' ) instead of
00457                         // requiring array( 'foo' => 'meaningless' ).
00458                         if ( is_int( $key )
00459                         && in_array( strtolower( $value ), self::$boolAttribs ) ) {
00460                                 $key = $value;
00461                         }
00462 
00463                         // Not technically required in HTML5, but required in XHTML 1.0,
00464                         // and we'd like consistency and better compression anyway.
00465                         $key = strtolower( $key );
00466 
00467                         // Here we're blacklisting some HTML5-only attributes...
00468                         if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs )
00469                          ) {
00470                                 continue;
00471                         }
00472 
00473                         // Bug 23769: Blacklist all form validation attributes for now.  Current
00474                         // (June 2010) WebKit has no UI, so the form just refuses to submit
00475                         // without telling the user why, which is much worse than failing
00476                         // server-side validation.  Opera is the only other implementation at
00477                         // this time, and has ugly UI, so just kill the feature entirely until
00478                         // we have at least one good implementation.
00479 
00480                         // As the default value of "1" for "step" rejects decimal
00481                         // numbers to be entered in 'type="number"' fields, allow
00482                         // the special case 'step="any"'.
00483 
00484                         if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) ) ||
00485                                  $key === 'step' && $value !== 'any' ) {
00486                                 continue;
00487                         }
00488 
00489                         // http://www.w3.org/TR/html401/index/attributes.html ("space-separated")
00490                         // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated")
00491                         $spaceSeparatedListAttributes = array(
00492                                 'class', // html4, html5
00493                                 'accesskey', // as of html5, multiple space-separated values allowed
00494                                 // html4-spec doesn't document rel= as space-separated
00495                                 // but has been used like that and is now documented as such
00496                                 // in the html5-spec.
00497                                 'rel',
00498                         );
00499 
00500                         // Specific features for attributes that allow a list of space-separated values
00501                         if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
00502                                 // Apply some normalization and remove duplicates
00503 
00504                                 // Convert into correct array. Array can contain space-seperated
00505                                 // values. Implode/explode to get those into the main array as well.
00506                                 if ( is_array( $value ) ) {
00507                                         // If input wasn't an array, we can skip this step
00508                                         $newValue = array();
00509                                         foreach ( $value as $k => $v ) {
00510                                                 if ( is_string( $v ) ) {
00511                                                         // String values should be normal `array( 'foo' )`
00512                                                         // Just append them
00513                                                         if ( !isset( $value[$v] ) ) {
00514                                                                 // As a special case don't set 'foo' if a
00515                                                                 // separate 'foo' => true/false exists in the array
00516                                                                 // keys should be authoritive
00517                                                                 $newValue[] = $v;
00518                                                         }
00519                                                 } elseif ( $v ) {
00520                                                         // If the value is truthy but not a string this is likely
00521                                                         // an array( 'foo' => true ), falsy values don't add strings
00522                                                         $newValue[] = $k;
00523                                                 }
00524                                         }
00525                                         $value = implode( ' ', $newValue );
00526                                 }
00527                                 $value = explode( ' ', $value );
00528 
00529                                 // Normalize spacing by fixing up cases where people used
00530                                 // more than 1 space and/or a trailing/leading space
00531                                 $value = array_diff( $value, array( '', ' ' ) );
00532 
00533                                 // Remove duplicates and create the string
00534                                 $value = implode( ' ', array_unique( $value ) );
00535                         }
00536 
00537                         // See the "Attributes" section in the HTML syntax part of HTML5,
00538                         // 9.1.2.3 as of 2009-08-10.  Most attributes can have quotation
00539                         // marks omitted, but not all.  (Although a literal " is not
00540                         // permitted, we don't check for that, since it will be escaped
00541                         // anyway.)
00542                         #
00543                         // See also research done on further characters that need to be
00544                         // escaped: http://code.google.com/p/html5lib/issues/detail?id=93
00545                         $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
00546                                 . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
00547                                 . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
00548                         if ( $wgWellFormedXml || $value === ''
00549                         || preg_match( "![$badChars]!u", $value ) ) {
00550                                 $quote = '"';
00551                         } else {
00552                                 $quote = '';
00553                         }
00554 
00555                         if ( in_array( $key, self::$boolAttribs ) ) {
00556                                 // In XHTML 1.0 Transitional, the value needs to be equal to the
00557                                 // key.  In HTML5, we can leave the value empty instead.  If we
00558                                 // don't need well-formed XML, we can omit the = entirely.
00559                                 if ( !$wgWellFormedXml ) {
00560                                         $ret .= " $key";
00561                                 } elseif ( $wgHtml5 ) {
00562                                         $ret .= " $key=\"\"";
00563                                 } else {
00564                                         $ret .= " $key=\"$key\"";
00565                                 }
00566                         } else {
00567                                 // Apparently we need to entity-encode \n, \r, \t, although the
00568                                 // spec doesn't mention that.  Since we're doing strtr() anyway,
00569                                 // and we don't need <> escaped here, we may as well not call
00570                                 // htmlspecialchars().
00571                                 // @todo FIXME: Verify that we actually need to
00572                                 // escape \n\r\t here, and explain why, exactly.
00573                                 #
00574                                 // We could call Sanitizer::encodeAttribute() for this, but we
00575                                 // don't because we're stubborn and like our marginal savings on
00576                                 // byte size from not having to encode unnecessary quotes.
00577                                 $map = array(
00578                                         '&' => '&amp;',
00579                                         '"' => '&quot;',
00580                                         "\n" => '&#10;',
00581                                         "\r" => '&#13;',
00582                                         "\t" => '&#9;'
00583                                 );
00584                                 if ( $wgWellFormedXml ) {
00585                                         // This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
00586                                         // But reportedly it breaks some XML tools?
00587                                         // @todo FIXME: Is this really true?
00588                                         $map['<'] = '&lt;';
00589                                 }
00590                                 $ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
00591                         }
00592                 }
00593                 return $ret;
00594         }
00595 
00605         public static function inlineScript( $contents ) {
00606                 global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
00607 
00608                 $attrs = array();
00609 
00610                 if ( !$wgHtml5 ) {
00611                         $attrs['type'] = $wgJsMimeType;
00612                 }
00613 
00614                 if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
00615                         $contents = "/*<![CDATA[*/$contents/*]]>*/";
00616                 }
00617 
00618                 return self::rawElement( 'script', $attrs, $contents );
00619         }
00620 
00628         public static function linkedScript( $url ) {
00629                 global $wgHtml5, $wgJsMimeType;
00630 
00631                 $attrs = array( 'src' => $url );
00632 
00633                 if ( !$wgHtml5 ) {
00634                         $attrs['type'] = $wgJsMimeType;
00635                 }
00636 
00637                 return self::element( 'script', $attrs );
00638         }
00639 
00649         public static function inlineStyle( $contents, $media = 'all' ) {
00650                 global $wgWellFormedXml;
00651 
00652                 if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
00653                         $contents = "/*<![CDATA[*/$contents/*]]>*/";
00654                 }
00655 
00656                 return self::rawElement( 'style', array(
00657                         'type' => 'text/css',
00658                         'media' => $media,
00659                 ), $contents );
00660         }
00661 
00670         public static function linkedStyle( $url, $media = 'all' ) {
00671                 return self::element( 'link', array(
00672                         'rel' => 'stylesheet',
00673                         'href' => $url,
00674                         'type' => 'text/css',
00675                         'media' => $media,
00676                 ) );
00677         }
00678 
00691         public static function input( $name, $value = '', $type = 'text', $attribs = array() ) {
00692                 $attribs['type'] = $type;
00693                 $attribs['value'] = $value;
00694                 $attribs['name'] = $name;
00695 
00696                 return self::element( 'input', $attribs );
00697         }
00698 
00708         public static function hidden( $name, $value, $attribs = array() ) {
00709                 return self::input( $name, $value, 'hidden', $attribs );
00710         }
00711 
00726         public static function textarea( $name, $value = '', $attribs = array() ) {
00727                 global $wgHtml5;
00728 
00729                 $attribs['name'] = $name;
00730 
00731                 if ( !$wgHtml5 ) {
00732                         if ( !isset( $attribs['cols'] ) ) {
00733                                 $attribs['cols'] = "";
00734                         }
00735 
00736                         if ( !isset( $attribs['rows'] ) ) {
00737                                 $attribs['rows'] = "";
00738                         }
00739                 }
00740 
00741                 if (substr($value, 0, 1) == "\n") {
00742                         // Workaround for bug 12130: browsers eat the initial newline
00743                         // assuming that it's just for show, but they do keep the later
00744                         // newlines, which we may want to preserve during editing.
00745                         // Prepending a single newline
00746                         $spacedValue = "\n" . $value;
00747                 } else {
00748                         $spacedValue = $value;
00749                 }
00750                 return self::element( 'textarea', $attribs, $spacedValue );
00751         }
00766         public static function namespaceSelector( array $params = array(), array $selectAttribs = array() ) {
00767                 global $wgContLang;
00768 
00769                 ksort( $selectAttribs );
00770 
00771                 // Is a namespace selected?
00772                 if ( isset( $params['selected'] ) ) {
00773                         // If string only contains digits, convert to clean int. Selected could also
00774                         // be "all" or "" etc. which needs to be left untouched.
00775                         // PHP is_numeric() has issues with large strings, PHP ctype_digit has other issues
00776                         // and returns false for already clean ints. Use regex instead..
00777                         if ( preg_match( '/^\d+$/', $params['selected'] ) ) {
00778                                 $params['selected'] = intval( $params['selected'] );
00779                         }
00780                         // else: leaves it untouched for later processing
00781                 } else {
00782                         $params['selected'] = '';
00783                 }
00784 
00785                 if ( !isset( $params['exclude'] ) || !is_array( $params['exclude'] ) ) {
00786                         $params['exclude'] = array();
00787                 }
00788                 if ( !isset( $params['disable'] ) || !is_array( $params['disable'] ) ) {
00789                         $params['disable'] = array();
00790                 }
00791 
00792                 // Associative array between option-values and option-labels
00793                 $options = array();
00794 
00795                 if ( isset( $params['all'] ) ) {
00796                         // add an option that would let the user select all namespaces.
00797                         // Value is provided by user, the name shown is localized for the user.
00798                         $options[$params['all']] = wfMessage( 'namespacesall' )->text();
00799                 }
00800                 // Add all namespaces as options (in the content langauge)
00801                 $options += $wgContLang->getFormattedNamespaces();
00802 
00803                 // Convert $options to HTML and filter out namespaces below 0
00804                 $optionsHtml = array();
00805                 foreach ( $options as $nsId => $nsName ) {
00806                         if ( $nsId < NS_MAIN || in_array( $nsId, $params['exclude'] ) ) {
00807                                 continue;
00808                         }
00809                         if ( $nsId === NS_MAIN ) {
00810                                 // For other namespaces use use the namespace prefix as label, but for
00811                                 // main we don't use "" but the user message descripting it (e.g. "(Main)" or "(Article)")
00812                                 $nsName = wfMessage( 'blanknamespace' )->text();
00813                         } elseif ( is_int( $nsId ) ) {
00814                                 $nsName = $wgContLang->convertNamespace( $nsId );
00815                         }
00816                         $optionsHtml[] = Html::element(
00817                                 'option', array(
00818                                         'disabled' => in_array( $nsId, $params['disable'] ),
00819                                         'value' => $nsId,
00820                                         'selected' => $nsId === $params['selected'],
00821                                 ), $nsName
00822                         );
00823                 }
00824 
00825                 if ( !array_key_exists( 'id', $selectAttribs ) ) {
00826                         $selectAttribs['id'] = 'namespace';
00827                 }
00828 
00829                 if ( !array_key_exists( 'name', $selectAttribs ) ) {
00830                         $selectAttribs['name'] = 'namespace';
00831                 }
00832 
00833                 $ret = '';
00834                 if ( isset( $params['label'] ) ) {
00835                         $ret .= Html::element(
00836                                 'label', array(
00837                                         'for' => isset( $selectAttribs['id'] ) ? $selectAttribs['id'] : null,
00838                                 ), $params['label']
00839                         ) . '&#160;';
00840                 }
00841 
00842                 // Wrap options in a <select>
00843                 $ret .= Html::openElement( 'select', $selectAttribs )
00844                         . "\n"
00845                         . implode( "\n", $optionsHtml )
00846                         . "\n"
00847                         . Html::closeElement( 'select' );
00848 
00849                 return $ret;
00850         }
00851 
00860         public static function htmlHeader( $attribs = array() ) {
00861                 $ret = '';
00862 
00863                 global $wgMimeType;
00864 
00865                 if ( self::isXmlMimeType( $wgMimeType ) ) {
00866                         $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
00867                 }
00868 
00869                 global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD;
00870                 global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace;
00871 
00872                 if ( $wgHtml5 ) {
00873                         $ret .= "<!DOCTYPE html>\n";
00874 
00875                         if ( $wgHtml5Version ) {
00876                                 $attribs['version'] = $wgHtml5Version;
00877                         }
00878                 } else {
00879                         $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
00880                         $attribs['xmlns'] = $wgXhtmlDefaultNamespace;
00881 
00882                         foreach ( $wgXhtmlNamespaces as $tag => $ns ) {
00883                                 $attribs["xmlns:$tag"] = $ns;
00884                         }
00885                 }
00886 
00887                 $html = Html::openElement( 'html', $attribs );
00888 
00889                 if ( $html ) {
00890                         $html .= "\n";
00891                 }
00892 
00893                 $ret .= $html;
00894 
00895                 return $ret;
00896         }
00897 
00904         public static function isXmlMimeType( $mimetype ) {
00905                 switch ( $mimetype ) {
00906                         case 'text/xml':
00907                         case 'application/xhtml+xml':
00908                         case 'application/xml':
00909                                 return true;
00910                         default:
00911                                 return false;
00912                 }
00913         }
00914 
00926         static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) {
00927                 global $wgStylePath;
00928 
00929                 if ( $useStylePath ) {
00930                         $icon = $wgStylePath.'/common/images/'.$icon;
00931                 }
00932 
00933                 $s  = Html::openElement( 'div', array( 'class' => "mw-infobox $class") );
00934 
00935                 $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
00936                                 Html::element( 'img',
00937                                         array(
00938                                                 'src' => $icon,
00939                                                 'alt' => $alt,
00940                                         )
00941                                 ).
00942                                 Html::closeElement( 'div' );
00943 
00944                 $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ).
00945                                 $text.
00946                                 Html::closeElement( 'div' );
00947                 $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
00948 
00949                 $s .= Html::closeElement( 'div' );
00950 
00951                 $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
00952 
00953                 return $s;
00954         }
00955 
00964         static function srcSet( $urls ) {
00965                 $candidates = array();
00966                 foreach( $urls as $density => $url ) {
00967                         // Image candidate syntax per current whatwg live spec, 2012-09-23:
00968                         // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset
00969                         $candidates[] = "{$url} {$density}x";
00970                 }
00971                 return implode( ", ", $candidates );
00972         }
00973 }