~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/class-wp-customize-manager.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Customize Manager.
 
4
 *
 
5
 * Bootstraps the Customize experience on the server-side.
 
6
 *
 
7
 * Sets up the theme-switching process if a theme other than the active one is
 
8
 * being previewed and customized.
 
9
 *
 
10
 * Serves as a factory for Customize Controls and Settings, and
 
11
 * instantiates default Customize Controls and Settings.
 
12
 *
 
13
 * @package WordPress
 
14
 * @subpackage Customize
 
15
 * @since 3.4.0
 
16
 */
 
17
final class WP_Customize_Manager {
 
18
        /**
 
19
         * An instance of the theme being previewed.
 
20
         *
 
21
         * @var WP_Theme
 
22
         */
 
23
        protected $theme;
 
24
 
 
25
        /**
 
26
         * The directory name of the previously active theme (within the theme_root).
 
27
         *
 
28
         * @var string
 
29
         */
 
30
        protected $original_stylesheet;
 
31
 
 
32
        /**
 
33
         * Whether this is a Customizer pageload.
 
34
         *
 
35
         * @var boolean
 
36
         */
 
37
        protected $previewing = false;
 
38
 
 
39
        /**
 
40
         * Methods and properties deailing with managing widgets in the customizer.
 
41
         *
 
42
         * @var WP_Customize_Widgets
 
43
         */
 
44
        public $widgets;
 
45
 
 
46
        protected $settings   = array();
 
47
        protected $containers = array();
 
48
        protected $panels     = array();
 
49
        protected $sections   = array();
 
50
        protected $controls   = array();
 
51
 
 
52
        protected $nonce_tick;
 
53
 
 
54
        protected $customized;
 
55
 
 
56
        /**
 
57
         * $_POST values for Customize Settings.
 
58
         *
 
59
         * @var array
 
60
         */
 
61
        private $_post_values;
 
62
 
 
63
        /**
 
64
         * Constructor.
 
65
         *
 
66
         * @since 3.4.0
 
67
         */
 
68
        public function __construct() {
 
69
                require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
 
70
                require( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
 
71
                require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
 
72
                require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
 
73
                require( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
 
74
 
 
75
                $this->widgets = new WP_Customize_Widgets( $this );
 
76
 
 
77
                add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
 
78
 
 
79
                add_action( 'setup_theme',  array( $this, 'setup_theme' ) );
 
80
                add_action( 'wp_loaded',    array( $this, 'wp_loaded' ) );
 
81
 
 
82
                // Run wp_redirect_status late to make sure we override the status last.
 
83
                add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
 
84
 
 
85
                // Do not spawn cron (especially the alternate cron) while running the customizer.
 
86
                remove_action( 'init', 'wp_cron' );
 
87
 
 
88
                // Do not run update checks when rendering the controls.
 
89
                remove_action( 'admin_init', '_maybe_update_core' );
 
90
                remove_action( 'admin_init', '_maybe_update_plugins' );
 
91
                remove_action( 'admin_init', '_maybe_update_themes' );
 
92
 
 
93
                add_action( 'wp_ajax_customize_save', array( $this, 'save' ) );
 
94
 
 
95
                add_action( 'customize_register',                 array( $this, 'register_controls' ) );
 
96
                add_action( 'customize_controls_init',            array( $this, 'prepare_controls' ) );
 
97
                add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
 
98
        }
 
99
 
 
100
        /**
 
101
         * Return true if it's an AJAX request.
 
102
         *
 
103
         * @since 3.4.0
 
104
         *
 
105
         * @return bool
 
106
         */
 
107
        public function doing_ajax() {
 
108
                return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
 
109
        }
 
110
 
 
111
        /**
 
112
         * Custom wp_die wrapper. Returns either the standard message for UI
 
113
         * or the AJAX message.
 
114
         *
 
115
         * @since 3.4.0
 
116
         *
 
117
         * @param mixed $ajax_message AJAX return
 
118
         * @param mixed $message UI message
 
119
         */
 
120
        protected function wp_die( $ajax_message, $message = null ) {
 
121
                if ( $this->doing_ajax() )
 
122
                        wp_die( $ajax_message );
 
123
 
 
124
                if ( ! $message )
 
125
                        $message = __( 'Cheatin&#8217; uh?' );
 
126
 
 
127
                wp_die( $message );
 
128
        }
 
129
 
 
130
        /**
 
131
         * Return the AJAX wp_die() handler if it's a customized request.
 
132
         *
 
133
         * @since 3.4.0
 
134
         *
 
135
         * @return string
 
136
         */
 
137
        public function wp_die_handler() {
 
138
                if ( $this->doing_ajax() )
 
139
                        return '_ajax_wp_die_handler';
 
140
 
 
141
                return '_default_wp_die_handler';
 
142
        }
 
143
 
 
144
        /**
 
145
         * Start preview and customize theme.
 
146
         *
 
147
         * Check if customize query variable exist. Init filters to filter the current theme.
 
148
         *
 
149
         * @since 3.4.0
 
150
         */
 
151
        public function setup_theme() {
 
152
                send_origin_headers();
 
153
 
 
154
                if ( is_admin() && ! $this->doing_ajax() )
 
155
                    auth_redirect();
 
156
                elseif ( $this->doing_ajax() && ! is_user_logged_in() )
 
157
                    $this->wp_die( 0 );
 
158
 
 
159
                show_admin_bar( false );
 
160
 
 
161
                if ( ! current_user_can( 'customize' ) ) {
 
162
                        $this->wp_die( -1 );
 
163
                }
 
164
 
 
165
                $this->original_stylesheet = get_stylesheet();
 
166
 
 
167
                $this->theme = wp_get_theme( isset( $_REQUEST['theme'] ) ? $_REQUEST['theme'] : null );
 
168
 
 
169
                if ( $this->is_theme_active() ) {
 
170
                        // Once the theme is loaded, we'll validate it.
 
171
                        add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
 
172
                } else {
 
173
                        // If the requested theme is not the active theme and the user doesn't have the
 
174
                        // switch_themes cap, bail.
 
175
                        if ( ! current_user_can( 'switch_themes' ) )
 
176
                                $this->wp_die( -1 );
 
177
 
 
178
                        // If the theme has errors while loading, bail.
 
179
                        if ( $this->theme()->errors() )
 
180
                                $this->wp_die( -1 );
 
181
 
 
182
                        // If the theme isn't allowed per multisite settings, bail.
 
183
                        if ( ! $this->theme()->is_allowed() )
 
184
                                $this->wp_die( -1 );
 
185
                }
 
186
 
 
187
                $this->start_previewing_theme();
 
188
        }
 
189
 
 
190
        /**
 
191
         * Callback to validate a theme once it is loaded
 
192
         *
 
193
         * @since 3.4.0
 
194
         */
 
195
        public function after_setup_theme() {
 
196
                if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
 
197
                        wp_redirect( 'themes.php?broken=true' );
 
198
                        exit;
 
199
                }
 
200
        }
 
201
 
 
202
        /**
 
203
         * If the theme to be previewed isn't the active theme, add filter callbacks
 
204
         * to swap it out at runtime.
 
205
         *
 
206
         * @since 3.4.0
 
207
         */
 
208
        public function start_previewing_theme() {
 
209
                // Bail if we're already previewing.
 
210
                if ( $this->is_preview() )
 
211
                        return;
 
212
 
 
213
                $this->previewing = true;
 
214
 
 
215
                if ( ! $this->is_theme_active() ) {
 
216
                        add_filter( 'template', array( $this, 'get_template' ) );
 
217
                        add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
 
218
                        add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 
219
 
 
220
                        // @link: http://core.trac.wordpress.org/ticket/20027
 
221
                        add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
 
222
                        add_filter( 'pre_option_template', array( $this, 'get_template' ) );
 
223
 
 
224
                        // Handle custom theme roots.
 
225
                        add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
 
226
                        add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
 
227
                }
 
228
 
 
229
                /**
 
230
                 * Fires once the Customizer theme preview has started.
 
231
                 *
 
232
                 * @since 3.4.0
 
233
                 *
 
234
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
235
                 */
 
236
                do_action( 'start_previewing_theme', $this );
 
237
        }
 
238
 
 
239
        /**
 
240
         * Stop previewing the selected theme.
 
241
         *
 
242
         * Removes filters to change the current theme.
 
243
         *
 
244
         * @since 3.4.0
 
245
         */
 
246
        public function stop_previewing_theme() {
 
247
                if ( ! $this->is_preview() )
 
248
                        return;
 
249
 
 
250
                $this->previewing = false;
 
251
 
 
252
                if ( ! $this->is_theme_active() ) {
 
253
                        remove_filter( 'template', array( $this, 'get_template' ) );
 
254
                        remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
 
255
                        remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 
256
 
 
257
                        // @link: http://core.trac.wordpress.org/ticket/20027
 
258
                        remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
 
259
                        remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
 
260
 
 
261
                        // Handle custom theme roots.
 
262
                        remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
 
263
                        remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
 
264
                }
 
265
 
 
266
                /**
 
267
                 * Fires once the Customizer theme preview has stopped.
 
268
                 *
 
269
                 * @since 3.4.0
 
270
                 *
 
271
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
272
                 */
 
273
                do_action( 'stop_previewing_theme', $this );
 
274
        }
 
275
 
 
276
        /**
 
277
         * Get the theme being customized.
 
278
         *
 
279
         * @since 3.4.0
 
280
         *
 
281
         * @return WP_Theme
 
282
         */
 
283
        public function theme() {
 
284
                return $this->theme;
 
285
        }
 
286
 
 
287
        /**
 
288
         * Get the registered settings.
 
289
         *
 
290
         * @since 3.4.0
 
291
         *
 
292
         * @return array
 
293
         */
 
294
        public function settings() {
 
295
                return $this->settings;
 
296
        }
 
297
 
 
298
        /**
 
299
         * Get the registered controls.
 
300
         *
 
301
         * @since 3.4.0
 
302
         *
 
303
         * @return array
 
304
         */
 
305
        public function controls() {
 
306
                return $this->controls;
 
307
        }
 
308
 
 
309
        /**
 
310
         * Get the registered containers.
 
311
         *
 
312
         * @since 4.0.0
 
313
         *
 
314
         * @return array
 
315
         */
 
316
        public function containers() {
 
317
                return $this->containers;
 
318
        }
 
319
 
 
320
        /**
 
321
         * Get the registered sections.
 
322
         *
 
323
         * @since 3.4.0
 
324
         *
 
325
         * @return array
 
326
         */
 
327
        public function sections() {
 
328
                return $this->sections;
 
329
        }
 
330
 
 
331
        /**
 
332
         * Get the registered panels.
 
333
         *
 
334
         * @since 4.0.0
 
335
         * @access public
 
336
         *
 
337
         * @return array Panels.
 
338
         */
 
339
        public function panels() {
 
340
                return $this->panels;
 
341
        }
 
342
 
 
343
        /**
 
344
         * Checks if the current theme is active.
 
345
         *
 
346
         * @since 3.4.0
 
347
         *
 
348
         * @return bool
 
349
         */
 
350
        public function is_theme_active() {
 
351
                return $this->get_stylesheet() == $this->original_stylesheet;
 
352
        }
 
353
 
 
354
        /**
 
355
         * Register styles/scripts and initialize the preview of each setting
 
356
         *
 
357
         * @since 3.4.0
 
358
         */
 
359
        public function wp_loaded() {
 
360
 
 
361
                /**
 
362
                 * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
 
363
                 *
 
364
                 * @since 3.4.0
 
365
                 *
 
366
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
367
                 */
 
368
                do_action( 'customize_register', $this );
 
369
 
 
370
                if ( $this->is_preview() && ! is_admin() )
 
371
                        $this->customize_preview_init();
 
372
        }
 
373
 
 
374
        /**
 
375
         * Prevents AJAX requests from following redirects when previewing a theme
 
376
         * by issuing a 200 response instead of a 30x.
 
377
         *
 
378
         * Instead, the JS will sniff out the location header.
 
379
         *
 
380
         * @since 3.4.0
 
381
         *
 
382
         * @param $status
 
383
         * @return int
 
384
         */
 
385
        public function wp_redirect_status( $status ) {
 
386
                if ( $this->is_preview() && ! is_admin() )
 
387
                        return 200;
 
388
 
 
389
                return $status;
 
390
        }
 
391
 
 
392
        /**
 
393
         * Decode the $_POST['customized'] values for a specific Customize Setting.
 
394
         *
 
395
         * @since 3.4.0
 
396
         *
 
397
         * @param mixed $setting A WP_Customize_Setting derived object
 
398
         * @return string $post_value Sanitized value
 
399
         */
 
400
        public function post_value( $setting ) {
 
401
                if ( ! isset( $this->_post_values ) ) {
 
402
                        if ( isset( $_POST['customized'] ) )
 
403
                                $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
 
404
                        else
 
405
                                $this->_post_values = false;
 
406
                }
 
407
 
 
408
                if ( isset( $this->_post_values[ $setting->id ] ) )
 
409
                        return $setting->sanitize( $this->_post_values[ $setting->id ] );
 
410
        }
 
411
 
 
412
        /**
 
413
         * Print javascript settings.
 
414
         *
 
415
         * @since 3.4.0
 
416
         */
 
417
        public function customize_preview_init() {
 
418
                $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
 
419
 
 
420
                $this->prepare_controls();
 
421
 
 
422
                wp_enqueue_script( 'customize-preview' );
 
423
                add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
 
424
                add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
 
425
                add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
 
426
                add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
 
427
                add_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
 
428
                add_filter( 'wp_die_handler', array( $this, 'remove_preview_signature' ) );
 
429
 
 
430
                foreach ( $this->settings as $setting ) {
 
431
                        $setting->preview();
 
432
                }
 
433
 
 
434
                /**
 
435
                 * Fires once the Customizer preview has initialized and JavaScript
 
436
                 * settings have been printed.
 
437
                 *
 
438
                 * @since 3.4.0
 
439
                 *
 
440
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
441
                 */
 
442
                do_action( 'customize_preview_init', $this );
 
443
        }
 
444
 
 
445
        /**
 
446
         * Prevent sending a 404 status when returning the response for the customize
 
447
         * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
 
448
         *
 
449
         * @since 4.0.0
 
450
         * @access public
 
451
         */
 
452
        public function customize_preview_override_404_status() {
 
453
                if ( is_404() ) {
 
454
                        status_header( 200 );
 
455
                }
 
456
        }
 
457
 
 
458
        /**
 
459
         * Print base element for preview frame.
 
460
         *
 
461
         * @since 3.4.0
 
462
         */
 
463
        public function customize_preview_base() {
 
464
                ?><base href="<?php echo home_url( '/' ); ?>" /><?php
 
465
        }
 
466
 
 
467
        /**
 
468
         * Print a workaround to handle HTML5 tags in IE < 9
 
469
         *
 
470
         * @since 3.4.0
 
471
         */
 
472
        public function customize_preview_html5() { ?>
 
473
                <!--[if lt IE 9]>
 
474
                <script type="text/javascript">
 
475
                        var e = [ 'abbr', 'article', 'aside', 'audio', 'canvas', 'datalist', 'details',
 
476
                                'figure', 'footer', 'header', 'hgroup', 'mark', 'menu', 'meter', 'nav',
 
477
                                'output', 'progress', 'section', 'time', 'video' ];
 
478
                        for ( var i = 0; i < e.length; i++ ) {
 
479
                                document.createElement( e[i] );
 
480
                        }
 
481
                </script>
 
482
                <![endif]--><?php
 
483
        }
 
484
 
 
485
        /**
 
486
         * Print javascript settings for preview frame.
 
487
         *
 
488
         * @since 3.4.0
 
489
         */
 
490
        public function customize_preview_settings() {
 
491
                $settings = array(
 
492
                        'values'  => array(),
 
493
                        'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
 
494
                        'activeControls' => array(),
 
495
                );
 
496
 
 
497
                if ( 2 == $this->nonce_tick ) {
 
498
                        $settings['nonce'] = array(
 
499
                                'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
 
500
                                'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
 
501
                        );
 
502
                }
 
503
 
 
504
                foreach ( $this->settings as $id => $setting ) {
 
505
                        $settings['values'][ $id ] = $setting->js_value();
 
506
                }
 
507
                foreach ( $this->controls as $id => $control ) {
 
508
                        $settings['activeControls'][ $id ] = $control->active();
 
509
                }
 
510
 
 
511
                ?>
 
512
                <script type="text/javascript">
 
513
                        var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
 
514
                </script>
 
515
                <?php
 
516
        }
 
517
 
 
518
        /**
 
519
         * Prints a signature so we can ensure the customizer was properly executed.
 
520
         *
 
521
         * @since 3.4.0
 
522
         */
 
523
        public function customize_preview_signature() {
 
524
                echo 'WP_CUSTOMIZER_SIGNATURE';
 
525
        }
 
526
 
 
527
        /**
 
528
         * Removes the signature in case we experience a case where the customizer was not properly executed.
 
529
         *
 
530
         * @since 3.4.0
 
531
         */
 
532
        public function remove_preview_signature( $return = null ) {
 
533
                remove_action( 'shutdown', array( $this, 'customize_preview_signature' ), 1000 );
 
534
 
 
535
                return $return;
 
536
        }
 
537
 
 
538
        /**
 
539
         * Is it a theme preview?
 
540
         *
 
541
         * @since 3.4.0
 
542
         *
 
543
         * @return bool True if it's a preview, false if not.
 
544
         */
 
545
        public function is_preview() {
 
546
                return (bool) $this->previewing;
 
547
        }
 
548
 
 
549
        /**
 
550
         * Retrieve the template name of the previewed theme.
 
551
         *
 
552
         * @since 3.4.0
 
553
         *
 
554
         * @return string Template name.
 
555
         */
 
556
        public function get_template() {
 
557
                return $this->theme()->get_template();
 
558
        }
 
559
 
 
560
        /**
 
561
         * Retrieve the stylesheet name of the previewed theme.
 
562
         *
 
563
         * @since 3.4.0
 
564
         *
 
565
         * @return string Stylesheet name.
 
566
         */
 
567
        public function get_stylesheet() {
 
568
                return $this->theme()->get_stylesheet();
 
569
        }
 
570
 
 
571
        /**
 
572
         * Retrieve the template root of the previewed theme.
 
573
         *
 
574
         * @since 3.4.0
 
575
         *
 
576
         * @return string Theme root.
 
577
         */
 
578
        public function get_template_root() {
 
579
                return get_raw_theme_root( $this->get_template(), true );
 
580
        }
 
581
 
 
582
        /**
 
583
         * Retrieve the stylesheet root of the previewed theme.
 
584
         *
 
585
         * @since 3.4.0
 
586
         *
 
587
         * @return string Theme root.
 
588
         */
 
589
        public function get_stylesheet_root() {
 
590
                return get_raw_theme_root( $this->get_stylesheet(), true );
 
591
        }
 
592
 
 
593
        /**
 
594
         * Filter the current theme and return the name of the previewed theme.
 
595
         *
 
596
         * @since 3.4.0
 
597
         *
 
598
         * @param $current_theme {@internal Parameter is not used}
 
599
         * @return string Theme name.
 
600
         */
 
601
        public function current_theme( $current_theme ) {
 
602
                return $this->theme()->display('Name');
 
603
        }
 
604
 
 
605
        /**
 
606
         * Switch the theme and trigger the save() method on each setting.
 
607
         *
 
608
         * @since 3.4.0
 
609
         */
 
610
        public function save() {
 
611
                if ( ! $this->is_preview() )
 
612
                        die;
 
613
 
 
614
                check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
 
615
 
 
616
                // Do we have to switch themes?
 
617
                if ( ! $this->is_theme_active() ) {
 
618
                        // Temporarily stop previewing the theme to allow switch_themes()
 
619
                        // to operate properly.
 
620
                        $this->stop_previewing_theme();
 
621
                        switch_theme( $this->get_stylesheet() );
 
622
                        update_option( 'theme_switched_via_customizer', true );
 
623
                        $this->start_previewing_theme();
 
624
                }
 
625
 
 
626
                /**
 
627
                 * Fires once the theme has switched in the Customizer, but before settings
 
628
                 * have been saved.
 
629
                 *
 
630
                 * @since 3.4.0
 
631
                 *
 
632
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
633
                 */
 
634
                do_action( 'customize_save', $this );
 
635
 
 
636
                foreach ( $this->settings as $setting ) {
 
637
                        $setting->save();
 
638
                }
 
639
 
 
640
                /**
 
641
                 * Fires after Customize settings have been saved.
 
642
                 *
 
643
                 * @since 3.6.0
 
644
                 *
 
645
                 * @param WP_Customize_Manager $this WP_Customize_Manager instance.
 
646
                 */
 
647
                do_action( 'customize_save_after', $this );
 
648
 
 
649
                die;
 
650
        }
 
651
 
 
652
        /**
 
653
         * Add a customize setting.
 
654
         *
 
655
         * @since 3.4.0
 
656
         *
 
657
         * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
 
658
         * @param array $args                     Setting arguments; passed to WP_Customize_Setting
 
659
         *                                        constructor.
 
660
         */
 
661
        public function add_setting( $id, $args = array() ) {
 
662
                if ( is_a( $id, 'WP_Customize_Setting' ) )
 
663
                        $setting = $id;
 
664
                else
 
665
                        $setting = new WP_Customize_Setting( $this, $id, $args );
 
666
 
 
667
                $this->settings[ $setting->id ] = $setting;
 
668
        }
 
669
 
 
670
        /**
 
671
         * Retrieve a customize setting.
 
672
         *
 
673
         * @since 3.4.0
 
674
         *
 
675
         * @param string $id Customize Setting ID.
 
676
         * @return WP_Customize_Setting
 
677
         */
 
678
        public function get_setting( $id ) {
 
679
                if ( isset( $this->settings[ $id ] ) )
 
680
                        return $this->settings[ $id ];
 
681
        }
 
682
 
 
683
        /**
 
684
         * Remove a customize setting.
 
685
         *
 
686
         * @since 3.4.0
 
687
         *
 
688
         * @param string $id Customize Setting ID.
 
689
         */
 
690
        public function remove_setting( $id ) {
 
691
                unset( $this->settings[ $id ] );
 
692
        }
 
693
 
 
694
        /**
 
695
         * Add a customize panel.
 
696
         *
 
697
         * @since 4.0.0
 
698
         * @access public
 
699
         *
 
700
         * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
 
701
         * @param array                     $args Optional. Panel arguments. Default empty array.
 
702
         */
 
703
        public function add_panel( $id, $args = array() ) {
 
704
                if ( is_a( $id, 'WP_Customize_Panel' ) ) {
 
705
                        $panel = $id;
 
706
                }
 
707
                else {
 
708
                        $panel = new WP_Customize_Panel( $this, $id, $args );
 
709
                }
 
710
 
 
711
                $this->panels[ $panel->id ] = $panel;
 
712
        }
 
713
 
 
714
        /**
 
715
         * Retrieve a customize panel.
 
716
         *
 
717
         * @since 4.0.0
 
718
         * @access public
 
719
         *
 
720
         * @param string $id Panel ID to get.
 
721
         * @return WP_Customize_Panel Requested panel instance.
 
722
         */
 
723
        public function get_panel( $id ) {
 
724
                if ( isset( $this->panels[ $id ] ) ) {
 
725
                        return $this->panels[ $id ];
 
726
                }
 
727
        }
 
728
 
 
729
        /**
 
730
         * Remove a customize panel.
 
731
         *
 
732
         * @since 4.0.0
 
733
         * @access public
 
734
         *
 
735
         * @param string $id Panel ID to remove.
 
736
         */
 
737
        public function remove_panel( $id ) {
 
738
                unset( $this->panels[ $id ] );
 
739
        }
 
740
 
 
741
        /**
 
742
         * Add a customize section.
 
743
         *
 
744
         * @since 3.4.0
 
745
         *
 
746
         * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
 
747
         * @param array                       $args Section arguments.
 
748
         */
 
749
        public function add_section( $id, $args = array() ) {
 
750
                if ( is_a( $id, 'WP_Customize_Section' ) )
 
751
                        $section = $id;
 
752
                else
 
753
                        $section = new WP_Customize_Section( $this, $id, $args );
 
754
 
 
755
                $this->sections[ $section->id ] = $section;
 
756
        }
 
757
 
 
758
        /**
 
759
         * Retrieve a customize section.
 
760
         *
 
761
         * @since 3.4.0
 
762
         *
 
763
         * @param string $id Section ID.
 
764
         * @return WP_Customize_Section
 
765
         */
 
766
        public function get_section( $id ) {
 
767
                if ( isset( $this->sections[ $id ] ) )
 
768
                        return $this->sections[ $id ];
 
769
        }
 
770
 
 
771
        /**
 
772
         * Remove a customize section.
 
773
         *
 
774
         * @since 3.4.0
 
775
         *
 
776
         * @param string $id Section ID.
 
777
         */
 
778
        public function remove_section( $id ) {
 
779
                unset( $this->sections[ $id ] );
 
780
        }
 
781
 
 
782
        /**
 
783
         * Add a customize control.
 
784
         *
 
785
         * @since 3.4.0
 
786
         *
 
787
         * @param WP_Customize_Control|string $id   Customize Control object, or ID.
 
788
         * @param array                       $args Control arguments; passed to WP_Customize_Control
 
789
         *                                          constructor.
 
790
         */
 
791
        public function add_control( $id, $args = array() ) {
 
792
                if ( is_a( $id, 'WP_Customize_Control' ) )
 
793
                        $control = $id;
 
794
                else
 
795
                        $control = new WP_Customize_Control( $this, $id, $args );
 
796
 
 
797
                $this->controls[ $control->id ] = $control;
 
798
        }
 
799
 
 
800
        /**
 
801
         * Retrieve a customize control.
 
802
         *
 
803
         * @since 3.4.0
 
804
         *
 
805
         * @param string $id ID of the control.
 
806
         * @return WP_Customize_Control $control The control object.
 
807
         */
 
808
        public function get_control( $id ) {
 
809
                if ( isset( $this->controls[ $id ] ) )
 
810
                        return $this->controls[ $id ];
 
811
        }
 
812
 
 
813
        /**
 
814
         * Remove a customize control.
 
815
         *
 
816
         * @since 3.4.0
 
817
         *
 
818
         * @param string $id ID of the control.
 
819
         */
 
820
        public function remove_control( $id ) {
 
821
                unset( $this->controls[ $id ] );
 
822
        }
 
823
 
 
824
        /**
 
825
         * Helper function to compare two objects by priority.
 
826
         *
 
827
         * @since 3.4.0
 
828
         *
 
829
         * @param object $a Object A.
 
830
         * @param object $b Object B.
 
831
         * @return int
 
832
         */
 
833
        protected final function _cmp_priority( $a, $b ) {
 
834
                $ap = $a->priority;
 
835
                $bp = $b->priority;
 
836
 
 
837
                if ( $ap == $bp )
 
838
                        return 0;
 
839
                return ( $ap > $bp ) ? 1 : -1;
 
840
        }
 
841
 
 
842
        /**
 
843
         * Prepare panels, sections, and controls.
 
844
         *
 
845
         * For each, check if required related components exist,
 
846
         * whether the user has the necessary capabilities,
 
847
         * and sort by priority.
 
848
         *
 
849
         * @since 3.4.0
 
850
         */
 
851
        public function prepare_controls() {
 
852
 
 
853
                $this->controls = array_reverse( $this->controls );
 
854
                $controls = array();
 
855
 
 
856
                foreach ( $this->controls as $id => $control ) {
 
857
                        if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
 
858
                                continue;
 
859
                        }
 
860
 
 
861
                        $this->sections[ $control->section ]->controls[] = $control;
 
862
                        $controls[ $id ] = $control;
 
863
                }
 
864
                $this->controls = $controls;
 
865
 
 
866
                // Prepare sections.
 
867
                // Reversing makes uasort sort by time added when conflicts occur.
 
868
                $this->sections = array_reverse( $this->sections );
 
869
                uasort( $this->sections, array( $this, '_cmp_priority' ) );
 
870
                $sections = array();
 
871
 
 
872
                foreach ( $this->sections as $section ) {
 
873
                        if ( ! $section->check_capabilities() || ! $section->controls ) {
 
874
                                continue;
 
875
                        }
 
876
 
 
877
                        usort( $section->controls, array( $this, '_cmp_priority' ) );
 
878
 
 
879
                        if ( ! $section->panel ) {
 
880
                                // Top-level section.
 
881
                                $sections[] = $section;
 
882
                        } else {
 
883
                                // This section belongs to a panel.
 
884
                                if ( isset( $this->panels [ $section->panel ] ) ) {
 
885
                                        $this->panels[ $section->panel ]->sections[] = $section;
 
886
                                }
 
887
                        }
 
888
                }
 
889
                $this->sections = $sections;
 
890
 
 
891
                // Prepare panels.
 
892
                // Reversing makes uasort sort by time added when conflicts occur.
 
893
                $this->panels = array_reverse( $this->panels );
 
894
                uasort( $this->panels, array( $this, '_cmp_priority' ) );
 
895
                $panels = array();
 
896
 
 
897
                foreach ( $this->panels as $panel ) {
 
898
                        if ( ! $panel->check_capabilities() || ! $panel->sections ) {
 
899
                                continue;
 
900
                        }
 
901
 
 
902
                        usort( $panel->sections, array( $this, '_cmp_priority' ) );
 
903
                        $panels[] = $panel;
 
904
                }
 
905
                $this->panels = $panels;
 
906
 
 
907
                // Sort panels and top-level sections together.
 
908
                $this->containers = array_merge( $this->panels, $this->sections );
 
909
                uasort( $this->containers, array( $this, '_cmp_priority' ) );
 
910
        }
 
911
 
 
912
        /**
 
913
         * Enqueue scripts for customize controls.
 
914
         *
 
915
         * @since 3.4.0
 
916
         */
 
917
        public function enqueue_control_scripts() {
 
918
                foreach ( $this->controls as $control ) {
 
919
                        $control->enqueue();
 
920
                }
 
921
        }
 
922
 
 
923
        /**
 
924
         * Register some default controls.
 
925
         *
 
926
         * @since 3.4.0
 
927
         */
 
928
        public function register_controls() {
 
929
 
 
930
                /* Site Title & Tagline */
 
931
 
 
932
                $this->add_section( 'title_tagline', array(
 
933
                        'title'    => __( 'Site Title & Tagline' ),
 
934
                        'priority' => 20,
 
935
                ) );
 
936
 
 
937
                $this->add_setting( 'blogname', array(
 
938
                        'default'    => get_option( 'blogname' ),
 
939
                        'type'       => 'option',
 
940
                        'capability' => 'manage_options',
 
941
                ) );
 
942
 
 
943
                $this->add_control( 'blogname', array(
 
944
                        'label'      => __( 'Site Title' ),
 
945
                        'section'    => 'title_tagline',
 
946
                ) );
 
947
 
 
948
                $this->add_setting( 'blogdescription', array(
 
949
                        'default'    => get_option( 'blogdescription' ),
 
950
                        'type'       => 'option',
 
951
                        'capability' => 'manage_options',
 
952
                ) );
 
953
 
 
954
                $this->add_control( 'blogdescription', array(
 
955
                        'label'      => __( 'Tagline' ),
 
956
                        'section'    => 'title_tagline',
 
957
                ) );
 
958
 
 
959
                /* Colors */
 
960
 
 
961
                $this->add_section( 'colors', array(
 
962
                        'title'          => __( 'Colors' ),
 
963
                        'priority'       => 40,
 
964
                ) );
 
965
 
 
966
                $this->add_setting( 'header_textcolor', array(
 
967
                        'theme_supports' => array( 'custom-header', 'header-text' ),
 
968
                        'default'        => get_theme_support( 'custom-header', 'default-text-color' ),
 
969
 
 
970
                        'sanitize_callback'    => array( $this, '_sanitize_header_textcolor' ),
 
971
                        'sanitize_js_callback' => 'maybe_hash_hex_color',
 
972
                ) );
 
973
 
 
974
                // Input type: checkbox
 
975
                // With custom value
 
976
                $this->add_control( 'display_header_text', array(
 
977
                        'settings' => 'header_textcolor',
 
978
                        'label'    => __( 'Display Header Text' ),
 
979
                        'section'  => 'title_tagline',
 
980
                        'type'     => 'checkbox',
 
981
                ) );
 
982
 
 
983
                $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
 
984
                        'label'   => __( 'Header Text Color' ),
 
985
                        'section' => 'colors',
 
986
                ) ) );
 
