WordPress.org

Make WordPress Core

Changeset 31370


Ignore:
Timestamp:
02/08/2015 11:10:05 PM (5 years ago)
Author:
ocean90
Message:

Customizer: Introduce an API to create WP_Customize_Settings for dynamically-created settings.

  • Introduce WP_Customize_Manager::add_dynamic_settings() to register dynamically-created settings.
  • Introduce customize_dynamic_setting_args filter to pass an array of args to a dynamic setting's constructor.
  • Add unit tests for WP_Customize_Manager and WP_Customize_Widgets.
  • See WP_Customize_Widgets as an example.

props westonruter.
fixes #30936.

Location:
trunk
Files:
1 added
4 edited

Legend:

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

    r31360 r31370  
    6666     * Unsanitized values for Customize Settings parsed from $_POST['customized'].
    6767     *
    68      * @var array|false
     68     * @var array
    6969     */
    7070    private $_post_values;
     
    103103
    104104        add_action( 'customize_register',                 array( $this, 'register_controls' ) );
     105        add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
    105106        add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
    106107        add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
     
    111112     *
    112113     * @since 3.4.0
    113      *
     114     * @since 4.2.0 Added $action param.
     115     *
     116     * @param string|null $action whether the supplied Ajax action is being run.
    114117     * @return bool
    115118     */
    116     public function doing_ajax() {
    117         return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
     119    public function doing_ajax( $action = null ) {
     120        $doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
     121        if ( ! $doing_ajax ) {
     122            return false;
     123        }
     124
     125        if ( ! $action ) {
     126            return true;
     127        } else {
     128            // Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need to check before admin-ajax.php gets to that point
     129            return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
     130        }
    118131    }
    119132
     
    412425                $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
    413426            }
    414             if ( empty( $this->_post_values ) ) { // if not isset or of JSON error
    415                 $this->_post_values = false;
     427            if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
     428                $this->_post_values = array();
    416429            }
    417430        }
     
    440453            return $default;
    441454        }
     455    }
     456
     457    /**
     458     * Override a setting's (unsanitized) value as found in any incoming $_POST['customized']
     459     *
     460     * @since 4.2.0
     461     *
     462     * @param string $setting_id  The ID for the WP_Customize_Setting instance.
     463     * @param mixed $value
     464     */
     465    public function set_post_value( $setting_id, $value ) {
     466        $this->unsanitized_post_values();
     467        $this->_post_values[ $setting_id ] = $value;
    442468    }
    443469
     
    728754
    729755    /**
     756     * Register any dynamically-created settings, such as those from $_POST['customized'] that have no corresponding setting created.
     757     *
     758     * This is a mechanism to "wake up" settings that have been dynamically created
     759     * on the frontend and have been sent to WordPress in $_POST['customized']. When WP
     760     * loads, the dynamically-created settings then will get created and previewed
     761     * even though they are not directly created statically with code.
     762     *
     763     * @since 4.2.0
     764     *
     765     * @param string[] $setting_ids The setting IDs to add.
     766     * @return WP_Customize_Setting[] The settings added.
     767     */
     768    public function add_dynamic_settings( $setting_ids ) {
     769        $new_settings = array();
     770        foreach ( $setting_ids as $setting_id ) {
     771            // Skip settings already created
     772            if ( $this->get_setting( $setting_id ) ) {
     773                continue;
     774            }
     775
     776            $setting_args = false;
     777            $setting_class = 'WP_Customize_Setting';
     778
     779            /**
     780             * Filter a dynamic setting's constructor args.
     781             *
     782             * For a dynamic setting to be registered, this filter must be employed
     783             * to override the default false value with an array of args to pass to
     784             * the WP_Customize_Setting constructor.
     785             *
     786             * @since 4.2.0
     787             *
     788             * @param false|array $setting_args  The arguments to the WP_Customize_Setting constructor.
     789             * @param string      $setting_id    ID for dynamic setting, usually coming from $_POST['customized'].
     790             */
     791            $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
     792            if ( false === $setting_args ) {
     793                continue;
     794            }
     795
     796            /**
     797             * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
     798             *
     799             * @since 4.2.0
     800             *
     801             * @param string $setting_class  WP_Customize_Setting or a subclass.
     802             * @param string $setting_id     ID for dynamic setting, usually coming from $_POST['customized'].
     803             * @param string $setting_args   WP_Customize_Setting or a subclass.
     804             */
     805            $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
     806
     807            $setting = new $setting_class( $this, $setting_id, $setting_args );
     808            $this->add_setting( $setting );
     809            $new_settings[] = $setting;
     810        }
     811        return $new_settings;
     812    }
     813
     814    /**
    730815     * Retrieve a customize setting.
    731816     *
     
    736821     */
    737822    public function get_setting( $id ) {
    738         if ( isset( $this->settings[ $id ] ) )
     823        if ( isset( $this->settings[ $id ] ) ) {
    739824            return $this->settings[ $id ];
     825        }
    740826    }
    741827
     
    12731359            ) );
    12741360        }
     1361    }
     1362
     1363    /**
     1364     * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
     1365     *
     1366     * @since 4.2.0
     1367     */
     1368    public function register_dynamic_settings() {
     1369        $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
    12751370    }
    12761371
  • trunk/src/wp-includes/class-wp-customize-setting.php

    r31329 r31370  
    5454
    5555    protected $id_data = array();
    56 
    57     /**
    58      * Cached and sanitized $_POST value for the setting.
    59      *
    60      * @access private
    61      * @var mixed
    62      */
    63     private $_post_value;
    6456
    6557    /**
     
    164156    public function _preview_filter( $original ) {
    165157        $undefined = new stdClass(); // symbol hack
    166         $post_value = $this->manager->post_value( $this, $undefined );
     158        $post_value = $this->post_value( $undefined );
    167159        if ( $undefined === $post_value ) {
    168160            $value = $this->_original_value;
     
    212204     */
    213205    final public function post_value( $default = null ) {
    214         // Check for a cached value
    215         if ( isset( $this->_post_value ) )
    216             return $this->_post_value;
    217 
    218         // Call the manager for the post value
    219         $result = $this->manager->post_value( $this );
    220 
    221         if ( isset( $result ) )
    222             return $this->_post_value = $result;
    223         else
    224             return $default;
     206        return $this->manager->post_value( $this, $default );
    225207    }
    226208
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r31226 r31370  
    3636     * @since 3.9.0
    3737     * @access protected
    38      * @var
    39      */
    40     protected $_customized;
     38     * @var array
     39     */
     40    protected $rendered_sidebars = array();
    4141
    4242    /**
     
    4545     * @var array
    4646     */
    47     protected $_prepreview_added_filters = array();
     47    protected $rendered_widgets = array();
    4848
    4949    /**
     
    5252     * @var array
    5353     */
    54     protected $rendered_sidebars = array();
    55 
    56     /**
    57      * @since 3.9.0
     54    protected $old_sidebars_widgets = array();
     55
     56    /**
     57     * Mapping of setting type to setting ID pattern.
     58     *
     59     * @since 4.2.0
    5860     * @access protected
    5961     * @var array
    6062     */
    61     protected $rendered_widgets = array();
    62 
    63     /**
    64      * @since 3.9.0
    65      * @access protected
    66      * @var array
    67      */
    68     protected $old_sidebars_widgets = array();
     63    protected $setting_id_patterns = array(
     64        'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
     65        'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
     66    );
    6967
    7068    /**
     
    7977        $this->manager = $manager;
    8078
    81         add_action( 'after_setup_theme',                       array( $this, 'setup_widget_addition_previews' ) );
     79        add_filter( 'customize_dynamic_setting_args',          array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
     80        add_action( 'after_setup_theme',                       array( $this, 'register_settings' ) );
    8281        add_action( 'wp_loaded',                               array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
    8382        add_action( 'customize_controls_init',                 array( $this, 'customize_controls_init' ) );
     
    9695
    9796    /**
     97     * Get the widget setting type given a setting ID.
     98     *
     99     * @since 4.2.0
     100     *
     101     * @param $setting_id
     102     *
     103     * @return string|null
     104     */
     105    protected function get_setting_type( $setting_id ) {
     106        static $cache = array();
     107        if ( isset( $cache[ $setting_id ] ) ) {
     108            return $cache[ $setting_id ];
     109        }
     110        foreach ( $this->setting_id_patterns as $type => $pattern ) {
     111            if ( preg_match( $pattern, $setting_id ) ) {
     112                $cache[ $setting_id ] = $type;
     113                return $type;
     114            }
     115        }
     116        return null;
     117    }
     118
     119    /**
     120     * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly.
     121     *
     122     * @since 4.2.0
     123     */
     124    public function register_settings() {
     125        $widget_setting_ids = array();
     126        $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
     127        foreach ( $incoming_setting_ids as $setting_id ) {
     128            if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) {
     129                $widget_setting_ids[] = $setting_id;
     130            }
     131        }
     132        if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) {
     133            $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
     134        }
     135
     136        $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
     137
     138        /*
     139         * Preview settings right away so that widgets and sidebars will get registered properly.
     140         * But don't do this if a customize_save because this will cause WP to think there is nothing
     141         * changed that needs to be saved.
     142         */
     143        if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
     144            foreach ( $settings as $setting ) {
     145                $setting->preview();
     146            }
     147        }
     148    }
     149
     150    /**
     151     * Determine the arguments for a dynamically-created setting.
     152     *
     153     * @since 4.2.0
     154     *
     155     * @param false|array $args
     156     * @param string $setting_id
     157     * @return false|array
     158     */
     159    public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
     160        if ( $this->get_setting_type( $setting_id ) ) {
     161            $args = $this->get_setting_args( $setting_id );
     162        }
     163        return $args;
     164    }
     165
     166    /**
    98167     * Get an unslashed post value or return a default.
    99168     *
     
    111180        }
    112181
    113         return wp_unslash( $_POST[$name] );
    114     }
    115 
    116     /**
    117      * Set up widget addition previews.
    118      *
    119      * Since the widgets get registered on 'widgets_init' before the Customizer
    120      * settings are set up on 'customize_register', we have to filter the options
    121      * similarly to how the setting previewer will filter the options later.
    122      *
    123      * @since 3.9.0
    124      *
    125      * @access public
    126      */
    127     public function setup_widget_addition_previews() {
    128         $is_customize_preview = false;
    129 
    130         if ( ! empty( $this->manager ) && ! is_admin() && 'on' === $this->get_post_value( 'wp_customize' ) ) {
    131             $is_customize_preview = check_ajax_referer( 'preview-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
    132         }
    133 
    134         $is_ajax_widget_update = false;
    135         if ( $this->manager->doing_ajax() && 'update-widget' === $this->get_post_value( 'action' ) ) {
    136             $is_ajax_widget_update = check_ajax_referer( 'update-widget', 'nonce', false );
    137         }
    138 
    139         $is_ajax_customize_save = false;
    140         if ( $this->manager->doing_ajax() && 'customize_save' === $this->get_post_value( 'action' ) ) {
    141             $is_ajax_customize_save = check_ajax_referer( 'save-customize_' . $this->manager->get_stylesheet(), 'nonce', false );
    142         }
    143 
    144         $is_valid_request = ( $is_ajax_widget_update || $is_customize_preview || $is_ajax_customize_save );
    145         if ( ! $is_valid_request ) {
    146             return;
    147         }
    148 
    149         // Input from Customizer preview.
    150         if ( isset( $_POST['customized'] ) ) {
    151             $this->_customized = json_decode( $this->get_post_value( 'customized' ), true );
    152         } else { // Input from ajax widget update request.
    153             $this->_customized = array();
    154             $id_base = $this->get_post_value( 'id_base' );
    155             $widget_number = $this->get_post_value( 'widget_number', false );
    156             $option_name = 'widget_' . $id_base;
    157             $this->_customized[ $option_name ] = array();
    158             if ( preg_match( '/^[0-9]+$/', $widget_number ) ) {
    159                 $option_name .= '[' . $widget_number . ']';
    160                 $this->_customized[ $option_name ][ $widget_number ] = array();
    161             }
    162         }
    163 
    164         $function = array( $this, 'prepreview_added_sidebars_widgets' );
    165 
    166         $hook = 'option_sidebars_widgets';
    167         add_filter( $hook, $function );
    168         $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
    169 
    170         $hook = 'default_option_sidebars_widgets';
    171         add_filter( $hook, $function );
    172         $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
    173 
    174         $function = array( $this, 'prepreview_added_widget_instance' );
    175         foreach ( $this->_customized as $setting_id => $value ) {
    176             if ( preg_match( '/^(widget_.+?)(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
    177                 $option = $matches[1];
    178 
    179                 $hook = sprintf( 'option_%s', $option );
    180                 if ( ! has_filter( $hook, $function ) ) {
    181                     add_filter( $hook, $function );
    182                     $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
    183                 }
    184 
    185                 $hook = sprintf( 'default_option_%s', $option );
    186                 if ( ! has_filter( $hook, $function ) ) {
    187                     add_filter( $hook, $function );
    188                     $this->_prepreview_added_filters[] = compact( 'hook', 'function' );
    189                 }
    190 
    191                 /*
    192                  * Make sure the option is registered so that the update_option()
    193                  * won't fail due to the filters providing a default value, which
    194                  * causes the update_option() to get confused.
    195                  */
    196                 add_option( $option, array() );
    197             }
    198         }
    199     }
    200 
    201     /**
    202      * Ensure that newly-added widgets will appear in the widgets_sidebars.
    203      *
    204      * This is necessary because the Customizer's setting preview filters
    205      * are added after the widgets_init action, which is too late for the
    206      * widgets to be set up properly.
    207      *
    208      * @since 3.9.0
    209      * @access public
    210      *
    211      * @param array $sidebars_widgets Associative array of sidebars and their widgets.
    212      * @return array Filtered array of sidebars and their widgets.
    213      */
    214     public function prepreview_added_sidebars_widgets( $sidebars_widgets ) {
    215         foreach ( $this->_customized as $setting_id => $value ) {
    216             if ( preg_match( '/^sidebars_widgets\[(.+?)\]$/', $setting_id, $matches ) ) {
    217                 $sidebar_id = $matches[1];
    218                 $sidebars_widgets[ $sidebar_id ] = $value;
    219             }
    220         }
    221         return $sidebars_widgets;
    222     }
    223 
    224     /**
    225      * Ensure newly-added widgets have empty instances so they
    226      * will be recognized.
    227      *
    228      * This is necessary because the Customizer's setting preview
    229      * filters are added after the widgets_init action, which is
    230      * too late for the widgets to be set up properly.
    231      *
    232      * @since 3.9.0
    233      * @access public
    234      *
    235      * @param array|bool|mixed $value Widget instance(s), false if open was empty.
    236      * @return array|mixed Widget instance(s) with additions.
    237      */
    238     public function prepreview_added_widget_instance( $value = false ) {
    239         if ( ! preg_match( '/^(?:default_)?option_(widget_(.+))/', current_filter(), $matches ) ) {
    240             return $value;
    241         }
    242         $id_base = $matches[2];
    243 
    244         foreach ( $this->_customized as $setting_id => $setting ) {
    245             $parsed_setting_id = $this->parse_widget_setting_id( $setting_id );
    246             if ( is_wp_error( $parsed_setting_id ) || $id_base !== $parsed_setting_id['id_base'] ) {
    247                 continue;
    248             }
    249             $widget_number = $parsed_setting_id['number'];
    250 
    251             if ( is_null( $widget_number ) ) {
    252                 // Single widget.
    253                 if ( false === $value ) {
    254                     $value = array();
    255                 }
    256             } else {
    257                 // Multi widget.
    258                 if ( empty( $value ) ) {
    259                     $value = array( '_multiwidget' => 1 );
    260                 }
    261                 if ( ! isset( $value[ $widget_number ] ) ) {
    262                     $value[ $widget_number ] = array();
    263                 }
    264             }
    265         }
    266 
    267         return $value;
    268     }
    269 
    270     /**
    271      * Remove pre-preview filters.
    272      *
    273      * Removes filters added in setup_widget_addition_previews()
    274      * to ensure widgets are populating the options during
    275      * 'widgets_init'.
    276      *
    277      * @since 3.9.0
    278      * @access public
    279      */
    280     public function remove_prepreview_filters() {
    281         foreach ( $this->_prepreview_added_filters as $prepreview_added_filter ) {
    282             remove_filter( $prepreview_added_filter['hook'], $prepreview_added_filter['function'] );
    283         }
    284         $this->_prepreview_added_filters = array();
     182        return wp_unslash( $_POST[ $name ] );
    285183    }
    286184
     
    381279     */
    382280    public function schedule_customize_register() {
    383         if ( is_admin() ) { // @todo for some reason, $wp_customize->is_preview() is true here?
     281        if ( is_admin() ) {
    384282            $this->customize_register();
    385283        } else {
     
    413311            $setting_id   = $this->get_setting_id( $widget_id );
    414312            $setting_args = $this->get_setting_args( $setting_id );
    415 
    416             $setting_args['sanitize_callback']    = array( $this, 'sanitize_widget_instance' );
    417             $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
    418 
    419             $this->manager->add_setting( $setting_id, $setting_args );
    420 
     313            if ( ! $this->manager->get_setting( $setting_id ) ) {
     314                $this->manager->add_setting( $setting_id, $setting_args );
     315            }
    421316            $new_setting_ids[] = $setting_id;
    422317        }
     
    453348                $setting_id   = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
    454349                $setting_args = $this->get_setting_args( $setting_id );
    455 
    456                 $setting_args['sanitize_callback']    = array( $this, 'sanitize_sidebar_widgets' );
    457                 $setting_args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
    458 
    459                 $this->manager->add_setting( $setting_id, $setting_args );
     350                if ( ! $this->manager->get_setting( $setting_id ) ) {
     351                    $this->manager->add_setting( $setting_id, $setting_args );
     352                }
    460353                $new_setting_ids[] = $setting_id;
    461354
     
    524417        }
    525418
    526         /*
    527          * We have to register these settings later than customize_preview_init
    528          * so that other filters have had a chance to run.
    529          */
    530         if ( did_action( 'customize_preview_init' ) ) {
     419        if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
    531420            foreach ( $new_setting_ids as $new_setting_id ) {
    532421                $this->manager->get_setting( $new_setting_id )->preview();
    533422            }
    534423        }
    535         $this->remove_prepreview_filters();
     424
     425        add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
    536426    }
    537427
     
    805695            'default'    => array(),
    806696        );
     697
     698        if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
     699            $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
     700            $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
     701        } else if ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
     702            $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
     703            $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
     704        }
     705
    807706        $args = array_merge( $args, $overrides );
    808707
     
    832731     */
    833732    public function sanitize_sidebar_widgets( $widget_ids ) {
    834         global $wp_registered_widgets;
    835 
    836         $widget_ids           = array_map( 'strval', (array) $widget_ids );
     733        $widget_ids = array_map( 'strval', (array) $widget_ids );
    837734        $sanitized_widget_ids = array();
    838 
    839735        foreach ( $widget_ids as $widget_id ) {
    840             if ( array_key_exists( $widget_id, $wp_registered_widgets ) ) {
    841                 $sanitized_widget_ids[] = $widget_id;
    842             }
     736            $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
    843737        }
    844738        return $sanitized_widget_ids;
     
    975869     */
    976870    public function customize_preview_init() {
    977         add_filter( 'sidebars_widgets',   array( $this, 'preview_sidebars_widgets' ), 1 );
    978871        add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
    979872        add_action( 'wp_print_styles',    array( $this, 'print_preview_css' ), 1 );
     
    13161209        // Clean up any input vars that were manually added
    13171210        foreach ( $added_input_vars as $key ) {
    1318             unset( $_POST[$key] );
    1319             unset( $_REQUEST[$key] );
     1211            unset( $_POST[ $key ] );
     1212            unset( $_REQUEST[ $key ] );
    13201213        }
    13211214
     
    13341227        }
    13351228
     1229        // Obtain the widget instance.
     1230        $option = $this->get_captured_option( $option_name );
     1231        if ( null !== $parsed_id['number'] ) {
     1232            $instance = $option[ $parsed_id['number'] ];
     1233        } else {
     1234            $instance = $option;
     1235        }
     1236
     1237        /*
     1238         * Override the incoming $_POST['customized'] for a newly-created widget's
     1239         * setting with the new $instance so that the preview filter currently
     1240         * in place from WP_Customize_Setting::preview() will use this value
     1241         * instead of the default widget instance value (an empty array).
     1242         */
     1243        $setting_id = $this->get_setting_id( $widget_id );
     1244        $this->manager->set_post_value( $setting_id, $instance );
     1245
    13361246        // Obtain the widget control with the updated instance in place.
    13371247        ob_start();
    1338 
    1339         $form = $wp_registered_widget_controls[$widget_id];
     1248        $form = $wp_registered_widget_controls[ $widget_id ];
    13401249        if ( $form ) {
    13411250            call_user_func_array( $form['callback'], $form['params'] );
    13421251        }
    1343 
    13441252        $form = ob_get_clean();
    1345 
    1346         // Obtain the widget instance.
    1347         $option = get_option( $option_name );
    1348 
    1349         if ( null !== $parsed_id['number'] ) {
    1350             $instance = $option[$parsed_id['number']];
    1351         } else {
    1352             $instance = $option;
    1353         }
    13541253
    13551254        $this->stop_capturing_option_updates();
     
    13841283        }
    13851284
    1386         if ( ! isset( $_POST['widget-id'] ) ) {
    1387             wp_send_json_error();
     1285        if ( empty( $_POST['widget-id'] ) ) {
     1286            wp_send_json_error( 'missing_widget-id' );
    13881287        }
    13891288
     
    13991298        $widget_id = $this->get_post_value( 'widget-id' );
    14001299        $parsed_id = $this->parse_widget_id( $widget_id );
    1401         $id_base   = $parsed_id['id_base'];
    1402 
    1403         if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
    1404             wp_send_json_error();
     1300        $id_base = $parsed_id['id_base'];
     1301
     1302        $is_updating_widget_template = (
     1303            isset( $_POST[ 'widget-' . $id_base ] )
     1304            &&
     1305            is_array( $_POST[ 'widget-' . $id_base ] )
     1306            &&
     1307            preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
     1308        );
     1309        if ( $is_updating_widget_template ) {
     1310            wp_send_json_error( 'template_widget_not_updatable' );
    14051311        }
    14061312
    14071313        $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
    14081314        if ( is_wp_error( $updated_widget ) ) {
    1409             wp_send_json_error();
     1315            wp_send_json_error( $updated_widget->get_error_message() );
    14101316        }
    14111317
     
    14641370
    14651371    /**
     1372     * Get the option that was captured from being saved.
     1373     *
     1374     * @since 4.2.0
     1375     * @access protected
     1376     *
     1377     * @param string $option_name Option name.
     1378     * @param mixed  $default     Optional. Default value to return if the option does not exist.
     1379     * @return mixed Value set for the option.
     1380     */
     1381    protected function get_captured_option( $option_name, $default = false ) {
     1382        if ( array_key_exists( $option_name, $this->_captured_options ) ) {
     1383            $value = $this->_captured_options[ $option_name ];
     1384        } else {
     1385            $value = $default;
     1386        }
     1387        return $value;
     1388    }
     1389
     1390    /**
    14661391     * Get the number of captured widget option updates.
    14671392     *
     
    14971422     * @access public
    14981423     *
    1499      * @param mixed $new_value
    1500      * @param string $option_name
    1501      * @param mixed $old_value
    1502      * @return mixed
     1424     * @param mixed  $new_value   The new option value.
     1425     * @param string $option_name Name of the option.
     1426     * @param mixed  $old_value   The old option value.
     1427     * @return mixed Filtered option value.
    15031428     */
    15041429    public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
     
    15071432        }
    15081433
    1509         if ( ! isset( $this->_captured_options[$option_name] ) ) {
     1434        if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
    15101435            add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
    15111436        }
    15121437
    1513         $this->_captured_options[$option_name] = $new_value;
     1438        $this->_captured_options[ $option_name ] = $new_value;
    15141439
    15151440        return $old_value;
     
    15221447     * @access public
    15231448     *
    1524      * @param mixed $value Option
    1525      * @return mixed
     1449     * @param mixed $value Value to return instead of the option value.
     1450     * @return mixed Filtered option value.
    15261451     */
    15271452    public function capture_filter_pre_get_option( $value ) {
    15281453        $option_name = preg_replace( '/^pre_option_/', '', current_filter() );
    15291454
    1530         if ( isset( $this->_captured_options[$option_name] ) ) {
    1531             $value = $this->_captured_options[$option_name];
     1455        if ( isset( $this->_captured_options[ $option_name ] ) ) {
     1456            $value = $this->_captured_options[ $option_name ];
    15321457
    15331458            /** This filter is documented in wp-includes/option.php */
     
    15581483        $this->_is_capturing_option_updates = false;
    15591484    }
     1485
     1486    /**
     1487     * @since 3.9.0
     1488     * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
     1489     */
     1490    public function setup_widget_addition_previews() {
     1491        _deprecated_function( __METHOD__, '4.2.0' );
     1492    }
     1493
     1494    /**
     1495     * @since 3.9.0
     1496     * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
     1497     */
     1498    public function prepreview_added_sidebars_widgets() {
     1499        _deprecated_function( __METHOD__, '4.2.0' );
     1500    }
     1501
     1502    /**
     1503     * @since 3.9.0
     1504     * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
     1505     */
     1506    public function prepreview_added_widget_instance() {
     1507        _deprecated_function( __METHOD__, '4.2.0' );
     1508    }
     1509
     1510    /**
     1511     * @since 3.9.0
     1512     * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
     1513     */
     1514    public function remove_prepreview_filters() {
     1515        _deprecated_function( __METHOD__, '4.2.0' );
     1516    }
    15601517}
  • trunk/tests/phpunit/tests/customize/manager.php

    r31329 r31370  
    3333
    3434    /**
    35      * Test WP_Customize_Manager::unsanitized_post_values()
     35     * Test WP_Customize_Manager::doing_ajax().
     36     *
     37     * @group ajax
     38     */
     39    function test_doing_ajax() {
     40        if ( ! defined( 'DOING_AJAX' ) ) {
     41            define( 'DOING_AJAX', true );
     42        }
     43
     44        $manager = $this->instantiate();
     45        $this->assertTrue( $manager->doing_ajax() );
     46
     47        $_REQUEST['action'] = 'customize_save';
     48        $this->assertTrue( $manager->doing_ajax( 'customize_save' ) );
     49        $this->assertFalse( $manager->doing_ajax( 'update-widget' ) );
     50    }
     51
     52    /**
     53     * Test ! WP_Customize_Manager::doing_ajax().
     54     */
     55    function test_not_doing_ajax() {
     56        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
     57            $this->markTestSkipped( 'Cannot test when DOING_AJAX' );
     58        }
     59
     60        $manager = $this->instantiate();
     61        $this->assertFalse( $manager->doing_ajax() );
     62    }
     63
     64    /**
     65     * Test WP_Customize_Manager::unsanitized_post_values().
    3666     *
    3767     * @ticket 30988
     
    5080
    5181    /**
    52      * Test the WP_Customize_Manager::post_value() method
     82     * Test the WP_Customize_Manager::post_value() method.
    5383     *
    5484     * @ticket 30988
     
    72102    }
    73103
     104    /**
     105     * Test the WP_Customize_Manager::add_dynamic_settings() method.
     106     *
     107     * @ticket 30936
     108     */
     109    function test_add_dynamic_settings() {
     110        $manager = $this->instantiate();
     111        $setting_ids = array( 'foo', 'bar' );
     112        $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
     113        $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected there to not be a bar setting up front.' );
     114        $manager->add_dynamic_settings( $setting_ids );
     115        $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected the bar setting to remain absent since filters not added.' );
     116
     117        $this->action_customize_register_for_dynamic_settings();
     118        $manager->add_dynamic_settings( $setting_ids );
     119        $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected bar setting to be created since filters were added.' );
     120        $this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->default, 'Expected static foo setting to not get overridden by dynamic setting.' );
     121        $this->assertEquals( 'dynamic_bar_default', $manager->get_setting( 'bar' )->default, 'Expected dynamic setting bar to have default providd by filter.' );
     122    }
     123
     124    /**
     125     * Test the WP_Customize_Manager::register_dynamic_settings() method.
     126     *
     127     * This is similar to test_add_dynamic_settings, except the settings are passed via $_POST['customized'].
     128     *
     129     * @ticket 30936
     130     */
     131    function test_register_dynamic_settings() {
     132        $posted_settings = array(
     133            'foo' => 'OOF',
     134            'bar' => 'RAB',
     135        );
     136        $_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) );
     137
     138        add_action( 'customize_register', array( $this, 'action_customize_register_for_dynamic_settings' ) );
     139
     140        $manager = $this->instantiate();
     141        $manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
     142
     143        $this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to not be registered.' );
     144        do_action( 'customize_register', $manager );
     145        $this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to be automatically registered after customize_register action.' );
     146        $this->assertEmpty( $manager->get_setting( 'baz' ), 'Expected unrecognized dynamic setting "baz" to remain unregistered.' );
     147    }
     148
     149    /**
     150     * In lieu of closures, callback for customize_register action added in test_register_dynamic_settings().
     151     */
     152    function action_customize_register_for_dynamic_settings() {
     153        add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args_for_test_dynamic_settings' ), 10, 2 );
     154        add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_customize_dynamic_setting_class_for_test_dynamic_settings' ), 10, 3 );
     155    }
     156
     157    /**
     158     * In lieu of closures, callback for customize_dynamic_setting_args filter added for test_register_dynamic_settings().
     159     */
     160    function filter_customize_dynamic_setting_args_for_test_dynamic_settings( $setting_args, $setting_id ) {
     161        $this->assertEquals( false, $setting_args, 'Expected $setting_args to be false by default.' );
     162        $this->assertInternalType( 'string', $setting_id );
     163        if ( in_array( $setting_id, array( 'foo', 'bar' ) ) ) {
     164            $setting_args = array( 'default' => "dynamic_{$setting_id}_default" );
     165        }
     166        return $setting_args;
     167    }
     168
     169    /**
     170     * In lieu of closures, callback for customize_dynamic_setting_class filter added for test_register_dynamic_settings().
     171     */
     172    function filter_customize_dynamic_setting_class_for_test_dynamic_settings( $setting_class, $setting_id, $setting_args ) {
     173        $this->assertEquals( 'WP_Customize_Setting', $setting_class );
     174        $this->assertInternalType( 'string', $setting_id );
     175        $this->assertInternalType( 'array', $setting_args );
     176        return $setting_class;
     177    }
    74178}
    75 
Note: See TracChangeset for help on using the changeset viewer.