WP_Customize_Selective_Refresh

Core Customizer class for implementing selective refresh.

Defined (1)

The class is defined in the following location(s).

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