987
 
 
988
                // Input type: Color
 
989
                // With sanitize_callback
 
990
                $this->add_setting( 'background_color', array(
 
991
                        'default'        => get_theme_support( 'custom-background', 'default-color' ),
 
992
                        'theme_supports' => 'custom-background',
 
993
 
 
994
                        'sanitize_callback'    => 'sanitize_hex_color_no_hash',
 
995
                        'sanitize_js_callback' => 'maybe_hash_hex_color',
 
996
                ) );
 
997
 
 
998
                $this->add_control( new WP_Customize_Color_Control( $this, 'background_color', array(
 
999
                        'label'   => __( 'Background Color' ),
 
1000
                        'section' => 'colors',
 
1001
                ) ) );
 
1002
 
 
1003
 
 
1004
                /* Custom Header */
 
1005
 
 
1006
                $this->add_section( 'header_image', array(
 
1007
                        'title'          => __( 'Header Image' ),
 
1008
                        'theme_supports' => 'custom-header',
 
1009
                        'priority'       => 60,
 
1010
                ) );
 
1011
 
 
1012
                $this->add_setting( new WP_Customize_Filter_Setting( $this, 'header_image', array(
 
1013
                        'default'        => get_theme_support( 'custom-header', 'default-image' ),
 
1014
                        'theme_supports' => 'custom-header',
 
1015
                ) ) );
 
