3
* Customize API: WP_Customize_Selective_Refresh class
6
* @subpackage Customize
11
* Core Customizer class for implementing selective refresh.
15
final class WP_Customize_Selective_Refresh {
18
* Query var used in requests to render partials.
22
const RENDER_QUERY_VAR = 'wp_customize_render_partials';
29
* @var WP_Customize_Manager
34
* Registered instances of WP_Customize_Partial.
38
* @var WP_Customize_Partial[]
40
protected $partials = array();
43
* Log of errors triggered when partials are rendered.
49
protected $triggered_errors = array();
52
* Keep track of the current partial being rendered.
58
protected $current_partial_id;
61
* Plugin bootstrap for Partial Refresh functionality.
66
* @param WP_Customize_Manager $manager Manager instance.
68
public function __construct( WP_Customize_Manager $manager ) {
69
$this->manager = $manager;
70
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-partial.php' );
72
add_action( 'customize_preview_init', array( $this, 'init_preview' ) );
76
* Retrieves the registered partials.
81
* @return array Partials.
83
public function partials() {
84
return $this->partials;
93
* @param WP_Customize_Partial|string $id Customize Partial object, or Panel ID.
94
* @param array $args Optional. Partial arguments. Default empty array.
95
* @return WP_Customize_Partial The instance of the panel that was added.
97
public function add_partial( $id, $args = array() ) {
98
if ( $id instanceof WP_Customize_Partial ) {
101
$class = 'WP_Customize_Partial';
103
/** This filter (will be) documented in wp-includes/class-wp-customize-manager.php */
104
$args = apply_filters( 'customize_dynamic_partial_args', $args, $id );
106
/** This filter (will be) documented in wp-includes/class-wp-customize-manager.php */
107
$class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args );
109
$partial = new $class( $this, $id, $args );
112
$this->partials[ $partial->id ] = $partial;
117
* Retrieves a partial.
122
* @param string $id Customize Partial ID.
123
* @return WP_Customize_Partial|null The partial, if set. Otherwise null.
125
public function get_partial( $id ) {
126
if ( isset( $this->partials[ $id ] ) ) {
127
return $this->partials[ $id ];
139
* @param string $id Customize Partial ID.
141
public function remove_partial( $id ) {
142
unset( $this->partials[ $id ] );
146
* Initializes the Customizer preview.
151
public function init_preview() {
152
add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) );
153
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
157
* Enqueues preview scripts.
162
public function enqueue_preview_scripts() {
163
wp_enqueue_script( 'customize-selective-refresh' );
164
add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 );
168
* Exports data in preview after it has finished rendering so that partials can be added at runtime.
173
public function export_preview_data() {
176
foreach ( $this->partials() as $partial ) {
177
if ( $partial->check_capabilities() ) {
178
$partials[ $partial->id ] = $partial->json();
183
'partials' => $partials,
184
'renderQueryVar' => self::RENDER_QUERY_VAR,
186
'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
187
/* translators: %s: document.write() */
188
'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ),
192
// Export data to JS.
193
echo sprintf( '<script>var _customizePartialRefreshExports = %s;</script>', wp_json_encode( $exports ) );
197
* Registers dynamically-created partials.
202
* @see WP_Customize_Manager::add_dynamic_settings()
204
* @param array $partial_ids The partial ID to add.
205
* @return array Added WP_Customize_Partial instances.
207
public function add_dynamic_partials( $partial_ids ) {
208
$new_partials = array();
210
foreach ( $partial_ids as $partial_id ) {
212
// Skip partials already created.
213
$partial = $this->get_partial( $partial_id );
218
$partial_args = false;
219
$partial_class = 'WP_Customize_Partial';
222
* Filters a dynamic partial's constructor arguments.
224
* For a dynamic partial to be registered, this filter must be employed
225
* to override the default false value with an array of args to pass to
226
* the WP_Customize_Partial constructor.
230
* @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
231
* @param string $partial_id ID for dynamic partial.
233
$partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id );
234
if ( false === $partial_args ) {
239
* Filters the class used to construct partials.
241
* Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass.
245
* @param string $partial_class WP_Customize_Partial or a subclass.
246
* @param string $partial_id ID for dynamic partial.
247
* @param array $partial_args The arguments to the WP_Customize_Partial constructor.
249
$partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args );
251
$partial = new $partial_class( $this, $partial_id, $partial_args );
253
$this->add_partial( $partial );
254
$new_partials[] = $partial;
256
return $new_partials;
260
* Checks whether the request is for rendering partials.
262
* Note that this will not consider whether the request is authorized or valid,
263
* just that essentially the route is a match.
268
* @return bool Whether the request is for rendering partials.
270
public function is_render_partials_request() {
271
return ! empty( $_POST[ self::RENDER_QUERY_VAR ] );
275
* Handles PHP errors triggered during rendering the partials.
277
* These errors will be relayed back to the client in the Ajax response.
282
* @param int $errno Error number.
283
* @param string $errstr Error string.
284
* @param string $errfile Error file.
285
* @param string $errline Error line.
286
* @return true Always true.
288
public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) {
289
$this->triggered_errors[] = array(
290
'partial' => $this->current_partial_id,
291
'error_number' => $errno,
292
'error_string' => $errstr,
293
'error_file' => $errfile,
294
'error_line' => $errline,
300
* Handles the Ajax request to return the rendered partials for the requested placements.
305
public function handle_render_partials_request() {
306
if ( ! $this->is_render_partials_request() ) {
310
$this->manager->remove_preview_signature();
313
* Note that is_customize_preview() returning true will entail that the
314
* user passed the 'customize' capability check and the nonce check, since
315
* WP_Customize_Manager::setup_theme() is where the previewing flag is set.
317
if ( ! is_customize_preview() ) {
318
status_header( 403 );
319
wp_send_json_error( 'expected_customize_preview' );
320
} else if ( ! isset( $_POST['partials'] ) ) {
321
status_header( 400 );
322
wp_send_json_error( 'missing_partials' );
325
$partials = json_decode( wp_unslash( $_POST['partials'] ), true );
327
if ( ! is_array( $partials ) ) {
328
wp_send_json_error( 'malformed_partials' );
331
$this->add_dynamic_partials( array_keys( $partials ) );
334
* Fires immediately before partials are rendered.
336
* Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts
337
* and styles which may get enqueued in the response.
341
* @param WP_Customize_Selective_Refresh $this Selective refresh component.
342
* @param array $partials Placements' context data for the partials rendered in the request.
343
* The array is keyed by partial ID, with each item being an array of
344
* the placements' context data.
346
do_action( 'customize_render_partials_before', $this, $partials );
348
set_error_handler( array( $this, 'handle_error' ), error_reporting() );
352
foreach ( $partials as $partial_id => $container_contexts ) {
353
$this->current_partial_id = $partial_id;
355
if ( ! is_array( $container_contexts ) ) {
356
wp_send_json_error( 'malformed_container_contexts' );
359
$partial = $this->get_partial( $partial_id );
361
if ( ! $partial || ! $partial->check_capabilities() ) {
362
$contents[ $partial_id ] = null;
366
$contents[ $partial_id ] = array();
368
// @todo The array should include not only the contents, but also whether the container is included?
369
if ( empty( $container_contexts ) ) {
370
// Since there are no container contexts, render just once.
371
$contents[ $partial_id ][] = $partial->render( null );
373
foreach ( $container_contexts as $container_context ) {
374
$contents[ $partial_id ][] = $partial->render( $container_context );
378
$this->current_partial_id = null;
380
restore_error_handler();
383
* Fires immediately after partials are rendered.
385
* Plugins may do things like call wp_footer() to scrape scripts output and return them
386
* via the {@see 'customize_render_partials_response'} filter.
390
* @param WP_Customize_Selective_Refresh $this Selective refresh component.
391
* @param array $partials Placements' context data for the partials rendered in the request.
392
* The array is keyed by partial ID, with each item being an array of
393
* the placements' context data.
395
do_action( 'customize_render_partials_after', $this, $partials );
398
'contents' => $contents,
401
if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
402
$response['errors'] = $this->triggered_errors;
406
* Filters the response from rendering the partials.
408
* Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies
409
* for the partials being rendered. The response data will be available to the client via
410
* the `render-partials-response` JS event, so the client can then inject the scripts and
411
* styles into the DOM if they have not already been enqueued there.
413
* If plugins do this, they'll need to take care for any scripts that do `document.write()`
414
* and make sure that these are not injected, or else to override the function to no-op,
415
* or else the page will be destroyed.
417
* Plugins should be aware that `$scripts` and `$styles` may eventually be included by
418
* default in the response.
422
* @param array $response {
425
* @type array $contents Associative array mapping a partial ID its corresponding array of contents
426
* for the containers requested.
427
* @type array $errors List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY`
430
* @param WP_Customize_Selective_Refresh $this Selective refresh component.
431
* @param array $partials Placements' context data for the partials rendered in the request.
432
* The array is keyed by partial ID, with each item being an array of
433
* the placements' context data.
435
$response = apply_filters( 'customize_render_partials_response', $response, $this, $partials );
437
wp_send_json_success( $response );