From 0df3218cc2e585d4054f16cdfc6faa92b56c25da Mon Sep 17 00:00:00 2001 From: koopersmith Date: Sat, 26 May 2012 18:44:31 +0000 Subject: [PATCH] Theme Customizer: Ensure that JS color controls always use real color values, even if the server-side value is a hex value without a hash. fixes #20448, see #19910. Adds WP_Customize_Setting->sanitize_js_callback and 'customize_sanitize_js_$settingID' filter, to filter values before they're passed to JS using WP_Customize_Setting->js_value(). Adds support for regular hex colors to the color picker. Changes color methods: * sanitize_hex_color() accepts 3 and 6 digit hex colors (with hashes) and the empty string. * sanitize_hex_color_no_hash() accepts 3 and 6 digit hex colors (without hashes) and the empty string. * maybe_hash_hex_color() ensures that a hex color has a hash, and otherwise leaves the value untouched. git-svn-id: http://core.svn.wordpress.org/trunk@20936 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- .../themes/twentyeleven/inc/theme-options.php | 13 +-- wp-includes/class-wp-customize-control.php | 5 +- wp-includes/class-wp-customize-manager.php | 82 +++++++++++++++---- wp-includes/class-wp-customize-setting.php | 25 +++--- wp-includes/css/customize-controls.dev.css | 34 ++------ wp-includes/js/customize-controls.dev.js | 14 ++-- wp-includes/js/customize-preview.dev.js | 2 +- 7 files changed, 101 insertions(+), 74 deletions(-) diff --git a/wp-content/themes/twentyeleven/inc/theme-options.php b/wp-content/themes/twentyeleven/inc/theme-options.php index 833ba4c547..698f2a0b5e 100644 --- a/wp-content/themes/twentyeleven/inc/theme-options.php +++ b/wp-content/themes/twentyeleven/inc/theme-options.php @@ -483,7 +483,7 @@ function twentyeleven_customize_register( $wp_customize ) { $wp_customize->add_setting( 'twentyeleven_theme_options[link_color]', array( 'default' => twentyeleven_get_default_link_color( $options['color_scheme'] ), 'type' => 'option', - 'sanitize_callback' => 'twentyeleven_sanitize_hexcolor', + 'sanitize_callback' => 'sanitize_hex_color', 'capability' => 'edit_theme_options', ) ); @@ -519,17 +519,6 @@ function twentyeleven_customize_register( $wp_customize ) { } add_action( 'customize_register', 'twentyeleven_customize_register' ); -/** - * Sanitize user input hex color value - * - * @uses sanitize_hexcolor() - * @param $color string - * @return string sanitized with prefixed # character - */ -function twentyeleven_sanitize_hexcolor( $color ) { - return '#' . sanitize_hexcolor( $color ); -} - /** * Bind JS handlers to make Theme Customizer preview reload changes asynchronously. * Used with blogname and blogdescription. diff --git a/wp-includes/class-wp-customize-control.php b/wp-includes/class-wp-customize-control.php index 554aa35931..3f936f39e6 100644 --- a/wp-includes/class-wp-customize-control.php +++ b/wp-includes/class-wp-customize-control.php @@ -265,10 +265,7 @@ class WP_Customize_Color_Control extends WP_Customize_Control { -
- # - link(); ?> /> -
+
diff --git a/wp-includes/class-wp-customize-manager.php b/wp-includes/class-wp-customize-manager.php index 55e46a907b..40ae19b47f 100644 --- a/wp-includes/class-wp-customize-manager.php +++ b/wp-includes/class-wp-customize-manager.php @@ -667,9 +667,11 @@ final class WP_Customize_Manager { ) ); $this->add_setting( 'header_textcolor', array( - 'sanitize_callback' => 'sanitize_header_textcolor', 'theme_supports' => array( 'custom-header', 'header-text' ), 'default' => get_theme_support( 'custom-header', 'default-text-color' ), + + 'sanitize_callback' => array( $this, '_sanitize_header_textcolor' ), + 'sanitize_js_callback' => 'maybe_hash_hex_color', ) ); // Input type: checkbox @@ -689,9 +691,11 @@ final class WP_Customize_Manager { // Input type: Color // With sanitize_callback $this->add_setting( 'background_color', array( - 'default' => get_theme_support( 'custom-background', 'default-color' ), - 'sanitize_callback' => 'sanitize_hexcolor', - 'theme_supports' => 'custom-background', + 'default' => get_theme_support( 'custom-background', 'default-color' ), + 'theme_supports' => 'custom-background', + + 'sanitize_callback' => 'sanitize_hex_color_no_hash', + 'sanitize_js_callback' => 'maybe_hash_hex_color', ) ); $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array( @@ -876,23 +880,69 @@ final class WP_Customize_Manager { 'type' => 'dropdown-pages', ) ); } + + /** + * Callback for validating the header_textcolor value. + * + * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). + * + * @since 3.4.0 + */ + public function _sanitize_header_textcolor( $color ) { + return ( 'blank' === $color ) ? 'blank' : sanitize_hex_color_no_hash( $color ); + } }; -// Callback function for sanitizing the header textcolor setting. -function sanitize_header_textcolor( $color ) { - if ( $color == 'blank' ) - return 'blank'; - - return sanitize_hexcolor( $color ); -} - -// Callback function for sanitizing a hex color -function sanitize_hexcolor( $color ) { - $color = preg_replace( '/[^0-9a-fA-F]/', '', $color ); +/** + * Validates a hex color. + * + * Returns either '', a 3 or 6 digit hex color (with #), or null. + * For validating values without a #, see sanitize_hex_color_no_hash(). + * + * @since 3.4.0 + */ +function sanitize_hex_color( $color ) { + if ( '' === $color ) + return ''; // 3 or 6 hex digits, or the empty string. - if ( preg_match('|^([A-Fa-f0-9]{3}){0,2}$|', $color ) ) + if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) return $color; return null; } + +/** + * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible. + * + * Saving hex colors without a hash puts the burden of adding the hash on the + * UI, which makes it difficult to use or upgrade to other color types such as + * rgba, hsl, rgb, and html color names. + * + * Returns either '', a 3 or 6 digit hex color (without a #), or null. + * + * @since 3.4.0 + */ +function sanitize_hex_color_no_hash( $color ) { + $color = ltrim( $color, '#' ); + + if ( '' === $color ) + return ''; + + return sanitize_hex_color( '#' . $color ) ? $color : null; +} + +/** + * Ensures that any hex color is properly hashed. + * Otherwise, returns value untouched. + * + * This method should only be necessary if using sanitize_hex_color_no_hash(). + * + * @since 3.4.0 + */ +function maybe_hash_hex_color( $color ) { + if ( $unhashed = sanitize_hex_color_no_hash( $color ) ) + return '#' . $unhashed; + + return $color; +} \ No newline at end of file diff --git a/wp-includes/class-wp-customize-setting.php b/wp-includes/class-wp-customize-setting.php index f6088c9f4c..92584f0639 100644 --- a/wp-includes/class-wp-customize-setting.php +++ b/wp-includes/class-wp-customize-setting.php @@ -11,12 +11,14 @@ class WP_Customize_Setting { public $manager; public $id; - public $type = 'theme_mod'; - public $capability = 'edit_theme_options'; - public $theme_supports = ''; - public $default = ''; - public $sanitize_callback = ''; - public $transport = 'refresh'; + public $type = 'theme_mod'; + public $capability = 'edit_theme_options'; + public $theme_supports = ''; + public $default = ''; + public $transport = 'refresh'; + + public $sanitize_callback = ''; + public $sanitize_js_callback = ''; protected $id_data = array(); private $_post_value; // Cached, sanitized $_POST value. @@ -49,8 +51,11 @@ class WP_Customize_Setting { if ( ! empty( $this->id_data[ 'keys' ] ) ) $this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']'; - if ( $this->sanitize_callback != '' ) - add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback ); + if ( $this->sanitize_callback ) + add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 ); + + if ( $this->sanitize_js_callback ) + add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); return $this; } @@ -138,7 +143,7 @@ class WP_Customize_Setting { */ public function sanitize( $value ) { $value = stripslashes_deep( $value ); - return apply_filters( "customize_sanitize_{$this->id}", $value ); + return apply_filters( "customize_sanitize_{$this->id}", $value, $this ); } /** @@ -238,7 +243,7 @@ class WP_Customize_Setting { * @return mixed The requested escaped value. */ public function js_value() { - $value = $this->value(); + $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this ); if ( is_string( $value ) ) return html_entity_decode( $value, ENT_QUOTES, 'UTF-8'); diff --git a/wp-includes/css/customize-controls.dev.css b/wp-includes/css/customize-controls.dev.css index d9aeaae619..fd51845dd4 100644 --- a/wp-includes/css/customize-controls.dev.css +++ b/wp-includes/css/customize-controls.dev.css @@ -319,37 +319,19 @@ body { border-color: rgba( 0, 0, 0, 0.25 ); } -.customize-section .color-picker-hex { +.customize-section input[type="text"].color-picker-hex { float: left; - width: 70px; + width: 85px; font-family: monospace; - background-color: #fff; - color: #777; - border: 1px solid #ccc; - -webkit-border-radius: 3px; - border-radius: 3px; + text-align: center; } -.customize-section .color-picker-hex span { - float: left; - display: block; - margin: 1px -2px 0 0; - line-height: 16px; - padding: 3px 0 3px 8px; - text-align: right; - -webkit-border-radius: 3px 0 0 3px; - border-radius: 3px 0 0 3px; +/* The centered cursor overlaps the placeholder in webkit. Hide it when selected. */ +.customize-section input[type="text"].color-picker-hex:focus::-webkit-input-placeholder { + color: transparent; } - -.customize-section .color-picker-hex input[type="text"] { - width: 50px; - height: 22px; - line-height: 16px; - color: #777; - background: transparent; - border: 0; - -webkit-border-radius: 0 3px 3px 0; - border-radius: 0 3px 3px 0; +.customize-section input[type="text"].color-picker-hex:-moz-placeholder { + color: #999; } .customize-control-color .farbtastic-placeholder { diff --git a/wp-includes/js/customize-controls.dev.js b/wp-includes/js/customize-controls.dev.js index 57f4c70000..59283e79ed 100644 --- a/wp-includes/js/customize-controls.dev.js +++ b/wp-includes/js/customize-controls.dev.js @@ -109,18 +109,22 @@ api.ColorControl = api.Control.extend({ ready: function() { var control = this, - spot, text, update; + rhex, spot, input, text, update; + rhex = /^#([A-Fa-f0-9]{3}){0,2}$/; spot = this.container.find('.dropdown-content'); + input = new api.Element( this.container.find('.color-picker-hex') ); update = function( color ) { - color = color ? '#' + color : ''; spot.css( 'background', color ); control.farbtastic.setColor( color ); }; - this.farbtastic = $.farbtastic( this.container.find('.farbtastic-placeholder'), function( color ) { - control.setting.set( color.replace( '#', '' ) ); - }); + this.farbtastic = $.farbtastic( this.container.find('.farbtastic-placeholder'), control.setting.set ); + + // Only pass through values that are valid hexes/empty. + input.link( this.setting ).validate = function( to ) { + return rhex.test( to ) ? to : null; + }; this.setting.bind( update ); update( this.setting() ); diff --git a/wp-includes/js/customize-preview.dev.js b/wp-includes/js/customize-preview.dev.js index 1aef6aa232..a80cf44220 100644 --- a/wp-includes/js/customize-preview.dev.js +++ b/wp-includes/js/customize-preview.dev.js @@ -101,7 +101,7 @@ body.toggleClass( 'custom-background', !! ( color() || image() ) ); if ( color() ) - css += 'background-color: #' + color() + ';'; + css += 'background-color: ' + color() + ';'; if ( image() ) { css += 'background-image: url("' + image() + '");';