| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Customize API: WP_Customize_Selective_Refresh class 4 * 5 * @package WordPress 6 * @subpackage Customize 7 * @since 4.5.0 8 */ 9 10 /** 11 * Core Customizer class for implementing selective refresh. 12 * 13 * @since 4.5.0 14 */ 15 #[AllowDynamicProperties] 16 final class WP_Customize_Selective_Refresh { 17 18 /** 19 * Query var used in requests to render partials. 20 * 21 * @since 4.5.0 22 */ 23 const RENDER_QUERY_VAR = 'wp_customize_render_partials'; 24 25 /** 26 * Customize manager. 27 * 28 * @since 4.5.0 29 * @var WP_Customize_Manager 30 */ 31 public $manager; 32 33 /** 34 * Registered instances of WP_Customize_Partial. 35 * 36 * @since 4.5.0 37 * @var WP_Customize_Partial[] 38 */ 39 protected $partials = array(); 40 41 /** 42 * Log of errors triggered when partials are rendered. 43 * 44 * @since 4.5.0 45 * @var array 46 */ 47 protected $triggered_errors = array(); 48 49 /** 50 * Keep track of the current partial being rendered. 51 * 52 * @since 4.5.0 53 * @var string|null 54 */ 55 protected $current_partial_id; 56 57 /** 58 * Plugin bootstrap for Partial Refresh functionality. 59 * 60 * @since 4.5.0 61 * 62 * @param WP_Customize_Manager $manager Customizer bootstrap instance. 63 */ 64 public function __construct( WP_Customize_Manager $manager ) { 65 $this->manager = $manager; 66 require_once ABSPATH . WPINC . '/customize/class-wp-customize-partial.php'; 67 68 add_action( 'customize_preview_init', array( $this, 'init_preview' ) ); 69 } 70 71 /** 72 * Retrieves the registered partials. 73 * 74 * @since 4.5.0 75 * 76 * @return array Partials. 77 */ 78 public function partials() { 79 return $this->partials; 80 } 81 82 /** 83 * Adds a partial. 84 * 85 * @since 4.5.0 86 * 87 * @see WP_Customize_Partial::__construct() 88 * 89 * @param WP_Customize_Partial|string $id Customize Partial object, or Partial ID. 90 * @param array $args Optional. Array of properties for the new Partials object. 91 * See WP_Customize_Partial::__construct() for information 92 * on accepted arguments. Default empty array. 93 * @return WP_Customize_Partial The instance of the partial that was added. 94 */ 95 public function add_partial( $id, $args = array() ) { 96 if ( $id instanceof WP_Customize_Partial ) { 97 $partial = $id; 98 } else { 99 $class = 'WP_Customize_Partial'; 100 101 /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */ 102 $args = apply_filters( 'customize_dynamic_partial_args', $args, $id ); 103 104 /** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */ 105 $class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args ); 106 107 $partial = new $class( $this, $id, $args ); 108 } 109 110 $this->partials[ $partial->id ] = $partial; 111 return $partial; 112 } 113 114 /** 115 * Retrieves a partial. 116 * 117 * @since 4.5.0 118 * 119 * @param string $id Customize Partial ID. 120 * @return WP_Customize_Partial|null The partial, if set. Otherwise null. 121 */ 122 public function get_partial( $id ) { 123 return $this->partials[ $id ] ?? null; 124 } 125 126 /** 127 * Removes a partial. 128 * 129 * @since 4.5.0 130 * 131 * @param string $id Customize Partial ID. 132 */ 133 public function remove_partial( $id ) { 134 unset( $this->partials[ $id ] ); 135 } 136 137 /** 138 * Initializes the Customizer preview. 139 * 140 * @since 4.5.0 141 */ 142 public function init_preview() { 143 add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) ); 144 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); 145 } 146 147 /** 148 * Enqueues preview scripts. 149 * 150 * @since 4.5.0 151 */ 152 public function enqueue_preview_scripts() { 153 wp_enqueue_script( 'customize-selective-refresh' ); 154 add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 ); 155 } 156 157 /** 158 * Exports data in preview after it has finished rendering so that partials can be added at runtime. 159 * 160 * @since 4.5.0 161 */ 162 public function export_preview_data() { 163 $partials = array(); 164 165 foreach ( $this->partials() as $partial ) { 166 if ( $partial->check_capabilities() ) { 167 $partials[ $partial->id ] = $partial->json(); 168 } 169 } 170 171 $switched_locale = switch_to_user_locale( get_current_user_id() ); 172 $l10n = array( 173 'shiftClickToEdit' => __( 'Shift-click to edit this element.' ), 174 'clickEditMenu' => __( 'Click to edit this menu.' ), 175 'clickEditWidget' => __( 'Click to edit this widget.' ), 176 'clickEditTitle' => __( 'Click to edit the site title.' ), 177 'clickEditMisc' => __( 'Click to edit this element.' ), 178 /* translators: %s: document.write() */ 179 'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ), 180 ); 181 if ( $switched_locale ) { 182 restore_previous_locale(); 183 } 184 185 $exports = array( 186 'partials' => $partials, 187 'renderQueryVar' => self::RENDER_QUERY_VAR, 188 'l10n' => $l10n, 189 ); 190 191 // Export data to JS. 192 wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ) . "\n//# sourceURL=" . rawurlencode( __METHOD__ ) ); 193 } 194 195 /** 196 * Registers dynamically-created partials. 197 * 198 * @since 4.5.0 199 * 200 * @see WP_Customize_Manager::add_dynamic_settings() 201 * 202 * @param string[] $partial_ids Array of the partial IDs to add. 203 * @return WP_Customize_Partial[] Array of added WP_Customize_Partial instances. 204 */ 205 public function add_dynamic_partials( $partial_ids ) { 206 $new_partials = array(); 207 208 foreach ( $partial_ids as $partial_id ) { 209 210 // Skip partials already created. 211 $partial = $this->get_partial( $partial_id ); 212 if ( $partial ) { 213 continue; 214 } 215 216 $partial_args = false; 217 $partial_class = 'WP_Customize_Partial'; 218 219 /** 220 * Filters a dynamic partial's constructor arguments. 221 * 222 * For a dynamic partial to be registered, this filter must be employed 223 * to override the default false value with an array of args to pass to 224 * the WP_Customize_Partial constructor. 225 * 226 * @since 4.5.0 227 * 228 * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor. 229 * @param string $partial_id ID for dynamic partial. 230 */ 231 $partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id ); 232 if ( false === $partial_args ) { 233 continue; 234 } 235 236 /** 237 * Filters the class used to construct partials. 238 * 239 * Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass. 240 * 241 * @since 4.5.0 242 * 243 * @param string $partial_class WP_Customize_Partial or a subclass. 244 * @param string $partial_id ID for dynamic partial. 245 * @param array $partial_args The arguments to the WP_Customize_Partial constructor. 246 */ 247 $partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args ); 248 249 $partial = new $partial_class( $this, $partial_id, $partial_args ); 250 251 $this->add_partial( $partial ); 252 $new_partials[] = $partial; 253 } 254 return $new_partials; 255 } 256 257 /** 258 * Checks whether the request is for rendering partials. 259 * 260 * Note that this will not consider whether the request is authorized or valid, 261 * just that essentially the route is a match. 262 * 263 * @since 4.5.0 264 * 265 * @return bool Whether the request is for rendering partials. 266 */ 267 public function is_render_partials_request() { 268 return ! empty( $_POST[ self::RENDER_QUERY_VAR ] ); 269 } 270 271 /** 272 * Handles PHP errors triggered during rendering the partials. 273 * 274 * These errors will be relayed back to the client in the Ajax response. 275 * 276 * @since 4.5.0 277 * 278 * @param int $errno Error number. 279 * @param string $errstr Error string. 280 * @param string $errfile Error file. 281 * @param int $errline Error line. 282 * @return true Always true. 283 */ 284 public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) { 285 $this->triggered_errors[] = array( 286 'partial' => $this->current_partial_id, 287 'error_number' => $errno, 288 'error_string' => $errstr, 289 'error_file' => $errfile, 290 'error_line' => $errline, 291 ); 292 return true; 293 } 294 295 /** 296 * Handles the Ajax request to return the rendered partials for the requested placements. 297 * 298 * @since 4.5.0 299 */ 300 public function handle_render_partials_request() { 301 if ( ! $this->is_render_partials_request() ) { 302 return; 303 } 304 305 /* 306 * Note that is_customize_preview() returning true will entail that the 307 * user passed the 'customize' capability check and the nonce check, since 308 * WP_Customize_Manager::setup_theme() is where the previewing flag is set. 309 */ 310 if ( ! is_customize_preview() ) { 311 wp_send_json_error( 'expected_customize_preview', 403 ); 312 } elseif ( ! isset( $_POST['partials'] ) ) { 313 wp_send_json_error( 'missing_partials', 400 ); 314 } 315 316 // Ensure that doing selective refresh on 404 template doesn't result in fallback rendering behavior (full refreshes). 317 status_header( 200 ); 318 319 $partials = json_decode( wp_unslash( $_POST['partials'] ), true ); 320 321 if ( ! is_array( $partials ) ) { 322 wp_send_json_error( 'malformed_partials' ); 323 } 324 325 $this->add_dynamic_partials( array_keys( $partials ) ); 326 327 /** 328 * Fires immediately before partials are rendered. 329 * 330 * Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts 331 * and styles which may get enqueued in the response. 332 * 333 * @since 4.5.0 334 * 335 * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. 336 * @param array $partials Placements' context data for the partials rendered in the request. 337 * The array is keyed by partial ID, with each item being an array of 338 * the placements' context data. 339 */ 340 do_action( 'customize_render_partials_before', $this, $partials ); 341 342 set_error_handler( array( $this, 'handle_error' ), error_reporting() ); 343 344 $contents = array(); 345 346 foreach ( $partials as $partial_id => $container_contexts ) { 347 $this->current_partial_id = $partial_id; 348 349 if ( ! is_array( $container_contexts ) ) { 350 wp_send_json_error( 'malformed_container_contexts' ); 351 } 352 353 $partial = $this->get_partial( $partial_id ); 354 355 if ( ! $partial || ! $partial->check_capabilities() ) { 356 $contents[ $partial_id ] = null; 357 continue; 358 } 359 360 $contents[ $partial_id ] = array(); 361 362 // @todo The array should include not only the contents, but also whether the container is included? 363 if ( empty( $container_contexts ) ) { 364 // Since there are no container contexts, render just once. 365 $contents[ $partial_id ][] = $partial->render( null ); 366 } else { 367 foreach ( $container_contexts as $container_context ) { 368 $contents[ $partial_id ][] = $partial->render( $container_context ); 369 } 370 } 371 } 372 $this->current_partial_id = null; 373 374 restore_error_handler(); 375 376 /** 377 * Fires immediately after partials are rendered. 378 * 379 * Plugins may do things like call wp_footer() to scrape scripts output and return them 380 * via the {@see 'customize_render_partials_response'} filter. 381 * 382 * @since 4.5.0 383 * 384 * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. 385 * @param array $partials Placements' context data for the partials rendered in the request. 386 * The array is keyed by partial ID, with each item being an array of 387 * the placements' context data. 388 */ 389 do_action( 'customize_render_partials_after', $this, $partials ); 390 391 $response = array( 392 'contents' => $contents, 393 ); 394 395 if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) { 396 $response['errors'] = $this->triggered_errors; 397 } 398 399 $setting_validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() ); 400 $exported_setting_validities = array_map( array( $this->manager, 'prepare_setting_validity_for_js' ), $setting_validities ); 401 $response['setting_validities'] = $exported_setting_validities; 402 403 /** 404 * Filters the response from rendering the partials. 405 * 406 * Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies 407 * for the partials being rendered. The response data will be available to the client via 408 * the `render-partials-response` JS event, so the client can then inject the scripts and 409 * styles into the DOM if they have not already been enqueued there. 410 * 411 * If plugins do this, they'll need to take care for any scripts that do `document.write()` 412 * and make sure that these are not injected, or else to override the function to no-op, 413 * or else the page will be destroyed. 414 * 415 * Plugins should be aware that `$scripts` and `$styles` may eventually be included by 416 * default in the response. 417 * 418 * @since 4.5.0 419 * 420 * @param array $response { 421 * Response. 422 * 423 * @type array $contents Associative array mapping a partial ID its corresponding array of contents 424 * for the containers requested. 425 * @type array $errors List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY` 426 * is enabled. 427 * } 428 * @param WP_Customize_Selective_Refresh $refresh Selective refresh component. 429 * @param array $partials Placements' context data for the partials rendered in the request. 430 * The array is keyed by partial ID, with each item being an array of 431 * the placements' context data. 432 */ 433 $response = apply_filters( 'customize_render_partials_response', $response, $this, $partials ); 434 435 wp_send_json_success( $response ); 436 } 437 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Aug 8 08:20:21 2026 | Cross-referenced by PHPXref |