MediaWiki  REL1_21
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                                 continue;
00470                         }
00471 
00472                         // Bug 23769: Blacklist all form validation attributes for now.  Current
00473                         // (June 2010) WebKit has no UI, so the form just refuses to submit
00474                         // without telling the user why, which is much worse than failing
00475                         // server-side validation.  Opera is the only other implementation at
00476                         // this time, and has ugly UI, so just kill the feature entirely until
00477                         // we have at least one good implementation.
00478 
00479                         // As the default value of "1" for "step" rejects decimal
00480                         // numbers to be entered in 'type="number"' fields, allow
00481                         // the special case 'step="any"'.
00482 
00483                         if ( in_array( $key, array( 'max', 'min', 'pattern', 'required' ) ) ||
00484                                  $key === 'step' && $value !== 'any' ) {
00485                                 continue;
00486                         }
00487 
00488                         // http://www.w3.org/TR/html401/index/attributes.html ("space-separated")
00489                         // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated")
00490                         $spaceSeparatedListAttributes = array(
00491                                 'class', // html4, html5
00492                                 'accesskey', // as of html5, multiple space-separated values allowed
00493                                 // html4-spec doesn't document rel= as space-separated
00494                                 // but has been used like that and is now documented as such
00495                                 // in the html5-spec.
00496                                 'rel',
00497                         );
00498 
00499                         // Specific features for attributes that allow a list of space-separated values
00500                         if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
00501                                 // Apply some normalization and remove duplicates
00502 
00503                                 // Convert into correct array. Array can contain space-separated
00504                                 // values. Implode/explode to get those into the main array as well.
00505                                 if ( is_array( $value ) ) {
00506                                         // If input wasn't an array, we can skip this step
00507                                         $newValue = array();
00508                                         foreach ( $value as $k => $v ) {
00509                                                 if ( is_string( $v ) ) {
00510                                                         // String values should be normal `array( 'foo' )`
00511                                                         // Just append them
00512                                                         if ( !isset( $value[$v] ) ) {
00513                                                                 // As a special case don't set 'foo' if a
00514                                                                 // separate 'foo' => true/false exists in the array
00515                                                                 // keys should be authoritative
00516                                                                 $newValue[] = $v;
00517                                                         }
00518                                                 } elseif ( $v ) {
00519                                                         // If the value is truthy but not a string this is likely
00520                                                         // an array( 'foo' => true ), falsy values don't add strings
00521                                                         $newValue[] = $k;
00522                                                 }
00523                                         }
00524                                         $value = implode( ' ', $newValue );
00525                                 }
00526                                 $value = explode( ' ', $value );
00527 
00528                                 // Normalize spacing by fixing up cases where people used
00529                                 // more than 1 space and/or a trailing/leading space
00530                                 $value = array_diff( $value, array( '', ' ' ) );
00531 
00532                                 // Remove duplicates and create the string
00533                                 $value = implode( ' ', array_unique( $value ) );
00534                         }
00535 
00536                         // See the "Attributes" section in the HTML syntax part of HTML5,
00537                         // 9.1.2.3 as of 2009-08-10.  Most attributes can have quotation
00538                         // marks omitted, but not all.  (Although a literal " is not
00539                         // permitted, we don't check for that, since it will be escaped
00540                         // anyway.)
00541                         #
00542                         // See also research done on further characters that need to be
00543                         // escaped: http://code.google.com/p/html5lib/issues/detail?id=93
00544                         $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}"
00545                                 . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}"
00546                                 . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}";
00547                         if ( $wgWellFormedXml || $value === ''
00548                         || preg_match( "![$badChars]!u", $value ) ) {
00549                                 $quote = '"';
00550                         } else {
00551                                 $quote = '';
00552                         }
00553 
00554                         if ( in_array( $key, self::$boolAttribs ) ) {
00555                                 // In XHTML 1.0 Transitional, the value needs to be equal to the
00556                                 // key.  In HTML5, we can leave the value empty instead.  If we
00557                                 // don't need well-formed XML, we can omit the = entirely.
00558                                 if ( !$wgWellFormedXml ) {
00559                                         $ret .= " $key";
00560                                 } elseif ( $wgHtml5 ) {
00561                                         $ret .= " $key=\"\"";
00562                                 } else {
00563                                         $ret .= " $key=\"$key\"";
00564                                 }
00565                         } else {
00566                                 // Apparently we need to entity-encode \n, \r, \t, although the
00567                                 // spec doesn't mention that.  Since we're doing strtr() anyway,
00568                                 // and we don't need <> escaped here, we may as well not call
00569                                 // htmlspecialchars().
00570                                 // @todo FIXME: Verify that we actually need to
00571                                 // escape \n\r\t here, and explain why, exactly.
00572                                 #
00573                                 // We could call Sanitizer::encodeAttribute() for this, but we
00574                                 // don't because we're stubborn and like our marginal savings on
00575                                 // byte size from not having to encode unnecessary quotes.
00576                                 $map = array(
00577                                         '&' => '&amp;',
00578                                         '"' => '&quot;',
00579                                         "\n" => '&#10;',
00580                                         "\r" => '&#13;',
00581                                         "\t" => '&#9;'
00582                                 );
00583                                 if ( $wgWellFormedXml ) {
00584                                         // This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
00585                                         // But reportedly it breaks some XML tools?
00586                                         // @todo FIXME: Is this really true?
00587                                         $map['<'] = '&lt;';
00588                                 }
00589                                 $ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
00590                         }
00591                 }
00592                 return $ret;
00593         }
00594 
00604         public static function inlineScript( $contents ) {
00605                 global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
00606 
00607                 $attrs = array();
00608 
00609                 if ( !$wgHtml5 ) {
00610                         $attrs['type'] = $wgJsMimeType;
00611                 }
00612 
00613                 if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
00614                         $contents = "/*<![CDATA[*/$contents/*]]>*/";
00615                 }
00616 
00617                 return self::rawElement( 'script', $attrs, $contents );
00618         }
00619 
00627         public static function linkedScript( $url ) {
00628                 global $wgHtml5, $wgJsMimeType;
00629 
00630                 $attrs = array( 'src' => $url );
00631 
00632                 if ( !$wgHtml5 ) {
00633                         $attrs['type'] = $wgJsMimeType;
00634                 }
00635 
00636                 return self::element( 'script', $attrs );
00637         }
00638 
00648         public static function inlineStyle( $contents, $media = 'all' ) {
00649                 global $wgWellFormedXml;
00650 
00651                 if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
00652                         $contents = "/*<![CDATA[*/$contents/*]]>*/";
00653                 }
00654 
00655                 return self::rawElement( 'style', array(
00656                         'type' => 'text/css',
00657                         'media' => $media,
00658                 ), $contents );
00659         }
00660 
00669         public static function linkedStyle( $url, $media = 'all' ) {
00670                 return self::element( 'link', array(
00671                         'rel' => 'stylesheet',
00672                         'href' => $url,
00673                         'type' => 'text/css',
00674                         'media' => $media,
00675                 ) );
00676         }
00677 
00690         public static function input( $name, $value = '', $type = 'text', $attribs = array() ) {
00691                 $attribs['type'] = $type;
00692                 $attribs['value'] = $value;
00693                 $attribs['name'] = $name;
00694 
00695                 return self::element( 'input', $attribs );
00696         }
00697 
00707         public static function hidden( $name, $value, $attribs = array() ) {
00708                 return self::input( $name, $value, 'hidden', $attribs );
00709         }
00710 
00725         public static function textarea( $name, $value = '', $attribs = array() ) {
00726                 global $wgHtml5;
00727 
00728                 $attribs['name'] = $name;
00729 
00730                 if ( !$wgHtml5 ) {
00731                         if ( !isset( $attribs['cols'] ) ) {
00732                                 $attribs['cols'] = "";
00733                         }
00734 
00735                         if ( !isset( $attribs['rows'] ) ) {
00736                                 $attribs['rows'] = "";
00737                         }
00738                 }
00739 
00740                 if ( substr( $value, 0, 1 ) == "\n" ) {
00741                         // Workaround for bug 12130: browsers eat the initial newline
00742                         // assuming that it's just for show, but they do keep the later
00743                         // newlines, which we may want to preserve during editing.
00744                         // Prepending a single newline
00745                         $spacedValue = "\n" . $value;
00746                 } else {
00747                         $spacedValue = $value;
00748                 }
00749                 return self::element( 'textarea', $attribs, $spacedValue );
00750         }
00765         public static function namespaceSelector( array $params = array(), array $selectAttribs = array() ) {
00766                 global $wgContLang;
00767 
00768                 ksort( $selectAttribs );
00769 
00770                 // Is a namespace selected?
00771                 if ( isset( $params['selected'] ) ) {
00772                         // If string only contains digits, convert to clean int. Selected could also
00773                         // be "all" or "" etc. which needs to be left untouched.
00774                         // PHP is_numeric() has issues with large strings, PHP ctype_digit has other issues
00775                         // and returns false for already clean ints. Use regex instead..
00776                         if ( preg_match( '/^\d+$/', $params['selected'] ) ) {
00777                                 $params['selected'] = intval( $params['selected'] );
00778                         }
00779                         // else: leaves it untouched for later processing
00780                 } else {
00781                         $params['selected'] = '';
00782                 }
00783 
00784                 if ( !isset( $params['exclude'] ) || !is_array( $params['exclude'] ) ) {
00785                         $params['exclude'] = array();
00786                 }
00787                 if ( !isset( $params['disable'] ) || !is_array( $params['disable'] ) ) {
00788                         $params['disable'] = array();
00789                 }
00790 
00791                 // Associative array between option-values and option-labels
00792                 $options = array();
00793 
00794                 if ( isset( $params['all'] ) ) {
00795                         // add an option that would let the user select all namespaces.
00796                         // Value is provided by user, the name shown is localized for the user.
00797                         $options[$params['all']] = wfMessage( 'namespacesall' )->text();
00798                 }
00799                 // Add all namespaces as options (in the content language)
00800                 $options += $wgContLang->getFormattedNamespaces();
00801 
00802                 // Convert $options to HTML and filter out namespaces below 0
00803                 $optionsHtml = array();
00804                 foreach ( $options as $nsId => $nsName ) {
00805                         if ( $nsId < NS_MAIN || in_array( $nsId, $params['exclude'] ) ) {
00806                                 continue;
00807                         }
00808                         if ( $nsId === NS_MAIN ) {
00809                                 // For other namespaces use use the namespace prefix as label, but for
00810                                 // main we don't use "" but the user message describing it (e.g. "(Main)" or "(Article)")
00811                                 $nsName = wfMessage( 'blanknamespace' )->text();
00812                         } elseif ( is_int( $nsId ) ) {
00813                                 $nsName = $wgContLang->convertNamespace( $nsId );
00814                         }
00815                         $optionsHtml[] = Html::element(
00816                                 'option', array(
00817                                         'disabled' => in_array( $nsId, $params['disable'] ),
00818                                         'value' => $nsId,
00819                                         'selected' => $nsId === $params['selected'],
00820                                 ), $nsName
00821                         );
00822                 }
00823 
00824                 if ( !array_key_exists( 'id', $selectAttribs ) ) {
00825                         $selectAttribs['id'] = 'namespace';
00826                 }
00827 
00828                 if ( !array_key_exists( 'name', $selectAttribs ) ) {
00829                         $selectAttribs['name'] = 'namespace';
00830                 }
00831 
00832                 $ret = '';
00833                 if ( isset( $params['label'] ) ) {
00834                         $ret .= Html::element(
00835                                 'label', array(
00836                                         'for' => isset( $selectAttribs['id'] ) ? $selectAttribs['id'] : null,
00837                                 ), $params['label']
00838                         ) . '&#160;';
00839                 }
00840 
00841                 // Wrap options in a <select>
00842                 $ret .= Html::openElement( 'select', $selectAttribs )
00843                         . "\n"
00844                         . implode( "\n", $optionsHtml )
00845                         . "\n"
00846                         . Html::closeElement( 'select' );
00847 
00848                 return $ret;
00849         }
00850 
00859         public static function htmlHeader( $attribs = array() ) {
00860                 $ret = '';
00861 
00862                 global $wgMimeType;
00863 
00864                 if ( self::isXmlMimeType( $wgMimeType ) ) {
00865                         $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\n";
00866                 }
00867 
00868                 global $wgHtml5, $wgHtml5Version, $wgDocType, $wgDTD;
00869                 global $wgXhtmlNamespaces, $wgXhtmlDefaultNamespace;
00870 
00871                 if ( $wgHtml5 ) {
00872                         $ret .= "<!DOCTYPE html>\n";
00873 
00874                         if ( $wgHtml5Version ) {
00875                                 $attribs['version'] = $wgHtml5Version;
00876                         }
00877                 } else {
00878                         $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
00879                         $attribs['xmlns'] = $wgXhtmlDefaultNamespace;
00880 
00881                         foreach ( $wgXhtmlNamespaces as $tag => $ns ) {
00882                                 $attribs["xmlns:$tag"] = $ns;
00883                         }
00884                 }
00885 
00886                 $html = Html::openElement( 'html', $attribs );
00887 
00888                 if ( $html ) {
00889                         $html .= "\n";
00890                 }
00891 
00892                 $ret .= $html;
00893 
00894                 return $ret;
00895         }
00896 
00903         public static function isXmlMimeType( $mimetype ) {
00904                 switch ( $mimetype ) {
00905                         case 'text/xml':
00906                         case 'application/xhtml+xml':
00907                         case 'application/xml':
00908                                 return true;
00909                         default:
00910                                 return false;
00911                 }
00912         }
00913 
00925         static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) {
00926                 global $wgStylePath;
00927 
00928                 if ( $useStylePath ) {
00929                         $icon = $wgStylePath.'/common/images/'.$icon;
00930                 }
00931 
00932                 $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) );
00933 
00934                 $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
00935                                 Html::element( 'img',
00936                                         array(
00937                                                 'src' => $icon,
00938                                                 'alt' => $alt,
00939                                         )
00940                                 ).
00941                                 Html::closeElement( 'div' );
00942 
00943                 $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ).
00944                                 $text.
00945                                 Html::closeElement( 'div' );
00946                 $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
00947 
00948                 $s .= Html::closeElement( 'div' );
00949 
00950                 $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
00951 
00952                 return $s;
00953         }
00954 
00963         static function srcSet( $urls ) {
00964                 $candidates = array();
00965                 foreach( $urls as $density => $url ) {
00966                         // Image candidate syntax per current whatwg live spec, 2012-09-23:
00967                         // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset
00968                         $candidates[] = "{$url} {$density}x";
00969                 }
00970                 return implode( ", ", $candidates );
00971         }
00972 }