1016
 
 
1017
                $this->add_setting( new WP_Customize_Header_Image_Setting( $this, 'header_image_data', array(
 
1018
                        // 'default'        => get_theme_support( 'custom-header', 'default-image' ),
 
1019
                        'theme_supports' => 'custom-header',
 
1020
                ) ) );
 
1021
 
 
1022
                $this->add_control( new WP_Customize_Header_Image_Control( $this ) );
 
1023
 
 
1024
                /* Custom Background */
 
1025
 
 
1026
                $this->add_section( 'background_image', array(
 
1027
                        'title'          => __( 'Background Image' ),
 
1028
                        'theme_supports' => 'custom-background',
 
1029
                        'priority'       => 80,
 
1030
                ) );
 
1031
 
 
1032
                $this->add_setting( 'background_image', array(
 
1033
                        'default'        => get_theme_support( 'custom-background', 'default-image' ),
 
1034
                        'theme_supports' => 'custom-background',
 
1035
                ) );
 
1036
 
 
1037
                $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array(
 
1038
                        'theme_supports' => 'custom-background',
 
1039
                ) ) );
 
1040
 
 
1041
                $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
 
1042
 
 
1043
                $this->add_setting( 'background_repeat', array(
 
1044
                        'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
 
1045
                        'theme_supports' => 'custom-background',
 
1046
                ) );
 
