mirror of
https://github.com/WordPress/WordPress.git
synced 2024-08-25 23:33:22 +02:00
Using script tag helper functions allows plugins to employ the `wp_script_attributes` and `wp_inline_script_attributes` filters to inject the `nonce` attribute to apply Content Security Policy (e.g. Strict CSP). Use of helper functions also simplifies logic in `WP_Scripts`. * Update `wp_get_inline_script_tag()` to wrap inline script in CDATA blocks for XHTML-compatibility when not using HTML5. * Ensure the `type` attribute is printed first in `wp_get_inline_script_tag()` for back-compat. * Wrap existing `<script>` tags in output buffering to retain IDE supports. * In `wp_get_inline_script_tag()`, append the newline to `$javascript` before it is passed into the `wp_inline_script_attributes` filter so that the CSP hash can be computed properly. * In `the_block_template_skip_link()`, opt to enqueue the inline script rather than print it. * Add `ext-php` to `composer.json` under `suggest` as previously it was an undeclared dependency for running PHPUnit tests. * Update tests to rely on `DOMDocument` to compare script markup, normalizing unsemantic differences. Props westonruter, spacedmonkey, flixos90, 10upsimon, dmsnell, mukesh27, joemcgill, swissspidy, azaozz. Fixes #58664. See #39941. Built from https://develop.svn.wordpress.org/trunk@56687 git-svn-id: http://core.svn.wordpress.org/trunk@56199 1a063a9b-81f0-0310-95a4-ce76da25c4cd
442 lines
13 KiB
PHP
442 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Customize API: WP_Customize_Selective_Refresh class
|
|
*
|
|
* @package WordPress
|
|
* @subpackage Customize
|
|
* @since 4.5.0
|
|
*/
|
|
|
|
/**
|
|
* Core Customizer class for implementing selective refresh.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
#[AllowDynamicProperties]
|
|
final class WP_Customize_Selective_Refresh {
|
|
|
|
/**
|
|
* Query var used in requests to render partials.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
const RENDER_QUERY_VAR = 'wp_customize_render_partials';
|
|
|
|
/**
|
|
* Customize manager.
|
|
*
|
|
* @since 4.5.0
|
|
* @var WP_Customize_Manager
|
|
*/
|
|
public $manager;
|
|
|
|
/**
|
|
* Registered instances of WP_Customize_Partial.
|
|
*
|
|
* @since 4.5.0
|
|
* @var WP_Customize_Partial[]
|
|
*/
|
|
protected $partials = array();
|
|
|
|
/**
|
|
* Log of errors triggered when partials are rendered.
|
|
*
|
|
* @since 4.5.0
|
|
* @var array
|
|
*/
|
|
protected $triggered_errors = array();
|
|
|
|
/**
|
|
* Keep track of the current partial being rendered.
|
|
*
|
|
* @since 4.5.0
|
|
* @var string|null
|
|
*/
|
|
protected $current_partial_id;
|
|
|
|
/**
|
|
* Plugin bootstrap for Partial Refresh functionality.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param WP_Customize_Manager $manager Customizer bootstrap instance.
|
|
*/
|
|
public function __construct( WP_Customize_Manager $manager ) {
|
|
$this->manager = $manager;
|
|
require_once ABSPATH . WPINC . '/customize/class-wp-customize-partial.php';
|
|
|
|
add_action( 'customize_preview_init', array( $this, 'init_preview' ) );
|
|
}
|
|
|
|
/**
|
|
* Retrieves the registered partials.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @return array Partials.
|
|
*/
|
|
public function partials() {
|
|
return $this->partials;
|
|
}
|
|
|
|
/**
|
|
* Adds a partial.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @see WP_Customize_Partial::__construct()
|
|
*
|
|
* @param WP_Customize_Partial|string $id Customize Partial object, or Partial ID.
|
|
* @param array $args Optional. Array of properties for the new Partials object.
|
|
* See WP_Customize_Partial::__construct() for information
|
|
* on accepted arguments. Default empty array.
|
|
* @return WP_Customize_Partial The instance of the partial that was added.
|
|
*/
|
|
public function add_partial( $id, $args = array() ) {
|
|
if ( $id instanceof WP_Customize_Partial ) {
|
|
$partial = $id;
|
|
} else {
|
|
$class = 'WP_Customize_Partial';
|
|
|
|
/** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */
|
|
$args = apply_filters( 'customize_dynamic_partial_args', $args, $id );
|
|
|
|
/** This filter is documented in wp-includes/customize/class-wp-customize-selective-refresh.php */
|
|
$class = apply_filters( 'customize_dynamic_partial_class', $class, $id, $args );
|
|
|
|
$partial = new $class( $this, $id, $args );
|
|
}
|
|
|
|
$this->partials[ $partial->id ] = $partial;
|
|
return $partial;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a partial.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param string $id Customize Partial ID.
|
|
* @return WP_Customize_Partial|null The partial, if set. Otherwise null.
|
|
*/
|
|
public function get_partial( $id ) {
|
|
if ( isset( $this->partials[ $id ] ) ) {
|
|
return $this->partials[ $id ];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a partial.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param string $id Customize Partial ID.
|
|
*/
|
|
public function remove_partial( $id ) {
|
|
unset( $this->partials[ $id ] );
|
|
}
|
|
|
|
/**
|
|
* Initializes the Customizer preview.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
public function init_preview() {
|
|
add_action( 'template_redirect', array( $this, 'handle_render_partials_request' ) );
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
|
|
}
|
|
|
|
/**
|
|
* Enqueues preview scripts.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
public function enqueue_preview_scripts() {
|
|
wp_enqueue_script( 'customize-selective-refresh' );
|
|
add_action( 'wp_footer', array( $this, 'export_preview_data' ), 1000 );
|
|
}
|
|
|
|
/**
|
|
* Exports data in preview after it has finished rendering so that partials can be added at runtime.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
public function export_preview_data() {
|
|
$partials = array();
|
|
|
|
foreach ( $this->partials() as $partial ) {
|
|
if ( $partial->check_capabilities() ) {
|
|
$partials[ $partial->id ] = $partial->json();
|
|
}
|
|
}
|
|
|
|
$switched_locale = switch_to_user_locale( get_current_user_id() );
|
|
$l10n = array(
|
|
'shiftClickToEdit' => __( 'Shift-click to edit this element.' ),
|
|
'clickEditMenu' => __( 'Click to edit this menu.' ),
|
|
'clickEditWidget' => __( 'Click to edit this widget.' ),
|
|
'clickEditTitle' => __( 'Click to edit the site title.' ),
|
|
'clickEditMisc' => __( 'Click to edit this element.' ),
|
|
/* translators: %s: document.write() */
|
|
'badDocumentWrite' => sprintf( __( '%s is forbidden' ), 'document.write()' ),
|
|
);
|
|
if ( $switched_locale ) {
|
|
restore_previous_locale();
|
|
}
|
|
|
|
$exports = array(
|
|
'partials' => $partials,
|
|
'renderQueryVar' => self::RENDER_QUERY_VAR,
|
|
'l10n' => $l10n,
|
|
);
|
|
|
|
// Export data to JS.
|
|
wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports ) ) );
|
|
}
|
|
|
|
/**
|
|
* Registers dynamically-created partials.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @see WP_Customize_Manager::add_dynamic_settings()
|
|
*
|
|
* @param string[] $partial_ids Array of the partial IDs to add.
|
|
* @return WP_Customize_Partial[] Array of added WP_Customize_Partial instances.
|
|
*/
|
|
public function add_dynamic_partials( $partial_ids ) {
|
|
$new_partials = array();
|
|
|
|
foreach ( $partial_ids as $partial_id ) {
|
|
|
|
// Skip partials already created.
|
|
$partial = $this->get_partial( $partial_id );
|
|
if ( $partial ) {
|
|
continue;
|
|
}
|
|
|
|
$partial_args = false;
|
|
$partial_class = 'WP_Customize_Partial';
|
|
|
|
/**
|
|
* Filters a dynamic partial's constructor arguments.
|
|
*
|
|
* For a dynamic partial to be registered, this filter must be employed
|
|
* to override the default false value with an array of args to pass to
|
|
* the WP_Customize_Partial constructor.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param false|array $partial_args The arguments to the WP_Customize_Partial constructor.
|
|
* @param string $partial_id ID for dynamic partial.
|
|
*/
|
|
$partial_args = apply_filters( 'customize_dynamic_partial_args', $partial_args, $partial_id );
|
|
if ( false === $partial_args ) {
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* Filters the class used to construct partials.
|
|
*
|
|
* Allow non-statically created partials to be constructed with custom WP_Customize_Partial subclass.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param string $partial_class WP_Customize_Partial or a subclass.
|
|
* @param string $partial_id ID for dynamic partial.
|
|
* @param array $partial_args The arguments to the WP_Customize_Partial constructor.
|
|
*/
|
|
$partial_class = apply_filters( 'customize_dynamic_partial_class', $partial_class, $partial_id, $partial_args );
|
|
|
|
$partial = new $partial_class( $this, $partial_id, $partial_args );
|
|
|
|
$this->add_partial( $partial );
|
|
$new_partials[] = $partial;
|
|
}
|
|
return $new_partials;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the request is for rendering partials.
|
|
*
|
|
* Note that this will not consider whether the request is authorized or valid,
|
|
* just that essentially the route is a match.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @return bool Whether the request is for rendering partials.
|
|
*/
|
|
public function is_render_partials_request() {
|
|
return ! empty( $_POST[ self::RENDER_QUERY_VAR ] );
|
|
}
|
|
|
|
/**
|
|
* Handles PHP errors triggered during rendering the partials.
|
|
*
|
|
* These errors will be relayed back to the client in the Ajax response.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param int $errno Error number.
|
|
* @param string $errstr Error string.
|
|
* @param string $errfile Error file.
|
|
* @param int $errline Error line.
|
|
* @return true Always true.
|
|
*/
|
|
public function handle_error( $errno, $errstr, $errfile = null, $errline = null ) {
|
|
$this->triggered_errors[] = array(
|
|
'partial' => $this->current_partial_id,
|
|
'error_number' => $errno,
|
|
'error_string' => $errstr,
|
|
'error_file' => $errfile,
|
|
'error_line' => $errline,
|
|
);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Handles the Ajax request to return the rendered partials for the requested placements.
|
|
*
|
|
* @since 4.5.0
|
|
*/
|
|
public function handle_render_partials_request() {
|
|
if ( ! $this->is_render_partials_request() ) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Note that is_customize_preview() returning true will entail that the
|
|
* user passed the 'customize' capability check and the nonce check, since
|
|
* WP_Customize_Manager::setup_theme() is where the previewing flag is set.
|
|
*/
|
|
if ( ! is_customize_preview() ) {
|
|
wp_send_json_error( 'expected_customize_preview', 403 );
|
|
} elseif ( ! isset( $_POST['partials'] ) ) {
|
|
wp_send_json_error( 'missing_partials', 400 );
|
|
}
|
|
|
|
// Ensure that doing selective refresh on 404 template doesn't result in fallback rendering behavior (full refreshes).
|
|
status_header( 200 );
|
|
|
|
$partials = json_decode( wp_unslash( $_POST['partials'] ), true );
|
|
|
|
if ( ! is_array( $partials ) ) {
|
|
wp_send_json_error( 'malformed_partials' );
|
|
}
|
|
|
|
$this->add_dynamic_partials( array_keys( $partials ) );
|
|
|
|
/**
|
|
* Fires immediately before partials are rendered.
|
|
*
|
|
* Plugins may do things like call wp_enqueue_scripts() and gather a list of the scripts
|
|
* and styles which may get enqueued in the response.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param WP_Customize_Selective_Refresh $refresh Selective refresh component.
|
|
* @param array $partials Placements' context data for the partials rendered in the request.
|
|
* The array is keyed by partial ID, with each item being an array of
|
|
* the placements' context data.
|
|
*/
|
|
do_action( 'customize_render_partials_before', $this, $partials );
|
|
|
|
set_error_handler( array( $this, 'handle_error' ), error_reporting() );
|
|
|
|
$contents = array();
|
|
|
|
foreach ( $partials as $partial_id => $container_contexts ) {
|
|
$this->current_partial_id = $partial_id;
|
|
|
|
if ( ! is_array( $container_contexts ) ) {
|
|
wp_send_json_error( 'malformed_container_contexts' );
|
|
}
|
|
|
|
$partial = $this->get_partial( $partial_id );
|
|
|
|
if ( ! $partial || ! $partial->check_capabilities() ) {
|
|
$contents[ $partial_id ] = null;
|
|
continue;
|
|
}
|
|
|
|
$contents[ $partial_id ] = array();
|
|
|
|
// @todo The array should include not only the contents, but also whether the container is included?
|
|
if ( empty( $container_contexts ) ) {
|
|
// Since there are no container contexts, render just once.
|
|
$contents[ $partial_id ][] = $partial->render( null );
|
|
} else {
|
|
foreach ( $container_contexts as $container_context ) {
|
|
$contents[ $partial_id ][] = $partial->render( $container_context );
|
|
}
|
|
}
|
|
}
|
|
$this->current_partial_id = null;
|
|
|
|
restore_error_handler();
|
|
|
|
/**
|
|
* Fires immediately after partials are rendered.
|
|
*
|
|
* Plugins may do things like call wp_footer() to scrape scripts output and return them
|
|
* via the {@see 'customize_render_partials_response'} filter.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param WP_Customize_Selective_Refresh $refresh Selective refresh component.
|
|
* @param array $partials Placements' context data for the partials rendered in the request.
|
|
* The array is keyed by partial ID, with each item being an array of
|
|
* the placements' context data.
|
|
*/
|
|
do_action( 'customize_render_partials_after', $this, $partials );
|
|
|
|
$response = array(
|
|
'contents' => $contents,
|
|
);
|
|
|
|
if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
|
|
$response['errors'] = $this->triggered_errors;
|
|
}
|
|
|
|
$setting_validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
|
|
$exported_setting_validities = array_map( array( $this->manager, 'prepare_setting_validity_for_js' ), $setting_validities );
|
|
$response['setting_validities'] = $exported_setting_validities;
|
|
|
|
/**
|
|
* Filters the response from rendering the partials.
|
|
*
|
|
* Plugins may use this filter to inject `$scripts` and `$styles`, which are dependencies
|
|
* for the partials being rendered. The response data will be available to the client via
|
|
* the `render-partials-response` JS event, so the client can then inject the scripts and
|
|
* styles into the DOM if they have not already been enqueued there.
|
|
*
|
|
* If plugins do this, they'll need to take care for any scripts that do `document.write()`
|
|
* and make sure that these are not injected, or else to override the function to no-op,
|
|
* or else the page will be destroyed.
|
|
*
|
|
* Plugins should be aware that `$scripts` and `$styles` may eventually be included by
|
|
* default in the response.
|
|
*
|
|
* @since 4.5.0
|
|
*
|
|
* @param array $response {
|
|
* Response.
|
|
*
|
|
* @type array $contents Associative array mapping a partial ID its corresponding array of contents
|
|
* for the containers requested.
|
|
* @type array $errors List of errors triggered during rendering of partials, if `WP_DEBUG_DISPLAY`
|
|
* is enabled.
|
|
* }
|
|
* @param WP_Customize_Selective_Refresh $refresh Selective refresh component.
|
|
* @param array $partials Placements' context data for the partials rendered in the request.
|
|
* The array is keyed by partial ID, with each item being an array of
|
|
* the placements' context data.
|
|
*/
|
|
$response = apply_filters( 'customize_render_partials_response', $response, $this, $partials );
|
|
|
|
wp_send_json_success( $response );
|
|
}
|
|
}
|