9f15d31f577149c4d546e16530e54c4206e2cd3c
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-manager.php
1 <?php
2 /**
3  * WordPress Customize Manager classes
4  *
5  * @package WordPress
6  * @subpackage Customize
7  * @since 3.4.0
8  */
9
10 /**
11  * Customize Manager class.
12  *
13  * Bootstraps the Customize experience on the server-side.
14  *
15  * Sets up the theme-switching process if a theme other than the active one is
16  * being previewed and customized.
17  *
18  * Serves as a factory for Customize Controls and Settings, and
19  * instantiates default Customize Controls and Settings.
20  *
21  * @since 3.4.0
22  */
23 final class WP_Customize_Manager {
24         /**
25          * An instance of the theme being previewed.
26          *
27          * @since 3.4.0
28          * @access protected
29          * @var WP_Theme
30          */
31         protected $theme;
32
33         /**
34          * The directory name of the previously active theme (within the theme_root).
35          *
36          * @since 3.4.0
37          * @access protected
38          * @var string
39          */
40         protected $original_stylesheet;
41
42         /**
43          * Whether this is a Customizer pageload.
44          *
45          * @since 3.4.0
46          * @access protected
47          * @var bool
48          */
49         protected $previewing = false;
50
51         /**
52          * Methods and properties dealing with managing widgets in the Customizer.
53          *
54          * @since 3.9.0
55          * @access public
56          * @var WP_Customize_Widgets
57          */
58         public $widgets;
59
60         /**
61          * Methods and properties dealing with managing nav menus in the Customizer.
62          *
63          * @since 4.3.0
64          * @access public
65          * @var WP_Customize_Nav_Menus
66          */
67         public $nav_menus;
68
69         /**
70          * Registered instances of WP_Customize_Setting.
71          *
72          * @since 3.4.0
73          * @access protected
74          * @var array
75          */
76         protected $settings = array();
77
78         /**
79          * Sorted top-level instances of WP_Customize_Panel and WP_Customize_Section.
80          *
81          * @since 4.0.0
82          * @access protected
83          * @var array
84          */
85         protected $containers = array();
86
87         /**
88          * Registered instances of WP_Customize_Panel.
89          *
90          * @since 4.0.0
91          * @access protected
92          * @var array
93          */
94         protected $panels = array();
95
96         /**
97          * Registered instances of WP_Customize_Section.
98          *
99          * @since 3.4.0
100          * @access protected
101          * @var array
102          */
103         protected $sections = array();
104
105         /**
106          * Registered instances of WP_Customize_Control.
107          *
108          * @since 3.4.0
109          * @access protected
110          * @var array
111          */
112         protected $controls = array();
113
114         /**
115          * Return value of check_ajax_referer() in customize_preview_init() method.
116          *
117          * @since 3.5.0
118          * @access protected
119          * @var false|int
120          */
121         protected $nonce_tick;
122
123         /**
124          * Panel types that may be rendered from JS templates.
125          *
126          * @since 4.3.0
127          * @access protected
128          * @var array
129          */
130         protected $registered_panel_types = array();
131
132         /**
133          * Section types that may be rendered from JS templates.
134          *
135          * @since 4.3.0
136          * @access protected
137          * @var array
138          */
139         protected $registered_section_types = array();
140
141         /**
142          * Control types that may be rendered from JS templates.
143          *
144          * @since 4.1.0
145          * @access protected
146          * @var array
147          */
148         protected $registered_control_types = array();
149
150         /**
151          * Initial URL being previewed.
152          *
153          * @since 4.4.0
154          * @access protected
155          * @var string
156          */
157         protected $preview_url;
158
159         /**
160          * URL to link the user to when closing the Customizer.
161          *
162          * @since 4.4.0
163          * @access protected
164          * @var string
165          */
166         protected $return_url;
167
168         /**
169          * Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
170          *
171          * @since 4.4.0
172          * @access protected
173          * @var array
174          */
175         protected $autofocus = array();
176
177         /**
178          * Unsanitized values for Customize Settings parsed from $_POST['customized'].
179          *
180          * @var array
181          */
182         private $_post_values;
183
184         /**
185          * Constructor.
186          *
187          * @since 3.4.0
188          */
189         public function __construct() {
190                 require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
191                 require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
192                 require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
193                 require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
194
195                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-color-control.php' );
196                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-media-control.php' );
197                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' );
198                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' );
199                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' );
200                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' );
201                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
202                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
203                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
204                 require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
205                 require_once( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
206                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php' );
207                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-control.php' );
208                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-location-control.php' );
209                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-name-control.php' );
210                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add-control.php' );
211                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
212
213                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' );
214
215                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' );
216                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' );
217                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' );
218                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' );
219
220                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
221                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
222                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
223                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
224                 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
225
226                 /**
227                  * Filter the core Customizer components to load.
228                  *
229                  * This allows Core components to be excluded from being instantiated by
230                  * filtering them out of the array. Note that this filter generally runs
231                  * during the <code>plugins_loaded</code> action, so it cannot be added
232                  * in a theme.
233                  *
234                  * @since 4.4.0
235                  *
236                  * @see WP_Customize_Manager::__construct()
237                  *
238                  * @param array                $components List of core components to load.
239                  * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
240                  */
241                 $components = apply_filters( 'customize_loaded_components', array( 'widgets', 'nav_menus' ), $this );
242
243                 if ( in_array( 'widgets', $components ) ) {
244                         require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
245                         $this->widgets = new WP_Customize_Widgets( $this );
246                 }
247                 if ( in_array( 'nav_menus', $components ) ) {
248                         require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
249                         $this->nav_menus = new WP_Customize_Nav_Menus( $this );
250                 }
251
252                 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
253
254                 add_action( 'setup_theme', array( $this, 'setup_theme' ) );
255                 add_action( 'wp_loaded',   array( $this, 'wp_loaded' ) );
256
257                 // Run wp_redirect_status late to make sure we override the status last.
258                 add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
259
260                 // Do not spawn cron (especially the alternate cron) while running the Customizer.
261                 remove_action( 'init', 'wp_cron' );
262
263                 // Do not run update checks when rendering the controls.
264                 remove_action( 'admin_init', '_maybe_update_core' );
265                 remove_action( 'admin_init', '_maybe_update_plugins' );
266                 remove_action( 'admin_init', '_maybe_update_themes' );
267
268                 add_action( 'wp_ajax_customize_save',           array( $this, 'save' ) );
269                 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
270
271                 add_action( 'customize_register',                 array( $this, 'register_controls' ) );
272                 add_action( 'customize_register',                 array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first
273                 add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
274                 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
275
276                 // Render Panel, Section, and Control templates.
277                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_panel_templates' ), 1 );
278                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_section_templates' ), 1 );
279                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_control_templates' ), 1 );
280
281                 // Export the settings to JS via the _wpCustomizeSettings variable.
282                 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 );
283         }
284
285         /**
286          * Return true if it's an AJAX request.
287          *
288          * @since 3.4.0
289          * @since 4.2.0 Added `$action` param.
290          * @access public
291          *
292          * @param string|null $action Whether the supplied AJAX action is being run.
293          * @return bool True if it's an AJAX request, false otherwise.
294          */
295         public function doing_ajax( $action = null ) {
296                 $doing_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX );
297                 if ( ! $doing_ajax ) {
298                         return false;
299                 }
300
301                 if ( ! $action ) {
302                         return true;
303                 } else {
304                         /*
305                          * Note: we can't just use doing_action( "wp_ajax_{$action}" ) because we need
306                          * to check before admin-ajax.php gets to that point.
307                          */
308                         return isset( $_REQUEST['action'] ) && wp_unslash( $_REQUEST['action'] ) === $action;
309                 }
310         }
311
312         /**
313          * Custom wp_die wrapper. Returns either the standard message for UI
314          * or the AJAX message.
315          *
316          * @since 3.4.0
317          *
318          * @param mixed $ajax_message AJAX return
319          * @param mixed $message UI message
320          */
321         protected function wp_die( $ajax_message, $message = null ) {
322                 if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
323                         wp_die( $ajax_message );
324                 }
325
326                 if ( ! $message ) {
327                         $message = __( 'Cheatin&#8217; uh?' );
328                 }
329
330                 wp_die( $message );
331         }
332
333         /**
334          * Return the AJAX wp_die() handler if it's a customized request.
335          *
336          * @since 3.4.0
337          *
338          * @return string
339          */
340         public function wp_die_handler() {
341                 if ( $this->doing_ajax() || isset( $_POST['customized'] ) ) {
342                         return '_ajax_wp_die_handler';
343                 }
344
345                 return '_default_wp_die_handler';
346         }
347
348         /**
349          * Start preview and customize theme.
350          *
351          * Check if customize query variable exist. Init filters to filter the current theme.
352          *
353          * @since 3.4.0
354          */
355         public function setup_theme() {
356                 send_origin_headers();
357
358                 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) );
359                 if ( is_admin() && ! $doing_ajax_or_is_customized ) {
360                         auth_redirect();
361                 } elseif ( $doing_ajax_or_is_customized && ! is_user_logged_in() ) {
362                         $this->wp_die( 0, __( 'You must be logged in to complete this action.' ) );
363                 }
364
365                 show_admin_bar( false );
366
367                 if ( ! current_user_can( 'customize' ) ) {
368                         $this->wp_die( -1, __( 'You are not allowed to customize the appearance of this site.' ) );
369                 }
370
371                 $this->original_stylesheet = get_stylesheet();
372
373                 $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
374
375                 if ( $this->is_theme_active() ) {
376                         // Once the theme is loaded, we'll validate it.
377                         add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
378                 } else {
379                         // If the requested theme is not the active theme and the user doesn't have the
380                         // switch_themes cap, bail.
381                         if ( ! current_user_can( 'switch_themes' ) ) {
382                                 $this->wp_die( -1, __( 'You are not allowed to edit theme options on this site.' ) );
383                         }
384
385                         // If the theme has errors while loading, bail.
386                         if ( $this->theme()->errors() ) {
387                                 $this->wp_die( -1, $this->theme()->errors()->get_error_message() );
388                         }
389
390                         // If the theme isn't allowed per multisite settings, bail.
391                         if ( ! $this->theme()->is_allowed() ) {
392                                 $this->wp_die( -1, __( 'The requested theme does not exist.' ) );
393                         }
394                 }
395
396                 $this->start_previewing_theme();
397         }
398
399         /**
400          * Callback to validate a theme once it is loaded
401          *
402          * @since 3.4.0
403          */
404         public function after_setup_theme() {
405                 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_SERVER['customized'] ) );
406                 if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) {
407                         wp_redirect( 'themes.php?broken=true' );
408                         exit;
409                 }
410         }
411
412         /**
413          * If the theme to be previewed isn't the active theme, add filter callbacks
414          * to swap it out at runtime.
415          *
416          * @since 3.4.0
417          */
418         public function start_previewing_theme() {
419                 // Bail if we're already previewing.
420                 if ( $this->is_preview() ) {
421                         return;
422                 }
423
424                 $this->previewing = true;
425
426                 if ( ! $this->is_theme_active() ) {
427                         add_filter( 'template', array( $this, 'get_template' ) );
428                         add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
429                         add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
430
431                         // @link: https://core.trac.wordpress.org/ticket/20027
432                         add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
433                         add_filter( 'pre_option_template', array( $this, 'get_template' ) );
434
435                         // Handle custom theme roots.
436                         add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
437                         add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
438                 }
439
440                 /**
441                  * Fires once the Customizer theme preview has started.
442                  *
443                  * @since 3.4.0
444                  *
445                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
446                  */
447                 do_action( 'start_previewing_theme', $this );
448         }
449
450         /**
451          * Stop previewing the selected theme.
452          *
453          * Removes filters to change the current theme.
454          *
455          * @since 3.4.0
456          */
457         public function stop_previewing_theme() {
458                 if ( ! $this->is_preview() ) {
459                         return;
460                 }
461
462                 $this->previewing = false;
463
464                 if ( ! $this->is_theme_active() ) {
465                         remove_filter( 'template', array( $this, 'get_template' ) );
466                         remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
467                         remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
468
469                         // @link: https://core.trac.wordpress.org/ticket/20027
470                         remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
471                         remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
472
473                         // Handle custom theme roots.
474                         remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
475                         remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
476                 }
477
478                 /**
479                  * Fires once the Customizer theme preview has stopped.
480                  *
481                  * @since 3.4.0
482                  *
483                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
484                  */
485                 do_action( 'stop_previewing_theme', $this );
486         }
487
488         /**
489          * Get the theme being customized.
490          *
491          * @since 3.4.0
492          *
493          * @return WP_Theme
494          */
495         public function theme() {
496                 if ( ! $this->theme ) {
497                         $this->theme = wp_get_theme();
498                 }
499                 return $this->theme;
500         }
501
502         /**
503          * Get the registered settings.
504          *
505          * @since 3.4.0
506          *
507          * @return array
508          */
509         public function settings() {
510                 return $this->settings;
511         }
512
513         /**
514          * Get the registered controls.
515          *
516          * @since 3.4.0
517          *
518          * @return array
519          */
520         public function controls() {
521                 return $this->controls;
522         }
523
524         /**
525          * Get the registered containers.
526          *
527          * @since 4.0.0
528          *
529          * @return array
530          */
531         public function containers() {
532                 return $this->containers;
533         }
534
535         /**
536          * Get the registered sections.
537          *
538          * @since 3.4.0
539          *
540          * @return array
541          */
542         public function sections() {
543                 return $this->sections;
544         }
545
546         /**
547          * Get the registered panels.
548          *
549          * @since 4.0.0
550          * @access public
551          *
552          * @return array Panels.
553          */
554         public function panels() {
555                 return $this->panels;
556         }
557
558         /**
559          * Checks if the current theme is active.
560          *
561          * @since 3.4.0
562          *
563          * @return bool
564          */
565         public function is_theme_active() {
566                 return $this->get_stylesheet() == $this->original_stylesheet;
567         }
568
569         /**
570          * Register styles/scripts and initialize the preview of each setting
571          *
572          * @since 3.4.0
573          */
574         public function wp_loaded() {
575
576                 /**
577                  * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
578                  *
579                  * @since 3.4.0
580                  *
581                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
582                  */
583                 do_action( 'customize_register', $this );
584
585                 if ( $this->is_preview() && ! is_admin() )
586                         $this->customize_preview_init();
587         }
588
589         /**
590          * Prevents AJAX requests from following redirects when previewing a theme
591          * by issuing a 200 response instead of a 30x.
592          *
593          * Instead, the JS will sniff out the location header.
594          *
595          * @since 3.4.0
596          *
597          * @param $status
598          * @return int
599          */
600         public function wp_redirect_status( $status ) {
601                 if ( $this->is_preview() && ! is_admin() )
602                         return 200;
603
604                 return $status;
605         }
606
607         /**
608          * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
609          * settings for subsequent post_value() lookups.
610          *
611          * @since 4.1.1
612          *
613          * @return array
614          */
615         public function unsanitized_post_values() {
616                 if ( ! isset( $this->_post_values ) ) {
617                         if ( isset( $_POST['customized'] ) ) {
618                                 $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
619                         }
620                         if ( empty( $this->_post_values ) ) { // if not isset or if JSON error
621                                 $this->_post_values = array();
622                         }
623                 }
624                 if ( empty( $this->_post_values ) ) {
625                         return array();
626                 } else {
627                         return $this->_post_values;
628                 }
629         }
630
631         /**
632          * Return the sanitized value for a given setting from the request's POST data.
633          *
634          * @since 3.4.0
635          * @since 4.1.1 Introduced 'default' parameter.
636          *
637          * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
638          * @param mixed $default value returned $setting has no post value (added in 4.2.0).
639          * @return string|mixed $post_value Sanitized value or the $default provided
640          */
641         public function post_value( $setting, $default = null ) {
642                 $post_values = $this->unsanitized_post_values();
643                 if ( array_key_exists( $setting->id, $post_values ) ) {
644                         return $setting->sanitize( $post_values[ $setting->id ] );
645                 } else {
646                         return $default;
647                 }
648         }
649
650         /**
651          * Override a setting's (unsanitized) value as found in any incoming $_POST['customized'].
652          *
653          * @since 4.2.0
654          * @access public
655          *
656          * @param string $setting_id ID for the WP_Customize_Setting instance.
657          * @param mixed  $value      Post value.
658          */
659         public function set_post_value( $setting_id, $value ) {
660                 $this->unsanitized_post_values();
661                 $this->_post_values[ $setting_id ] = $value;
662
663                 /**
664                  * Announce when a specific setting's unsanitized post value has been set.
665                  *
666                  * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called.
667                  *
668                  * The dynamic portion of the hook name, `$setting_id`, refers to the setting ID.
669                  *
670                  * @since 4.4.0
671                  *
672                  * @param mixed                $value Unsanitized setting post value.
673                  * @param WP_Customize_Manager $this  WP_Customize_Manager instance.
674                  */
675                 do_action( "customize_post_value_set_{$setting_id}", $value, $this );
676
677                 /**
678                  * Announce when any setting's unsanitized post value has been set.
679                  *
680                  * Fires when the {@see WP_Customize_Manager::set_post_value()} method is called.
681                  *
682                  * This is useful for <code>WP_Customize_Setting</code> instances to watch
683                  * in order to update a cached previewed value.
684                  *
685                  * @since 4.4.0
686                  *
687                  * @param string               $setting_id Setting ID.
688                  * @param mixed                $value      Unsanitized setting post value.
689                  * @param WP_Customize_Manager $this       WP_Customize_Manager instance.
690                  */
691                 do_action( 'customize_post_value_set', $setting_id, $value, $this );
692         }
693
694         /**
695          * Print JavaScript settings.
696          *
697          * @since 3.4.0
698          */
699         public function customize_preview_init() {
700                 $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
701
702                 $this->prepare_controls();
703
704                 wp_enqueue_script( 'customize-preview' );
705                 add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
706                 add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
707                 add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
708                 add_action( 'wp_head', array( $this, 'customize_preview_loading_style' ) );
709                 add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
710                 add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
711                 add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
712
713                 foreach ( $this->settings as $setting ) {
714                         $setting->preview();
715                 }
716
717                 /**
718                  * Fires once the Customizer preview has initialized and JavaScript
719                  * settings have been printed.
720                  *
721                  * @since 3.4.0
722                  *
723                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
724                  */
725                 do_action( 'customize_preview_init', $this );
726         }
727
728         /**
729          * Prevent sending a 404 status when returning the response for the customize
730          * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
731          *
732          * @since 4.0.0
733          * @access public
734          */
735         public function customize_preview_override_404_status() {
736                 if ( is_404() ) {
737                         status_header( 200 );
738                 }
739         }
740
741         /**
742          * Print base element for preview frame.
743          *
744          * @since 3.4.0
745          */
746         public function customize_preview_base() {
747                 ?><base href="<?php echo home_url( '/' ); ?>" /><?php
748         }
749
750         /**
751          * Print a workaround to handle HTML5 tags in IE < 9.
752          *
753          * @since 3.4.0
754          */
755         public function customize_preview_html5() { ?>
756                 <!--[if lt IE 9]>
757                 <script type="text/javascript">
758                         var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
759                                 'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
760                                 'output', 'progress', 'section', 'time', 'video' ];
761                         for ( var i = 0; i < e.length; i++ ) {
762                                 document.createElement( e[i] );
763                         }
764                 </script>
765                 <![endif]--><?php
766         }
767
768         /**
769          * Print CSS for loading indicators for the Customizer preview.
770          *
771          * @since 4.2.0
772          * @access public
773          */
774         public function customize_preview_loading_style() {
775                 ?><style>
776                         body.wp-customizer-unloading {
777                                 opacity: 0.25;
778                                 cursor: progress !important;
779                                 -webkit-transition: opacity 0.5s;
780                                 transition: opacity 0.5s;
781                         }
782                         body.wp-customizer-unloading * {
783                                 pointer-events: none !important;
784                         }
785                 </style><?php
786         }
787
788         /**
789          * Print JavaScript settings for preview frame.
790          *
791          * @since 3.4.0
792          */
793         public function customize_preview_settings() {
794                 $settings = array(
795                         'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
796                         'activePanels' => array(),
797                         'activeSections' => array(),
798                         'activeControls' => array(),
799                 );
800
801                 if ( 2 == $this->nonce_tick ) {
802                         $settings['nonce'] = array(
803                                 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
804                                 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
805                         );
806                 }
807
808                 foreach ( $this->panels as $panel_id => $panel ) {
809                         if ( $panel->check_capabilities() ) {
810                                 $settings['activePanels'][ $panel_id ] = $panel->active();
811                                 foreach ( $panel->sections as $section_id => $section ) {
812                                         if ( $section->check_capabilities() ) {
813                                                 $settings['activeSections'][ $section_id ] = $section->active();
814                                         }
815                                 }
816                         }
817                 }
818                 foreach ( $this->sections as $id => $section ) {
819                         if ( $section->check_capabilities() ) {
820                                 $settings['activeSections'][ $id ] = $section->active();
821                         }
822                 }
823                 foreach ( $this->controls as $id => $control ) {
824                         if ( $control->check_capabilities() ) {
825                                 $settings['activeControls'][ $id ] = $control->active();
826                         }
827                 }
828
829                 ?>
830                 <script type="text/javascript">
831                         var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
832                         _wpCustomizeSettings.values = {};
833                         (function( v ) {
834                                 <?php
835                                 /*
836                                  * Serialize settings separately from the initial _wpCustomizeSettings
837                                  * serialization in order to avoid a peak memory usage spike.
838                                  * @todo We may not even need to export the values at all since the pane syncs them anyway.
839                                  */
840                                 foreach ( $this->settings as $id => $setting ) {
841                                         if ( $setting->check_capabilities() ) {
842                                                 printf(
843                                                         "v[%s] = %s;\n",
844                                                         wp_json_encode( $id ),
845                                                         wp_json_encode( $setting->js_value() )
846                                                 );
847                                         }
848                                 }
849                                 ?>
850                         })( _wpCustomizeSettings.values );
851                 </script>
852                 <?php
853         }
854
855         /**
856          * Prints a signature so we can ensure the Customizer was properly executed.
857          *
858          * @since 3.4.0
859          */
860         public function customize_preview_signature() {
861                 echo 'WP_CUSTOMIZER_SIGNATURE';
862         }
863
864         /**
865          * Removes the signature in case we experience a case where the Customizer was not properly executed.
866          *
867          * @since 3.4.0
868          *
869          * @param mixed $return Value passed through for wp_die_handler filter.
870          * @return mixed Value passed through for wp_die_handler filter.
871          */
872         public function remove_preview_signature( $return = null ) {
873                 remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
874
875                 return $return;
876         }
877
878         /**
879          * Is it a theme preview?
880          *
881          * @since 3.4.0
882          *
883          * @return bool True if it's a preview, false if not.
884          */
885         public function is_preview() {
886                 return (bool) $this->previewing;
887         }
888
889         /**
890          * Retrieve the template name of the previewed theme.
891          *
892          * @since 3.4.0
893          *
894          * @return string Template name.
895          */
896         public function get_template() {
897                 return $this->theme()->get_template();
898         }
899
900         /**
901          * Retrieve the stylesheet name of the previewed theme.
902          *
903          * @since 3.4.0
904          *
905          * @return string Stylesheet name.
906          */
907         public function get_stylesheet() {
908                 return $this->theme()->get_stylesheet();
909         }
910
911         /**
912          * Retrieve the template root of the previewed theme.
913          *
914          * @since 3.4.0
915          *
916          * @return string Theme root.
917          */
918         public function get_template_root() {
919                 return get_raw_theme_root( $this->get_template(), true );
920         }
921
922         /**
923          * Retrieve the stylesheet root of the previewed theme.
924          *
925          * @since 3.4.0
926          *
927          * @return string Theme root.
928          */
929         public function get_stylesheet_root() {
930                 return get_raw_theme_root( $this->get_stylesheet(), true );
931         }
932
933         /**
934          * Filter the current theme and return the name of the previewed theme.
935          *
936          * @since 3.4.0
937          *
938          * @param $current_theme {@internal Parameter is not used}
939          * @return string Theme name.
940          */
941         public function current_theme( $current_theme ) {
942                 return $this->theme()->display('Name');
943         }
944
945         /**
946          * Switch the theme and trigger the save() method on each setting.
947          *
948          * @since 3.4.0
949          */
950         public function save() {
951                 if ( ! $this->is_preview() ) {
952                         wp_send_json_error( 'not_preview' );
953                 }
954
955                 $action = 'save-customize_' . $this->get_stylesheet();
956                 if ( ! check_ajax_referer( $action, 'nonce', false ) ) {
957                         wp_send_json_error( 'invalid_nonce' );
958                 }
959
960                 // Do we have to switch themes?
961                 if ( ! $this->is_theme_active() ) {
962                         // Temporarily stop previewing the theme to allow switch_themes()
963                         // to operate properly.
964                         $this->stop_previewing_theme();
965                         switch_theme( $this->get_stylesheet() );
966                         update_option( 'theme_switched_via_customizer', true );
967                         $this->start_previewing_theme();
968                 }
969
970                 /**
971                  * Fires once the theme has switched in the Customizer, but before settings
972                  * have been saved.
973                  *
974                  * @since 3.4.0
975                  *
976                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
977                  */
978                 do_action( 'customize_save', $this );
979
980                 foreach ( $this->settings as $setting ) {
981                         $setting->save();
982                 }
983
984                 /**
985                  * Fires after Customize settings have been saved.
986                  *
987                  * @since 3.6.0
988                  *
989                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
990                  */
991                 do_action( 'customize_save_after', $this );
992
993                 /**
994                  * Filter response data for a successful customize_save AJAX request.
995                  *
996                  * This filter does not apply if there was a nonce or authentication failure.
997                  *
998                  * @since 4.2.0
999                  *
1000                  * @param array                $data Additional information passed back to the 'saved'
1001                  *                                   event on `wp.customize`.
1002                  * @param WP_Customize_Manager $this WP_Customize_Manager instance.
1003                  */
1004                 $response = apply_filters( 'customize_save_response', array(), $this );
1005                 wp_send_json_success( $response );
1006         }
1007
1008         /**
1009          * Refresh nonces for the current preview.
1010          *
1011          * @since 4.2.0
1012          */
1013         public function refresh_nonces() {
1014                 if ( ! $this->is_preview() ) {
1015                         wp_send_json_error( 'not_preview' );
1016                 }
1017
1018                 $nonces = array(
1019                         'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
1020                         'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
1021                 );
1022
1023                 /**
1024                  * Filter nonces for a customize_refresh_nonces AJAX request.
1025                  *
1026                  * @since 4.2.0
1027                  *
1028                  * @param array                $nonces Array of refreshed nonces for save and
1029                  *                                     preview actions.
1030                  * @param WP_Customize_Manager $this   WP_Customize_Manager instance.
1031                  */
1032                 $nonces = apply_filters( 'customize_refresh_nonces', $nonces, $this );
1033                 wp_send_json_success( $nonces );
1034         }
1035
1036         /**
1037          * Add a customize setting.
1038          *
1039          * @since 3.4.0
1040          *
1041          * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
1042          * @param array $args                     Setting arguments; passed to WP_Customize_Setting
1043          *                                        constructor.
1044          */
1045         public function add_setting( $id, $args = array() ) {
1046                 if ( $id instanceof WP_Customize_Setting ) {
1047                         $setting = $id;
1048                 } else {
1049                         $setting = new WP_Customize_Setting( $this, $id, $args );
1050                 }
1051                 $this->settings[ $setting->id ] = $setting;
1052         }
1053
1054         /**
1055          * Register any dynamically-created settings, such as those from $_POST['customized']
1056          * that have no corresponding setting created.
1057          *
1058          * This is a mechanism to "wake up" settings that have been dynamically created
1059          * on the frontend and have been sent to WordPress in `$_POST['customized']`. When WP
1060          * loads, the dynamically-created settings then will get created and previewed
1061          * even though they are not directly created statically with code.
1062          *
1063          * @since 4.2.0
1064          *
1065          * @param array $setting_ids The setting IDs to add.
1066          * @return array The WP_Customize_Setting objects added.
1067          */
1068         public function add_dynamic_settings( $setting_ids ) {
1069                 $new_settings = array();
1070                 foreach ( $setting_ids as $setting_id ) {
1071                         // Skip settings already created
1072                         if ( $this->get_setting( $setting_id ) ) {
1073                                 continue;
1074                         }
1075
1076                         $setting_args = false;
1077                         $setting_class = 'WP_Customize_Setting';
1078
1079                         /**
1080                          * Filter a dynamic setting's constructor args.
1081                          *
1082                          * For a dynamic setting to be registered, this filter must be employed
1083                          * to override the default false value with an array of args to pass to
1084                          * the WP_Customize_Setting constructor.
1085                          *
1086                          * @since 4.2.0
1087                          *
1088                          * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
1089                          * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
1090                          */
1091                         $setting_args = apply_filters( 'customize_dynamic_setting_args', $setting_args, $setting_id );
1092                         if ( false === $setting_args ) {
1093                                 continue;
1094                         }
1095
1096                         /**
1097                          * Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
1098                          *
1099                          * @since 4.2.0
1100                          *
1101                          * @param string $setting_class WP_Customize_Setting or a subclass.
1102                          * @param string $setting_id    ID for dynamic setting, usually coming from `$_POST['customized']`.
1103                          * @param array  $setting_args  WP_Customize_Setting or a subclass.
1104                          */
1105                         $setting_class = apply_filters( 'customize_dynamic_setting_class', $setting_class, $setting_id, $setting_args );
1106
1107                         $setting = new $setting_class( $this, $setting_id, $setting_args );
1108
1109                         $this->add_setting( $setting );
1110                         $new_settings[] = $setting;
1111                 }
1112                 return $new_settings;
1113         }
1114
1115         /**
1116          * Retrieve a customize setting.
1117          *
1118          * @since 3.4.0
1119          *
1120          * @param string $id Customize Setting ID.
1121          * @return WP_Customize_Setting|void The setting, if set.
1122          */
1123         public function get_setting( $id ) {
1124                 if ( isset( $this->settings[ $id ] ) ) {
1125                         return $this->settings[ $id ];
1126                 }
1127         }
1128
1129         /**
1130          * Remove a customize setting.
1131          *
1132          * @since 3.4.0
1133          *
1134          * @param string $id Customize Setting ID.
1135          */
1136         public function remove_setting( $id ) {
1137                 unset( $this->settings[ $id ] );
1138         }
1139
1140         /**
1141          * Add a customize panel.
1142          *
1143          * @since 4.0.0
1144          * @access public
1145          *
1146          * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
1147          * @param array                     $args Optional. Panel arguments. Default empty array.
1148          */
1149         public function add_panel( $id, $args = array() ) {
1150                 if ( $id instanceof WP_Customize_Panel ) {
1151                         $panel = $id;
1152                 } else {
1153                         $panel = new WP_Customize_Panel( $this, $id, $args );
1154                 }
1155
1156                 $this->panels[ $panel->id ] = $panel;
1157         }
1158
1159         /**
1160          * Retrieve a customize panel.
1161          *
1162          * @since 4.0.0
1163          * @access public
1164          *
1165          * @param string $id Panel ID to get.
1166          * @return WP_Customize_Panel|void Requested panel instance, if set.
1167          */
1168         public function get_panel( $id ) {
1169                 if ( isset( $this->panels[ $id ] ) ) {
1170                         return $this->panels[ $id ];
1171                 }
1172         }
1173
1174         /**
1175          * Remove a customize panel.
1176          *
1177          * @since 4.0.0
1178          * @access public
1179          *
1180          * @param string $id Panel ID to remove.
1181          */
1182         public function remove_panel( $id ) {
1183                 unset( $this->panels[ $id ] );
1184         }
1185
1186         /**
1187          * Register a customize panel type.
1188          *
1189          * Registered types are eligible to be rendered via JS and created dynamically.
1190          *
1191          * @since 4.3.0
1192          * @access public
1193          *
1194          * @see WP_Customize_Panel
1195          *
1196          * @param string $panel Name of a custom panel which is a subclass of WP_Customize_Panel.
1197          */
1198         public function register_panel_type( $panel ) {
1199                 $this->registered_panel_types[] = $panel;
1200         }
1201
1202         /**
1203          * Render JS templates for all registered panel types.
1204          *
1205          * @since 4.3.0
1206          * @access public
1207          */
1208         public function render_panel_templates() {
1209                 foreach ( $this->registered_panel_types as $panel_type ) {
1210                         $panel = new $panel_type( $this, 'temp', array() );
1211                         $panel->print_template();
1212                 }
1213         }
1214
1215         /**
1216          * Add a customize section.
1217          *
1218          * @since 3.4.0
1219          *
1220          * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
1221          * @param array                       $args Section arguments.
1222          */
1223         public function add_section( $id, $args = array() ) {
1224                 if ( $id instanceof WP_Customize_Section ) {
1225                         $section = $id;
1226                 } else {
1227                         $section = new WP_Customize_Section( $this, $id, $args );
1228                 }
1229                 $this->sections[ $section->id ] = $section;
1230         }
1231
1232         /**
1233          * Retrieve a customize section.
1234          *
1235          * @since 3.4.0
1236          *
1237          * @param string $id Section ID.
1238          * @return WP_Customize_Section|void The section, if set.
1239          */
1240         public function get_section( $id ) {
1241                 if ( isset( $this->sections[ $id ] ) )
1242                         return $this->sections[ $id ];
1243         }
1244
1245         /**
1246          * Remove a customize section.
1247          *
1248          * @since 3.4.0
1249          *
1250          * @param string $id Section ID.
1251          */
1252         public function remove_section( $id ) {
1253                 unset( $this->sections[ $id ] );
1254         }
1255
1256         /**
1257          * Register a customize section type.
1258          *
1259          * Registered types are eligible to be rendered via JS and created dynamically.
1260          *
1261          * @since 4.3.0
1262          * @access public
1263          *
1264          * @see WP_Customize_Section
1265          *
1266          * @param string $section Name of a custom section which is a subclass of WP_Customize_Section.
1267          */
1268         public function register_section_type( $section ) {
1269                 $this->registered_section_types[] = $section;
1270         }
1271
1272         /**
1273          * Render JS templates for all registered section types.
1274          *
1275          * @since 4.3.0
1276          * @access public
1277          */
1278         public function render_section_templates() {
1279                 foreach ( $this->registered_section_types as $section_type ) {
1280                         $section = new $section_type( $this, 'temp', array() );
1281                         $section->print_template();
1282                 }
1283         }
1284
1285         /**
1286          * Add a customize control.
1287          *
1288          * @since 3.4.0
1289          *
1290          * @param WP_Customize_Control|string $id   Customize Control object, or ID.
1291          * @param array                       $args Control arguments; passed to WP_Customize_Control
1292          *                                          constructor.
1293          */
1294         public function add_control( $id, $args = array() ) {
1295                 if ( $id instanceof WP_Customize_Control ) {
1296                         $control = $id;
1297                 } else {
1298                         $control = new WP_Customize_Control( $this, $id, $args );
1299                 }
1300                 $this->controls[ $control->id ] = $control;
1301         }
1302
1303         /**
1304          * Retrieve a customize control.
1305          *
1306          * @since 3.4.0
1307          *
1308          * @param string $id ID of the control.
1309          * @return WP_Customize_Control|void The control object, if set.
1310          */
1311         public function get_control( $id ) {
1312                 if ( isset( $this->controls[ $id ] ) )
1313                         return $this->controls[ $id ];
1314         }
1315
1316         /**
1317          * Remove a customize control.
1318          *
1319          * @since 3.4.0
1320          *
1321          * @param string $id ID of the control.
1322          */
1323         public function remove_control( $id ) {
1324                 unset( $this->controls[ $id ] );
1325         }
1326
1327         /**
1328          * Register a customize control type.
1329          *
1330          * Registered types are eligible to be rendered via JS and created dynamically.
1331          *
1332          * @since 4.1.0
1333          * @access public
1334          *
1335          * @param string $control Name of a custom control which is a subclass of
1336          *                        {@see WP_Customize_Control}.
1337          */
1338         public function register_control_type( $control ) {
1339                 $this->registered_control_types[] = $control;
1340         }
1341
1342         /**
1343          * Render JS templates for all registered control types.
1344          *
1345          * @since 4.1.0
1346          * @access public
1347          */
1348         public function render_control_templates() {
1349                 foreach ( $this->registered_control_types as $control_type ) {
1350                         $control = new $control_type( $this, 'temp', array() );
1351                         $control->print_template();
1352                 }
1353         }
1354
1355         /**
1356          * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
1357          *
1358          * @since 3.4.0
1359          *
1360          * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $a Object A.
1361          * @param WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control $b Object B.
1362          * @return int
1363          */
1364         protected function _cmp_priority( $a, $b ) {
1365                 if ( $a->priority === $b->priority ) {
1366                         return $a->instance_number - $b->instance_number;
1367                 } else {
1368                         return $a->priority - $b->priority;
1369                 }
1370         }
1371
1372         /**
1373          * Prepare panels, sections, and controls.
1374          *
1375          * For each, check if required related components exist,
1376          * whether the user has the necessary capabilities,
1377          * and sort by priority.
1378          *
1379          * @since 3.4.0
1380          */
1381         public function prepare_controls() {
1382
1383                 $controls = array();
1384                 uasort( $this->controls, array( $this, '_cmp_priority' ) );
1385
1386                 foreach ( $this->controls as $id => $control ) {
1387                         if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
1388                                 continue;
1389                         }
1390
1391                         $this->sections[ $control->section ]->controls[] = $control;
1392                         $controls[ $id ] = $control;
1393                 }
1394                 $this->controls = $controls;
1395
1396                 // Prepare sections.
1397                 uasort( $this->sections, array( $this, '_cmp_priority' ) );
1398                 $sections = array();
1399
1400                 foreach ( $this->sections as $section ) {
1401                         if ( ! $section->check_capabilities() ) {
1402                                 continue;
1403                         }
1404
1405                         usort( $section->controls, array( $this, '_cmp_priority' ) );
1406
1407                         if ( ! $section->panel ) {
1408                                 // Top-level section.
1409                                 $sections[ $section->id ] = $section;
1410                         } else {
1411                                 // This section belongs to a panel.
1412                                 if ( isset( $this->panels [ $section->panel ] ) ) {
1413                                         $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
1414                                 }
1415                         }
1416                 }
1417                 $this->sections = $sections;
1418
1419                 // Prepare panels.
1420                 uasort( $this->panels, array( $this, '_cmp_priority' ) );
1421                 $panels = array();
1422
1423                 foreach ( $this->panels as $panel ) {
1424                         if ( ! $panel->check_capabilities() ) {
1425                                 continue;
1426                         }
1427
1428                         uasort( $panel->sections, array( $this, '_cmp_priority' ) );
1429                         $panels[ $panel->id ] = $panel;
1430                 }
1431                 $this->panels = $panels;
1432
1433                 // Sort panels and top-level sections together.
1434                 $this->containers = array_merge( $this->panels, $this->sections );
1435                 uasort( $this->containers, array( $this, '_cmp_priority' ) );
1436         }
1437
1438         /**
1439          * Enqueue scripts for customize controls.
1440          *
1441          * @since 3.4.0
1442          */
1443         public function enqueue_control_scripts() {
1444                 foreach ( $this->controls as $control ) {
1445                         $control->enqueue();
1446                 }
1447         }
1448
1449         /**
1450          * Determine whether the user agent is iOS.
1451          *
1452          * @since 4.4.0
1453          * @access public
1454          *
1455          * @return bool Whether the user agent is iOS.
1456          */
1457         public function is_ios() {
1458                 return wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] );
1459         }
1460
1461         /**
1462          * Get the template string for the Customizer pane document title.
1463          *
1464          * @since 4.4.0
1465          * @access public
1466          *
1467          * @return string The template string for the document title.
1468          */
1469         public function get_document_title_template() {
1470                 if ( $this->is_theme_active() ) {
1471                         /* translators: %s: document title from the preview */
1472                         $document_title_tmpl = __( 'Customize: %s' );
1473                 } else {
1474                         /* translators: %s: document title from the preview */
1475                         $document_title_tmpl = __( 'Live Preview: %s' );
1476                 }
1477                 $document_title_tmpl = html_entity_decode( $document_title_tmpl, ENT_QUOTES, 'UTF-8' ); // Because exported to JS and assigned to document.title.
1478                 return $document_title_tmpl;
1479         }
1480
1481         /**
1482          * Set the initial URL to be previewed.
1483          *
1484          * URL is validated.
1485          *
1486          * @since 4.4.0
1487          * @access public
1488          *
1489          * @param string $preview_url URL to be previewed.
1490          */
1491         public function set_preview_url( $preview_url ) {
1492                 $this->preview_url = wp_validate_redirect( $preview_url, home_url( '/' ) );
1493         }
1494
1495         /**
1496          * Get the initial URL to be previewed.
1497          *
1498          * @since 4.4.0
1499          * @access public
1500          *
1501          * @return string URL being previewed.
1502          */
1503         public function get_preview_url() {
1504                 if ( empty( $this->preview_url ) ) {
1505                         $preview_url = home_url( '/' );
1506                 } else {
1507                         $preview_url = $this->preview_url;
1508                 }
1509                 return $preview_url;
1510         }
1511
1512         /**
1513          * Set URL to link the user to when closing the Customizer.
1514          *
1515          * URL is validated.
1516          *
1517          * @since 4.4.0
1518          * @access public
1519          *
1520          * @param string $return_url URL for return link.
1521          */
1522         public function set_return_url( $return_url ) {
1523                 $return_url = remove_query_arg( wp_removable_query_args(), $return_url );
1524                 $return_url = wp_validate_redirect( $return_url );
1525                 $this->return_url = $return_url;
1526         }
1527
1528         /**
1529          * Get URL to link the user to when closing the Customizer.
1530          *
1531          * @since 4.4.0
1532          * @access public
1533          *
1534          * @return string URL for link to close Customizer.
1535          */
1536         public function get_return_url() {
1537                 $referer = wp_get_referer();
1538                 if ( $this->return_url ) {
1539                         $return_url = $this->return_url;
1540                 } else if ( $referer && 'customize.php' !== basename( parse_url( $referer, PHP_URL_PATH ) ) ) {
1541                         $return_url = $referer;
1542                 } else if ( $this->preview_url ) {
1543                         $return_url = $this->preview_url;
1544                 } else {
1545                         $return_url = home_url( '/' );
1546                 }
1547                 return $return_url;
1548         }
1549
1550         /**
1551          * Set the autofocused constructs.
1552          *
1553          * @since 4.4.0
1554          * @access public
1555          *
1556          * @param array $autofocus {
1557          *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1558          *
1559          *     @type string [$control]  ID for control to be autofocused.
1560          *     @type string [$section]  ID for section to be autofocused.
1561          *     @type string [$panel]    ID for panel to be autofocused.
1562          * }
1563          */
1564         public function set_autofocus( $autofocus ) {
1565                 $this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
1566         }
1567
1568         /**
1569          * Get the autofocused constructs.
1570          *
1571          * @since 4.4.0
1572          * @access public
1573          *
1574          * @return array {
1575          *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1576          *
1577          *     @type string [$control]  ID for control to be autofocused.
1578          *     @type string [$section]  ID for section to be autofocused.
1579          *     @type string [$panel]    ID for panel to be autofocused.
1580          * }
1581          */
1582         public function get_autofocus() {
1583                 return $this->autofocus;
1584         }
1585
1586         /**
1587          * Print JavaScript settings for parent window.
1588          *
1589          * @since 4.4.0
1590          */
1591         public function customize_pane_settings() {
1592                 /*
1593                  * If the frontend and the admin are served from the same domain, load the
1594                  * preview over ssl if the Customizer is being loaded over ssl. This avoids
1595                  * insecure content warnings. This is not attempted if the admin and frontend
1596                  * are on different domains to avoid the case where the frontend doesn't have
1597                  * ssl certs. Domain mapping plugins can allow other urls in these conditions
1598                  * using the customize_allowed_urls filter.
1599                  */
1600
1601                 $allowed_urls = array( home_url( '/' ) );
1602                 $admin_origin = parse_url( admin_url() );
1603                 $home_origin  = parse_url( home_url() );
1604                 $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
1605
1606                 if ( is_ssl() && ! $cross_domain ) {
1607                         $allowed_urls[] = home_url( '/', 'https' );
1608                 }
1609
1610                 /**
1611                  * Filter the list of URLs allowed to be clicked and followed in the Customizer preview.
1612                  *
1613                  * @since 3.4.0
1614                  *
1615                  * @param array $allowed_urls An array of allowed URLs.
1616                  */
1617                 $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
1618
1619                 $login_url = add_query_arg( array(
1620                         'interim-login' => 1,
1621                         'customize-login' => 1,
1622                 ), wp_login_url() );
1623
1624                 // Prepare Customizer settings to pass to JavaScript.
1625                 $settings = array(
1626                         'theme'    => array(
1627                                 'stylesheet' => $this->get_stylesheet(),
1628                                 'active'     => $this->is_theme_active(),
1629                         ),
1630                         'url'      => array(
1631                                 'preview'       => esc_url_raw( $this->get_preview_url() ),
1632                                 'parent'        => esc_url_raw( admin_url() ),
1633                                 'activated'     => esc_url_raw( home_url( '/' ) ),
1634                                 'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
1635                                 'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
1636                                 'isCrossDomain' => $cross_domain,
1637                                 'home'          => esc_url_raw( home_url( '/' ) ),
1638                                 'login'         => esc_url_raw( $login_url ),
1639                         ),
1640                         'browser'  => array(
1641                                 'mobile' => wp_is_mobile(),
1642                                 'ios'    => $this->is_ios(),
1643                         ),
1644                         'panels'   => array(),
1645                         'sections' => array(),
1646                         'nonce'    => array(
1647                                 'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
1648                                 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
1649                         ),
1650                         'autofocus' => array(),
1651                         'documentTitleTmpl' => $this->get_document_title_template(),
1652                 );
1653
1654                 // Prepare Customize Section objects to pass to JavaScript.
1655                 foreach ( $this->sections() as $id => $section ) {
1656                         if ( $section->check_capabilities() ) {
1657                                 $settings['sections'][ $id ] = $section->json();
1658                         }
1659                 }
1660
1661                 // Prepare Customize Panel objects to pass to JavaScript.
1662                 foreach ( $this->panels() as $panel_id => $panel ) {
1663                         if ( $panel->check_capabilities() ) {
1664                                 $settings['panels'][ $panel_id ] = $panel->json();
1665                                 foreach ( $panel->sections as $section_id => $section ) {
1666                                         if ( $section->check_capabilities() ) {
1667                                                 $settings['sections'][ $section_id ] = $section->json();
1668                                         }
1669                                 }
1670                         }
1671                 }
1672
1673                 // Pass to frontend the Customizer construct being deeplinked.
1674                 foreach ( $this->get_autofocus() as $type => $id ) {
1675                         $can_autofocus = (
1676                                 ( 'control' === $type && $this->get_control( $id ) && $this->get_control( $id )->check_capabilities() )
1677                                 ||
1678                                 ( 'section' === $type && isset( $settings['sections'][ $id ] ) )
1679                                 ||
1680                                 ( 'panel' === $type && isset( $settings['panels'][ $id ] ) )
1681                         );
1682                         if ( $can_autofocus ) {
1683                                 $settings['autofocus'][ $type ] = $id;
1684                         }
1685                 }
1686
1687                 ?>
1688                 <script type="text/javascript">
1689                         var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
1690                         _wpCustomizeSettings.controls = {};
1691                         _wpCustomizeSettings.settings = {};
1692                         <?php
1693
1694                         // Serialize settings one by one to improve memory usage.
1695                         echo "(function ( s ){\n";
1696                         foreach ( $this->settings() as $setting ) {
1697                                 if ( $setting->check_capabilities() ) {
1698                                         printf(
1699                                                 "s[%s] = %s;\n",
1700                                                 wp_json_encode( $setting->id ),
1701                                                 wp_json_encode( array(
1702                                                         'value'     => $setting->js_value(),
1703                                                         'transport' => $setting->transport,
1704                                                         'dirty'     => $setting->dirty,
1705                                                 ) )
1706                                         );
1707                                 }
1708                         }
1709                         echo "})( _wpCustomizeSettings.settings );\n";
1710
1711                         // Serialize controls one by one to improve memory usage.
1712                         echo "(function ( c ){\n";
1713                         foreach ( $this->controls() as $control ) {
1714                                 if ( $control->check_capabilities() ) {
1715                                         printf(
1716                                                 "c[%s] = %s;\n",
1717                                                 wp_json_encode( $control->id ),
1718                                                 wp_json_encode( $control->json() )
1719                                         );
1720                                 }
1721                         }
1722                         echo "})( _wpCustomizeSettings.controls );\n";
1723                 ?>
1724                 </script>
1725                 <?php
1726         }
1727
1728         /**
1729          * Register some default controls.
1730          *
1731          * @since 3.4.0
1732          */
1733         public function register_controls() {
1734
1735                 /* Panel, Section, and Control Types */
1736                 $this->register_panel_type( 'WP_Customize_Panel' );
1737                 $this->register_section_type( 'WP_Customize_Section' );
1738                 $this->register_section_type( 'WP_Customize_Sidebar_Section' );
1739                 $this->register_control_type( 'WP_Customize_Color_Control' );
1740                 $this->register_control_type( 'WP_Customize_Media_Control' );
1741                 $this->register_control_type( 'WP_Customize_Upload_Control' );
1742                 $this->register_control_type( 'WP_Customize_Image_Control' );
1743                 $this->register_control_type( 'WP_Customize_Background_Image_Control' );
1744                 $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
1745                 $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
1746                 $this->register_control_type( 'WP_Customize_Theme_Control' );
1747
1748                 /* Themes */
1749
1750                 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
1751                         'title'      => $this->theme()->display( 'Name' ),
1752                         'capability' => 'switch_themes',
1753                         'priority'   => 0,
1754                 ) ) );
1755
1756                 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
1757                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
1758                         'capability' => 'switch_themes',
1759                 ) ) );
1760
1761                 require_once( ABSPATH . 'wp-admin/includes/theme.php' );
1762
1763                 // Theme Controls.
1764
1765                 // Add a control for the active/original theme.
1766                 if ( ! $this->is_theme_active() ) {
1767                         $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
1768                         $active_theme = current( $themes );
1769                         $active_theme['isActiveTheme'] = true;
1770                         $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
1771                                 'theme'    => $active_theme,
1772                                 'section'  => 'themes',
1773                                 'settings' => 'active_theme',
1774                         ) ) );
1775                 }
1776
1777                 $themes = wp_prepare_themes_for_js();
1778                 foreach ( $themes as $theme ) {
1779                         if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
1780                                 continue;
1781                         }
1782
1783                         $theme_id = 'theme_' . $theme['id'];
1784                         $theme['isActiveTheme'] = false;
1785                         $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
1786                                 'theme'    => $theme,
1787                                 'section'  => 'themes',
1788                                 'settings' => 'active_theme',
1789                         ) ) );
1790                 }
1791
1792                 /* Site Identity */
1793
1794                 $this->add_section( 'title_tagline', array(
1795                         'title'    => __( 'Site Identity' ),
1796                         'priority' => 20,
1797                 ) );
1798
1799                 $this->add_setting( 'blogname', array(
1800                         'default'    => get_option( 'blogname' ),
1801                         'type'       => 'option',
1802                         'capability' => 'manage_options',
1803                 ) );
1804
1805                 $this->add_control( 'blogname', array(
1806                         'label'      => __( 'Site Title' ),
1807                         'section'    => 'title_tagline',
1808                 ) );
1809
1810                 $this->add_setting( 'blogdescription', array(
1811                         'default'    => get_option( 'blogdescription' ),
1812                         'type'       => 'option',
1813                         'capability' => 'manage_options',
1814                 ) );
1815
1816                 $this->add_control( 'blogdescription', array(
1817                         'label'      => __( 'Tagline' ),
1818                         'section'    => 'title_tagline',
1819                 ) );
1820
1821                 $this->add_setting( 'site_icon', array(
1822                         'type'       => 'option',
1823                         'capability' => 'manage_options',
1824                         'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
1825                 ) );
1826
1827                 $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
1828                         'label'       => __( 'Site Icon' ),
1829                         'description' => __( 'The Site Icon is used as a browser and app icon for your site. Icons must be square, and at least 512px wide and tall.' ),
1830                         'section'     => 'title_tagline',
1831                         'priority'    => 60,
1832                         'height'      => 512,
1833                         'width'       => 512,
1834                 ) ) );
1835
1836                 /* Colors */
1837
1838                 $this->add_section( 'colors', array(
1839                         'title'          => __( 'Colors' ),
1840                         'priority'       => 40,
1841                 ) );
1842
1843                 $this->add_setting( 'header_textcolor', array(
1844                         'theme_supports' => array( 'custom-header', 'header-text' ),
1845                         'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
1846
1847                         'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
1848                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1849                 ) );
1850
1851                 // Input type: checkbox
1852                 // With custom value
1853                 $this->add_control( 'display_header_text', array(
1854                         'settings' => 'header_textcolor',
1855                         'label'    => __( 'Display Header Text' ),
1856                         'section'  => 'title_tagline',
1857                         'type'     => 'checkbox',
1858                         'priority' => 40,
1859                 ) );
1860
1861                 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
1862                         'label'   => __( 'Header Text Color' ),
1863                         'section' => 'colors',
1864                 ) ) );
1865
1866                 // Input type: Color
1867                 // With sanitize_callback
1868                 $this->add_setting( 'background_color', array(
1869                         'default'        => get_theme_support( 'custom-background', 'default-color' ),
1870                         'theme_supports' => 'custom-background',
1871
1872                         'sanitize_callback'    => 'sanitize_hex_color_no_hash',
1873                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1874                 ) );
1875
1876                 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
1877                         'label'   => __( 'Background Color' ),
1878                         'section' => 'colors',
1879                 ) ) );
1880
1881
1882                 /* Custom Header */
1883
1884                 $this->add_section( 'header_image', array(
1885                         'title'          => __( 'Header Image' ),
1886                         'theme_supports' => 'custom-header',
1887                         'priority'       => 60,
1888                 ) );
1889
1890                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1891                         'default'        => get_theme_support( 'custom-header', 'default-image' ),
1892                         'theme_supports' => 'custom-header',
1893                 ) ) );
1894
1895                 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1896                         // 'default'        => get_theme_support( 'custom-header', 'default-image' ),
1897                         'theme_supports' => 'custom-header',
1898                 ) ) );
1899
1900                 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1901
1902                 /* Custom Background */
1903
1904                 $this->add_section( 'background_image', array(
1905                         'title'          => __( 'Background Image' ),
1906                         'theme_supports' => 'custom-background',
1907                         'priority'       => 80,
1908                 ) );
1909
1910                 $this->add_setting( 'background_image', array(
1911                         'default'        => get_theme_support( 'custom-background', 'default-image' ),
1912                         'theme_supports' => 'custom-background',
1913                 ) );
1914
1915                 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1916                         'theme_supports' => 'custom-background',
1917                 ) ) );
1918
1919                 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1920
1921                 $this->add_setting( 'background_repeat', array(
1922                         'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
1923                         'theme_supports' => 'custom-background',
1924                 ) );
1925
1926                 $this->add_control( 'background_repeat', array(
1927                         'label'      => __( 'Background Repeat' ),
1928                         'section'    => 'background_image',
1929                         'type'       => 'radio',
1930                         'choices'    => array(
1931                                 'no-repeat'  => __('No Repeat'),
1932                                 'repeat'     => __('Tile'),
1933                                 'repeat-x'   => __('Tile Horizontally'),
1934                                 'repeat-y'   => __('Tile Vertically'),
1935                         ),
1936                 ) );
1937
1938                 $this->add_setting( 'background_position_x', array(
1939                         'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
1940                         'theme_supports' => 'custom-background',
1941                 ) );
1942
1943                 $this->add_control( 'background_position_x', array(
1944                         'label'      => __( 'Background Position' ),
1945                         'section'    => 'background_image',
1946                         'type'       => 'radio',
1947                         'choices'    => array(
1948                                 'left'       => __('Left'),
1949                                 'center'     => __('Center'),
1950                                 'right'      => __('Right'),
1951                         ),
1952                 ) );
1953
1954                 $this->add_setting( 'background_attachment', array(
1955                         'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
1956                         'theme_supports' => 'custom-background',
1957                 ) );
1958
1959                 $this->add_control( 'background_attachment', array(
1960                         'label'      => __( 'Background Attachment' ),
1961                         'section'    => 'background_image',
1962                         'type'       => 'radio',
1963                         'choices'    => array(
1964                                 'scroll'     => __('Scroll'),
1965                                 'fixed'      => __('Fixed'),
1966                         ),
1967                 ) );
1968
1969                 // If the theme is using the default background callback, we can update
1970                 // the background CSS using postMessage.
1971                 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1972                         foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1973                                 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1974                         }
1975                 }
1976
1977                 /* Static Front Page */
1978                 // #WP19627
1979
1980                 // Replicate behavior from options-reading.php and hide front page options if there are no pages
1981                 if ( get_pages() ) {
1982                         $this->add_section( 'static_front_page', array(
1983                                 'title'          => __( 'Static Front Page' ),
1984                         //      'theme_supports' => 'static-front-page',
1985                                 'priority'       => 120,
1986                                 'description'    => __( 'Your theme supports a static front page.' ),
1987                         ) );
1988
1989                         $this->add_setting( 'show_on_front', array(
1990                                 'default'        => get_option( 'show_on_front' ),
1991                                 'capability'     => 'manage_options',
1992                                 'type'           => 'option',
1993                         //      'theme_supports' => 'static-front-page',
1994                         ) );
1995
1996                         $this->add_control( 'show_on_front', array(
1997                                 'label'   => __( 'Front page displays' ),
1998                                 'section' => 'static_front_page',
1999                                 'type'    => 'radio',
2000                                 'choices' => array(
2001                                         'posts' => __( 'Your latest posts' ),
2002                                         'page'  => __( 'A static page' ),
2003                                 ),
2004                         ) );
2005
2006                         $this->add_setting( 'page_on_front', array(
2007                                 'type'       => 'option',
2008                                 'capability' => 'manage_options',
2009                         //      'theme_supports' => 'static-front-page',
2010                         ) );
2011
2012                         $this->add_control( 'page_on_front', array(
2013                                 'label'      => __( 'Front page' ),
2014                                 'section'    => 'static_front_page',
2015                                 'type'       => 'dropdown-pages',
2016                         ) );
2017
2018                         $this->add_setting( 'page_for_posts', array(
2019                                 'type'           => 'option',
2020                                 'capability'     => 'manage_options',
2021                         //      'theme_supports' => 'static-front-page',
2022                         ) );
2023
2024                         $this->add_control( 'page_for_posts', array(
2025                                 'label'      => __( 'Posts page' ),
2026                                 'section'    => 'static_front_page',
2027                                 'type'       => 'dropdown-pages',
2028                         ) );
2029                 }
2030         }
2031
2032         /**
2033          * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
2034          *
2035          * @since 4.2.0
2036          * @access public
2037          *
2038          * @see add_dynamic_settings()
2039          */
2040         public function register_dynamic_settings() {
2041                 $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
2042         }
2043
2044         /**
2045          * Callback for validating the header_textcolor value.
2046          *
2047          * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
2048          * Returns default text color if hex color is empty.
2049          *
2050          * @since 3.4.0
2051          *
2052          * @param string $color
2053          * @return mixed
2054          */
2055         public function _sanitize_header_textcolor( $color ) {
2056                 if ( 'blank' === $color )
2057                         return 'blank';
2058
2059                 $color = sanitize_hex_color_no_hash( $color );
2060                 if ( empty( $color ) )
2061                         $color = get_theme_support( 'custom-header', 'default-text-color' );
2062
2063                 return $color;
2064         }
2065 }
2066
2067 /**
2068  * Sanitizes a hex color.
2069  *
2070  * Returns either '', a 3 or 6 digit hex color (with #), or nothing.
2071  * For sanitizing values without a #, see sanitize_hex_color_no_hash().
2072  *
2073  * @since 3.4.0
2074  *
2075  * @param string $color
2076  * @return string|void
2077  */
2078 function sanitize_hex_color( $color ) {
2079         if ( '' === $color )
2080                 return '';
2081
2082         // 3 or 6 hex digits, or the empty string.
2083         if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
2084                 return $color;
2085 }
2086
2087 /**
2088  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
2089  *
2090  * Saving hex colors without a hash puts the burden of adding the hash on the
2091  * UI, which makes it difficult to use or upgrade to other color types such as
2092  * rgba, hsl, rgb, and html color names.
2093  *
2094  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
2095  *
2096  * @since 3.4.0
2097  *
2098  * @param string $color
2099  * @return string|null
2100  */
2101 function sanitize_hex_color_no_hash( $color ) {
2102         $color = ltrim( $color, '#' );
2103
2104         if ( '' === $color )
2105                 return '';
2106
2107         return sanitize_hex_color( '#' . $color ) ? $color : null;
2108 }
2109
2110 /**
2111  * Ensures that any hex color is properly hashed.
2112  * Otherwise, returns value untouched.
2113  *
2114  * This method should only be necessary if using sanitize_hex_color_no_hash().
2115  *
2116  * @since 3.4.0
2117  *
2118  * @param string $color
2119  * @return string
2120  */
2121 function maybe_hash_hex_color( $color ) {
2122         if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
2123                 return '#' . $unhashed;
2124
2125         return $color;
2126 }