1047
 
 
1048
                $this->add_control( 'background_repeat', array(
 
1049
                        'label'      => __( 'Background Repeat' ),
 
1050
                        'section'    => 'background_image',
 
1051
                        'type'       => 'radio',
 
1052
                        'choices'    => array(
 
1053
                                'no-repeat'  => __('No Repeat'),
 
1054
                                'repeat'     => __('Tile'),
 
1055
                                'repeat-x'   => __('Tile Horizontally'),
 
1056
                                'repeat-y'   => __('Tile Vertically'),
 
1057
                        ),
 
1058
                ) );
 
1059
 
 
1060
                $this->add_setting( 'background_position_x', array(
 
1061
                        'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
 
1062
                        'theme_supports' => 'custom-background',
 
1063
                ) );
 
1064
 
 
1065
                $this->add_control( 'background_position_x', array(
 
1066
                        'label'      => __( 'Background Position' ),
 
1067
                        'section'    => 'background_image',
 
1068
                        'type'       => 'radio',
 
1069
                        'choices'    => array(
 
1070
                                'left'       => __('Left'),
 
1071
                                'center'     => __('Center'),
 
1072
                                'right'      => __('Right'),
 
1073
                        ),
 
1074
                ) );
 
1075
 
 
1076
                $this->add_setting( 'background_attachment', array(
 
1077
                        'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
 
1078
                        'theme_supports' => 'custom-background',
 
1079
                ) );
 
