X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FHtml.php;h=b56a6d0c959e8743b4e2768fba54b6a59b40ecf5;hb=50fb614a08ac7c5c8f361fb0de9666b8cfd5e8f0;hp=089e3f7980bbb120f292e8e8039201dccee7d8b1;hpb=a039be051b946c4a25b04009b693f486b71ea90a;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Html.php b/includes/Html.php index 089e3f7980..b56a6d0c95 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -1,5 +1,5 @@ elements. + * + * @since 1.16 */ class Html { - # List of void elements from HTML 5, section 9.1.2 as of 2009-08-10 + # List of void elements from HTML5, section 9.1.2 as of 2009-08-10 private static $voidElements = array( 'area', 'base', @@ -59,7 +61,7 @@ class Html { ); # Boolean attributes, which may have the value omitted entirely. Manually - # collected from the HTML 5 spec as of 2009-08-10. + # collected from the HTML5 spec as of 2009-08-10. private static $boolAttribs = array( 'async', 'autobuffer', @@ -99,25 +101,62 @@ class Html { * * @param $element string The element's name, e.g., 'a' * @param $attribs array Associative array of attributes, e.g., array( - * 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped. - * A value of false means to omit the attribute. + * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for + * further documentation. * @param $contents string The raw HTML contents of the element: *not* * escaped! * @return string Raw HTML */ public static function rawElement( $element, $attribs = array(), $contents = '' ) { + global $wgWellFormedXml; + $start = self::openElement( $element, $attribs ); + if ( in_array( $element, self::$voidElements ) ) { + if ( $wgWellFormedXml ) { + # Silly XML. + return substr( $start, 0, -1 ) . ' />'; + } + return $start; + } else { + return "$start$contents" . self::closeElement( $element ); + } + } + + /** + * Identical to rawElement(), but HTML-escapes $contents (like + * Xml::element()). + */ + public static function element( $element, $attribs = array(), $contents = '' ) { + return self::rawElement( $element, $attribs, strtr( $contents, array( + # There's no point in escaping quotes, >, etc. in the contents of + # elements. + '&' => '&', + '<' => '<' + ) ) ); + } + + /** + * Identical to rawElement(), but has no third parameter and omits the end + * tag (and the self-closing '/' in XML mode for empty elements). + */ + public static function openElement( $element, $attribs = array() ) { global $wgHtml5, $wgWellFormedXml; - # This is not required in HTML 5, but let's do it anyway, for + $attribs = (array)$attribs; + # This is not required in HTML5, but let's do it anyway, for # consistency and better compression. $element = strtolower( $element ); - # Element-specific hacks to slim down output and ensure validity - if ( $element == 'input' ) { - if ( !$wgHtml5 ) { - # With $wgHtml5 off we want to validate as XHTML 1, so we - # strip out any fancy HTML 5-only input types for now. - # - # Whitelist of valid types: + # In text/html, initial and tags can be omitted under + # pretty much any sane circumstances, if they have no attributes. See: + # + if ( !$wgWellFormedXml && !$attribs + && in_array( $element, array( 'html', 'head' ) ) ) { + return ''; + } + + # Remove HTML5-only attributes if we aren't doing HTML5 + if ( !$wgHtml5 ) { + if ( $element == 'input' ) { + # Whitelist of valid XHTML1 types $validTypes = array( 'hidden', 'text', @@ -135,47 +174,60 @@ class Html { # Fall back to type=text, the default unset( $attribs['type'] ); } - # Here we're blacklisting some HTML5-only attributes... - $html5attribs = array( - 'autocomplete', - 'autofocus', - 'max', - 'min', - 'multiple', - 'pattern', - 'placeholder', - 'required', - 'step', - ); - foreach ( $html5attribs as $badAttr ) { - unset( $attribs[$badAttr] ); - } } - } - - $start = "<$element" . self::expandAttributes( - self::dropDefaults( $element, $attribs ) ); - if ( in_array( $element, self::$voidElements ) ) { - if ( $wgWellFormedXml ) { - return "$start />"; + if ( $element == 'textarea' && isset( $attribs['maxlength'] ) ) { + unset( $attribs['maxlength'] ); + } + # Here we're blacklisting some HTML5-only attributes... + $html5attribs = array( + 'autocomplete', + 'autofocus', + 'max', + 'min', + 'multiple', + 'pattern', + 'placeholder', + 'required', + 'step', + 'spellcheck', + ); + foreach ( $html5attribs as $badAttr ) { + unset( $attribs[$badAttr] ); } - return "$start>"; - } else { - return "$start>$contents"; } + + return "<$element" . self::expandAttributes( + self::dropDefaults( $element, $attribs ) ) . '>'; } /** - * Identical to rawElement(), but HTML-escapes $contents (like - * Xml::element()). + * Returns "", except if $wgWellFormedXml is off, in which case + * it returns the empty string when that's guaranteed to be safe. + * + * @param $element string Name of the element, e.g., 'a' + * @return string A closing tag, if required */ - public static function element( $element, $attribs = array(), $contents = '' ) { - return self::rawElement( $element, $attribs, strtr( $contents, array( - # There's no point in escaping quotes, >, etc. in the contents of - # elements. - '&' => '&', - '<' => '<' - ) ) ); + public static function closeElement( $element ) { + global $wgWellFormedXml; + + $element = strtolower( $element ); + + # Reference: + # http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags + if ( !$wgWellFormedXml && in_array( $element, array( + 'html', + 'head', + 'body', + 'li', + 'dt', + 'dd', + 'tr', + 'td', + 'th', + ) ) ) { + return ''; + } + return ""; } /** @@ -191,10 +243,18 @@ class Html { * * @param $element string Name of the element, e.g., 'a' * @param $attribs array Associative array of attributes, e.g., array( - * 'href' => 'http://www.mediawiki.org/' ). + * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for + * further documentation. * @return array An array of attributes functionally identical to $attribs */ private static function dropDefaults( $element, $attribs ) { + # Don't bother doing anything if we aren't outputting HTML5; it's too + # much of a pain to maintain two sets of defaults. + global $wgHtml5; + if ( !$wgHtml5 ) { + return $attribs; + } + static $attribDefaults = array( 'area' => array( 'shape' => 'rect' ), 'button' => array( @@ -221,7 +281,7 @@ class Html { 'link' => array( 'media' => 'all' ), 'menu' => array( 'type' => 'list' ), # Note: the use of text/javascript here instead of other JavaScript - # MIME types follows the HTML 5 spec. + # MIME types follows the HTML5 spec. 'script' => array( 'type' => 'text/javascript' ), 'style' => array( 'media' => 'all', @@ -232,19 +292,24 @@ class Html { $element = strtolower( $element ); - # Simple checks using $attribDefaults foreach ( $attribs as $attrib => $value ) { $lcattrib = strtolower( $attrib ); + $value = strval( $value ); + # Simple checks using $attribDefaults if ( isset( $attribDefaults[$element][$lcattrib] ) && - $attribDefaults[$element][$lcattrib] === $value ) { + $attribDefaults[$element][$lcattrib] == $value ) { + unset( $attribs[$attrib] ); + } + + if ( $lcattrib == 'class' && $value == '' ) { unset( $attribs[$attrib] ); } } # More subtle checks if ( $element === 'link' && isset( $attribs['type'] ) - && $attribs['type'] === 'text/css' ) { + && strval( $attribs['type'] ) == 'text/css' ) { unset( $attribs['type'] ); } if ( $element === 'select' && isset( $attribs['size'] ) ) { @@ -252,12 +317,12 @@ class Html { || ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false ) ) { # A multi-select - if ( $attribs['size'] === '4' ) { + if ( strval( $attribs['size'] ) == '4' ) { unset( $attribs['size'] ); } } else { # Single select - if ( $attribs['size'] === '1' ) { + if ( strval( $attribs['size'] ) == '1' ) { unset( $attribs['size'] ); } } @@ -277,7 +342,9 @@ class Html { * * @param $attribs array Associative array of attributes, e.g., array( * 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped. - * A value of false means to omit the attribute. + * A value of false means to omit the attribute. For boolean attributes, + * you can omit the key, e.g., array( 'checked' ) instead of + * array( 'checked' => 'checked' ) or such. * @return string HTML fragment that goes between element name and '>' * (starting with a space if at least one attribute is output) */ @@ -285,6 +352,7 @@ class Html { global $wgHtml5, $wgWellFormedXml; $ret = ''; + $attribs = (array)$attribs; foreach ( $attribs as $key => $value ) { if ( $value === false ) { continue; @@ -297,17 +365,23 @@ class Html { $key = $value; } - # Not technically required in HTML 5, but required in XHTML 1.0, + # Not technically required in HTML5, but required in XHTML 1.0, # and we'd like consistency and better compression anyway. $key = strtolower( $key ); - # See the "Attributes" section in the HTML syntax part of HTML 5, + # See the "Attributes" section in the HTML syntax part of HTML5, # 9.1.2.3 as of 2009-08-10. Most attributes can have quotation # marks omitted, but not all. (Although a literal " is not # permitted, we don't check for that, since it will be escaped # anyway.) + # + # See also research done on further characters that need to be + # escaped: http://code.google.com/p/html5lib/issues/detail?id=93 + $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}" + . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}" + . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}"; if ( $wgWellFormedXml || $value === '' - || preg_match( "/[ '=<>]/", $value ) ) { + || preg_match( "![$badChars]!u", $value ) ) { $quote = '"'; } else { $quote = ''; @@ -315,7 +389,7 @@ class Html { if ( in_array( $key, self::$boolAttribs ) ) { # In XHTML 1.0 Transitional, the value needs to be equal to the - # key. In HTML 5, we can leave the value empty instead. If we + # key. In HTML5, we can leave the value empty instead. If we # don't need well-formed XML, we can omit the = entirely. if ( !$wgWellFormedXml ) { $ret .= " $key"; @@ -330,13 +404,24 @@ class Html { # and we don't need <> escaped here, we may as well not call # htmlspecialchars(). FIXME: verify that we actually need to # escape \n\r\t here, and explain why, exactly. - $ret .= " $key=$quote" . strtr( $value, array( + # + # We could call Sanitizer::encodeAttribute() for this, but we + # don't because we're stubborn and like our marginal savings on + # byte size from not having to encode unnecessary quotes. + $map = array( '&' => '&', '"' => '"', "\n" => ' ', "\r" => ' ', "\t" => ' ' - ) ) . $quote; + ); + if ( $wgWellFormedXml ) { + # This is allowed per spec: + # But reportedly it breaks some XML tools? FIXME: is this + # really true? + $map['<'] = '<'; + } + $ret .= " $key=$quote" . strtr( $value, $map ) . $quote; } } return $ret; @@ -420,7 +505,7 @@ class Html { /** * Convenience function to produce an element. This supports the - * new HTML 5 input types and attributes, and will silently strip them if + * new HTML5 input types and attributes, and will silently strip them if * $wgHtml5 is false. * * @param $name string name attribute @@ -451,4 +536,29 @@ class Html { public static function hidden( $name, $value, $attribs = array() ) { return self::input( $name, $value, 'hidden', $attribs ); } + + /** + * Convenience function to produce an element. This supports leaving + * out the cols= and rows= which Xml requires and are required by HTML4/XHTML + * but not required by HTML5 and will silently set cols="" and rows="" if + * $wgHtml5 is false and cols and rows are omitted (HTML4 validates present + * but empty cols="" and rows="" as valid). + * + * @param $name string name attribute + * @param $value string value attribute + * @param $attribs array Associative array of miscellaneous extra + * attributes, passed to Html::element() + * @return string Raw HTML + */ + public static function textarea( $name, $value = '', $attribs = array() ) { + global $wgHtml5; + $attribs['name'] = $name; + if ( !$wgHtml5 ) { + if ( !isset( $attribs['cols'] ) ) + $attribs['cols'] = ""; + if ( !isset( $attribs['rows'] ) ) + $attribs['rows'] = ""; + } + return self::element( 'textarea', $attribs, $value ); + } }