Partial revert of r56602: remove what is probably accidentally committed debugging...
[lhc/web/wiklou.git] / includes / Html.php
index 1c762b6..8aa1de6 100644 (file)
@@ -100,6 +100,7 @@ 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.
         * @param $contents string The raw HTML contents of the element: *not*
         *   escaped!
         * @return string Raw HTML
@@ -152,7 +153,8 @@ class Html {
                        }
                }
 
-               $start = "<$element" . self::expandAttributes( $attribs );
+               $start = "<$element" . self::expandAttributes(
+                       self::dropDefaults( $element, $attribs ) );
                if ( in_array( $element, self::$voidElements ) ) {
                        if ( $wgWellFormedXml ) {
                                return "$start />";
@@ -176,6 +178,106 @@ class Html {
                ) ) );
        }
 
+       /**
+        * Given an element name and an associative array of element attributes,
+        * return an array that is functionally identical to the input array, but
+        * possibly smaller.  In particular, attributes might be stripped if they
+        * are given their default values.
+        *
+        * This method is not guaranteed to remove all redundant attributes, only
+        * some common ones and some others selected arbitrarily at random.  It
+        * only guarantees that the output array should be functionally identical
+        * to the input array (currently per the HTML 5 draft as of 2009-09-06).
+        *
+        * @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/' ).
+        * @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(
+                               'formaction' => 'GET',
+                               'formenctype' => 'application/x-www-form-urlencoded',
+                               'type' => 'submit',
+                       ),
+                       'canvas' => array(
+                               'height' => '150',
+                               'width' => '300',
+                       ),
+                       'command' => array( 'type' => 'command' ),
+                       'form' => array(
+                               'action' => 'GET',
+                               'autocomplete' => 'on',
+                               'enctype' => 'application/x-www-form-urlencoded',
+                       ),
+                       'input' => array(
+                               'formaction' => 'GET',
+                               'type' => 'text',
+                               'value' => '',
+                       ),
+                       'keygen' => array( 'keytype' => 'rsa' ),
+                       '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.
+                       'script' => array( 'type' => 'text/javascript' ),
+                       'style' => array(
+                               'media' => 'all',
+                               'type' => 'text/css',
+                       ),
+                       'textarea' => array( 'wrap' => 'soft' ),
+               );
+
+               $element = strtolower( $element );
+
+               foreach ( $attribs as $attrib => $value ) {
+                       $lcattrib = strtolower( $attrib );
+                       $value = strval( $value );
+
+                       # Simple checks using $attribDefaults
+                       if ( isset( $attribDefaults[$element][$lcattrib] ) &&
+                       $attribDefaults[$element][$lcattrib] == $value ) {
+                               unset( $attribs[$attrib] );
+                       }
+
+                       if ( $lcattrib == 'class' && $value == '' ) {
+                               unset( $attribs[$attrib] );
+                       }
+               }
+
+               # More subtle checks
+               if ( $element === 'link' && isset( $attribs['type'] )
+               && strval( $attribs['type'] ) == 'text/css' ) {
+                       unset( $attribs['type'] );
+               }
+               if ( $element === 'select' && isset( $attribs['size'] ) ) {
+                       if ( in_array( 'multiple', $attribs )
+                               || ( isset( $attribs['multiple'] ) && $attribs['multiple'] !== false )
+                       ) {
+                               # A multi-select
+                               if ( strval( $attribs['size'] ) == '4' ) {
+                                       unset( $attribs['size'] );
+                               }
+                       } else {
+                               # Single select
+                               if ( strval( $attribs['size'] ) == '1' ) {
+                                       unset( $attribs['size'] );
+                               }
+                       }
+               }
+
+               return $attribs;
+       }
+
        /**
         * Given an associative array of element attributes, generate a string
         * to stick after the element name in HTML output.  Like array( 'href' =>
@@ -187,14 +289,19 @@ 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.
         * @return string HTML fragment that goes between element name and '>'
         *   (starting with a space if at least one attribute is output)
         */
