Make WordPress Core

Changeset 35007


Ignore:
Timestamp:
10/10/2015 09:05:04 AM (11 years ago)
Author:
westonruter
Message:

Customizer: Fix scalability performance problem for previewing multidimensional settings.

As the number of multidimensional settings (serialized options and theme mods) increase for a given ID base (e.g. a widget of a certain type), the number of calls to the multidimensional methods on WP_Customize_Setting increase exponentially, and the time for the preview to refresh grows in time exponentially as well.

To improve performance, this change reduces the number of filters needed to preview the settings off of a multidimensional root from N to 1. This improves performance from O(n^2) to O(n), but the linear increase is so low that the performance is essentially O(1) in comparison. This is achieved by introducing the concept of an "aggregated multidimensional" setting, where the root value of the multidimensional serialized setting value gets cached in a static array variable shared across all settings.

Also improves performance by only adding preview filters if there is actually a need to do so: there is no need to add a filter if there is an initial value and if there is no posted value for a given setting (if it is not dirty).

Fixes #32103.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-customize-setting.php

    r34838 r35007  
    8383
    8484        /**
     85         * Cache of multidimensional values to improve performance.
     86         *
     87         * @since 4.4.0
     88         * @access protected
     89         * @var array
     90         * @static
     91         */
     92        protected static $aggregated_multidimensionals = array();
     93
     94        /**
     95         * Whether the multidimensional setting is aggregated.
     96         *
     97         * @since 4.4.0
     98         * @access protected
     99         * @var bool
     100         */
     101        protected $is_multidimensional_aggregated = false;
     102
     103        /**
    85104         * Constructor.
    86105         *
     
    97116                $keys = array_keys( get_object_vars( $this ) );
    98117                foreach ( $keys as $key ) {
    99                         if ( isset( $args[ $key ] ) )
     118                        if ( isset( $args[ $key ] ) ) {
    100119                                $this->$key = $args[ $key ];
     120                        }
    101121                }
    102122
     
    105125
    106126                // Parse the ID for array keys.
    107                 $this->id_data[ 'keys' ] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
    108                 $this->id_data[ 'base' ] = array_shift( $this->id_data[ 'keys' ] );
     127                $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
     128                $this->id_data['base'] = array_shift( $this->id_data['keys'] );
    109129
    110130                // Rebuild the ID.
    111131                $this->id = $this->id_data[ 'base' ];
    112                 if ( ! empty( $this->id_data[ 'keys' ] ) )
    113                         $this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']';
    114 
    115                 if ( $this->sanitize_callback )
     132                if ( ! empty( $this->id_data[ 'keys' ] ) ) {
     133                        $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
     134                }
     135
     136                if ( $this->sanitize_callback ) {
    116137                        add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
    117 
    118                 if ( $this->sanitize_js_callback )
     138                }
     139                if ( $this->sanitize_js_callback ) {
    119140                        add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
     141                }
     142
     143                if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
     144                        // Other setting types can opt-in to aggregate multidimensional explicitly.
     145                        $this->aggregate_multidimensional();
     146                }
     147        }
     148
     149        /**
     150         * Get parsed ID data for multidimensional setting.
     151         *
     152         * @since 4.4.0
     153         * @access public
     154         *
     155         * @return array {
     156         *     ID data for multidimensional setting.
     157         *
     158         *     @type string $base ID base
     159         *     @type array  $keys Keys for multidimensional array.
     160         * }
     161         */
     162        final public function id_data() {
     163                return $this->id_data;
     164        }
     165
     166        /**
     167         * Set up the setting for aggregated multidimensional values.
     168         *
     169         * When a multidimensional setting gets aggregated, all of its preview and update
     170         * calls get combined into one call, greatly improving performance.
     171         *
     172         * @since 4.4.0
     173         * @access protected
     174         */
     175        protected function aggregate_multidimensional() {
     176                if ( empty( $this->id_data['keys'] ) ) {
     177                        return;
     178                }
     179
     180                $id_base = $this->id_data['base'];
     181                if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) {
     182                        self::$aggregated_multidimensionals[ $this->type ] = array();
     183                }
     184                if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) {
     185                        self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array(
     186                                'previewed_instances'       => array(), // Calling preview() will add the $setting to the array.
     187                                'preview_applied_instances' => array(), // Flags for which settings have had their values applied.
     188                                'root_value'                => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls.
     189                        );
     190                }
     191                $this->is_multidimensional_aggregated = true;
    120192        }
    121193
     
    154226
    155227        /**
    156          * Set up filters for the setting so that the preview request
    157          * will render the drafted changes.
     228         * Add filters to supply the setting's value when accessed.
     229         *
     230         * If the setting already has a pre-existing value and there is no incoming
     231         * post value for the setting, then this method will short-circuit since
     232         * there is no change to preview.
    158233         *
    159234         * @since 3.4.0
     235         * @since 4.4.0 Added boolean return value.
     236         * @access public
     237         *
     238         * @return bool False when preview short-circuits due no change needing to be previewed.
    160239         */
    161240        public function preview() {
    162                 if ( ! isset( $this->_original_value ) ) {
    163                         $this->_original_value = $this->value();
    164                 }
    165241                if ( ! isset( $this->_previewed_blog_id ) ) {
    166242                        $this->_previewed_blog_id = get_current_blog_id();
    167243                }
    168 
    169                 switch( $this->type ) {
     244                $id_base = $this->id_data['base'];
     245                $is_multidimensional = ! empty( $this->id_data['keys'] );
     246                $multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
     247
     248                /*
     249                 * Check if the setting has a pre-existing value (an isset check),
     250                 * and if doesn't have any incoming post value. If both checks are true,
     251                 * then the preview short-circuits because there is nothing that needs
     252                 * to be previewed.
     253                 */
     254                $undefined = new stdClass();
     255                $needs_preview = ( $undefined !== $this->post_value( $undefined ) );
     256                $value = null;
     257
     258                // Since no post value was defined, check if we have an initial value set.
     259                if ( ! $needs_preview ) {
     260                        if ( $this->is_multidimensional_aggregated ) {
     261                                $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
     262                                $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
     263                        } else {
     264                                $default = $this->default;
     265                                $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
     266                                $value = $this->value();
     267                                $this->default = $default;
     268                        }
     269                        $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
     270                }
     271
     272                if ( ! $needs_preview ) {
     273                        return false;
     274                }
     275
     276                switch ( $this->type ) {
    170277                        case 'theme_mod' :
    171                                 add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
     278                                if ( ! $is_multidimensional ) {
     279                                        add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
     280                                } else {
     281                                        if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
     282                                                // Only add this filter once for this ID base.
     283                                                add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
     284                                        }
     285                                        self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
     286                                }
    172287                                break;
    173288                        case 'option' :
    174                                 if ( empty( $this->id_data[ 'keys' ] ) )
    175                                         add_filter( 'pre_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
    176                                 else {
    177                                         add_filter( 'option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
    178                                         add_filter( 'default_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
     289                                if ( ! $is_multidimensional ) {
     290                                        add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
     291                                } else {
     292                                        if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
     293                                                // Only add these filters once for this ID base.
     294                                                add_filter( "option_{$id_base}", $multidimensional_filter );
     295                                                add_filter( "default_option_{$id_base}", $multidimensional_filter );
     296                                        }
     297                                        self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
    179298                                }
    180299                                break;
     
    205324                                do_action( "customize_preview_{$this->type}", $this );
    206325                }
    207         }
    208 
    209         /**
    210          * Callback function to filter the theme mods and options.
     326                return true;
     327        }
     328
     329        /**
     330         * Callback function to filter non-multidimensional theme mods and options.
    211331         *
    212332         * If switch_to_blog() was called after the preview() method, and the current
     
    215335         *
    216336         * @since 3.4.0
    217          * @uses WP_Customize_Setting::multidimensional_replace()
    218337         *
    219338         * @param mixed $original Old value.
     
    225344                }
    226345
    227                 $undefined = new stdClass(); // symbol hack
     346                $undefined = new stdClass(); // Symbol hack.
    228347                $post_value = $this->post_value( $undefined );
    229                 if ( $undefined === $post_value ) {
    230                         $value = $this->_original_value;
     348                if ( $undefined !== $post_value ) {
     349                        $value = $post_value;
    231350                } else {
    232                         $value = $post_value;
    233                 }
    234 
    235                 return $this->multidimensional_replace( $original, $this->id_data['keys'], $value );
     351                        /*
     352                         * Note that we don't use $original here because preview() will
     353                         * not add the filter in the first place if it has an initial value
     354                         * and there is no post value.
     355                         */
     356                        $value = $this->default;
     357                }
     358                return $value;
     359        }
     360
     361        /**
     362         * Callback function to filter multidimensional theme mods and options.
     363         *
     364         * For all multidimensional settings of a given type, the preview filter for
     365         * the first setting previewed will be used to apply the values for the others.
     366         *
     367         * @since 4.4.0
     368         * @access public
     369         *
     370         * @see WP_Customize_Setting::$aggregated_multidimensionals
     371         * @param mixed $original Original root value.
     372         * @return mixed New or old value.
     373         */
     374        public function _multidimensional_preview_filter( $original ) {
     375                if ( ! $this->is_current_blog_previewed() ) {
     376                        return $original;
     377                }
     378
     379                $id_base = $this->id_data['base'];
     380
     381                // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
     382                if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
     383                        return $original;
     384                }
     385
     386                foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
     387                        // Skip applying previewed value for any settings that have already been applied.
     388                        if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
     389                                continue;
     390                        }
     391
     392                        // Do the replacements of the posted/default sub value into the root value.
     393                        $value = $previewed_setting->post_value( $previewed_setting->default );
     394                        $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
     395                        $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
     396                        self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
     397
     398                        // Mark this setting having been applied so that it will be skipped when the filter is called again.
     399                        self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
     400                }
     401
     402                return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
    236403        }
    237404
     
    300467
    301468        /**
     469         * Get the root value for a setting, especially for multidimensional ones.
     470         *
     471         * @since 4.4.0
     472         * @access protected
     473         *
     474         * @param mixed $default Value to return if root does not exist.
     475         * @return mixed
     476         */
     477        protected function get_root_value( $default = null ) {
     478                $id_base = $this->id_data['base'];
     479                if ( 'option' === $this->type ) {
     480                        return get_option( $id_base, $default );
     481                } else if ( 'theme_mod' ) {
     482                        return get_theme_mod( $id_base, $default );
     483                } else {
     484                        /*
     485                         * Any WP_Customize_Setting subclass implementing aggregate multidimensional
     486                         * will need to override this method to obtain the data from the appropriate
     487                         * location.
     488                         */
     489                        return $default;
     490                }
     491        }
     492
     493        /**
     494         * Set the root value for a setting, especially for multidimensional ones.
     495         *
     496         * @since 4.4.0
     497         * @access protected
     498         *
     499         * @param mixed $value Value to set as root of multidimensional setting.
     500         * @return bool Whether the multidimensional root was updated successfully.
     501         */
     502        protected function set_root_value( $value ) {
     503                $id_base = $this->id_data['base'];
     504                if ( 'option' === $this->type ) {
     505                        return update_option( $id_base, $value );
     506                } else if ( 'theme_mod' ) {
     507                        set_theme_mod( $id_base, $value );
     508                        return true;
     509                } else {
     510                        /*
     511                         * Any WP_Customize_Setting subclass implementing aggregate multidimensional
     512                         * will need to override this method to obtain the data from the appropriate
     513                         * location.
     514                         */
     515                        return false;
     516                }
     517        }
     518
     519        /**
    302520         * Save the value of the setting, using the related API.
    303521         *
     
    308526         */
    309527        protected function update( $value ) {
    310                 switch ( $this->type ) {
    311                         case 'theme_mod' :
    312                                 $this->_update_theme_mod( $value );
    313                                 return true;
    314 
    315                         case 'option' :
    316                                 return $this->_update_option( $value );
    317 
    318                         default :
    319 
    320                                 /**
    321                                  * Fires when the {@see WP_Customize_Setting::update()} method is called for settings
    322                                  * not handled as theme_mods or options.
    323                                  *
    324                                  * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
    325                                  *
    326                                  * @since 3.4.0
    327                                  *
    328                                  * @param mixed                $value Value of the setting.
    329                                  * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
    330                                  */
    331                                 do_action( "customize_update_{$this->type}", $value, $this );
    332 
    333                                 return has_action( "customize_update_{$this->type}" );
    334                 }
    335         }
    336 
    337         /**
    338          * Update the theme mod from the value of the parameter.
     528                $id_base = $this->id_data['base'];
     529                if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
     530                        if ( ! $this->is_multidimensional_aggregated ) {
     531                                return $this->set_root_value( $value );
     532                        } else {
     533                                $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
     534                                $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
     535                                self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
     536                                return $this->set_root_value( $root );
     537                        }
     538                } else {
     539                        /**
     540                         * Fires when the {@see WP_Customize_Setting::update()} method is called for settings
     541                         * not handled as theme_mods or options.
     542                         *
     543                         * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
     544                         *
     545                         * @since 3.4.0
     546                         *
     547                         * @param mixed                $value Value of the setting.
     548                         * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
     549                         */
     550                        do_action( "customize_update_{$this->type}", $value, $this );
     551
     552                        return has_action( "customize_update_{$this->type}" );
     553                }
     554        }
     555
     556        /**
     557         * Deprecated method.
    339558         *
    340559         * @since 3.4.0
    341          *
    342          * @param mixed $value The value to update.
    343          */
    344         protected function _update_theme_mod( $value ) {
    345                 // Handle non-array theme mod.
    346                 if ( empty( $this->id_data[ 'keys' ] ) ) {
    347                         set_theme_mod( $this->id_data[ 'base' ], $value );
    348                         return;
    349                 }
    350                 // Handle array-based theme mod.
    351                 $mods = get_theme_mod( $this->id_data[ 'base' ] );
    352                 $mods = $this->multidimensional_replace( $mods, $this->id_data[ 'keys' ], $value );
    353                 if ( isset( $mods ) ) {
    354                         set_theme_mod( $this->id_data[ 'base' ], $mods );
    355                 }
    356         }
    357 
    358         /**
    359          * Update the option from the value of the setting.
     560         * @deprecated 4.4.0 Deprecated in favor of update() method.
     561         */
     562        protected function _update_theme_mod() {
     563                _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
     564        }
     565
     566        /**
     567         * Deprecated method.
    360568         *
    361569         * @since 3.4.0
    362          *
    363          * @param mixed $value The value to update.
    364          * @return bool The result of saving the value.
    365          */
    366         protected function _update_option( $value ) {
    367                 // Handle non-array option.
    368                 if ( empty( $this->id_data[ 'keys' ] ) )
    369                         return update_option( $this->id_data[ 'base' ], $value );
    370 
    371                 // Handle array-based options.
    372                 $options = get_option( $this->id_data[ 'base' ] );
    373                 $options = $this->multidimensional_replace( $options, $this->id_data[ 'keys' ], $value );
    374                 if ( isset( $options ) )
    375                         return update_option( $this->id_data[ 'base' ], $options );
     570         * @deprecated 4.4.0 Deprecated in favor of update() method.
     571         */
     572        protected function _update_option() {
     573                _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
    376574        }
    377575
     
    384582         */
    385583        public function value() {
    386                 // Get the callback that corresponds to the setting type.
    387                 switch( $this->type ) {
    388                         case 'theme_mod' :
    389                                 $function = 'get_theme_mod';
    390                                 break;
    391                         case 'option' :
    392                                 $function = 'get_option';
    393                                 break;
    394                         default :
    395 
    396                                 /**
    397                                  * Filter a Customize setting value not handled as a theme_mod or option.
    398                                  *
    399                                  * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
    400                                  * the base slug of the setting name.
    401                                  *
    402                                  * For settings handled as theme_mods or options, see those corresponding
    403                                  * functions for available hooks.
    404                                  *
    405                                  * @since 3.4.0
    406                                  *
    407                                  * @param mixed $default The setting default value. Default empty.
    408                                  */
    409                                 return apply_filters( 'customize_value_' . $this->id_data[ 'base' ], $this->default );
    410                 }
    411 
    412                 // Handle non-array value
    413                 if ( empty( $this->id_data[ 'keys' ] ) )
    414                         return $function( $this->id_data[ 'base' ], $this->default );
    415 
    416                 // Handle array-based value
    417                 $values = $function( $this->id_data[ 'base' ] );
    418                 return $this->multidimensional_get( $values, $this->id_data[ 'keys' ], $this->default );
     584                $id_base = $this->id_data['base'];
     585                $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
     586
     587                if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
     588                        $value = $this->get_root_value( $this->default );
     589
     590                        /**
     591                         * Filter a Customize setting value not handled as a theme_mod or option.
     592                         *
     593                         * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
     594                         * the base slug of the setting name.
     595                         *
     596                         * For settings handled as theme_mods or options, see those corresponding
     597                         * functions for available hooks.
     598                         *
     599                         * @since 3.4.0
     600                         *
     601                         * @param mixed $default The setting default value. Default empty.
     602                         */
     603                        $value = apply_filters( "customize_value_{$id_base}", $value );
     604                } else if ( $this->is_multidimensional_aggregated ) {
     605                        $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
     606                        $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
     607                } else {
     608                        $value = $this->get_root_value( $this->default );
     609                }
     610                return $value;
    419611        }
    420612
     
    521713         * @param $keys
    522714         * @param mixed $value The value to update.
    523          * @return
     715         * @return mixed
    524716         */
    525717        final protected function multidimensional_replace( $root, $keys, $value ) {
     
    8541046
    8551047        /**
    856          * Get the instance data for a given widget setting.
     1048         * Get the instance data for a given nav_menu_item setting.
    8571049         *
    8581050         * @since 4.3.0
     
    9881180         *
    9891181         * @since 4.3.0
     1182         * @since 4.4.0 Added boolean return value.
    9901183         * @access public
    9911184         *
    9921185         * @see WP_Customize_Manager::post_value()
     1186         *
     1187         * @return bool False if method short-circuited due to no-op.
    9931188         */
    9941189        public function preview() {
    9951190                if ( $this->is_previewed ) {
    996                         return;
     1191                        return false;
     1192                }
     1193
     1194                $undefined = new stdClass();
     1195                $is_placeholder = ( $this->post_id < 0 );
     1196                $is_dirty = ( $undefined !== $this->post_value( $undefined ) );
     1197                if ( ! $is_placeholder && ! $is_dirty ) {
     1198                        return false;
    9971199                }
    9981200
     
    10101212
    10111213                // @todo Add get_post_metadata filters for plugins to add their data.
     1214
     1215                return true;
    10121216        }
    10131217
     
    16221826         *
    16231827         * @since 4.3.0
     1828         * @since 4.4.0 Added boolean return value
    16241829         * @access public
    16251830         *
    16261831         * @see WP_Customize_Manager::post_value()
     1832         *
     1833         * @return bool False if method short-circuited due to no-op.
    16271834         */
    16281835        public function preview() {
    16291836                if ( $this->is_previewed ) {
    1630                         return;
     1837                        return false;
     1838                }
     1839
     1840                $undefined = new stdClass();
     1841                $is_placeholder = ( $this->term_id < 0 );
     1842                $is_dirty = ( $undefined !== $this->post_value( $undefined ) );
     1843                if ( ! $is_placeholder && ! $is_dirty ) {
     1844                        return false;
    16311845                }
    16321846
     
    16391853                add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
    16401854                add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
     1855
     1856                return true;
    16411857        }
    16421858
  • trunk/tests/phpunit/tests/customize/setting.php

    r34838 r35007  
    103103                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) );
    104104                        $this->assertEquals( $default, $setting->value() );
    105                         $setting->preview();
     105                        $this->assertTrue( $setting->preview(), 'Preview should not no-op since setting has no existing value.' );
    106106                        $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 ) );
    107107                        $this->assertEquals( $default, $setting->value() );
     
    115115                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) );
    116116                        $this->assertEquals( $initial_value, $setting->value() );
    117                         $setting->preview();
     117                        $this->assertFalse( $setting->preview(), 'Preview should no-op since setting value was extant and no post value was present.' );
    118118                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
    119119                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     
    121121                        $this->assertEquals( $initial_value, $setting->value() );
    122122
    123                         // @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
    124123                        $overridden_value = "overridden_value_$name";
    125124                        call_user_func( $type_options['setter'], $name, $overridden_value );
    126                         $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name ) );
    127                         $this->assertEquals( $initial_value, $setting->value() );
    128                         $this->assertNotEquals( $overridden_value, $setting->value() );
     125                        $message = 'Initial value should be overridden because initial preview() was no-op due to setting having existing value and/or post value was absent.';
     126                        $this->assertEquals( $overridden_value, call_user_func( $type_options['getter'], $name ), $message );
     127                        $this->assertEquals( $overridden_value, $setting->value(), $message );
     128                        $this->assertNotEquals( $initial_value, $setting->value(), $message );
    129129
    130130                        // Non-multidimensional: Test unset setting being overridden by a post value
     
    134134                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $name, $this->undefined ) );
    135135                        $this->assertEquals( $default, $setting->value() );
    136                         $setting->preview(); // activate post_data
     136                        $this->assertTrue( $setting->preview(), 'Preview applies because setting has post_data_overrides.' ); // activate post_data
    137137                        $this->assertEquals( $this->post_data_overrides[ $name ], call_user_func( $type_options['getter'], $name, $this->undefined ) );
    138138                        $this->assertEquals( $this->post_data_overrides[ $name ], $setting->value() );
     
    146146                        $this->assertEquals( $initial_value, call_user_func( $type_options['getter'], $name, $this->undefined ) );
    147147                        $this->assertEquals( $initial_value, $setting->value() );
    148                         $setting->preview(); // activate post_data
     148                        $this->assertTrue( $setting->preview(), 'Preview applies because setting has post_data_overrides.' ); // activate post_data
    149149                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ) ); // only applicable for custom types (not options or theme_mods)
    150150                        $this->assertEquals( 0, did_action( "customize_preview_{$setting->type}" ) ); // only applicable for custom types (not options or theme_mods)
     
    168168                        $this->assertEquals( $this->undefined, call_user_func( $type_options['getter'], $base_name, $this->undefined ) );
    169169                        $this->assertEquals( $default, $setting->value() );
    170                         $setting->preview();
     170                        $this->assertTrue( $setting->preview() );
    171171                        $base_value = call_user_func( $type_options['getter'], $base_name, $this->undefined );
    172172                        $this->assertArrayHasKey( 'foo', $base_value );
     
    312312                $this->assertEquals( $initial_value, $setting->value() );
    313313                $setting->preview();
    314                 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
    315                 $this->assertEquals( 2, did_action( "customize_preview_{$setting->type}" ) );
     314                $this->assertEquals( 0, did_action( "customize_preview_{$setting->id}" ), 'Zero preview actions because initial value is set with no incoming post value, so there is no preview to apply.' );
     315                $this->assertEquals( 1, did_action( "customize_preview_{$setting->type}" ) );
    316316                $this->assertEquals( $initial_value, $this->custom_type_getter( $name, $this->undefined ) ); // should be same as above
    317317                $this->assertEquals( $initial_value, $setting->value() ); // should be same as above
     
    326326                $this->assertEquals( $default, $setting->value() );
    327327                $setting->preview();
    328                 $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
    329                 $this->assertEquals( 3, did_action( "customize_preview_{$setting->type}" ) );
     328                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ), 'One preview action now because initial value was not set and/or there is no incoming post value, so there is is a preview to apply.' );
     329                $this->assertEquals( 2, did_action( "customize_preview_{$setting->type}" ) );
    330330                $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) );
    331331                $this->assertEquals( $post_data_overrides[ $name ], $setting->value() );
     
    343343                $setting->preview();
    344344                $this->assertEquals( 1, did_action( "customize_preview_{$setting->id}" ) );
    345                 $this->assertEquals( 4, did_action( "customize_preview_{$setting->type}" ) );
     345                $this->assertEquals( 3, did_action( "customize_preview_{$setting->type}" ) );
    346346                $this->assertEquals( $post_data_overrides[ $name ], $this->custom_type_getter( $name, $this->undefined ) );
    347347                $this->assertEquals( $post_data_overrides[ $name ], $setting->value() );
Note: See TracChangeset for help on using the changeset viewer.