diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index baa50df..50ee7f2 100644
|
|
final class WP_Customize_Manager { |
628 | 628 | public function set_post_value( $setting_id, $value ) { |
629 | 629 | $this->unsanitized_post_values(); |
630 | 630 | $this->_post_values[ $setting_id ] = $value; |
| 631 | |
| 632 | /** |
| 633 | * Announce when a setting's unsanitized post value has been set. |
| 634 | * |
| 635 | * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called. |
| 636 | * |
| 637 | * This is useful for <code>WP_Customize_Setting</code> instances to watch |
| 638 | * in order to update a cached previewed value. |
| 639 | * |
| 640 | * @since 4.4.0 |
| 641 | * |
| 642 | * @param string $setting_id Setting ID. |
| 643 | * @param mixed $value Unsanitized setting post value. |
| 644 | * @param WP_Customize_Manager $this WP_Customize_Manager instance. |
| 645 | */ |
| 646 | do_action( 'customize_post_value_set', $setting_id, $value, $this ); |
| 647 | |
| 648 | /** |
| 649 | * Announce when a specific setting's unsanitized post value has been set. |
| 650 | * |
| 651 | * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called. |
| 652 | * |
| 653 | * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID. |
| 654 | * |
| 655 | * @since 4.4.0 |
| 656 | * |
| 657 | * @param mixed $value Unsanitized setting post value. |
| 658 | * @param WP_Customize_Manager $this WP_Customize_Manager instance. |
| 659 | */ |
| 660 | do_action( "customize_post_value_set_{$setting_id}", $value, $this ); |
631 | 661 | } |
632 | 662 | |
633 | 663 | /** |
diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
index 12f76d4..230fe6f 100644
|
|
class WP_Customize_Setting { |
82 | 82 | protected $id_data = array(); |
83 | 83 | |
84 | 84 | /** |
| 85 | * Whether or not preview() was called. |
| 86 | * |
| 87 | * @since 4.4.0 |
| 88 | * @access protected |
| 89 | * @var bool |
| 90 | */ |
| 91 | protected $is_previewed = false; |
| 92 | |
| 93 | /** |
85 | 94 | * Cache of multidimensional values to improve performance. |
86 | 95 | * |
87 | 96 | * @since 4.4.0 |
… |
… |
class WP_Customize_Setting { |
183 | 192 | self::$aggregated_multidimensionals[ $this->type ] = array(); |
184 | 193 | } |
185 | 194 | if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) { |
| 195 | // Only add one action per multidimensional value. |
| 196 | add_action( 'customize_post_value_set', array( $this, '_clear_aggregated_multidimensional_perview_applied_flag' ) ); |
| 197 | |
186 | 198 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array( |
187 | 199 | 'previewed_instances' => array(), // Calling preview() will add the $setting to the array. |
188 | 200 | 'preview_applied_instances' => array(), // Flags for which settings have had their values applied. |
… |
… |
class WP_Customize_Setting { |
245 | 257 | if ( ! isset( $this->_previewed_blog_id ) ) { |
246 | 258 | $this->_previewed_blog_id = get_current_blog_id(); |
247 | 259 | } |
| 260 | |
| 261 | // Prevent re-previewing an already-previewed setting. |
| 262 | if ( $this->is_previewed ) { |
| 263 | return true; |
| 264 | } |
| 265 | |
248 | 266 | $id_base = $this->id_data['base']; |
249 | 267 | $is_multidimensional = ! empty( $this->id_data['keys'] ); |
250 | 268 | $multidimensional_filter = array( $this, '_multidimensional_preview_filter' ); |
… |
… |
class WP_Customize_Setting { |
273 | 291 | $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied. |
274 | 292 | } |
275 | 293 | |
| 294 | // If the setting does not need previewing now, defer to when it has a value to preview. |
276 | 295 | if ( ! $needs_preview ) { |
| 296 | if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) { |
| 297 | add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ); |
| 298 | } |
277 | 299 | return false; |
278 | 300 | } |
279 | 301 | |
… |
… |
class WP_Customize_Setting { |
327 | 349 | */ |
328 | 350 | do_action( "customize_preview_{$this->type}", $this ); |
329 | 351 | } |
| 352 | |
| 353 | $this->is_previewed = true; |
| 354 | |
330 | 355 | return true; |
331 | 356 | } |
332 | 357 | |
333 | 358 | /** |
| 359 | * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated. |
| 360 | * |
| 361 | * This ensures that the new value will get sanitized and used the next time |
| 362 | * that <code>WP_Customize_Setting::_multidimensional_preview_filter()</code> |
| 363 | * is called for this setting. |
| 364 | * |
| 365 | * @access private |
| 366 | * @see WP_Customize_Manager::set_post_value() |
| 367 | * @see WP_Customize_Setting::_multidimensional_preview_filter() |
| 368 | * |
| 369 | * @param string $setting_id Setting ID. |
| 370 | */ |
| 371 | final public function _clear_aggregated_multidimensional_perview_applied_flag( $setting_id ) { |
| 372 | if ( 0 === strpos( $setting_id, $this->id_data['base'] . '[' ) ) { |
| 373 | unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $setting_id ] ); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | /** |
334 | 378 | * Callback function to filter non-multidimensional theme mods and options. |
335 | 379 | * |
336 | 380 | * If switch_to_blog() was called after the preview() method, and the current |
… |
… |
class WP_Customize_Setting { |
369 | 413 | * the first setting previewed will be used to apply the values for the others. |
370 | 414 | * |
371 | 415 | * @since 4.4.0 |
372 | | * @access public |
| 416 | * @access private |
373 | 417 | * |
374 | 418 | * @see WP_Customize_Setting::$aggregated_multidimensionals |
375 | 419 | * @param mixed $original Original root value. |
376 | 420 | * @return mixed New or old value. |
377 | 421 | */ |
378 | | public function _multidimensional_preview_filter( $original ) { |
| 422 | final public function _multidimensional_preview_filter( $original ) { |
| 423 | $id_base = $this->id_data['base']; |
| 424 | |
379 | 425 | if ( ! $this->is_current_blog_previewed() ) { |
380 | 426 | return $original; |
381 | 427 | } |
382 | 428 | |
383 | | $id_base = $this->id_data['base']; |
384 | | |
385 | 429 | // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value. |
386 | 430 | if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
387 | 431 | return $original; |
388 | 432 | } |
389 | 433 | |
390 | 434 | foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) { |
391 | | // Skip applying previewed value for any settings that have already been applied. |
392 | | if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) { |
393 | | continue; |
394 | | } |
| 435 | $previewed_setting->apply_multidimensional_preview_value(); |
| 436 | } |
395 | 437 | |
396 | | // Do the replacements of the posted/default sub value into the root value. |
397 | | $value = $previewed_setting->post_value( $previewed_setting->default ); |
398 | | $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value']; |
399 | | $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value ); |
400 | | self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root; |
| 438 | return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
| 439 | } |
401 | 440 | |
402 | | // Mark this setting having been applied so that it will be skipped when the filter is called again. |
403 | | self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true; |
| 441 | /** |
| 442 | * Ensure that a multidimensional setting's post value is applied to preview. |
| 443 | * |
| 444 | * @since 4.4.0 |
| 445 | * @access private |
| 446 | * |
| 447 | * @see WP_Customize_Setting::_multidimensional_preview_filter() |
| 448 | */ |
| 449 | final protected function apply_multidimensional_preview_value() { |
| 450 | $id_base = $this->id_data['base']; |
| 451 | |
| 452 | // Skip applying previewed value for any settings that have already been applied. |
| 453 | if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $this->id ] ) ) { |
| 454 | return; |
404 | 455 | } |
405 | 456 | |
406 | | return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
| 457 | // Do the replacements of the posted/default sub value into the root value. |
| 458 | $value = $this->post_value( $this->default ); |
| 459 | $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
| 460 | $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value ); |
| 461 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root; |
| 462 | |
| 463 | // Mark this setting having been applied so that it will be skipped when the filter is called again. |
| 464 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $this->id ] = true; |
407 | 465 | } |
408 | 466 | |
409 | 467 | /** |
… |
… |
class WP_Customize_Setting { |
610 | 668 | */ |
611 | 669 | $value = apply_filters( "customize_value_{$id_base}", $value ); |
612 | 670 | } else if ( $this->is_multidimensional_aggregated ) { |
| 671 | $undefined = new stdClass(); |
| 672 | if ( $this->is_previewed && $undefined !== $this->post_value( $undefined ) ) { |
| 673 | $this->apply_multidimensional_preview_value(); |
| 674 | } |
613 | 675 | $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value']; |
614 | 676 | $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default ); |
615 | 677 | } else { |
diff --git src/wp-includes/class-wp-customize-widgets.php src/wp-includes/class-wp-customize-widgets.php
index 6ee6942..7639d50 100644
|
|
final class WP_Customize_Widgets { |
1380 | 1380 | * in place from WP_Customize_Setting::preview() will use this value |
1381 | 1381 | * instead of the default widget instance value (an empty array). |
1382 | 1382 | */ |
1383 | | $this->manager->set_post_value( $setting_id, $instance ); |
| 1383 | $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance ) ); |
1384 | 1384 | |
1385 | 1385 | // Obtain the widget control with the updated instance in place. |
1386 | 1386 | ob_start(); |
diff --git src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php
index 2fa0b5c..073423e 100644
|
|
class WP_Customize_Nav_Menu_Item_Setting extends WP_Customize_Setting { |
120 | 120 | public $original_nav_menu_term_id; |
121 | 121 | |
122 | 122 | /** |
123 | | * Whether or not preview() was called. |
124 | | * |
125 | | * @since 4.3.0 |
126 | | * @access protected |
127 | | * @var bool |
128 | | */ |
129 | | protected $is_previewed = false; |
130 | | |
131 | | /** |
132 | 123 | * Whether or not update() was called. |
133 | 124 | * |
134 | 125 | * @since 4.3.0 |
diff --git src/wp-includes/customize/class-wp-customize-nav-menu-setting.php src/wp-includes/customize/class-wp-customize-nav-menu-setting.php
index 766099e..5562a8d 100644
|
|
class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting { |
89 | 89 | public $previous_term_id; |
90 | 90 | |
91 | 91 | /** |
92 | | * Whether or not preview() was called. |
93 | | * |
94 | | * @since 4.3.0 |
95 | | * @access protected |
96 | | * @var bool |
97 | | */ |
98 | | protected $is_previewed = false; |
99 | | |
100 | | /** |
101 | 92 | * Whether or not update() was called. |
102 | 93 | * |
103 | 94 | * @since 4.3.0 |
diff --git tests/phpunit/tests/customize/setting.php tests/phpunit/tests/customize/setting.php
index da789b1..939a7ff 100644
|
|
class Tests_WP_Customize_Setting extends WP_UnitTestCase { |
160 | 160 | $_POST['customized'] = wp_slash( wp_json_encode( $this->post_data_overrides ) ); |
161 | 161 | |
162 | 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) |
| 163 | // Multidimensional: See what effect the preview filter has on a non-existent setting (default value should be seen). |
164 | 164 | $base_name = "unset_{$type}_multi"; |
165 | 165 | $name = $base_name . '[foo]'; |
166 | 166 | $default = "default_value_{$name}"; |
167 | 167 | $setting = new WP_Customize_Setting( $this->manager, $name, compact( 'type', 'default' ) ); |
168 | 168 | $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); |
169 | 169 | $this->assertEquals( $default, $setting->value() ); |
170 | | $this->assertTrue( $setting->preview() ); |
| 170 | $this->assertTrue( $setting->preview(), $setting->id ); |
171 | 171 | $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); |
172 | 172 | $this->assertArrayHasKey( 'foo', $base_value ); |
173 | 173 | $this->assertEquals( $default, $base_value['foo'] ); |
174 | 174 | |
175 | | // Multidimensional: See what effect the preview has on an extant setting (default value should not be seen) |
| 175 | // Multidimensional: See what effect the preview has on an extant setting (default value should not be seen). |
176 | 176 | $base_name = "set_{$type}_multi"; |
177 | 177 | $name = $base_name . '[foo]'; |
178 | 178 | $default = "default_value_{$name}"; |
… |
… |
class Tests_WP_Customize_Setting extends WP_UnitTestCase { |
184 | 184 | $this->assertEquals( $initial_value, $base_value['foo'] ); |
185 | 185 | $this->assertEquals( $initial_value, $setting->value() ); |
186 | 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) |
| 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 | 189 | $base_value = call_user_func( $type_options['getter'], $base_name, array() ); |
190 | 190 | $this->assertEquals( $initial_value, $base_value['foo'] ); |
191 | 191 | $this->assertEquals( $initial_value, $setting->value() ); |
192 | 192 | |
193 | | // Multidimensional: Test unset setting being overridden by a post value |
| 193 | // Multidimensional: Test unset setting being overridden by a post value. |
194 | 194 | $base_name = "unset_{$type}_multi_overridden"; |
195 | 195 | $name = $base_name . '[foo]'; |
196 | 196 | $default = "default_value_{$name}"; |
… |
… |
class Tests_WP_Customize_Setting extends WP_UnitTestCase { |
198 | 198 | $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) ); |
199 | 199 | $this->assertEquals( $default, $setting->value() ); |
200 | 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) |
| 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 | 203 | $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); |
204 | 204 | $this->assertArrayHasKey( 'foo', $base_value ); |
205 | 205 | $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); |
206 | 206 | |
207 | | // Multidimemsional: Test set setting being overridden by a post value |
| 207 | // Multidimensional: Test set setting being overridden by a post value. |
208 | 208 | $base_name = "set_{$type}_multi_overridden"; |
209 | 209 | $name = $base_name . '[foo]'; |
210 | 210 | $default = "default_value_{$name}"; |
… |
… |
class Tests_WP_Customize_Setting extends WP_UnitTestCase { |
221 | 221 | $this->assertEquals( $base_initial_value['bar'], $getter['bar'] ); |
222 | 222 | $this->assertEquals( $initial_value, $setting->value() ); |
223 | 223 | $setting->preview(); |
224 | | $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods) |
225 | | $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods) |
| 224 | $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // Only applicable for custom types (not options or theme_mods). |
| 225 | $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // Only applicable for custom types (not options or theme_mods). |
226 | 226 | $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined ); |
227 | 227 | $this->assertArrayHasKey( 'foo', $base_value ); |
228 | 228 | $this->assertEquals( $this->post_data_overrides[ $name ], $base_value['foo'] ); |