Theme Customizer: Migrate to an ajax-based solution for refreshing the preview and saving. see #20507, #19910.
* Use ajax-based saving, add saving indicator. * Use ajax-based refreshing instead of form targets. * Instead of using hidden inputs with prefixed names to track the canonical data, use the values stored in wp.customize. Encode the values as JSON before sending to avoid bugs with ids that contain square brackets (PHP mangles POST values with nested brackets). * Use wp.customize.Previewer solely for the purpose of previewing; move the postMessage connection with the parent frame and other unrelated code snippets into the 'ready' handler. git-svn-id: http://svn.automattic.com/wordpress/trunk@20645 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
17ee4064b1
commit
2f2cb0926f
|
@ -21,9 +21,6 @@ class WP_Customize_Setting {
|
||||||
protected $id_data = array();
|
protected $id_data = array();
|
||||||
private $_post_value; // Cached, sanitized $_POST value.
|
private $_post_value; // Cached, sanitized $_POST value.
|
||||||
|
|
||||||
// Prefix for $_POST values to prevent naming conflicts.
|
|
||||||
const name_prefix = 'customize_';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -121,16 +118,8 @@ class WP_Customize_Setting {
|
||||||
if ( isset( $this->_post_value ) )
|
if ( isset( $this->_post_value ) )
|
||||||
return $this->_post_value;
|
return $this->_post_value;
|
||||||
|
|
||||||
$base = self::name_prefix . $this->id_data[ 'base' ];
|
$result = $this->manager->post_value( $this );
|
||||||
|
|
||||||
if ( ! isset( $_POST[ $base ] ) )
|
|
||||||
return $default;
|
|
||||||
|
|
||||||
$result = $this->multidimensional_get( $_POST[ $base ], $this->id_data[ 'keys' ] );
|
|
||||||
if ( ! isset( $result ) )
|
|
||||||
return $default;
|
|
||||||
|
|
||||||
$result = $this->sanitize( $result );
|
|
||||||
if ( isset( $result ) )
|
if ( isset( $result ) )
|
||||||
return $this->_post_value = $result;
|
return $this->_post_value = $result;
|
||||||
else
|
else
|
||||||
|
|
|
@ -17,6 +17,10 @@ final class WP_Customize {
|
||||||
protected $sections = array();
|
protected $sections = array();
|
||||||
protected $controls = array();
|
protected $controls = array();
|
||||||
|
|
||||||
|
protected $customized;
|
||||||
|
|
||||||
|
private $_post_values;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
@ -31,6 +35,8 @@ final class WP_Customize {
|
||||||
add_action( 'admin_init', array( $this, 'admin_init' ) );
|
add_action( 'admin_init', array( $this, 'admin_init' ) );
|
||||||
add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
|
add_action( 'wp_loaded', array( $this, 'wp_loaded' ) );
|
||||||
|
|
||||||
|
add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
|
||||||
|
|
||||||
add_action( 'customize_register', array( $this, 'register_controls' ) );
|
add_action( 'customize_register', array( $this, 'register_controls' ) );
|
||||||
add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
|
add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
|
||||||
add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
|
add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
|
||||||
|
@ -148,6 +154,24 @@ final class WP_Customize {
|
||||||
$this->customize_preview_init();
|
$this->customize_preview_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the $_POST attribute used to override the WP_Customize_Setting values.
|
||||||
|
*
|
||||||
|
* @since 3.4.0
|
||||||
|
*/
|
||||||
|
public function post_value( $setting ) {
|
||||||
|
if ( ! isset( $this->_post_values ) ) {
|
||||||
|
if ( isset( $_POST['customized'] ) )
|
||||||
|
$this->_post_values = json_decode( stripslashes( $_POST['customized'] ), true );
|
||||||
|
else
|
||||||
|
$this->_post_values = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $this->_post_values[ $setting->id ] ) )
|
||||||
|
return $setting->sanitize( $this->_post_values[ $setting->id ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print javascript settings.
|
* Print javascript settings.
|
||||||
*
|
*
|
||||||
|
@ -267,9 +291,6 @@ final class WP_Customize {
|
||||||
* @since 3.4.0
|
* @since 3.4.0
|
||||||
*/
|
*/
|
||||||
public function admin_init() {
|
public function admin_init() {
|
||||||
if ( isset( $_REQUEST['save_customize_controls'] ) )
|
|
||||||
$this->save();
|
|
||||||
|
|
||||||
if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) )
|
if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -297,14 +318,14 @@ final class WP_Customize {
|
||||||
*/
|
*/
|
||||||
public function save() {
|
public function save() {
|
||||||
if ( ! $this->is_preview() )
|
if ( ! $this->is_preview() )
|
||||||
return;
|
die;
|
||||||
|
|
||||||
check_admin_referer( 'customize_controls' );
|
check_ajax_referer( 'customize_controls', 'nonce' );
|
||||||
|
|
||||||
// Do we have to switch themes?
|
// Do we have to switch themes?
|
||||||
if ( $this->get_stylesheet() != $this->original_stylesheet ) {
|
if ( $this->get_stylesheet() != $this->original_stylesheet ) {
|
||||||
if ( ! current_user_can( 'switch_themes' ) )
|
if ( ! current_user_can( 'switch_themes' ) )
|
||||||
return;
|
die;
|
||||||
|
|
||||||
// Temporarily stop previewing the theme to allow switch_themes()
|
// Temporarily stop previewing the theme to allow switch_themes()
|
||||||
// to operate properly.
|
// to operate properly.
|
||||||
|
@ -320,6 +341,8 @@ final class WP_Customize {
|
||||||
}
|
}
|
||||||
|
|
||||||
add_action( 'admin_notices', array( $this, '_save_feedback' ) );
|
add_action( 'admin_notices', array( $this, '_save_feedback' ) );
|
||||||
|
|
||||||
|
die;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -128,6 +128,16 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#customize-footer-actions img {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.saving #customize-footer-actions img {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
.customize-control {
|
.customize-control {
|
||||||
float: left;
|
float: left;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
|
|
@ -41,10 +41,8 @@ do_action( 'customize_controls_print_scripts' );
|
||||||
?>
|
?>
|
||||||
</head>
|
</head>
|
||||||
<body class="wp-full-overlay">
|
<body class="wp-full-overlay">
|
||||||
<form id="customize-controls" method="post" class="wrap wp-full-overlay-sidebar" target="_parent" action="<?php echo esc_url( add_query_arg( 'save_customize_controls', '1', admin_url( 'themes.php' ) ) ); ?>">
|
<form id="customize-controls" class="wrap wp-full-overlay-sidebar">
|
||||||
<?php wp_nonce_field( 'customize_controls' ); ?>
|
<?php wp_nonce_field( 'customize_controls' ); ?>
|
||||||
<input type="hidden" name="customize" value="on" />
|
|
||||||
<input type="hidden" name="theme" value="<?php echo esc_attr( $this->get_stylesheet() ); ?>" />
|
|
||||||
<div id="customize-header-actions" class="customize-section wp-full-overlay-header">
|
<div id="customize-header-actions" class="customize-section wp-full-overlay-header">
|
||||||
<a class="back" href="<?php echo esc_url( admin_url( 'themes.php' ) ); ?>">
|
<a class="back" href="<?php echo esc_url( admin_url( 'themes.php' ) ); ?>">
|
||||||
<?php printf( __( '← Return to %s' ), __('Manage Themes') ); ?>
|
<?php printf( __( '← Return to %s' ), __('Manage Themes') ); ?>
|
||||||
|
@ -79,15 +77,15 @@ do_action( 'customize_controls_print_scripts' );
|
||||||
$save_text = $this->get_stylesheet() == $this->original_stylesheet ? __('Save') : __('Save and Activate');
|
$save_text = $this->get_stylesheet() == $this->original_stylesheet ? __('Save') : __('Save and Activate');
|
||||||
submit_button( $save_text, 'primary', 'save', false );
|
submit_button( $save_text, 'primary', 'save', false );
|
||||||
?>
|
?>
|
||||||
|
<img src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" />
|
||||||
|
|
||||||
<a href="#" class="collapse-sidebar button-secondary" title="<?php esc_attr_e('Collapse Sidebar'); ?>">
|
<a href="#" class="collapse-sidebar button-secondary" title="<?php esc_attr_e('Collapse Sidebar'); ?>">
|
||||||
<span class="collapse-sidebar-label"><?php _e('Collapse'); ?></span>
|
<span class="collapse-sidebar-label"><?php _e('Collapse'); ?></span>
|
||||||
<span class="collapse-sidebar-arrow"></span>
|
<span class="collapse-sidebar-arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div id="customize-preview" class="wp-full-overlay-main">
|
<div id="customize-preview" class="wp-full-overlay-main"></div>
|
||||||
<iframe name="customize-target"></iframe>
|
|
||||||
</div>
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
do_action( 'customize_controls_print_footer_scripts' );
|
do_action( 'customize_controls_print_footer_scripts' );
|
||||||
|
@ -95,11 +93,12 @@ do_action( 'customize_controls_print_scripts' );
|
||||||
// Check current scheme and load the preview with the same scheme
|
// Check current scheme and load the preview with the same scheme
|
||||||
$scheme = is_ssl() ? 'https' : 'http';
|
$scheme = is_ssl() ? 'https' : 'http';
|
||||||
$settings = array(
|
$settings = array(
|
||||||
|
'theme' => $this->get_stylesheet(),
|
||||||
'preview' => esc_url( home_url( '/', $scheme ) ),
|
'preview' => esc_url( home_url( '/', $scheme ) ),
|
||||||
'settings' => array(),
|
'settings' => array(),
|
||||||
'controls' => array(),
|
'controls' => array(),
|
||||||
'prefix' => WP_Customize_Setting::name_prefix,
|
|
||||||
'parent' => esc_url( admin_url() ),
|
'parent' => esc_url( admin_url() ),
|
||||||
|
'ajax' => esc_url( admin_url( 'admin-ajax.php', 'relative' ) ),
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( $this->settings as $id => $setting ) {
|
foreach ( $this->settings as $id => $setting ) {
|
||||||
|
|
|
@ -265,6 +265,18 @@ if ( typeof wp === 'undefined' )
|
||||||
return this._value[ id ];
|
return this._value[ id ];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get: function() {
|
||||||
|
var result = {};
|
||||||
|
|
||||||
|
if ( arguments.length )
|
||||||
|
return this.pass( 'get', arguments );
|
||||||
|
|
||||||
|
$.each( this._value, function( key, obj ) {
|
||||||
|
result[ key ] = obj.get();
|
||||||
|
} );
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
set: function( id ) {
|
set: function( id ) {
|
||||||
if ( this.has( id ) )
|
if ( this.has( id ) )
|
||||||
return this.pass( 'set', arguments );
|
return this.pass( 'set', arguments );
|
||||||
|
@ -326,7 +338,7 @@ if ( typeof wp === 'undefined' )
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each( [ 'get', 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
|
$.each( [ 'bind', 'unbind', 'link', 'unlink', 'sync', 'unsync', 'setter', 'resetSetter' ], function( i, method ) {
|
||||||
api.Values.prototype[ method ] = function() {
|
api.Values.prototype[ method ] = function() {
|
||||||
return this.pass( method, arguments );
|
return this.pass( method, arguments );
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,16 +15,6 @@
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.transport = this.transport || 'refresh';
|
this.transport = this.transport || 'refresh';
|
||||||
|
|
||||||
element = $( '<input />', {
|
|
||||||
type: 'hidden',
|
|
||||||
value: this.get(),
|
|
||||||
name: api.settings.prefix + id
|
|
||||||
});
|
|
||||||
|
|
||||||
element.appendTo( this.previewer.form );
|
|
||||||
this.element = new api.Element( element );
|
|
||||||
|
|
||||||
this.sync( this.element );
|
|
||||||
this.bind( this.preview );
|
this.bind( this.preview );
|
||||||
},
|
},
|
||||||
preview: function() {
|
preview: function() {
|
||||||
|
@ -271,8 +261,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requires params:
|
* Requires params:
|
||||||
* - iframe - a selector or jQuery element
|
* - container - a selector or jQuery element
|
||||||
* - form - a selector or jQuery element
|
|
||||||
* - url - the URL of preview frame
|
* - url - the URL of preview frame
|
||||||
*/
|
*/
|
||||||
initialize: function( params, options ) {
|
initialize: function( params, options ) {
|
||||||
|
@ -282,8 +271,6 @@
|
||||||
|
|
||||||
this.loaded = $.proxy( this.loaded, this );
|
this.loaded = $.proxy( this.loaded, this );
|
||||||
|
|
||||||
this.loaderUuid = 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wrap this.refresh to prevent it from hammering the servers:
|
* Wrap this.refresh to prevent it from hammering the servers:
|
||||||
*
|
*
|
||||||
|
@ -320,18 +307,9 @@
|
||||||
};
|
};
|
||||||
})( this );
|
})( this );
|
||||||
|
|
||||||
this.iframe = api.ensure( params.iframe );
|
this.container = api.ensure( params.container );
|
||||||
this.form = api.ensure( params.form );
|
|
||||||
this.name = this.iframe.prop('name');
|
|
||||||
|
|
||||||
this.container = this.iframe.parent();
|
api.Messenger.prototype.initialize.call( this, params.url );
|
||||||
|
|
||||||
api.Messenger.prototype.initialize.call( this, params.url, this.iframe[0].contentWindow );
|
|
||||||
|
|
||||||
this._formOriginalProps = {
|
|
||||||
target: this.form.prop('target'),
|
|
||||||
action: this.form.prop('action')
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bind( 'url', function( url ) {
|
this.bind( 'url', function( url ) {
|
||||||
// Bail if we're navigating to the current url, to a different origin, or wp-admin.
|
// Bail if we're navigating to the current url, to a different origin, or wp-admin.
|
||||||
|
@ -341,61 +319,39 @@
|
||||||
this.url( url );
|
this.url( url );
|
||||||
this.refresh();
|
this.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
|
|
||||||
// Prevent the form from saving when enter is pressed.
|
|
||||||
this.form.on( 'keydown', function( e ) {
|
|
||||||
if ( 13 === e.which ) // Enter
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a potential postMessage connection with the parent frame.
|
|
||||||
this.parent = new api.Messenger( api.settings.parent );
|
|
||||||
|
|
||||||
// If we receive a 'back' event, we're inside an iframe.
|
|
||||||
// Send any clicks to the 'Return' link to the parent page.
|
|
||||||
this.parent.bind( 'back', function( text ) {
|
|
||||||
self.form.find('.back').text( text ).click( function( event ) {
|
|
||||||
event.preventDefault();
|
|
||||||
self.parent.send( 'close' );
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize the connection with the parent frame.
|
|
||||||
this.parent.send( 'ready' );
|
|
||||||
},
|
},
|
||||||
loader: function() {
|
loader: function() {
|
||||||
if ( this.loading )
|
if ( this.loading )
|
||||||
return this.loading;
|
return this.loading;
|
||||||
|
|
||||||
this.loading = $('<iframe />', {
|
this.loading = $('<iframe />').appendTo( this.container );
|
||||||
name: this.name + '-loading-' + this.loaderUuid++
|
|
||||||
}).appendTo( this.container );
|
|
||||||
|
|
||||||
return this.loading;
|
return this.loading;
|
||||||
},
|
},
|
||||||
loaded: function() {
|
loaded: function() {
|
||||||
|
if ( this.iframe )
|
||||||
this.iframe.remove();
|
this.iframe.remove();
|
||||||
this.iframe = this.loading;
|
this.iframe = this.loading;
|
||||||
delete this.loading;
|
delete this.loading;
|
||||||
this.iframe.prop( 'name', this.name );
|
|
||||||
this.targetWindow( this.iframe[0].contentWindow );
|
this.targetWindow( this.iframe[0].contentWindow );
|
||||||
},
|
},
|
||||||
|
query: function() {},
|
||||||
refresh: function() {
|
refresh: function() {
|
||||||
this.loader().one( 'load', this.loaded );
|
var self = this;
|
||||||
|
|
||||||
this.submit({
|
if ( this.request )
|
||||||
target: this.loader().prop('name'),
|
this.request.abort();
|
||||||
action: this.url()
|
|
||||||
|
this.request = $.post( this.url(), this.query() || {}, function( response ) {
|
||||||
|
var iframe = self.loader()[0].contentWindow;
|
||||||
|
|
||||||
|
self.loader().one( 'load', self.loaded );
|
||||||
|
|
||||||
|
iframe.document.open();
|
||||||
|
iframe.document.write( response );
|
||||||
|
iframe.document.close();
|
||||||
});
|
});
|
||||||
},
|
|
||||||
submit: function( props ) {
|
|
||||||
if ( props )
|
|
||||||
this.form.prop( props );
|
|
||||||
this.form.submit();
|
|
||||||
if ( props )
|
|
||||||
this.form.prop( this._formOriginalProps );
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -415,10 +371,41 @@
|
||||||
|
|
||||||
// Initialize Previewer
|
// Initialize Previewer
|
||||||
var body = $( document.body ),
|
var body = $( document.body ),
|
||||||
|
query, previewer, parent;
|
||||||
|
|
||||||
|
// Prevent the form from saving when enter is pressed.
|
||||||
|
$('#customize-controls').on( 'keydown', function( e ) {
|
||||||
|
if ( 13 === e.which ) // Enter
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
previewer = new api.Previewer({
|
previewer = new api.Previewer({
|
||||||
iframe: '#customize-preview iframe',
|
container: '#customize-preview',
|
||||||
form: '#customize-controls',
|
form: '#customize-controls',
|
||||||
url: api.settings.preview
|
url: api.settings.preview
|
||||||
|
}, {
|
||||||
|
query: function() {
|
||||||
|
return {
|
||||||
|
customize: 'on',
|
||||||
|
theme: api.settings.theme,
|
||||||
|
customized: JSON.stringify( api.get() )
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
nonce: $('#_wpnonce').val(),
|
||||||
|
|
||||||
|
save: function() {
|
||||||
|
var query = $.extend( this.query(), {
|
||||||
|
action: 'customize_save',
|
||||||
|
nonce: this.nonce
|
||||||
|
}),
|
||||||
|
request = $.post( api.settings.ajax, query );
|
||||||
|
|
||||||
|
body.addClass('saving');
|
||||||
|
request.always( function() {
|
||||||
|
body.removeClass('saving');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each( api.settings.settings, function( id, data ) {
|
$.each( api.settings.settings, function( id, data ) {
|
||||||
|
@ -438,6 +425,9 @@
|
||||||
} ) );
|
} ) );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load the preview frame.
|
||||||
|
previewer.refresh();
|
||||||
|
|
||||||
// Temporary accordion code.
|
// Temporary accordion code.
|
||||||
$('.customize-section-title').click( function() {
|
$('.customize-section-title').click( function() {
|
||||||
$( this ).parents('.customize-section').toggleClass( 'open' );
|
$( this ).parents('.customize-section').toggleClass( 'open' );
|
||||||
|
@ -446,7 +436,7 @@
|
||||||
|
|
||||||
// Button bindings.
|
// Button bindings.
|
||||||
$('#save').click( function( event ) {
|
$('#save').click( function( event ) {
|
||||||
previewer.submit();
|
previewer.save();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -455,6 +445,21 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create a potential postMessage connection with the parent frame.
|
||||||
|
parent = new api.Messenger( api.settings.parent );
|
||||||
|
|
||||||
|
// If we receive a 'back' event, we're inside an iframe.
|
||||||
|
// Send any clicks to the 'Return' link to the parent page.
|
||||||
|
parent.bind( 'back', function( text ) {
|
||||||
|
$('.back').text( text ).click( function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
parent.send( 'close' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the connection with the parent frame.
|
||||||
|
parent.send( 'ready' );
|
||||||
|
|
||||||
// Control visibility for default controls
|
// Control visibility for default controls
|
||||||
$.each({
|
$.each({
|
||||||
'background_image': {
|
'background_image': {
|
||||||
|
|
Loading…
Reference in New Issue