~canonical-sysadmins/wordpress/4.6.1

« back to all changes in this revision

Viewing changes to wp-includes/customize/class-wp-customize-selective-refresh.php

  • Committer: Nick Moffitt
  • Date: 2016-04-14 10:44:19 UTC
  • mfrom: (1.1.14 upstream)
  • Revision ID: nick.moffitt@canonical.com-20160414104419-w6lxcr3ru4enc2w5
Merge WP4.5 from upstream

Show diffs side-by-side

added added

removed removed

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