[ 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          if ( isset( $this->partials[ $id ] ) ) {
 124              return $this->partials[ $id ];
 125          } else {
 126              return null;
 127          }
 128      }
 129  
 130      /**
 131       * Removes a partial.
 132       *
 133       * @since 4.5.0
 134       *
 135       * @param string $id Customize Partial ID.
 136       */
 137  	public function remove_partial( $id ) {
 138          unset( $this->partials[ $id ] );
 139      }
 140  
 141      /**
 142       * Initializes the Customizer preview.
 143       *
 144       * @since 4.5.0
 145       */
 146  	public function init_preview() {
 147          add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) );
 148          add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
 149      }
 150  
 151      /**
 152       * Enqueues preview scripts.
 153       *
 154       * @since 4.5.0
 155       */
 156  	public function enqueue_preview_scripts() {
 157          wp_enqueue_script( 'customize-selective-refresh' );
 158          add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 );
 159      }
 160  
 161      /**
 162       * Exports data in preview after it has finished rendering so that partials can be added at runtime.
 163       *
 164       * @since 4.5.0
 165       */
 166  	public function export_preview_data() {
 167          $partials = array();
 168  
 169          foreach ( $this->partials() as $partial ) {
 170              if ( $partial->check_capabilities() ) {
 171                  $partials[ $partial->id ] = $partial->json();
 172              }
 173          }
 174  
 175          $switched_locale = switch_to_user_locale( get_current_user_id() );
 176          $l10n            = array(
 177              'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
 178              'clickEditMenu'    => __( 'Click to edit this menu.' ),
 179              'clickEditWidget'  => __( 'Click to edit this widget.' ),
 180              'clickEditTitle'   => __( 'Click to edit the site title.' ),
 181              'clickEditMisc'    => __( 'Click to edit this element.' ),
 182              /* translators: %s: document.write() */
 183              'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ),
 184          );
 185          if ( $switched_locale ) {
 186              restore_previous_locale();
 187          }
 188  
 189          $exports = array(
 190              'partials'       => $partials,
 191              'renderQueryVar' => self::RENDER_QUERY_VAR,
 192              'l10n'           => $l10n,
 193          );
 194  
 195          // Export data to JS.
 196          wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports ) ) );
 197      }
 198  
 199      /**
 200       * Registers dynamically-created partials.
 201       *
 202       * @since 4.5.0
 203       *
 204       * @see WP_Customize_Manager::add_dynamic_settings()
 205       *
 206       * @param string[] $partial_ids Array of the partial IDs to add.
 207       * @return WP_Customize_Partial[] Array of added WP_Customize_Partial instances.
 208       */
 209  	public function add_dynamic_partials( $partial_ids ) {
 210          $new_partials = array();
 211  
 212          foreach ( $partial_ids as $partial_id ) {
 213  
 214              // Skip partials already created.
 215              $partial = $this->get_partial( $partial_id );
 216              if ( $partial ) {
 217                  continue;
 218              }
 219  
 220              $partial_args  = false;
 221              $partial_class = 'WP_Customize_Partial';
 222  
 223              /**
 224               * Filters a dynamic partial's constructor arguments.
 225               *
 226               * For a dynamic partial to be registered, this filter must be employed
 227               * to override the default false value with an array of args to pass to
 228               * the WP_Customize_Partial constructor.
 229               *
 230               * @since 4.5.0
 231               *
 232               * @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
 233               * @param string      $partial_id   ID for dynamic partial.
 234               */
 235              $partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id );
 236              if ( false === $partial_args ) {
 237                  continue;
 238              }
 239  
 240              /**
 241               * Filters the class used to construct partials.
 242               *
 243               * Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass.
 244               *
 245               * @since 4.5.0
 246               *
 247               * @param string $partial_class WP_Customize_Partial or a subclass.
 248               * @param string $partial_id    ID for dynamic partial.
 249               * @param array  $partial_args  The arguments to the WP_Customize_Partial constructor.
 250               */
 251              $partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args );
 252  
 253              $partial = new $partial_class( $this, $partial_id, $partial_args );
 254  
 255              $this->add_partial( $partial );
 256              $new_partials[] = $partial;
 257          }
 258          return $new_partials;
 259      }
 260  
 261      /**
 262       * Checks whether the request is for rendering partials.
 263       *
 264       * Note that this will not consider whether the request is authorized or valid,
 265       * just that essentially the route is a match.
 266       *
 267       * @since 4.5.0
 268       *
 269       * @return bool Whether the request is for rendering partials.
 270       */
 271  	public function is_render_partials_request() {
 272          return ! empty( $_POST[ self::RENDER_QUERY_VAR ] );
 273      }
 274  
 275      /**
 276       * Handles PHP errors triggered during rendering the partials.
 277       *
 278       * These errors will be relayed back to the client in the Ajax response.
 279       *
 280       * @since 4.5.0
 281       *
 282       * @param int    $errno   Error number.
 283       * @param string $errstr  Error string.
 284       * @param string $errfile Error file.
 285       * @param int    $errline Error line.
 286       * @return true Always true.
 287       */
 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,
 295          );
 296          return true;
 297      }
 298  
 299      /**
 300       * Handles the Ajax request to return the rendered partials for the requested placements.
 301       *
 302       * @since 4.5.0
 303       */
 304  	public function handle_render_partials_request() {
 305          if ( ! $this->is_render_partials_request() ) {
 306              return;
 307          }
 308  
 309          /*
 310           * Note that is_customize_preview() returning true will entail that the
 311           * user passed the 'customize' capability check and the nonce check, since
 312           * WP_Customize_Manager::setup_theme() is where the previewing flag is set.
 313           */
 314          if ( ! is_customize_preview() ) {
 315              wp_send_json_error( 'expected_customize_preview', 403 );
 316          } elseif ( ! isset( $_POST['partials'] ) ) {
 317              wp_send_json_error( 'missing_partials', 400 );
 318          }
 319  
 320          // Ensure that doing selective refresh on 404 template doesn't result in fallback rendering behavior (full refreshes).
 321          status_header( 200 );
 322  
 323          $partials = json_decode( wp_unslash( $_POST['partials'] ), true );
 324  
 325          if ( ! is_array( $partials ) ) {
 326              wp_send_json_error( 'malformed_partials' );
 327          }
 328  
 329          $this->add_dynamic_partials( array_keys( $partials ) );
 330  
 331          /**
 332           * Fires immediately before partials are rendered.
 333           *
 334           * Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts
 335           * and styles which may get enqueued in the response.
 336           *
 337           * @since 4.5.0
 338           *
 339           * @param WP_Customize_Selective_Refresh $refresh  Selective refresh component.
 340           * @param array                          $partials Placements' context data for the partials rendered in the request.
 341           *                                                 The array is keyed by partial ID, with each item being an array of
 342           *                                                 the placements' context data.
 343           */
 344          do_action( 'customize_render_partials_before', $this, $partials );
 345  
 346          set_error_handler( array( $this, 'handle_error' ), error_reporting() );
 347  
 348          $contents = array();
 349  
 350          foreach ( $partials as $partial_id => $container_contexts ) {
 351              $this->current_partial_id = $partial_id;
 352  
 353              if ( ! is_array( $container_contexts ) ) {
 354                  wp_send_json_error( 'malformed_container_contexts' );
 355              }
 356  
 357              $partial = $this->get_partial( $partial_id );
 358  
 359              if ( ! $partial || ! $partial->check_capabilities() ) {
 360                  $contents[ $partial_id ] = null;
 361                  continue;
 362              }
 363  
 364              $contents[ $partial_id ] = array();
 365  
 366              // @todo The array should include not only the contents, but also whether the container is included?
 367              if ( empty( $container_contexts ) ) {
 368                  // Since there are no container contexts, render just once.
 369                  $contents[ $partial_id ][] = $partial->render( null );
 370              } else {
 371                  foreach ( $container_contexts as $container_context ) {
 372                      $contents[ $partial_id ][] = $partial->render( $container_context );
 373                  }
 374              }
 375          }
 376          $this->current_partial_id = null;
 377  
 378          restore_error_handler();
 379  
 380          /**
 381           * Fires immediately after partials are rendered.
 382           *
 383           * Plugins may do things like call wp_footer() to scrape scripts output and return them
 384           * via the {@see 'customize_render_partials_response'} filter.
 385           *
 386           * @since 4.5.0
 387           *
 388           * @param WP_Customize_Selective_Refresh $refresh  Selective refresh component.
 389           * @param array                          $partials Placements' context data for the partials rendered in the request.
 390           *                                                 The array is keyed by partial ID, with each item being an array of
 391           *                                                 the placements' context data.
 392           */
 393          do_action( 'customize_render_partials_after', $this, $partials );
 394  
 395          $response = array(
 396              'contents' => $contents,
 397          );
 398  
 399          if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
 400              $response['errors'] = $this->triggered_errors;
 401          }
 402  
 403          $setting_validities             = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
 404          $exported_setting_validities    = array_map( array( $this->manager, 'prepare_setting_validity_for_js' ), $setting_validities );
 405          $response['setting_validities'] = $exported_setting_validities;
 406  
 407          /**
 408           * Filters the response from rendering the partials.
 409           *
 410           * Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies
 411           * for the partials being rendered. The response data will be available to the client via
 412           * the `render-partials-response` JS event, so the client can then inject the scripts and
 413           * styles into the DOM if they have not already been enqueued there.
 414           *
 415           * If plugins do this, they'll need to take care for any scripts that do `document.write()`
 416           * and make sure that these are not injected, or else to override the function to no-op,
 417           * or else the page will be destroyed.
 418           *
 419           * Plugins should be aware that `$scripts` and `$styles` may eventually be included by
 420           * default in the response.
 421           *
 422           * @since 4.5.0
 423           *
 424           * @param array $response {
 425           *     Response.
 426           *
 427           *     @type array $contents Associative array mapping a partial ID its corresponding array of contents
 428           *                           for the containers requested.
 429           *     @type array $errors   List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY`
 430           *                           is enabled.
 431           * }
 432           * @param WP_Customize_Selective_Refresh $refresh  Selective refresh component.
 433           * @param array                          $partials Placements' context data for the partials rendered in the request.
 434           *                                                 The array is keyed by partial ID, with each item being an array of
 435           *                                                 the placements' context data.
 436           */
 437          $response = apply_filters( 'customize_render_partials_response', $response, $this, $partials );
 438  
 439          wp_send_json_success( $response );
 440      }
 441  }


Generated : Tue Apr 16 08:20:01 2024 Cross-referenced by PHPXref