]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/class-wp-customize-manager.php
WordPress 4.4.2
[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                 $excluded_referer_basenames = array( 'customize.php', 'wp-login.php' );
1539
1540                 if ( $this->return_url ) {
1541                         $return_url = $this->return_url;
1542                 } else if ( $referer && ! in_array( basename( parse_url( $referer, PHP_URL_PATH ) ), $excluded_referer_basenames, true ) ) {
1543                         $return_url = $referer;
1544                 } else if ( $this->preview_url ) {
1545                         $return_url = $this->preview_url;
1546                 } else {
1547                         $return_url = home_url( '/' );
1548                 }
1549                 return $return_url;
1550         }
1551
1552         /**
1553          * Set the autofocused constructs.
1554          *
1555          * @since 4.4.0
1556          * @access public
1557          *
1558          * @param array $autofocus {
1559          *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1560          *
1561          *     @type string [$control]  ID for control to be autofocused.
1562          *     @type string [$section]  ID for section to be autofocused.
1563          *     @type string [$panel]    ID for panel to be autofocused.
1564          * }
1565          */
1566         public function set_autofocus( $autofocus ) {
1567                 $this->autofocus = array_filter( wp_array_slice_assoc( $autofocus, array( 'panel', 'section', 'control' ) ), 'is_string' );
1568         }
1569
1570         /**
1571          * Get the autofocused constructs.
1572          *
1573          * @since 4.4.0
1574          * @access public
1575          *
1576          * @return array {
1577          *     Mapping of 'panel', 'section', 'control' to the ID which should be autofocused.
1578          *
1579          *     @type string [$control]  ID for control to be autofocused.
1580          *     @type string [$section]  ID for section to be autofocused.
1581          *     @type string [$panel]    ID for panel to be autofocused.
1582          * }
1583          */
1584         public function get_autofocus() {
1585                 return $this->autofocus;
1586         }
1587
1588         /**
1589          * Print JavaScript settings for parent window.
1590          *
1591          * @since 4.4.0
1592          */
1593         public function customize_pane_settings() {
1594                 /*
1595                  * If the frontend and the admin are served from the same domain, load the
1596                  * preview over ssl if the Customizer is being loaded over ssl. This avoids
1597                  * insecure content warnings. This is not attempted if the admin and frontend
1598                  * are on different domains to avoid the case where the frontend doesn't have
1599                  * ssl certs. Domain mapping plugins can allow other urls in these conditions
1600                  * using the customize_allowed_urls filter.
1601                  */
1602
1603                 $allowed_urls = array( home_url( '/' ) );
1604                 $admin_origin = parse_url( admin_url() );
1605                 $home_origin  = parse_url( home_url() );
1606                 $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
1607
1608                 if ( is_ssl() && ! $cross_domain ) {
1609                         $allowed_urls[] = home_url( '/', 'https' );
1610                 }
1611
1612                 /**
1613                  * Filter the list of URLs allowed to be clicked and followed in the Customizer preview.
1614                  *
1615                  * @since 3.4.0
1616                  *
1617                  * @param array $allowed_urls An array of allowed URLs.
1618                  */
1619                 $allowed_urls = array_unique( apply_filters( 'customize_allowed_urls', $allowed_urls ) );
1620
1621                 $login_url = add_query_arg( array(
1622                         'interim-login' => 1,
1623                         'customize-login' => 1,
1624                 ), wp_login_url() );
1625
1626                 // Prepare Customizer settings to pass to JavaScript.
1627                 $settings = array(
1628                         'theme'    => array(
1629                                 'stylesheet' => $this->get_stylesheet(),
1630                                 'active'     => $this->is_theme_active(),
1631                         ),
1632                         'url'      => array(
1633                                 'preview'       => esc_url_raw( $this->get_preview_url() ),
1634                                 'parent'        => esc_url_raw( admin_url() ),
1635                                 'activated'     => esc_url_raw( home_url( '/' ) ),
1636                                 'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
1637                                 'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
1638                                 'isCrossDomain' => $cross_domain,
1639                                 'home'          => esc_url_raw( home_url( '/' ) ),
1640                                 'login'         => esc_url_raw( $login_url ),
1641                         ),
1642                         'browser'  => array(
1643                                 'mobile' => wp_is_mobile(),
1644                                 'ios'    => $this->is_ios(),
1645                         ),
1646                         'panels'   => array(),
1647                         'sections' => array(),
1648                         'nonce'    => array(
1649                                 'save'    => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
1650                                 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ),
1651                         ),
1652                         'autofocus' => array(),
1653                         'documentTitleTmpl' => $this->get_document_title_template(),
1654                 );
1655
1656                 // Prepare Customize Section objects to pass to JavaScript.
1657                 foreach ( $this->sections() as $id => $section ) {
1658                         if ( $section->check_capabilities() ) {
1659                                 $settings['sections'][ $id ] = $section->json();
1660                         }
1661                 }
1662
1663                 // Prepare Customize Panel objects to pass to JavaScript.
1664                 foreach ( $this->panels() as $panel_id => $panel ) {
1665                         if ( $panel->check_capabilities() ) {
1666                                 $settings['panels'][ $panel_id ] = $panel->json();
1667                                 foreach ( $panel->sections as $section_id => $section ) {
1668                                         if ( $section->check_capabilities() ) {
1669                                                 $settings['sections'][ $section_id ] = $section->json();
1670                                         }
1671                                 }
1672                         }
1673                 }
1674
1675                 // Pass to frontend the Customizer construct being deeplinked.
1676                 foreach ( $this->get_autofocus() as $type => $id ) {
1677                         $can_autofocus = (
1678                                 ( 'control' === $type && $this->get_control( $id ) && $this->get_control( $id )->check_capabilities() )
1679                                 ||
1680                                 ( 'section' === $type && isset( $settings['sections'][ $id ] ) )
1681                                 ||
1682                                 ( 'panel' === $type && isset( $settings['panels'][ $id ] ) )
1683                         );
1684                         if ( $can_autofocus ) {
1685                                 $settings['autofocus'][ $type ] = $id;
1686                         }
1687                 }
1688
1689                 ?>
1690                 <script type="text/javascript">
1691                         var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
1692                         _wpCustomizeSettings.controls = {};
1693                         _wpCustomizeSettings.settings = {};
1694                         <?php
1695
1696                         // Serialize settings one by one to improve memory usage.
1697                         echo "(function ( s ){\n";
1698                         foreach ( $this->settings() as $setting ) {
1699                                 if ( $setting->check_capabilities() ) {
1700                                         printf(
1701                                                 "s[%s] = %s;\n",
1702                                                 wp_json_encode( $setting->id ),
1703                                                 wp_json_encode( array(
1704                                                         'value'     => $setting->js_value(),
1705                                                         'transport' => $setting->transport,
1706                                                         'dirty'     => $setting->dirty,
1707                                                 ) )
1708                                         );
1709                                 }
1710                         }
1711                         echo "})( _wpCustomizeSettings.settings );\n";
1712
1713                         // Serialize controls one by one to improve memory usage.
1714                         echo "(function ( c ){\n";
1715                         foreach ( $this->controls() as $control ) {
1716                                 if ( $control->check_capabilities() ) {
1717                                         printf(
1718                                                 "c[%s] = %s;\n",
1719                                                 wp_json_encode( $control->id ),
1720                                                 wp_json_encode( $control->json() )
1721                                         );
1722                                 }
1723                         }
1724                         echo "})( _wpCustomizeSettings.controls );\n";
1725                 ?>
1726                 </script>
1727                 <?php
1728         }
1729
1730         /**
1731          * Register some default controls.
1732          *
1733          * @since 3.4.0
1734          */
1735         public function register_controls() {
1736
1737                 /* Panel, Section, and Control Types */
1738                 $this->register_panel_type( 'WP_Customize_Panel' );
1739                 $this->register_section_type( 'WP_Customize_Section' );
1740                 $this->register_section_type( 'WP_Customize_Sidebar_Section' );
1741                 $this->register_control_type( 'WP_Customize_Color_Control' );
1742                 $this->register_control_type( 'WP_Customize_Media_Control' );
1743                 $this->register_control_type( 'WP_Customize_Upload_Control' );
1744                 $this->register_control_type( 'WP_Customize_Image_Control' );
1745                 $this->register_control_type( 'WP_Customize_Background_Image_Control' );
1746                 $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
1747                 $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
1748                 $this->register_control_type( 'WP_Customize_Theme_Control' );
1749
1750                 /* Themes */
1751
1752                 $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
1753                         'title'      => $this->theme()->display( 'Name' ),
1754                         'capability' => 'switch_themes',
1755                         'priority'   => 0,
1756                 ) ) );
1757
1758                 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
1759                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
1760                         'capability' => 'switch_themes',
1761                 ) ) );
1762
1763                 require_once( ABSPATH . 'wp-admin/includes/theme.php' );
1764
1765                 // Theme Controls.
1766
1767                 // Add a control for the active/original theme.
1768                 if ( ! $this->is_theme_active() ) {
1769                         $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) );
1770                         $active_theme = current( $themes );
1771                         $active_theme['isActiveTheme'] = true;
1772                         $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array(
1773                                 'theme'    => $active_theme,
1774                                 'section'  => 'themes',
1775                                 'settings' => 'active_theme',
1776                         ) ) );
1777                 }
1778
1779                 $themes = wp_prepare_themes_for_js();
1780                 foreach ( $themes as $theme ) {
1781                         if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) {
1782                                 continue;
1783                         }
1784
1785                         $theme_id = 'theme_' . $theme['id'];
1786                         $theme['isActiveTheme'] = false;
1787                         $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
1788                                 'theme'    => $theme,
1789                                 'section'  => 'themes',
1790                                 'settings' => 'active_theme',
1791                         ) ) );
1792                 }
1793
1794                 /* Site Identity */
1795
1796                 $this->add_section( 'title_tagline', array(
1797                         'title'    => __( 'Site Identity' ),
1798                         'priority' => 20,
1799                 ) );
1800
1801                 $this->add_setting( 'blogname', array(
1802                         'default'    => get_option( 'blogname' ),
1803                         'type'       => 'option',
1804                         'capability' => 'manage_options',
1805                 ) );
1806
1807                 $this->add_control( 'blogname', array(
1808                         'label'      => __( 'Site Title' ),
1809                         'section'    => 'title_tagline',
1810                 ) );
1811
1812                 $this->add_setting( 'blogdescription', array(
1813                         'default'    => get_option( 'blogdescription' ),
1814                         'type'       => 'option',
1815                         'capability' => 'manage_options',
1816                 ) );
1817
1818                 $this->add_control( 'blogdescription', array(
1819                         'label'      => __( 'Tagline' ),
1820                         'section'    => 'title_tagline',
1821                 ) );
1822
1823                 $this->add_setting( 'site_icon', array(
1824                         'type'       => 'option',
1825                         'capability' => 'manage_options',
1826                         'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
1827                 ) );
1828
1829                 $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
1830                         'label'       => __( 'Site Icon' ),
1831                         '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.' ),
1832                         'section'     => 'title_tagline',
1833                         'priority'    => 60,
1834                         'height'      => 512,
1835                         'width'       => 512,
1836                 ) ) );
1837
1838                 /* Colors */
1839
1840                 $this->add_section( 'colors', array(
1841                         'title'          => __( 'Colors' ),
1842                         'priority'       => 40,
1843                 ) );
1844
1845                 $this->add_setting( 'header_textcolor', array(
1846                         'theme_supports' => array( 'custom-header', 'header-text' ),
1847                         'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
1848
1849                         'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
1850                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1851                 ) );
1852
1853                 // Input type: checkbox
1854                 // With custom value
1855                 $this->add_control( 'display_header_text', array(
1856                         'settings' => 'header_textcolor',
1857                         'label'    => __( 'Display Header Text' ),
1858                         'section'  => 'title_tagline',
1859                         'type'     => 'checkbox',
1860                         'priority' => 40,
1861                 ) );
1862
1863                 $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
1864                         'label'   => __( 'Header Text Color' ),
1865                         'section' => 'colors',
1866                 ) ) );
1867
1868                 // Input type: Color
1869                 // With sanitize_callback
1870                 $this->add_setting( 'background_color', array(
1871                         'default'        => get_theme_support( 'custom-background', 'default-color' ),
1872                         'theme_supports' => 'custom-background',
1873
1874                         'sanitize_callback'    => 'sanitize_hex_color_no_hash',
1875                         'sanitize_js_callback' => 'maybe_hash_hex_color',
1876                 ) );
1877
1878                 $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
1879                         'label'   => __( 'Background Color' ),
1880                         'section' => 'colors',
1881                 ) ) );
1882
1883
1884                 /* Custom Header */
1885
1886                 $this->add_section( 'header_image', array(
1887                         'title'          => __( 'Header Image' ),
1888                         'theme_supports' => 'custom-header',
1889                         'priority'       => 60,
1890                 ) );
1891
1892                 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
1893                         'default'        => get_theme_support( 'custom-header', 'default-image' ),
1894                         'theme_supports' => 'custom-header',
1895                 ) ) );
1896
1897                 $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
1898                         // 'default'        => get_theme_support( 'custom-header', 'default-image' ),
1899                         'theme_supports' => 'custom-header',
1900                 ) ) );
1901
1902                 $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
1903
1904                 /* Custom Background */
1905
1906                 $this->add_section( 'background_image', array(
1907                         'title'          => __( 'Background Image' ),
1908                         'theme_supports' => 'custom-background',
1909                         'priority'       => 80,
1910                 ) );
1911
1912                 $this->add_setting( 'background_image', array(
1913                         'default'        => get_theme_support( 'custom-background', 'default-image' ),
1914                         'theme_supports' => 'custom-background',
1915                 ) );
1916
1917                 $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
1918                         'theme_supports' => 'custom-background',
1919                 ) ) );
1920
1921                 $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
1922
1923                 $this->add_setting( 'background_repeat', array(
1924                         'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
1925                         'theme_supports' => 'custom-background',
1926                 ) );
1927
1928                 $this->add_control( 'background_repeat', array(
1929                         'label'      => __( 'Background Repeat' ),
1930                         'section'    => 'background_image',
1931                         'type'       => 'radio',
1932                         'choices'    => array(
1933                                 'no-repeat'  => __('No Repeat'),
1934                                 'repeat'     => __('Tile'),
1935                                 'repeat-x'   => __('Tile Horizontally'),
1936                                 'repeat-y'   => __('Tile Vertically'),
1937                         ),
1938                 ) );
1939
1940                 $this->add_setting( 'background_position_x', array(
1941                         'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
1942                         'theme_supports' => 'custom-background',
1943                 ) );
1944
1945                 $this->add_control( 'background_position_x', array(
1946                         'label'      => __( 'Background Position' ),
1947                         'section'    => 'background_image',
1948                         'type'       => 'radio',
1949                         'choices'    => array(
1950                                 'left'       => __('Left'),
1951                                 'center'     => __('Center'),
1952                                 'right'      => __('Right'),
1953                         ),
1954                 ) );
1955
1956                 $this->add_setting( 'background_attachment', array(
1957                         'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
1958                         'theme_supports' => 'custom-background',
1959                 ) );
1960
1961                 $this->add_control( 'background_attachment', array(
1962                         'label'      => __( 'Background Attachment' ),
1963                         'section'    => 'background_image',
1964                         'type'       => 'radio',
1965                         'choices'    => array(
1966                                 'scroll'     => __('Scroll'),
1967                                 'fixed'      => __('Fixed'),
1968                         ),
1969                 ) );
1970
1971                 // If the theme is using the default background callback, we can update
1972                 // the background CSS using postMessage.
1973                 if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
1974                         foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
1975                                 $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
1976                         }
1977                 }
1978
1979                 /* Static Front Page */
1980                 // #WP19627
1981
1982                 // Replicate behavior from options-reading.php and hide front page options if there are no pages
1983                 if ( get_pages() ) {
1984                         $this->add_section( 'static_front_page', array(
1985                                 'title'          => __( 'Static Front Page' ),
1986                         //      'theme_supports' => 'static-front-page',
1987                                 'priority'       => 120,
1988                                 'description'    => __( 'Your theme supports a static front page.' ),
1989                         ) );
1990
1991                         $this->add_setting( 'show_on_front', array(
1992                                 'default'        => get_option( 'show_on_front' ),
1993                                 'capability'     => 'manage_options',
1994                                 'type'           => 'option',
1995                         //      'theme_supports' => 'static-front-page',
1996                         ) );
1997
1998                         $this->add_control( 'show_on_front', array(
1999                                 'label'   => __( 'Front page displays' ),
2000                                 'section' => 'static_front_page',
2001                                 'type'    => 'radio',
2002                                 'choices' => array(
2003                                         'posts' => __( 'Your latest posts' ),
2004                                         'page'  => __( 'A static page' ),
2005                                 ),
2006                         ) );
2007
2008                         $this->add_setting( 'page_on_front', array(
2009                                 'type'       => 'option',
2010                                 'capability' => 'manage_options',
2011                         //      'theme_supports' => 'static-front-page',
2012                         ) );
2013
2014                         $this->add_control( 'page_on_front', array(
2015                                 'label'      => __( 'Front page' ),
2016                                 'section'    => 'static_front_page',
2017                                 'type'       => 'dropdown-pages',
2018                         ) );
2019
2020                         $this->add_setting( 'page_for_posts', array(
2021                                 'type'           => 'option',
2022                                 'capability'     => 'manage_options',
2023                         //      'theme_supports' => 'static-front-page',
2024                         ) );
2025
2026                         $this->add_control( 'page_for_posts', array(
2027                                 'label'      => __( 'Posts page' ),
2028                                 'section'    => 'static_front_page',
2029                                 'type'       => 'dropdown-pages',
2030                         ) );
2031                 }
2032         }
2033
2034         /**
2035          * Add settings from the POST data that were not added with code, e.g. dynamically-created settings for Widgets
2036          *
2037          * @since 4.2.0
2038          * @access public
2039          *
2040          * @see add_dynamic_settings()
2041          */
2042         public function register_dynamic_settings() {
2043                 $this->add_dynamic_settings( array_keys( $this->unsanitized_post_values() ) );
2044         }
2045
2046         /**
2047          * Callback for validating the header_textcolor value.
2048          *
2049          * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
2050          * Returns default text color if hex color is empty.
2051          *
2052          * @since 3.4.0
2053          *
2054          * @param string $color
2055          * @return mixed
2056          */
2057         public function _sanitize_header_textcolor( $color ) {
2058                 if ( 'blank' === $color )
2059                         return 'blank';
2060
2061                 $color = sanitize_hex_color_no_hash( $color );
2062                 if ( empty( $color ) )
2063                         $color = get_theme_support( 'custom-header', 'default-text-color' );
2064
2065                 return $color;
2066         }
2067 }
2068
2069 /**
2070  * Sanitizes a hex color.
2071  *
2072  * Returns either '', a 3 or 6 digit hex color (with #), or nothing.
2073  * For sanitizing values without a #, see sanitize_hex_color_no_hash().
2074  *
2075  * @since 3.4.0
2076  *
2077  * @param string $color
2078  * @return string|void
2079  */
2080 function sanitize_hex_color( $color ) {
2081         if ( '' === $color )
2082                 return '';
2083
2084         // 3 or 6 hex digits, or the empty string.
2085         if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
2086                 return $color;
2087 }
2088
2089 /**
2090  * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
2091  *
2092  * Saving hex colors without a hash puts the burden of adding the hash on the
2093  * UI, which makes it difficult to use or upgrade to other color types such as
2094  * rgba, hsl, rgb, and html color names.
2095  *
2096  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
2097  *
2098  * @since 3.4.0
2099  *
2100  * @param string $color
2101  * @return string|null
2102  */
2103 function sanitize_hex_color_no_hash( $color ) {
2104         $color = ltrim( $color, '#' );
2105
2106         if ( '' === $color )
2107                 return '';
2108
2109         return sanitize_hex_color( '#' . $color ) ? $color : null;
2110 }
2111
2112 /**
2113  * Ensures that any hex color is properly hashed.
2114  * Otherwise, returns value untouched.
2115  *
2116  * This method should only be necessary if using sanitize_hex_color_no_hash().
2117  *
2118  * @since 3.4.0
2119  *
2120  * @param string $color
2121  * @return string
2122  */
2123 function maybe_hash_hex_color( $color ) {
2124         if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
2125                 return '#' . $unhashed;
2126
2127         return $color;
2128 }