WordPress.org

Make WordPress Core

Changeset 27652


Ignore:
Timestamp:
03/22/2014 08:54:17 PM (7 years ago)
Author:
ocean90
Message:

Widget Customizer: Improve error handling. First pass.

  • Replace Widget_Customizer_Exception with WP_Error
  • Call Previewer.cheatin() to show the cheating message if a user can't change widgets
  • Call Previewer.login() to show the login form if a user is logged out
  • Show a generic error message on failures

see #27419.

Location:
trunk/src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/js/customize-widgets.js

    r27650 r27652  
    1717            save_btn_tooltip: '',
    1818            remove_btn_label: '',
    19             remove_btn_tooltip: ''
     19            remove_btn_tooltip: '',
     20            error: '',
    2021        },
    2122        available_widgets: [], // available widgets for instantiating
     
    11731174            widget_content = control.container.find( '.widget-content' );
    11741175
     1176            // Remove a previous error message
     1177            widget_content.find( '.widget-error' ).remove();
     1178
    11751179            // @todo Support more selectors than IDs?
    11761180            if ( $.contains( control.container[0], document.activeElement ) && $( document.activeElement ).is( '[id]' ) ) {
     
    12191223                    has_same_inputs_in_response,
    12201224                    is_instance_identical;
     1225
     1226                // Check if the user is logged out.
     1227                if ( '0' === r ) {
     1228                    self.previewer.preview.iframe.hide();
     1229                    self.previewer.login().done( function() {
     1230                        control.updateWidget( args );
     1231                        self.previewer.preview.iframe.show();
     1232                    } );
     1233                    return;
     1234                }
     1235
     1236                // Check for cheaters.
     1237                if ( '-1' === r ) {
     1238                    self.previewer.cheatin();
     1239                    return;
     1240                }
    12211241
    12221242                if ( r.success ) {
     
    12751295                     */
    12761296                    is_instance_identical = _( control.setting() ).isEqual( r.data.instance );
    1277                     if ( is_instance_identical ) {
    1278                         control.container.removeClass( 'previewer-loading' );
    1279                     } else {
     1297                    if ( ! is_instance_identical ) {
    12801298                        control.is_widget_updating = true; // suppress triggering another updateWidget
    12811299                        control.setting( r.data.instance );
     
    12871305                    }
    12881306                } else {
    1289                     window.console && window.console.log( r );
    1290                     message = 'FAIL';
     1307                    message = self.i18n.error;
    12911308                    if ( r.data && r.data.message ) {
    12921309                        message = r.data.message;
     
    12951312                        complete_callback.call( control, message );
    12961313                    } else {
    1297                         throw new Error( message );
     1314                        widget_content.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
    12981315                    }
    12991316                }
     
    13021319                if ( complete_callback ) {
    13031320                    complete_callback.call( control, textStatus );
    1304                 } else {
    1305                     throw new Error( textStatus );
    13061321                }
    13071322            } );
    13081323            jqxhr.always( function () {
     1324                control.container.removeClass( 'previewer-loading' );
    13091325                control.container.removeClass( 'widget-form-loading' );
    13101326                inputs.each( function () {
  • trunk/src/wp-includes/class-wp-customize-widgets.php

    r27620 r27652  
    425425     * Convert a widget setting ID (option path) to its id_base and number components
    426426     *
    427      * @throws Widget_Customizer_Exception
    428      * @throws Exception
    429      *
    430427     * @param string $setting_id
    431      * @param array
    432      * @return array
     428     * @return WP_Error|array
    433429     */
    434430    static function parse_widget_setting_id( $setting_id ) {
    435431        if ( ! preg_match( '/^(widget_(.+?))(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
    436             throw new Widget_Customizer_Exception( sprintf( 'Invalid widget setting ID: %s', $setting_id ) );
    437         }
     432            return new WP_Error( 'invalid_setting_id', 'Invalid widget setting ID' );
     433        }
     434
    438435        $id_base = $matches[2];
    439436        $number  = isset( $matches[3] ) ? intval( $matches[3] ) : null;
     
    501498                'remove_btn_label' => __( 'Remove' ),
    502499                'remove_btn_tooltip' => ( 'Trash widget by moving it to the inactive widgets sidebar.' ),
     500                'error' => __('An error has occurred. Please reload the page and try again.'),
    503501            ),
    504502            'tpl' => array(
     
    913911     * $_POST be populated with the instance data.
    914912     *
    915      * @throws Widget_Customizer_Exception
    916      * @throws Exception
    917      *
    918      * @param string $widget_id
    919      * @return array
     913     * @param  string $widget_id
     914     * @return WP_Error|array
    920915     */
    921916    static function call_widget_update( $widget_id ) {
     
    924919        $options_transaction = new Options_Transaction();
    925920
    926         try {
    927             $options_transaction->start();
    928             $parsed_id   = self::parse_widget_id( $widget_id );
    929             $option_name = 'widget_' . $parsed_id['id_base'];
    930 
    931             /**
    932              * If a previously-sanitized instance is provided, populate the input vars
    933              * with its values so that the widget update callback will read this instance
    934              */
    935             $added_input_vars = array();
    936             if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
    937                 $sanitized_widget_setting = json_decode( self::get_post_value( 'sanitized_widget_setting' ), true );
    938                 if ( empty( $sanitized_widget_setting ) ) {
    939                     throw new Widget_Customizer_Exception( 'Malformed sanitized_widget_setting' );
    940                 }
    941                 $instance = self::sanitize_widget_instance( $sanitized_widget_setting );
    942                 if ( is_null( $instance ) ) {
    943                     throw new Widget_Customizer_Exception( 'Unsanitary sanitized_widget_setting' );
    944                 }
    945                 if ( ! is_null( $parsed_id['number'] ) ) {
    946                     $value = array();
    947                     $value[$parsed_id['number']] = $instance;
    948                     $key = 'widget-' . $parsed_id['id_base'];
     921        $options_transaction->start();
     922        $parsed_id   = self::parse_widget_id( $widget_id );
     923        $option_name = 'widget_' . $parsed_id['id_base'];
     924
     925        /**
     926         * If a previously-sanitized instance is provided, populate the input vars
     927         * with its values so that the widget update callback will read this instance
     928         */
     929        $added_input_vars = array();
     930        if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
     931            $sanitized_widget_setting = json_decode( self::get_post_value( 'sanitized_widget_setting' ), true );
     932            if ( empty( $sanitized_widget_setting ) ) {
     933                $options_transaction->rollback();
     934                return new WP_Error( 'malformed_data', 'Malformed sanitized_widget_setting' );
     935            }
     936
     937            $instance = self::sanitize_widget_instance( $sanitized_widget_setting );
     938            if ( is_null( $instance ) ) {
     939                $options_transaction->rollback();
     940                return new WP_Error( 'unsanitary_data', 'Unsanitary sanitized_widget_setting' );
     941            }
     942
     943            if ( ! is_null( $parsed_id['number'] ) ) {
     944                $value = array();
     945                $value[$parsed_id['number']] = $instance;
     946                $key = 'widget-' . $parsed_id['id_base'];
     947                $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
     948                $added_input_vars[] = $key;
     949            } else {
     950                foreach ( $instance as $key => $value ) {
    949951                    $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
    950952                    $added_input_vars[] = $key;
    951                 } else {
    952                     foreach ( $instance as $key => $value ) {
    953                         $_REQUEST[$key] = $_POST[$key] = wp_slash( $value );
    954                         $added_input_vars[] = $key;
    955                     }
    956953                }
    957954            }
    958 
    959             /**
    960              * Invoke the widget update callback
    961              */
    962             foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
    963                 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
    964                     ob_start();
    965                     call_user_func_array( $control['callback'], $control['params'] );
    966                     ob_end_clean();
    967                     break;
    968                 }
    969             }
    970 
    971             // Clean up any input vars that were manually added
    972             foreach ( $added_input_vars as $key ) {
    973                 unset( $_POST[$key] );
    974                 unset( $_REQUEST[$key] );
    975             }
    976 
    977             /**
    978              * Make sure the expected option was updated
    979              */
    980             if ( 0 !== $options_transaction->count() ) {
    981                 if ( count( $options_transaction->options ) > 1 ) {
    982                     throw new Widget_Customizer_Exception( sprintf( 'Widget %1$s unexpectedly updated more than one option.', $widget_id ) );
    983                 }
    984                 $updated_option_name = key( $options_transaction->options );
    985                 if ( $updated_option_name !== $option_name ) {
    986                     throw new Widget_Customizer_Exception( sprintf( 'Widget %1$s updated option "%2$s", but expected "%3$s".', $widget_id, $updated_option_name, $option_name ) );
    987                 }
    988             }
    989 
    990             /**
    991              * Obtain the widget control with the updated instance in place
    992              */
    993             ob_start();
    994             $form = $wp_registered_widget_controls[$widget_id];
    995             if ( $form ) {
    996                 call_user_func_array( $form['callback'], $form['params'] );
    997             }
    998             $form = ob_get_clean();
    999 
    1000             /**
    1001              * Obtain the widget instance
    1002              */
    1003             $option = get_option( $option_name );
    1004             if ( null !== $parsed_id['number'] ) {
    1005                 $instance = $option[$parsed_id['number']];
    1006             } else {
    1007                 $instance = $option;
    1008             }
    1009 
    1010             $options_transaction->rollback();
    1011             return compact( 'instance', 'form' );
    1012         }
    1013         catch ( Exception $e ) {
    1014             $options_transaction->rollback();
    1015             throw $e;
    1016         }
     955        }
     956
     957        /**
     958         * Invoke the widget update callback
     959         */
     960        foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
     961            if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
     962                ob_start();
     963                call_user_func_array( $control['callback'], $control['params'] );
     964                ob_end_clean();
     965                break;
     966            }
     967        }
     968
     969        // Clean up any input vars that were manually added
     970        foreach ( $added_input_vars as $key ) {
     971            unset( $_POST[$key] );
     972            unset( $_REQUEST[$key] );
     973        }
     974
     975        /**
     976         * Make sure the expected option was updated
     977         */
     978        if ( 0 !== $options_transaction->count() ) {
     979            if ( count( $options_transaction->options ) > 1 ) {
     980                $options_transaction->rollback();
     981                return new WP_Error( 'unexpected_update', 'Widget unexpectedly updated more than one option.' );
     982            }
     983
     984            $updated_option_name = key( $options_transaction->options );
     985            if ( $updated_option_name !== $option_name ) {
     986                $options_transaction->rollback();
     987                return new WP_Error( 'wrong_option', sprintf( 'Widget updated option "%1$s", but expected "%2$s".', $updated_option_name, $option_name ) );
     988            }
     989        }
     990
     991        /**
     992         * Obtain the widget control with the updated instance in place
     993         */
     994        ob_start();
     995        $form = $wp_registered_widget_controls[$widget_id];
     996        if ( $form ) {
     997            call_user_func_array( $form['callback'], $form['params'] );
     998        }
     999        $form = ob_get_clean();
     1000
     1001        /**
     1002         * Obtain the widget instance
     1003         */
     1004        $option = get_option( $option_name );
     1005        if ( null !== $parsed_id['number'] ) {
     1006            $instance = $option[$parsed_id['number']];
     1007        } else {
     1008            $instance = $option;
     1009        }
     1010
     1011        $options_transaction->rollback();
     1012        return compact( 'instance', 'form' );
    10171013    }
    10181014
     
    10271023     */
    10281024    static function wp_ajax_update_widget() {
    1029         $generic_error = __( 'An error has occurred. Please reload the page and try again.' );
    1030 
    1031         try {
    1032             if ( ! check_ajax_referer( self::UPDATE_WIDGET_AJAX_ACTION, self::UPDATE_WIDGET_NONCE_POST_KEY, false ) ) {
    1033                 throw new Widget_Customizer_Exception( ( 'Nonce check failed. Reload and try again?' ) );
    1034             }
    1035             if ( ! current_user_can( 'edit_theme_options' ) ) {
    1036                 throw new Widget_Customizer_Exception( ( 'Current user cannot!' ) ); // @todo translate
    1037             }
    1038             if ( ! isset( $_POST['widget-id'] ) ) {
    1039                 throw new Widget_Customizer_Exception( ( 'Incomplete request' ) ); // @todo translate
    1040             }
    1041 
    1042             unset( $_POST[self::UPDATE_WIDGET_NONCE_POST_KEY], $_POST['action'] );
    1043 
    1044             do_action( 'load-widgets.php' );
    1045             do_action( 'widgets.php' );
    1046             do_action( 'sidebar_admin_setup' );
    1047 
    1048             $widget_id = self::get_post_value( 'widget-id' );
    1049             $parsed_id = self::parse_widget_id( $widget_id );
    1050             $id_base   = $parsed_id['id_base'];
    1051 
    1052             if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
    1053                 throw new Widget_Customizer_Exception( 'Cannot pass widget templates to create new instances; apply template vars in JS' );
    1054             }
    1055 
    1056             $updated_widget = self::call_widget_update( $widget_id ); // => {instance,form}
    1057             $form = $updated_widget['form'];
    1058             $instance = self::sanitize_widget_js_instance( $updated_widget['instance'] );
    1059 
    1060             wp_send_json_success( compact( 'form', 'instance' ) );
    1061         }
    1062         catch( Exception $e ) {
    1063             if ( $e instanceof Widget_Customizer_Exception ) {
    1064                 $message = $e->getMessage();
    1065             } else {
    1066                 error_log( sprintf( '%s in %s: %s', get_class( $e ), __FUNCTION__, $e->getMessage() ) );
    1067                 $message = $generic_error;
    1068             }
    1069             wp_send_json_error( compact( 'message' ) );
    1070         }
     1025
     1026        if ( ! is_user_logged_in() ) {
     1027            wp_die( 0 );
     1028        }
     1029
     1030        check_ajax_referer( self::UPDATE_WIDGET_AJAX_ACTION, self::UPDATE_WIDGET_NONCE_POST_KEY );
     1031
     1032        if ( ! current_user_can( 'edit_theme_options' ) ) {
     1033            wp_die( -1 );
     1034        }
     1035
     1036        if ( ! isset( $_POST['widget-id'] ) ) {
     1037            wp_send_json_error();
     1038        }
     1039
     1040        unset( $_POST[self::UPDATE_WIDGET_NONCE_POST_KEY], $_POST['action'] );
     1041
     1042        do_action( 'load-widgets.php' );
     1043        do_action( 'widgets.php' );
     1044        do_action( 'sidebar_admin_setup' );
     1045
     1046        $widget_id = self::get_post_value( 'widget-id' );
     1047        $parsed_id = self::parse_widget_id( $widget_id );
     1048        $id_base   = $parsed_id['id_base'];
     1049
     1050        if ( isset( $_POST['widget-' . $id_base] ) && is_array( $_POST['widget-' . $id_base] ) && preg_match( '/__i__|%i%/', key( $_POST['widget-' . $id_base] ) ) ) {
     1051            wp_send_json_error();
     1052        }
     1053
     1054        $updated_widget = self::call_widget_update( $widget_id ); // => {instance,form}
     1055        if ( is_wp_error( $updated_widget ) ) {
     1056            wp_send_json_error();
     1057        }
     1058
     1059        $form = $updated_widget['form'];
     1060        $instance = self::sanitize_widget_js_instance( $updated_widget['instance'] );
     1061
     1062        wp_send_json_success( compact( 'form', 'instance' ) );
    10711063    }
    10721064}
    1073 
    1074 class Widget_Customizer_Exception extends Exception {}
    10751065
    10761066class Options_Transaction {
     
    12051195                update_option( $option_operation['option_name'], $option_operation['old_value'] );
    12061196            }
    1207             else {
    1208                 throw new Exception( 'Unexpected operation' );
    1209             }
    12101197        }
    12111198        $this->_is_current = false;
Note: See TracChangeset for help on using the changeset viewer.