Ticket #30936: 30936.diff
File 30936.diff, 61.7 KB (added by , 9 years ago) |
---|
-
src/wp-includes/class-wp-customize-manager.php
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php index b695271..9ede050 100644
final class WP_Customize_Manager { 63 63 protected $registered_control_types = array(); 64 64 65 65 /** 66 * $_POST values for Customize Settings.66 * Unsanitized values for Customize Settings parsed from $_POST['customized']. 67 67 * 68 * @var array 68 * @var array|false 69 69 */ 70 70 private $_post_values; 71 71 … … final class WP_Customize_Manager { 75 75 * @since 3.4.0 76 76 */ 77 77 public function __construct() { 78 require ( ABSPATH . WPINC . '/class-wp-customize-setting.php' );79 require ( ABSPATH . WPINC . '/class-wp-customize-panel.php' );80 require ( ABSPATH . WPINC . '/class-wp-customize-section.php' );81 require ( ABSPATH . WPINC . '/class-wp-customize-control.php' );82 require ( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );78 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' ); 79 require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' ); 80 require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' ); 81 require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' ); 82 require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' ); 83 83 84 84 $this->widgets = new WP_Customize_Widgets( $this ); 85 85 … … final class WP_Customize_Manager { 102 102 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 103 103 104 104 add_action( 'customize_register', array( $this, 'register_controls' ) ); 105 add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first 105 106 add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) ); 106 107 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) ); 107 108 } … … final class WP_Customize_Manager { 111 112 * 112 113 * @since 3.4.0 113 114 * 115 * @param string|null $action whether the supplied Ajax action is being run (since 4.2.0). 116 * 114 117 * @return bool 115 118 */ 116 public function doing_ajax() { 117 return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ); 119 public function doing_ajax( $action = null ) { 120 $doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX ); 121 if ( ! $doing_ajax ) { 122 return false; 123 } 124 125 if ( ! $action ) { 126 return true; 127 } else { 128 // Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need to check before admin-ajax.php gets to that point 129 return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action; 130 } 118 131 } 119 132 120 133 /** … … final class WP_Customize_Manager { 399 412 } 400 413 401 414 /** 402 * Decode the $_POST['customized'] values for a specific Customize Setting. 415 * Parse the incoming $_POST['customized'] JSON data and store the unsanitized 416 * settings for subsequent post_value() lookups. 403 417 * 404 * @since 3.4.0418 * @since 4.1.1 405 419 * 406 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object 407 * @return string $post_value Sanitized value 420 * @return array 408 421 */ 409 public function post_value( $setting) {422 public function unsanitized_post_values() { 410 423 if ( ! isset( $this->_post_values ) ) { 411 if ( isset( $_POST['customized'] ) ) 424 if ( isset( $_POST['customized'] ) ) { 412 425 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true ); 413 else 426 } 427 if ( empty( $this->_post_values ) ) { // if not isset or of JSON error 414 428 $this->_post_values = false; 429 } 430 } 431 if ( empty( $this->_post_values ) ) { 432 return array(); 433 } else { 434 return $this->_post_values; 415 435 } 436 } 416 437 417 if ( isset( $this->_post_values[ $setting->id ] ) ) 418 return $setting->sanitize( $this->_post_values[ $setting->id ] ); 438 /** 439 * Return the sanitized value for a given setting from the request's POST data. 440 * 441 * @since 3.4.0 442 * 443 * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object 444 * @param mixed $default value returned $setting has no post value (added in 4.2.0). 445 * @return string|mixed $post_value Sanitized value or the $default provided 446 */ 447 public function post_value( $setting, $default = null ) { 448 $post_values = $this->unsanitized_post_values(); 449 if ( array_key_exists( $setting->id, $post_values ) ) { 450 return $setting->sanitize( $post_values[ $setting->id ] ); 451 } else { 452 return $default; 453 } 419 454 } 420 455 421 456 /** … … final class WP_Customize_Manager { 704 739 } 705 740 706 741 /** 742 * Register any dynamically-created settings, such as those from $_POST['customized'] that have no corresponding setting created. 743 * 744 * This is a mechanism to "wake up" settings that have been dynamically created 745 * on the frontend and have been added to a transaction. When the transaction is 746 * loaded, the dynamically-created settings then will get created and previewed 747 * even though they are not directly created statically with code. 748 * 749 * @param string[] $setting_ids The setting IDs to add. 750 * @return WP_Customize_Setting[] The settings added. 751 */ 752 public function add_dynamic_settings( $setting_ids ) { 753 $new_settings = array(); 754 foreach ( $setting_ids as $setting_id ) { 755 // Skip settings already created 756 if ( $this->get_setting( $setting_id ) ) { 757 continue; 758 } 759 $setting_args = false; 760 $setting_class = 'WP_Customize_Setting'; 761 762 /** 763 * Filter a dynamic setting's constructor args. 764 * 765 * For a dynamic setting to be registered, this filter must be employed 766 * to override the default false value with an array of args to pass to 767 * the WP_Customize_Setting constructor. 768 * 769 * @since 4.2.0 770 * 771 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor. 772 * @param string $setting_id ID for dynamic setting, usually coming from $_POST['customized']. 773 */ 774 $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id ); 775 if ( false === $setting_args ) { 776 continue; 777 } 778 779 /** 780 * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass. 781 * 782 * @since 4.2.0 783 * 784 * @param string $setting_class WP_Customize_Setting or a subclass. 785 * @param string $setting_id ID for dynamic setting, usually coming from $_POST['customized']. 786 * @param string $setting_args WP_Customize_Setting or a subclass. 787 */ 788 $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args ); 789 790 $setting = new $setting_class( $this, $setting_id, $setting_args ); 791 $this->add_setting( $setting ); 792 $new_settings[] = $setting; 793 } 794 return $new_settings; 795 } 796 797 /** 707 798 * Retrieve a customize setting. 708 799 * 709 800 * @since 3.4.0 … … final class WP_Customize_Manager { 1252 1343 } 1253 1344 1254 1345 /** 1346 * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets 1347 * 1348 * @since 4.2.0 1349 */ 1350 public function register_dynamic_settings() { 1351 $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) ); 1352 } 1353 1354 /** 1255 1355 * Callback for validating the header_textcolor value. 1256 1356 * 1257 1357 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). -
src/wp-includes/class-wp-customize-setting.php
diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php index 7a7be45..ffcd85c 100644
class WP_Customize_Setting { 55 55 protected $id_data = array(); 56 56 57 57 /** 58 * Cached and sanitized $_POST value for the setting.59 *60 * @access private61 * @var mixed62 */63 private $_post_value;64 65 /**66 58 * Constructor. 67 59 * 68 60 * Any supplied $args override class property defaults. … … class WP_Customize_Setting { 100 92 add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 ); 101 93 } 102 94 95 protected $_original_value; 96 103 97 /** 104 98 * Handle previewing the setting. 105 99 * 106 100 * @since 3.4.0 107 101 */ 108 102 public function preview() { 103 104 if ( ! isset( $this->_original_value ) ) { 105 $this->_original_value = $this->value(); 106 } 107 109 108 switch( $this->type ) { 110 109 case 'theme_mod' : 111 110 add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); … … class WP_Customize_Setting { 156 155 * @return mixed New or old value. 157 156 */ 158 157 public function _preview_filter( $original ) { 159 return $this->multidimensional_replace( $original, $this->id_data[ 'keys' ], $this->post_value() ); 158 $undefined = new stdClass(); // symbol hack 159 $post_value = $this->post_value( $undefined ); 160 if ( $undefined === $post_value ) { 161 $value = $this->_original_value; 162 } else { 163 $value = $post_value; 164 } 165 $replaced = $this->multidimensional_replace( $original, $this->id_data['keys'], $value ); 166 return $replaced; 160 167 } 161 168 162 169 /** … … class WP_Customize_Setting { 197 204 * @return mixed The default value on failure, otherwise the sanitized value. 198 205 */ 199 206 final public function post_value( $default = null ) { 200 // Check for a cached value 201 if ( isset( $this->_post_value ) ) 202 return $this->_post_value; 203 204 // Call the manager for the post value 205 $result = $this->manager->post_value( $this ); 206 207 if ( isset( $result ) ) 208 return $this->_post_value = $result; 209 else 210 return $default; 207 return $this->manager->post_value( $this, $default ); 211 208 } 212 209 213 210 /** … … class WP_Customize_Setting { 422 419 $node = &$node[ $key ]; 423 420 } 424 421 425 if ( $create && ! isset( $node[ $last ] ) ) 426 $node[ $last ] = array(); 422 if ( $create ) { 423 if ( ! is_array( $node ) ) { 424 // account for an array overriding a string or object value 425 $node = array(); 426 } 427 if ( ! isset( $node[ $last ] ) ) { 428 $node[ $last ] = array(); 429 } 430 } 427 431 428 432 if ( ! isset( $node[ $last ] ) ) 429 433 return; -
src/wp-includes/class-wp-customize-widgets.php
diff --git src/wp-includes/class-wp-customize-widgets.php src/wp-includes/class-wp-customize-widgets.php index ba14828..9258e18 100644
final class WP_Customize_Widgets { 35 35 /** 36 36 * @since 3.9.0 37 37 * @access protected 38 * @var39 */40 protected $_customized;41 42 /**43 * @since 3.9.044 * @access protected45 38 * @var array 46 39 */ 47 protected $ _prepreview_added_filters = array();40 protected $rendered_sidebars = array(); 48 41 49 42 /** 50 43 * @since 3.9.0 51 44 * @access protected 52 45 * @var array 53 46 */ 54 protected $rendered_ sidebars = array();47 protected $rendered_widgets = array(); 55 48 56 49 /** 57 50 * @since 3.9.0 58 51 * @access protected 59 52 * @var array 60 53 */ 61 protected $ rendered_widgets = array();54 protected $old_sidebars_widgets = array(); 62 55 63 56 /** 64 * @since 3.9.0 57 * Mapping of setting type to setting ID pattern. 58 * 59 * @since 4.2.0 65 60 * @access protected 66 61 * @var array 67 62 */ 68 protected $old_sidebars_widgets = array(); 63 protected $setting_id_patterns = array( 64 'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/', 65 'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/', 66 ); 69 67 70 68 /** 71 69 * Initial loader. … … final class WP_Customize_Widgets { 78 76 public function __construct( $manager ) { 79 77 $this->manager = $manager; 80 78 81 add_action( 'after_setup_theme', array( $this, 'setup_widget_addition_previews' ) ); 79 add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 ); 80 add_action( 'after_setup_theme', array( $this, 'register_settings' ) ); 82 81 add_action( 'wp_loaded', array( $this, 'override_sidebars_widgets_for_theme_switch' ) ); 83 82 add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) ); 84 83 add_action( 'customize_register', array( $this, 'schedule_customize_register' ), 1 ); … … final class WP_Customize_Widgets { 95 94 } 96 95 97 96 /** 98 * Get an unslashed post value or return a default.97 * Get the widget setting type given a setting ID. 99 98 * 100 * @since 3.9.099 * @since 4.2.0 101 100 * 102 * @ access protected101 * @param $setting_id 103 102 * 104 * @param string $name Post value. 105 * @param mixed $default Default post value. 106 * @return mixed Unslashed post value or default value. 103 * @return string|null 107 104 */ 108 protected function get_post_value( $name, $default = null ) { 109 if ( ! isset( $_POST[ $name ] ) ) { 110 return $default; 105 protected function get_setting_type( $setting_id ) { 106 static $cache = array(); 107 if ( isset( $cache[ $setting_id ] ) ) { 108 return $cache[ $setting_id ]; 111 109 } 112 113 return wp_unslash( $_POST[$name] ); 110 foreach ( $this->setting_id_patterns as $type => $pattern ) { 111 if ( preg_match( $pattern, $setting_id ) ) { 112 $cache[ $setting_id ] = $type; 113 return $type; 114 } 115 } 116 return null; 114 117 } 115 118 116 119 /** 117 * Set up widget addition previews.120 * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly. 118 121 * 119 * Since the widgets get registered on 'widgets_init' before the Customizer 120 * settings are set up on 'customize_register', we have to filter the options 121 * similarly to how the setting previewer will filter the options later. 122 * 123 * @since 3.9.0 124 * 125 * @access public 122 * @since 4.2.0 126 123 */ 127 public function setup_widget_addition_previews() { 128 $is_customize_preview = false; 129 130 if ( ! empty( $this->manager ) && ! is_admin() && 'on' === $this->get_post_value( 'wp_customize' ) ) { 131 $is_customize_preview = check_ajax_referer( 'preview-customize_' . $this->manager->get_stylesheet(), 'nonce', false ); 132 } 133 134 $is_ajax_widget_update = false; 135 if ( $this->manager->doing_ajax() && 'update-widget' === $this->get_post_value( 'action' ) ) { 136 $is_ajax_widget_update = check_ajax_referer( 'update-widget', 'nonce', false ); 137 } 138 139 $is_ajax_customize_save = false; 140 if ( $this->manager->doing_ajax() && 'customize_save' === $this->get_post_value( 'action' ) ) { 141 $is_ajax_customize_save = check_ajax_referer( 'save-customize_' . $this->manager->get_stylesheet(), 'nonce', false ); 142 } 143 144 $is_valid_request = ( $is_ajax_widget_update || $is_customize_preview || $is_ajax_customize_save ); 145 if ( ! $is_valid_request ) { 146 return; 147 } 148 149 // Input from Customizer preview. 150 if ( isset( $_POST['customized'] ) ) { 151 $this->_customized = json_decode( $this->get_post_value( 'customized' ), true ); 152 } else { // Input from ajax widget update request. 153 $this->_customized = array(); 154 $id_base = $this->get_post_value( 'id_base' ); 155 $widget_number = $this->get_post_value( 'widget_number', false ); 156 $option_name = 'widget_' . $id_base; 157 $this->_customized[ $option_name ] = array(); 158 if ( preg_match( '/^[0-9]+$/', $widget_number ) ) { 159 $option_name .= '[' . $widget_number . ']'; 160 $this->_customized[ $option_name ][ $widget_number ] = array(); 124 public function register_settings() { 125 $widget_setting_ids = array(); 126 $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() ); 127 foreach ( $incoming_setting_ids as $setting_id ) { 128 if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) { 129 $widget_setting_ids[] = $setting_id; 161 130 } 162 131 } 132 if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) { 133 $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) ); 134 } 163 135 164 $function = array( $this, 'prepreview_added_sidebars_widgets' ); 165 166 $hook = 'option_sidebars_widgets'; 167 add_filter( $hook, $function ); 168 $this->_prepreview_added_filters[] = compact( 'hook', 'function' ); 169 170 $hook = 'default_option_sidebars_widgets'; 171 add_filter( $hook, $function ); 172 $this->_prepreview_added_filters[] = compact( 'hook', 'function' ); 173 174 $function = array( $this, 'prepreview_added_widget_instance' ); 175 foreach ( $this->_customized as $setting_id => $value ) { 176 if ( preg_match( '/^(widget_.+?)(?:\[(\d+)\])?$/', $setting_id, $matches ) ) { 177 $option = $matches[1]; 178 179 $hook = sprintf( 'option_%s', $option ); 180 if ( ! has_filter( $hook, $function ) ) { 181 add_filter( $hook, $function ); 182 $this->_prepreview_added_filters[] = compact( 'hook', 'function' ); 183 } 184 185 $hook = sprintf( 'default_option_%s', $option ); 186 if ( ! has_filter( $hook, $function ) ) { 187 add_filter( $hook, $function ); 188 $this->_prepreview_added_filters[] = compact( 'hook', 'function' ); 189 } 136 $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) ); 190 137 191 /* 192 * Make sure the option is registered so that the update_option() 193 * won't fail due to the filters providing a default value, which 194 * causes the update_option() to get confused. 195 */ 196 add_option( $option, array() ); 138 /* 139 * Preview settings right away so that widgets and sidebars will get registered properly. 140 * But don't do this if a customize_save because this will cause WP to think there is nothing 141 * changed that needs to be saved. 142 */ 143 if ( ! $this->manager->doing_ajax( 'customize_save' ) ) { 144 foreach ( $settings as $setting ) { 145 $setting->preview(); 197 146 } 198 147 } 199 148 } 200 149 201 150 /** 202 * Ensure that newly-added widgets will appear in the widgets_sidebars.151 * Determine the arguments for a dynamically-created setting. 203 152 * 204 * This is necessary because the Customizer's setting preview filters 205 * are added after the widgets_init action, which is too late for the 206 * widgets to be set up properly. 153 * @since 4.2.0 207 154 * 208 * @since 3.9.0 209 * @access public 210 * 211 * @param array $sidebars_widgets Associative array of sidebars and their widgets. 212 * @return array Filtered array of sidebars and their widgets. 155 * @param false|array $args 156 * @param string $setting_id 157 * @return false|array 213 158 */ 214 public function prepreview_added_sidebars_widgets( $sidebars_widgets ) { 215 foreach ( $this->_customized as $setting_id => $value ) { 216 if ( preg_match( '/^sidebars_widgets\[(.+?)\]$/', $setting_id, $matches ) ) { 217 $sidebar_id = $matches[1]; 218 $sidebars_widgets[ $sidebar_id ] = $value; 219 } 159 public function filter_customize_dynamic_setting_args( $args, $setting_id ) { 160 if ( $this->get_setting_type( $setting_id ) ) { 161 $args = $this->get_setting_args( $setting_id ); 220 162 } 221 return $ sidebars_widgets;163 return $args; 222 164 } 223 165 224 166 /** 225 * Ensure newly-added widgets have empty instances so they 226 * will be recognized. 227 * 228 * This is necessary because the Customizer's setting preview 229 * filters are added after the widgets_init action, which is 230 * too late for the widgets to be set up properly. 167 * Get an unslashed post value or return a default. 231 168 * 232 169 * @since 3.9.0 233 * @access public234 170 * 235 * @param array|bool|mixed $value Widget instance(s), false if open was empty. 236 * @return array|mixed Widget instance(s) with additions. 237 */ 238 public function prepreview_added_widget_instance( $value = false ) { 239 if ( ! preg_match( '/^(?:default_)?option_(widget_(.+))/', current_filter(), $matches ) ) { 240 return $value; 241 } 242 $id_base = $matches[2]; 243 244 foreach ( $this->_customized as $setting_id => $setting ) { 245 $parsed_setting_id = $this->parse_widget_setting_id( $setting_id ); 246 if ( is_wp_error( $parsed_setting_id ) || $id_base !== $parsed_setting_id['id_base'] ) { 247 continue; 248 } 249 $widget_number = $parsed_setting_id['number']; 250 251 if ( is_null( $widget_number ) ) { 252 // Single widget. 253 if ( false === $value ) { 254 $value = array(); 255 } 256 } else { 257 // Multi widget. 258 if ( empty( $value ) ) { 259 $value = array( '_multiwidget' => 1 ); 260 } 261 if ( ! isset( $value[ $widget_number ] ) ) { 262 $value[ $widget_number ] = array(); 263 } 264 } 265 } 266 267 return $value; 268 } 269 270 /** 271 * Remove pre-preview filters. 272 * 273 * Removes filters added in setup_widget_addition_previews() 274 * to ensure widgets are populating the options during 275 * 'widgets_init'. 171 * @access protected 276 172 * 277 * @since 3.9.0 278 * @access public 173 * @param string $name Post value. 174 * @param mixed $default Default post value. 175 * @return mixed Unslashed post value or default value. 279 176 */ 280 p ublic function remove_prepreview_filters() {281 foreach ( $this->_prepreview_added_filters as $prepreview_added_filter) {282 re move_filter( $prepreview_added_filter['hook'], $prepreview_added_filter['function'] );177 protected function get_post_value( $name, $default = null ) { 178 if ( ! isset( $_POST[ $name ] ) ) { 179 return $default; 283 180 } 284 $this->_prepreview_added_filters = array(); 181 182 return wp_unslash( $_POST[ $name ] ); 285 183 } 286 184 287 185 /** … … final class WP_Customize_Widgets { 380 278 * @access public 381 279 */ 382 280 public function schedule_customize_register() { 383 if ( is_admin() ) { // @todo for some reason, $wp_customize->is_preview() is true here?281 if ( is_admin() ) { 384 282 $this->customize_register(); 385 283 } else { 386 284 add_action( 'wp', array( $this, 'customize_register' ) ); … … final class WP_Customize_Widgets { 412 310 foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) { 413 311 $setting_id = $this->get_setting_id( $widget_id ); 414 312 $setting_args = $this->get_setting_args( $setting_id ); 415 416 $setting_args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' ); 417 $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' ); 418 419 $this->manager->add_setting( $setting_id, $setting_args ); 420 313 if ( ! $this->manager->get_setting( $setting_id ) ) { 314 $this->manager->add_setting( $setting_id, $setting_args ); 315 } 421 316 $new_setting_ids[] = $setting_id; 422 317 } 423 318 … … final class WP_Customize_Widgets { 452 347 if ( $is_registered_sidebar || $is_inactive_widgets ) { 453 348 $setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id ); 454 349 $setting_args = $this->get_setting_args( $setting_id ); 455 456 $setting_args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' ); 457 $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' ); 458 459 $this->manager->add_setting( $setting_id, $setting_args ); 350 if ( ! $this->manager->get_setting( $setting_id ) ) { 351 $this->manager->add_setting( $setting_id, $setting_args ); 352 } 460 353 $new_setting_ids[] = $setting_id; 461 354 462 355 // Add section to contain controls. … … final class WP_Customize_Widgets { 527 420 * We have to register these settings later than customize_preview_init 528 421 * so that other filters have had a chance to run. 529 422 */ 530 if ( did_action( 'customize_preview_init' ) ) {423 if ( ! $this->manager->doing_ajax( 'customize_save' ) ) { 531 424 foreach ( $new_setting_ids as $new_setting_id ) { 532 425 $this->manager->get_setting( $new_setting_id )->preview(); 533 426 } 534 427 } 535 $this->remove_prepreview_filters(); 428 429 add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 ); 536 430 } 537 431 538 432 /** … … final class WP_Customize_Widgets { 804 698 'transport' => 'refresh', 805 699 'default' => array(), 806 700 ); 701 702 if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) { 703 $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' ); 704 $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' ); 705 } else if ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) { 706 $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' ); 707 $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' ); 708 } 709 807 710 $args = array_merge( $args, $overrides ); 808 711 809 712 /** … … final class WP_Customize_Widgets { 831 734 * @return array Array of sanitized widget IDs. 832 735 */ 833 736 public function sanitize_sidebar_widgets( $widget_ids ) { 834 global $wp_registered_widgets; 835 836 $widget_ids = array_map( 'strval', (array) $widget_ids ); 737 $widget_ids = array_map( 'strval', (array) $widget_ids ); 837 738 $sanitized_widget_ids = array(); 838 839 739 foreach ( $widget_ids as $widget_id ) { 840 if ( array_key_exists( $widget_id, $wp_registered_widgets ) ) { 841 $sanitized_widget_ids[] = $widget_id; 842 } 740 $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id ); 843 741 } 844 742 return $sanitized_widget_ids; 845 743 } … … final class WP_Customize_Widgets { 974 872 * @access public 975 873 */ 976 874 public function customize_preview_init() { 977 add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );978 875 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) ); 979 876 add_action( 'wp_print_styles', array( $this, 'print_preview_css' ), 1 ); 980 877 add_action( 'wp_footer', array( $this, 'export_preview_data' ), 20 ); … … final class WP_Customize_Widgets { 1344 1241 $form = ob_get_clean(); 1345 1242 1346 1243 // Obtain the widget instance. 1347 $option = get_option( $option_name ); 1348 1244 $option = $this->get_captured_option( $option_name ); 1349 1245 if ( null !== $parsed_id['number'] ) { 1350 1246 $instance = $option[$parsed_id['number']]; 1351 1247 } else { … … final class WP_Customize_Widgets { 1383 1279 wp_die( -1 ); 1384 1280 } 1385 1281 1386 if ( ! isset( $_POST['widget-id'] ) ) {1387 wp_send_json_error( );1282 if ( empty( $_POST['widget-id'] ) ) { 1283 wp_send_json_error( 'missing_widget-id' ); 1388 1284 } 1389 1285 1390 1286 /** This action is documented in wp-admin/includes/ajax-actions.php */ … … final class WP_Customize_Widgets { 1398 1294 1399 1295 $widget_id = $this->get_post_value( 'widget-id' ); 1400 1296 $parsed_id = $this->parse_widget_id( $widget_id ); 1401 $id_base = $parsed_id['id_base']; 1402 1403 if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) { 1404 wp_send_json_error(); 1297 $id_base = $parsed_id['id_base']; 1298 1299 $is_updating_widget_template = ( 1300 isset( $_POST[ 'widget-' . $id_base ] ) 1301 && 1302 is_array( $_POST[ 'widget-' . $id_base ] ) 1303 && 1304 preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) ) 1305 ); 1306 if ( $is_updating_widget_template ) { 1307 wp_send_json_error( 'template_widget_not_updatable' ); 1405 1308 } 1406 1309 1407 1310 $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form} 1408 1311 if ( is_wp_error( $updated_widget ) ) { 1409 wp_send_json_error( );1312 wp_send_json_error( $updated_widget->get_error_message() ); 1410 1313 } 1411 1314 1412 1315 $form = $updated_widget['form']; … … final class WP_Customize_Widgets { 1463 1366 } 1464 1367 1465 1368 /** 1369 * Get the option that was captured from being saved. 1370 * 1371 * @since 4.2.0 1372 * @access protected 1373 * @return mixed 1374 */ 1375 protected function get_captured_option( $name, $default = false ) { 1376 if ( array_key_exists( $name, $this->_captured_options ) ) { 1377 $value = $this->_captured_options[ $name ]; 1378 } else { 1379 $value = $default; 1380 } 1381 return $value; 1382 } 1383 1384 /** 1466 1385 * Get the number of captured widget option updates. 1467 1386 * 1468 1387 * @since 3.9.0 -
new file tests/phpunit/tests/customize/manager.php
diff --git tests/phpunit/tests/customize/manager.php tests/phpunit/tests/customize/manager.php new file mode 100644 index 0000000..e131735
- + 1 <?php 2 3 /** 4 * Tests for the WP_Customize_Manager class. 5 * 6 * @group customize 7 */ 8 class Tests_WP_Customize_Manager extends WP_UnitTestCase { 9 10 function setUp() { 11 parent::setUp(); 12 require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' ); 13 $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok 14 $this->manager = $GLOBALS['wp_customize']; 15 $this->undefined = new stdClass(); 16 } 17 18 function tearDown() { 19 parent::tearDown(); 20 $this->manager = null; 21 unset( $GLOBALS['wp_customize'] ); 22 } 23 24 /** 25 * Instantiate class, set global $wp_customize, and return instance. 26 * 27 * @return WP_Customize_Manager 28 */ 29 function instantiate() { 30 $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok 31 return $GLOBALS['wp_customize']; 32 } 33 34 /** 35 * Test WP_Customize_Manager::doing_ajax() 36 * 37 * @group ajax 38 */ 39 function test_doing_ajax() { 40 if ( ! defined( 'DOING_AJAX' ) ) { 41 define( 'DOING_AJAX', true ); 42 } 43 44 $manager = $this->instantiate(); 45 $this->assertTrue( $manager->doing_ajax() ); 46 47 $_REQUEST['action'] = 'customize_save'; 48 $this->assertTrue( $manager->doing_ajax( 'customize_save' ) ); 49 $this->assertFalse( $manager->doing_ajax( 'update-widget' ) ); 50 } 51 52 /** 53 * Test ! WP_Customize_Manager::doing_ajax() 54 */ 55 function test_not_doing_ajax() { 56 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 57 $this->markTestSkipped( 'Cannot test when DOING_AJAX' ); 58 } 59 60 $manager = $this->instantiate(); 61 $this->assertFalse( $manager->doing_ajax() ); 62 } 63 64 /** 65 * Test WP_Customize_Manager::unsanitized_post_values() 66 * 67 * @ticket 30988 68 */ 69 function test_unsanitized_post_values() { 70 $manager = $this->instantiate(); 71 72 $customized = array( 73 'foo' => 'bar', 74 'baz[quux]' => 123, 75 ); 76 $_POST['customized'] = wp_slash( wp_json_encode( $customized ) ); 77 $post_values = $manager->unsanitized_post_values(); 78 $this->assertEquals( $customized, $post_values ); 79 } 80 81 /** 82 * Test the WP_Customize_Manager::post_value() method 83 * 84 * @ticket 30936 85 */ 86 function test_post_value() { 87 $posted_settings = array( 88 'foo' => 'OOF', 89 ); 90 $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) ); 91 92 $manager = $this->instantiate(); 93 94 $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) ); // @todo this should return the setting instance 95 $foo_setting = $manager->get_setting( 'foo' ); 96 $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->value(), 'Expected non-previewed setting to return default when value() method called.' ); 97 $this->assertEquals( $posted_settings['foo'], $manager->post_value( $foo_setting, 'post_value_foo_default' ), 'Expected post_value($foo_setting) to return value supplied in $_POST[customized][foo]' ); 98 99 $manager->add_setting( 'bar', array( 'default' => 'bar_default' ) ); 100 $bar_setting = $manager->get_setting( 'bar' ); 101 $this->assertEquals( 'post_value_bar_default', $manager->post_value( $bar_setting, 'post_value_bar_default' ), 'Expected post_value($bar_setting, $default) to return $default since no value supplied in $_POST[customized][bar]' ); 102 } 103 104 /** 105 * Test the WP_Customize_Manager::add_dynamic_settings() method. 106 * 107 * @ticket 30936 108 */ 109 function test_add_dynamic_settings() { 110 $manager = $this->instantiate(); 111 $setting_ids = array( 'foo', 'bar' ); 112 $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) ); 113 $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected there to not be a bar setting up front.' ); 114 $manager->add_dynamic_settings( $setting_ids ); 115 $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected the bar setting to remain absent since filters not added.' ); 116 117 $this->action_customize_register_for_dynamic_settings(); 118 $manager->add_dynamic_settings( $setting_ids ); 119 $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected bar setting to be created since filters were added.' ); 120 $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->default, 'Expected static foo setting to not get overridden by dynamic setting.' ); 121 $this->assertEquals( 'dynamic_bar_default', $manager->get_setting( 'bar' )->default, 'Expected dynamic setting bar to have default providd by filter.' ); 122 } 123 124 /** 125 * Test the WP_Customize_Manager::register_dynamic_settings() method. 126 * 127 * This is similar to test_add_dynamic_settings, except the settings are passed via $_POST['customized']. 128 * 129 * @ticket 30936 130 */ 131 function test_register_dynamic_settings() { 132 $posted_settings = array( 133 'foo' => 'OOF', 134 'bar' => 'RAB', 135 ); 136 $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) ); 137 138 add_action( 'customize_register', array( $this, 'action_customize_register_for_dynamic_settings' ) ); 139 140 $manager = $this->instantiate(); 141 $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) ); 142 143 $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to not be registered.' ); 144 do_action( 'customize_register', $manager ); 145 $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to be automatically registered after customize_register action.' ); 146 $this->assertEmpty( $manager->get_setting( 'baz' ), 'Expected unrecognized dynamic setting "baz" to remain unregistered.' ); 147 } 148 149 /** 150 * In lieu of closures, callback for customize_register action added in test_register_dynamic_settings() 151 */ 152 function action_customize_register_for_dynamic_settings() { 153 add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args_for_test_dynamic_settings' ), 10, 2 ); 154 add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_customize_dynamic_setting_class_for_test_dynamic_settings' ), 10, 3 ); 155 } 156 157 /** 158 * In lieu of closures, callback for customize_dynamic_setting_args filter added for test_register_dynamic_settings() 159 */ 160 function filter_customize_dynamic_setting_args_for_test_dynamic_settings( $setting_args, $setting_id ) { 161 $this->assertEquals( false, $setting_args, 'Expected $setting_args to be false by default.' ); 162 $this->assertInternalType( 'string', $setting_id ); 163 if ( in_array( $setting_id, array( 'foo', 'bar' ) ) ) { 164 $setting_args = array( 'default' => "dynamic_{$setting_id}_default" ); 165 } 166 return $setting_args; 167 } 168 169 /** 170 * In lieu of closures, callback for customize_dynamic_setting_class filter added for test_register_dynamic_settings() 171 */ 172 function filter_customize_dynamic_setting_class_for_test_dynamic_settings( $setting_class, $setting_id, $setting_args ) { 173 $this->assertEquals( 'WP_Customize_Setting', $setting_class ); 174 $this->assertInternalType( 'string', $setting_id ); 175 $this->assertInternalType( 'array', $setting_args ); 176 return $setting_class; 177 } 178 179 } 180 -
new file tests/phpunit/tests/customize/setting.php
diff --git tests/phpunit/tests/customize/setting.php tests/phpunit/tests/customize/setting.php new file mode 100644 index 0000000..625335f
- + 1 <?php 2 3 /** 4 * Tests for the WP_Customize_Setting class. 5 * 6 * @group customize 7 */ 8 class Tests_WP_Customize_Setting extends WP_UnitTestCase { 9 10 /** 11 * @var WP_Customize_Manager 12 */ 13 protected $manager; 14 15 /** 16 * @var stdClass an instance which serves as a symbol to do identity checks with 17 */ 18 public $undefined; 19 20 function setUp() { 21 parent::setUp(); 22 require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' ); 23 $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok 24 $this->manager = $GLOBALS['wp_customize']; 25 $this->undefined = new stdClass(); 26 } 27 28 function tearDown() { 29 parent::tearDown(); 30 $this->manager = null; 31 unset( $GLOBALS['wp_customize'] ); 32 } 33 34 function test_construct() { 35 $foo = new WP_Customize_Setting( $this->manager, 'foo' ); 36 $this->assertEquals( $this->manager, $foo->manager ); 37 $this->assertEquals( 'foo', $foo->id ); 38 $this->assertEquals( 'theme_mod', $foo->type ); 39 $this->assertEquals( 'edit_theme_options', $foo->capability ); 40 $this->assertEquals( '', $foo->theme_supports ); 41 $this->assertEquals( '', $foo->default ); 42 $this->assertEquals( 'refresh', $foo->transport ); 43 $this->assertEquals( '', $foo->sanitize_callback ); 44 $this->assertEquals( '', $foo->sanitize_js_callback ); 45 $this->assertFalse( has_filter( "customize_sanitize_{$foo->id}" ) ); 46 $this->assertFalse( has_filter( "customize_sanitize_js_{$foo->id}" ) ); 47 48 $args = array( 49 'type' => 'option', 50 'capability' => 'edit_posts', 51 'theme_supports' => 'widgets', 52 'default' => 'barbar', 53 'transport' => 'postMessage', 54 'sanitize_callback' => create_function( '$value', 'return $value . ":sanitize_callback";' ), 55 'sanitize_js_callback' => create_function( '$value', 'return $value . ":sanitize_js_callback";' ), 56 ); 57 $bar = new WP_Customize_Setting( $this->manager, 'bar', $args ); 58 $this->assertEquals( 'bar', $bar->id ); 59 foreach ( $args as $key => $value ) { 60 $this->assertEquals( $value, $bar->$key ); 61 } 62 $this->assertEquals( 10, has_filter( "customize_sanitize_{$bar->id}", $args['sanitize_callback'] ) ); 63 $this->assertEquals( 10, has_filter( "customize_sanitize_js_{$bar->id}" ), $args['sanitize_js_callback'] ); 64 } 65 66 public $post_data_overrides = array( 67 'unset_option_overridden' => 'unset_option_post_override_value', 68 'unset_theme_mod_overridden' => 'unset_theme_mod_post_override_value', 69 'set_option_overridden' => 'set_option_post_override_value', 70 'set_theme_mod_overridden' => 'set_theme_mod_post_override_value', 71 'unset_option_multi_overridden[foo]' => 'unset_option_multi_overridden[foo]_post_override_value', 72 'unset_theme_mod_multi_overridden[foo]' => 'unset_theme_mod_multi_overridden[foo]_post_override_value', 73 'set_option_multi_overridden[foo]' => 'set_option_multi_overridden[foo]_post_override_value', 74 'set_theme_mod_multi_overridden[foo]' => 'set_theme_mod_multi_overridden[foo]_post_override_value', 75 ); 76 77 public $standard_type_configs = array( 78 'option' => array( 79 'getter' => 'get_option', 80 'setter' => 'update_option', 81 ), 82 'theme_mod' => array( 83 'getter' => 'get_theme_mod', 84 'setter' => 'set_theme_mod', 85 ), 86 ); 87 88 /** 89 * Run assertions on non-multidimensional standard settings 90 */ 91 function test_preview_standard_types_non_multidimensional() { 92 93 // @todo this is hacky. The manager should provide a mechanism to override the post_values 94 $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); 95 96 // Try non-multidimensional settings 97 foreach ( $this->standard_type_configs as $type => $type_options ) { 98 // Non-multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen) 99 $name = "unset_{$type}_without_post_value"; 100 $default = "default_value_{$name}"; 101 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 102 $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) ); 103 $this->assertEquals( $default, $setting->value() ); 104 $setting->preview(); 105 $this->assertEquals( $default, call_user_func( $type_options['getter'], $name, $this->undefined ), sprintf( 'Expected %s(%s) to return setting default: %s.', $type_options['getter'], $name, $default ) ); 106 $this->assertEquals( $default, $setting->value() ); 107 108 // Non-multidimensional: See what effect the preview has on an extant setting (default value should not be seen) 109 $name = "set_{$type}_without_post_value"; 110 $default = "default_value_{$name}"; 111 $initial_value = "initial_value_{$name}"; 112 call_user_func( $type_options['setter'], $name, $initial_value ); 113 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 114 $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); 115 $this->assertEquals( $initial_value, $setting->value() ); 116 $setting->preview(); 117 $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) 118 $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) 119 $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); 120 $this->assertEquals( $initial_value, $setting->value() ); 121 122 // @todo What if we call the setter after preview() is called? If no post_value, should the new set value be stored? If that happens, then the following 3 assertions should be inverted 123 $overridden_value = "overridden_value_$name"; 124 call_user_func( $type_options['setter'], $name, $overridden_value ); 125 $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) ); 126 $this->assertEquals( $initial_value, $setting->value() ); 127 $this->assertNotEquals( $overridden_value, $setting->value() ); 128 129 // Non-multidimensional: Test unset setting being overridden by a post value 130 $name = "unset_{$type}_overridden"; 131 $default = "default_value_{$name}"; 132 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 133 $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) ); 134 $this->assertEquals( $default, $setting->value() ); 135 $setting->preview(); // activate post_data 136 $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) ); 137 $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() ); 138 139 // Non-multidimensional: Test set setting being overridden by a post value 140 $name = "set_{$type}_overridden"; 141 $default = "default_value_{$name}"; 142 $initial_value = "initial_value_{$name}"; 143 call_user_func( $type_options['setter'], $name, $initial_value ); 144 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 145 $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name, $this->undefined ) ); 146 $this->assertEquals( $initial_value, $setting->value() ); 147 $setting->preview(); // activate post_data 148 $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) 149 $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) 150 $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) ); 151 $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() ); 152 } 153 } 154 155 /** 156 * Run assertions on multidimensional standard settings 157 */ 158 function test_preview_standard_types_multidimensional() { 159 // @todo this is hacky. The manager should provide a mechanism to override the post_values 160 $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); 161 162 foreach ( $this->standard_type_configs as $type => $type_options ) { 163 // Multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen) 164 $base_name = "unset_{$type}_multi"; 165 $name = $base_name . '[foo]'; 166 $default = "default_value_{$name}"; 167 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 168 $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); 169 $this->assertEquals( $default, $setting->value() ); 170 $setting->preview(); 171 $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); 172 $this->assertArrayHasKey( 'foo', $base_value ); 173 $this->assertEquals( $default, $base_value['foo'] ); 174 175 // Multidimensional: See what effect the preview has on an extant setting (default value should not be seen) 176 $base_name = "set_{$type}_multi"; 177 $name = $base_name . '[foo]'; 178 $default = "default_value_{$name}"; 179 $initial_value = "initial_value_{$name}"; 180 $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' ); 181 call_user_func( $type_options['setter'], $base_name, $base_initial_value ); 182 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 183 $base_value = call_user_func( $type_options['getter'], $base_name, array() ); 184 $this->assertEquals( $initial_value, $base_value['foo'] ); 185 $this->assertEquals( $initial_value, $setting->value() ); 186 $setting->preview(); 187 $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) 188 $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) 189 $base_value = call_user_func( $type_options['getter'], $base_name, array() ); 190 $this->assertEquals( $initial_value, $base_value['foo'] ); 191 $this->assertEquals( $initial_value, $setting->value() ); 192 193 // Multidimensional: Test unset setting being overridden by a post value 194 $base_name = "unset_{$type}_multi_overridden"; 195 $name = $base_name . '[foo]'; 196 $default = "default_value_{$name}"; 197 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 198 $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); 199 $this->assertEquals( $default, $setting->value() ); 200 $setting->preview(); 201 $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) 202 $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) 203 $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); 204 $this->assertArrayHasKey( 'foo', $base_value ); 205 $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); 206 207 // Multidimemsional: Test set setting being overridden by a post value 208 $base_name = "set_{$type}_multi_overridden"; 209 $name = $base_name . '[foo]'; 210 $default = "default_value_{$name}"; 211 $initial_value = "initial_value_{$name}"; 212 $base_initial_value = array( 'foo' => $initial_value, 'bar' => 'persisted' ); 213 call_user_func( $type_options['setter'], $base_name, $base_initial_value ); 214 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 215 $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); 216 $this->arrayHasKey( 'foo', $base_value ); 217 $this->arrayHasKey( 'bar', $base_value ); 218 $this->assertEquals( $base_initial_value['foo'], $base_value['foo'] ); 219 $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] ); 220 $this->assertEquals( $initial_value, $setting->value() ); 221 $setting->preview(); 222 $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) 223 $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) 224 $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); 225 $this->assertArrayHasKey( 'foo', $base_value ); 226 $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); 227 $this->arrayHasKey( 'bar', call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); 228 $this->assertEquals( $base_initial_value['bar'], call_user_func( $type_options['getter'], $base_name, $this->undefined )['bar'] ); 229 } 230 } 231 232 /** 233 * @var array storage for custom types that 234 */ 235 protected $custom_type_data_saved; 236 237 protected $custom_type_data_previewed; 238 239 function custom_type_getter( $name, $default = null ) { 240 if ( did_action( "customize_preview_{$name}" ) && array_key_exists( $name, $this->custom_type_data_previewed ) ) { 241 $value = $this->custom_type_data_previewed[ $name ]; 242 } else if ( array_key_exists( $name, $this->custom_type_data_saved ) ) { 243 $value = $this->custom_type_data_saved[ $name ]; 244 } else { 245 $value = $default; 246 } 247 return $value; 248 } 249 250 function custom_type_setter( $name, $value ) { 251 $this->custom_type_data_saved[ $name ] = $value; 252 } 253 254 function custom_type_value_filter( $default ) { 255 $name = preg_replace( '/^customize_value_/', '', current_filter() ); 256 return $this->custom_type_getter( $name, $default ); 257 } 258 259 /** 260 * @var WP_Customize_Setting $setting 261 */ 262 function custom_type_preview( $setting ) { 263 $previewed_value = $setting->post_value( $this->undefined ); 264 if ( $this->undefined !== $previewed_value ) { 265 $this->custom_type_data_previewed[ $setting->id ] = $previewed_value; 266 } 267 } 268 269 function test_preview_custom_type() { 270 $type = 'custom_type'; 271 $post_data_overrides = array( 272 "unset_{$type}_with_post_value" => "unset_{$type}_without_post_value", 273 "set_{$type}_with_post_value" => "set_{$type}_without_post_value", 274 ); 275 $_POST['customized'] = wp_slash( wp_json_encode( $post_data_overrides ) ); 276 277 $this->custom_type_data_saved = array(); 278 $this->custom_type_data_previewed = array(); 279 280 add_action( "customize_preview_{$type}", array( $this, custom_type_preview ) ); 281 282 // Custom type not existing and no post value override 283 $name = "unset_{$type}_without_post_value"; 284 $default = "default_value_{$name}"; 285 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 286 // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need 287 288 add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); 289 $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); 290 $this->assertEquals( $default, $setting->value() ); 291 $setting->preview(); 292 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); 293 $this->assertEquals( 1, did_action( "customize_preview_{$setting->type}" ) ); 294 $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); // Note: for a non-custom type this is $default 295 $this->assertEquals( $default, $setting->value() ); // should be same as above 296 297 // Custom type existing and no post value override 298 $name = "set_{$type}_without_post_value"; 299 $default = "default_value_{$name}"; 300 $initial_value = "initial_value_{$name}"; 301 $this->custom_type_setter( $name, $initial_value ); 302 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 303 // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need 304 add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); 305 $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); 306 $this->assertEquals( $initial_value, $setting->value() ); 307 $setting->preview(); 308 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); 309 $this->assertEquals( 2, did_action( "customize_preview_{$setting->type}" ) ); 310 $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); // should be same as above 311 $this->assertEquals( $initial_value, $setting->value() ); // should be same as above 312 313 // Custom type not existing and with a post value override 314 $name = "unset_{$type}_with_post_value"; 315 $default = "default_value_{$name}"; 316 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 317 // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need 318 add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); 319 $this->assertEquals( $this->undefined, $this->custom_type_getter( $name, $this->undefined ) ); 320 $this->assertEquals( $default, $setting->value() ); 321 $setting->preview(); 322 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); 323 $this->assertEquals( 3, did_action( "customize_preview_{$setting->type}" ) ); 324 $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) ); 325 $this->assertEquals( $post_data_overrides[ $name ], $setting->value() ); 326 327 // Custom type not existing and with a post value override 328 $name = "set_{$type}_with_post_value"; 329 $default = "default_value_{$name}"; 330 $initial_value = "initial_value_{$name}"; 331 $this->custom_type_setter( $name, $initial_value ); 332 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 333 // Note: #29316 will allow us to have one filter for all settings of a given type, which is what we need 334 add_filter( "customize_value_{$name}", array( $this, 'custom_type_value_filter' ) ); 335 $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); 336 $this->assertEquals( $initial_value, $setting->value() ); 337 $setting->preview(); 338 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) ); 339 $this->assertEquals( 4, did_action( "customize_preview_{$setting->type}" ) ); 340 $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) ); 341 $this->assertEquals( $post_data_overrides[ $name ], $setting->value() ); 342 343 unset( $this->custom_type_data_previewed, $this->custom_type_data_saved ); 344 } 345 346 /** 347 * Test specific fix for setting's default value not applying on preview window 348 * 349 * @ticket 30988 350 */ 351 function test_non_posted_setting_applying_default_value_in_preview() { 352 $type = 'option'; 353 $name = 'unset_option_without_post_value'; 354 $default = "default_value_{$name}"; 355 $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); 356 $this->assertEquals( $this->undefined, get_option( $name, $this->undefined ) ); 357 $this->assertEquals( $default, $setting->value() ); 358 $setting->preview(); 359 $this->assertEquals( $default, get_option( $name, $this->undefined ), sprintf( 'Expected get_option(%s) to return setting default: %s.', $name, $default ) ); 360 $this->assertEquals( $default, $setting->value() ); 361 } 362 363 // @todo function test_save() { 364 // @todo test do_action( 'customize_save_' . $this->id_data[ 'base' ], $this ); 365 // @todo test_post_value() 366 // @todo test_sanitize( $value ) 367 // @todo apply_filters( "customize_sanitize_{$this->id}", $value, $this ); 368 // @todo function update( $value ) 369 // @todo test_value() 370 // @todo test customize_value_{$name} filter 371 // @todo test_js_value() 372 // @todo test apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this ); 373 // @todo test_check_capabilities() { 374 375 // @todo final protected function multidimensional( &$root, $keys, $create = false ) 376 // @todo final protected function multidimensional_replace( $root, $keys, $value ) 377 // @todo final protected function multidimensional_get( $root, $keys, $default = null ) { 378 // @todo final protected function multidimensional_isset( $root, $keys ) 379 } 380 -
new file tests/phpunit/tests/customize/widgets.php
diff --git tests/phpunit/tests/customize/widgets.php tests/phpunit/tests/customize/widgets.php new file mode 100644 index 0000000..f070df2
- + 1 <?php 2 3 /** 4 * Tests for the WP_Customize_Widgets class. 5 * 6 * @group customize 7 */ 8 class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 9 10 /** 11 * @var WP_Customize_Manager 12 */ 13 protected $manager; 14 15 function setUp() { 16 parent::setUp(); 17 require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' ); 18 $GLOBALS['wp_customize'] = new WP_Customize_Manager(); // wpcs: override ok 19 $this->manager = $GLOBALS['wp_customize']; 20 21 unset( $GLOBALS['_wp_sidebars_widgets'] ); // clear out cache set by wp_get_sidebars_widgets() 22 $sidebars_widgets = wp_get_sidebars_widgets(); 23 $this->assertEqualSets( array( 'wp_inactive_widgets', 'sidebar-1' ), array_keys( wp_get_sidebars_widgets() ) ); 24 $this->assertContains( 'search-2', $sidebars_widgets['sidebar-1'] ); 25 $this->assertContains( 'categories-2', $sidebars_widgets['sidebar-1'] ); 26 $this->assertArrayHasKey( 2, get_option( 'widget_search' ) ); 27 $widget_categories = get_option( 'widget_categories' ); 28 $this->assertArrayHasKey( 2, $widget_categories ); 29 $this->assertEquals( '', $widget_categories['title'] ); 30 31 remove_action( 'after_setup_theme', 'twentyfifteen_setup' ); // @todo We should not be including a theme anyway 32 33 $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); 34 wp_set_current_user( $user_id ); 35 } 36 37 function tearDown() { 38 parent::tearDown(); 39 $this->manager = null; 40 unset( $GLOBALS['wp_customize'] ); 41 } 42 43 function set_customized_post_data( $customized ) { 44 $_POST['customized'] = wp_slash( wp_json_encode( $customized ) ); 45 } 46 47 function do_customize_boot_actions() { 48 do_action( 'setup_theme' ); 49 $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->manager->theme()->get_stylesheet() ); 50 do_action( 'after_setup_theme' ); 51 do_action( 'init' ); 52 do_action( 'wp_loaded' ); 53 do_action( 'wp', $GLOBALS['wp'] ); 54 } 55 56 /** 57 * Test WP_Customize_Widgets::__construct() 58 */ 59 function test_construct() { 60 $this->assertInstanceOf( 'WP_Customize_Widgets', $this->manager->widgets ); 61 $this->assertEquals( $this->manager, $this->manager->widgets->manager ); 62 } 63 64 /** 65 * Test WP_Customize_Widgets::register_settings() 66 * 67 * @ticket 30988 68 */ 69 function test_register_settings() { 70 71 $raw_widget_customized = array( 72 'widget_categories[2]' => array( 73 'title' => 'Taxonomies Brand New Value', 74 'count' => 0, 75 'hierarchical' => 0, 76 'dropdown' => 0, 77 ), 78 'widget_search[3]' => array( 79 'title' => 'Not as good as Google!', 80 ), 81 ); 82 $customized = array(); 83 foreach ( $raw_widget_customized as $setting_id => $instance ) { 84 $customized[ $setting_id ] = $this->manager->widgets->sanitize_widget_js_instance( $instance ); 85 } 86 87 $this->set_customized_post_data( $customized ); 88 $this->do_customize_boot_actions(); 89 $this->assertTrue( is_customize_preview() ); 90 91 $this->assertNotEmpty( $this->manager->get_setting( 'widget_categories[2]' ), 'Expected setting for pre-existing widget category-2, being customized.' ); 92 $this->assertNotEmpty( $this->manager->get_setting( 'widget_search[2]' ), 'Expected setting for pre-existing widget search-2, not being customized.' ); 93 $this->assertNotEmpty( $this->manager->get_setting( 'widget_search[3]' ), 'Expected dynamic setting for non-existing widget search-3, being customized.' ); 94 95 $widget_categories = get_option( 'widget_categories' ); 96 $this->assertEquals( $raw_widget_customized['widget_categories[2]'], $widget_categories[2], 'Expected $wp_customize->get_setting(widget_categories[2])->preview() to have been called.' ); 97 } 98 99 /** 100 * Test WP_Customize_Widgets::get_setting_args() 101 */ 102 function test_get_setting_args() { 103 104 add_filter( 'widget_customizer_setting_args', array( $this, 'filter_widget_customizer_setting_args' ), 10, 2 ); 105 106 $default_args = array( 107 'type' => 'option', 108 'capability' => 'edit_theme_options', 109 'transport' => 'refresh', 110 'default' => array(), 111 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ), 112 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ), 113 ); 114 115 $args = $this->manager->widgets->get_setting_args( 'widget_foo[2]' ); 116 foreach ( $default_args as $key => $default_value ) { 117 $this->assertEquals( $default_value, $args[ $key ] ); 118 } 119 $this->assertEquals( 'WIDGET_FOO[2]', $args['uppercase_id_set_by_filter'] ); 120 121 $override_args = array( 122 'type' => 'theme_mod', 123 'capability' => 'edit_posts', 124 'transport' => 'postMessage', 125 'default' => array( 'title' => 'asd' ), 126 'sanitize_callback' => '__return_empty_array', 127 'sanitize_js_callback' => '__return_empty_array', 128 ); 129 $args = $this->manager->widgets->get_setting_args( 'widget_bar[3]', $override_args ); 130 foreach ( $override_args as $key => $override_value ) { 131 $this->assertEquals( $override_value, $args[ $key ] ); 132 } 133 $this->assertEquals( 'WIDGET_BAR[3]', $args['uppercase_id_set_by_filter'] ); 134 135 $default_args = array( 136 'type' => 'option', 137 'capability' => 'edit_theme_options', 138 'transport' => 'refresh', 139 'default' => array(), 140 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets' ), 141 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets_js_instance' ), 142 ); 143 $args = $this->manager->widgets->get_setting_args( 'sidebars_widgets[sidebar-1]' ); 144 foreach ( $default_args as $key => $default_value ) { 145 $this->assertEquals( $default_value, $args[ $key ] ); 146 } 147 $this->assertEquals( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] ); 148 149 $override_args = array( 150 'type' => 'theme_mod', 151 'capability' => 'edit_posts', 152 'transport' => 'postMessage', 153 'default' => array( 'title' => 'asd' ), 154 'sanitize_callback' => '__return_empty_array', 155 'sanitize_js_callback' => '__return_empty_array', 156 ); 157 $args = $this->manager->widgets->get_setting_args( 'sidebars_widgets[sidebar-2]', $override_args ); 158 foreach ( $override_args as $key => $override_value ) { 159 $this->assertEquals( $override_value, $args[ $key ] ); 160 } 161 $this->assertEquals( 'SIDEBARS_WIDGETS[SIDEBAR-2]', $args['uppercase_id_set_by_filter'] ); 162 } 163 164 function filter_widget_customizer_setting_args( $args, $id ) { 165 $args['uppercase_id_set_by_filter'] = strtoupper( $id ); 166 return $args; 167 } 168 169 /** 170 * Test WP_Customize_Widgets::sanitize_widget_js_instance() and WP_Customize_Widgets::sanitize_widget_instance() 171 */ 172 function test_sanitize_widget_js_instance() { 173 $this->do_customize_boot_actions(); 174 175 $new_categories_instance = array( 176 'title' => 'Taxonomies Brand New Value', 177 'count' => '1', 178 'hierarchical' => '1', 179 'dropdown' => '1', 180 ); 181 182 $sanitized_for_js = $this->manager->widgets->sanitize_widget_js_instance( $new_categories_instance ); 183 $this->assertArrayHasKey( 'encoded_serialized_instance', $sanitized_for_js ); 184 $this->assertTrue( is_serialized( base64_decode( $sanitized_for_js['encoded_serialized_instance'] ), true ) ); 185 $this->assertEquals( $new_categories_instance['title'], $sanitized_for_js['title'] ); 186 $this->assertTrue( $sanitized_for_js['is_widget_customizer_js_value'] ); 187 $this->assertArrayHasKey( 'instance_hash_key', $sanitized_for_js ); 188 189 $corrupted_sanitized_for_js = $sanitized_for_js; 190 $corrupted_sanitized_for_js['encoded_serialized_instance'] = base64_encode( serialize( array( 'title' => 'EVIL' ) ) ); 191 $this->assertNull( $this->manager->widgets->sanitize_widget_instance( $corrupted_sanitized_for_js ), 'Expected sanitize_widget_instance to reject corrupted data.' ); 192 193 $unsanitized_from_js = $this->manager->widgets->sanitize_widget_instance( $sanitized_for_js ); 194 $this->assertEquals( $unsanitized_from_js, $new_categories_instance ); 195 } 196 }