balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-customize-selective-refresh.php (13705B)


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