[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/customize/ -> class-wp-customize-selective-refresh.php (source)

   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  }


Generated : Sat Aug 8 08:20:21 2026 Cross-referenced by PHPXref