1080
 
 
1081
                $this->add_control( 'background_attachment', array(
 
1082
                        'label'      => __( 'Background Attachment' ),
 
1083
                        'section'    => 'background_image',
 
1084
                        'type'       => 'radio',
 
1085
                        'choices'    => array(
 
1086
                                'scroll'     => __('Scroll'),
 
1087
                                'fixed'      => __('Fixed'),
 
1088
                        ),
 
1089
                ) );
 
1090
 
 
1091
                // If the theme is using the default background callback, we can update
 
1092
                // the background CSS using postMessage.
 
1093
                if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) {
 
1094
                        foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) {
 
1095
                                $this->get_setting( 'background_' . $prop )->transport = 'postMessage';
 
1096
                        }
 
1097
                }
 
1098
 
 
1099
                /* Nav Menus */
 
1100
 
 
1101
                $locations      = get_registered_nav_menus();
 
1102
                $menus          = wp_get_nav_menus();
 
1103
                $num_locations  = count( array_keys( $locations ) );
 
1104
 
 
1105
                $this->add_section( 'nav', array(
 
1106
                        'title'          => __( 'Navigation' ),
 
1107
                        'theme_supports' => 'menus',
 
1108
                        'priority'       => 100,
 
1109
                        'description'    => sprintf( _n('Your theme supports %s menu. Select which menu you would like to use.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) ) . "\n\n" . __('You can edit your menu content on the Menus screen in the Appearance section.'),
 
1110
                ) );
 
1111
 
 
1112
                if ( $menus ) {
 
1113
                        $choices = array( 0 => __( '&mdash; Select &mdash;' ) );
 
1114
                        foreach ( $menus as $menu ) {
 
1115
                                $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
 
1116
                        }
 
1117
 
 
1118
                        foreach ( $locations as $location => $description ) {
 
1119
                                $menu_setting_id = "nav_menu_locations[{$location}]";
 
1120
 
 
1121
                                $this->add_setting( $menu_setting_id, array(
 
1122
                                        'sanitize_callback' => 'absint',
 
1123
                                        'theme_supports'    => 'menus',
 
1124
                                ) );
 
1125
 
 
1126
                                $this->add_control( $menu_setting_id, array(
 
1127
                                        'label'   => $description,
 
1128
                                        'section' => 'nav',
 
1129
                                        'type'    => 'select',
 
1130
                                        'choices' => $choices,
 
1131
                                ) );
 
1132
                        }
 
1133
                }
 
1134
 
 
1135
                /* Static Front Page */
 
1136
                // #WP19627
 
1137
 
 
1138
                $this->add_section( 'static_front_page', array(
 
1139
                        'title'          => __( 'Static Front Page' ),
 
1140
                //      'theme_supports' => 'static-front-page',
 
1141
                        'priority'       => 120,
 
1142
                        'description'    => __( 'Your theme supports a static front page.' ),
 
1143
                ) );
 
1144
 
 
1145
                $this->add_setting( 'show_on_front', array(
 
1146
                        'default'        => get_option( 'show_on_front' ),
 
1147
                        'capability'     => 'manage_options',
 
1148
                        'type'           => 'option',
 
1149
                //      'theme_supports' => 'static-front-page',
 
1150
                ) );
 
1151
 
 
1152
                $this->add_control( 'show_on_front', array(
 
1153
                        'label'   => __( 'Front page displays' ),
 
1154
                        'section' => 'static_front_page',
 
1155
                        'type'    => 'radio',
 
1156
                        'choices' => array(
 
1157
                                'posts' => __( 'Your latest posts' ),
 
1158
                                'page'  => __( 'A static page' ),
 
1159
                        ),
 
1160
                ) );
 
1161
 
 
1162
                $this->add_setting( 'page_on_front', array(
 
1163
                        'type'       => 'option',
 
1164
                        'capability' => 'manage_options',
 
1165
                //      'theme_supports' => 'static-front-page',
 
1166
                ) );
 
1167
 
 
1168
                $this->add_control( 'page_on_front', array(
 
1169
                        'label'      => __( 'Front page' ),
 
1170
                        'section'    => 'static_front_page',
 
1171
                        'type'       => 'dropdown-pages',
 
1172
                ) );
 
1173
 
 
1174
                $this->add_setting( 'page_for_posts', array(
 
1175
                        'type'           => 'option',
 
1176
                        'capability'     => 'manage_options',
 
1177
                //      'theme_supports' => 'static-front-page',
 
1178
                ) );
 
1179
 
 
1180
                $this->add_control( 'page_for_posts', array(
 
1181
                        'label'      => __( 'Posts page' ),
 
1182
                        'section'    => 'static_front_page',
 
1183
                        'type'       => 'dropdown-pages',
 
1184
                ) );
 
1185
        }
 
1186
 
 
1187
        /**
 
1188
         * Callback for validating the header_textcolor value.
 
1189
         *
 
1190
         * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
 
1191
         * Returns default text color if hex color is empty.
 
1192
         *
 
1193
         * @since 3.4.0
 
1194
         *
 
1195
         * @param string $color
 
1196
         * @return string
 
1197
         */
 
1198
        public function _sanitize_header_textcolor( $color ) {
 
1199
                if ( 'blank' === $color )
 
1200
                        return 'blank';
 
1201
 
 
1202
                $color = sanitize_hex_color_no_hash( $color );
 
1203
                if ( empty( $color ) )
 
1204
                        $color = get_theme_support( 'custom-header', 'default-text-color' );
 
1205
 
 
1206
                return $color;
 
1207
        }
 
1208
}
 
