WordPress.org

Make WordPress Core

Changeset 35007


Ignore:
Timestamp:
10/10/2015 09:05:04 AM (4 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.