-       public static function expandAttributes( $attribs ) {
+       public static function expandAttributes( $attribs = array() ) {
                global $wgHtml5, $wgWellFormedXml;
 
                $ret = '';
                foreach ( $attribs as $key => $value ) {
+                       if ( $value === false ) {
+                               continue;
+                       }
+
                        # For boolean attributes, support array( 'foo' ) instead of
                        # requiring array( 'foo' => 'meaningless' ).
                        if ( is_int( $key )
@@ -211,7 +318,7 @@ class Html {
                        # marks omitted, but not all.  (Although a literal " is not
                        # permitted, we don't check for that, since it will be escaped
                        # anyway.)
-                       if ( $wgWellFormedXml || $value == ''
+                       if ( $wgWellFormedXml || $value === ''
                        || preg_match( "/[ '=<>]/", $value ) ) {
                                $quote = '"';
                        } else {
@@ -235,13 +342,17 @@ 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(
-                                       '&' => '&amp;',
-                                       '"' => '&quot;',
-                                       "\n" => '&#10;',
-                                       "\r" => '&#13;',
-                                       "\t" => '&#9;'
-                               ) ) . $quote;
+                               if ( $wgHtml5 ) {
+                                       $ret .= " $key=$quote" . strtr( $value, array(
+                                               '&' => '&amp;',
+                                               '"' => '&quot;',
+                                               "\n" => '&#10;',
+                                               "\r" => '&#13;',
+                                               "\t" => '&#9;'
+                                       ) ) . $quote;
+                               } else {
+                                       $ret .= " $key=$quote" . Sanitizer::encodeAttribute( $value ) . $quote;
+                               }
                        }
                }
                return $ret;
@@ -256,11 +367,13 @@ class Html {
         * @return string Raw HTML
         */
        public static function inlineScript( $contents ) {
-               global $wgHtml5, $wgJsMimeType;
+               global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
 
                $attrs = array();
                if ( !$wgHtml5 ) {
                        $attrs['type'] = $wgJsMimeType;
+               }
+               if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
                        $contents = "/*<![CDATA[*/$contents/*]]>*/";
                }
                return self::rawElement( 'script', $attrs, $contents );
@@ -289,24 +402,19 @@ class Html {
         * contains literal '</style>' (admittedly unlikely).
         *
         * @param $contents string CSS
-        * @param $media mixed A media type string, like 'screen', or null for all
-        *   media
+        * @param $media mixed A media type string, like 'screen'
         * @return string Raw HTML
         */
-       public static function inlineStyle( $contents, $media = null ) {
-               global $wgHtml5;
+       public static function inlineStyle( $contents, $media = 'all' ) {
+               global $wgWellFormedXml;
 
-               $attrs = array();
-               if ( !$wgHtml5 ) {
-                       # Technically we should probably add CDATA stuff here like with
-                       # scripts, but in practice, stylesheets tend not to have
-                       # problematic characters anyway.
-                       $attrs['type'] = 'text/css';
-               }
-               if ( $media !== null ) {
-                       $attrs['media'] = $media;
+               if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
+                       $contents = "/*<![CDATA[*/$contents/*]]>*/";
                }
-               return self::rawElement( 'style', $attrs, $contents );
+               return self::rawElement( 'style', array(
+                       'type' => 'text/css',
+                       'media' => $media,
+               ), $contents );
        }
 
        /**
@@ -314,21 +422,16 @@ class Html {
         * media type (if any).
         *
         * @param $url string
-        * @param $media mixed A media type string, like 'screen', or null for all
-        *   media
+        * @param $media mixed A media type string, like 'screen'
         * @return string Raw HTML
         */
-       public static function linkedStyle( $url, $media = null ) {
-               global $wgHtml5;
-
-               $attrs = array( 'rel' => 'stylesheet', 'href' => $url );
-               if ( !$wgHtml5 ) {
-                       $attrs['type'] = 'text/css';
-               }
-               if ( $media !== null ) {
-                       $attrs['media'] = $media;
-               }
-               return self::element( 'link', $attrs );
+       public static function linkedStyle( $url, $media = 'all' ) {
+               return self::element( 'link', array(
+                       'rel' => 'stylesheet',
+                       'href' => $url,
+                       'type' => 'text/css',
+                       'media' => $media,
+               ) );
        }
 
        /**
@@ -337,19 +440,15 @@ class Html {
         * $wgHtml5 is false.
         *
         * @param $name    string name attribute
-        * @param $value   mixed  value attribute (null = omit)
+        * @param $value   mixed  value attribute
         * @param $type    string type attribute
         * @param $attribs array  Associative array of miscellaneous extra
         *   attributes, passed to Html::element()
         * @return string Raw HTML
         */
-       public static function input( $name, $value = null, $type = 'text', $attribs = array() ) {
-               if ( $type != 'text' ) {
-                       $attribs['type'] = $type;
-               }
-               if ( $value !== null && $value !== '' ) {
-                       $attribs['value'] = $value;
-               }
+       public static function input( $name, $value = '', $type = 'text', $attribs = array() ) {
+               $attribs['type'] = $type;
+               $attribs['value'] = $value;
                $attribs['name'] = $name;
 
                return self::element( 'input', $attribs );