1209
 
 
1210
/**
 
1211
 * Sanitizes a hex color.
 
1212
 *
 
1213
 * Returns either '', a 3 or 6 digit hex color (with #), or null.
 
1214
 * For sanitizing values without a #, see sanitize_hex_color_no_hash().
 
1215
 *
 
1216
 * @since 3.4.0
 
1217
 *
 
1218
 * @param string $color
 
1219
 * @return string|null
 
1220
 */
 
1221
function sanitize_hex_color( $color ) {
 
1222
        if ( '' === $color )
 
1223
                return '';
 
1224
 
 
1225
        // 3 or 6 hex digits, or the empty string.
 
1226
        if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) )
 
1227
                return $color;
 
1228
 
 
1229
        return null;
 
1230
}
 
1231
 
 
1232
/**
 
1233
 * Sanitizes a hex color without a hash. Use sanitize_hex_color() when possible.
 
1234
 *
 
1235
 * Saving hex colors without a hash puts the burden of adding the hash on the
 
1236
 * UI, which makes it difficult to use or upgrade to other color types such as
 
1237
 * rgba, hsl, rgb, and html color names.
 
1238
 *
 
1239
 * Returns either '', a 3 or 6 digit hex color (without a #), or null.
 
1240
 *
 
1241
 * @since 3.4.0
 
1242
 * @uses sanitize_hex_color()
 
1243
 *
 
1244
 * @param string $color
 
1245
 * @return string|null
 
1246
 */
 
1247
function sanitize_hex_color_no_hash( $color ) {
 
1248
        $color = ltrim( $color, '#' );
 
1249
 
 
1250
        if ( '' === $color )
 
1251
                return '';
 
1252
 
 
1253
        return sanitize_hex_color( '#' . $color ) ? $color : null;
 
1254
}
 
1255
 
 
1256
/**
 
1257
 * Ensures that any hex color is properly hashed.
 
1258
 * Otherwise, returns value untouched.
 
1259
 *
 
1260
 * This method should only be necessary if using sanitize_hex_color_no_hash().
 
1261
 *
 
1262
 * @since 3.4.0
 
1263
 *
 
1264
 * @param string $color
 
1265
 * @return string
 
1266
 */
 
1267
function maybe_hash_hex_color( $color ) {
 
1268
        if ( $unhashed = sanitize_hex_color_no_hash( $color ) )
 
1269
                return '#' . $unhashed;
 
1270
 
 
1271
        return $color;
 
1272
}