]> scripts.mit.edu Git - autoinstalls/wordpress.git/blobdiff - wp-includes/class-wp-customize-manager.php
WordPress 4.1.1-scripts
[autoinstalls/wordpress.git] / wp-includes / class-wp-customize-manager.php
index c1ce0510e7d43e107ccb630a9a7499f23cb56273..c14d61652ab28e114cb7b66d8e57b1e6f0d4125d 100644 (file)
@@ -1,24 +1,72 @@
 <?php
 /**
- * Customize
+ * Customize Manager.
+ *
+ * Bootstraps the Customize experience on the server-side.
+ *
+ * Sets up the theme-switching process if a theme other than the active one is
+ * being previewed and customized.
+ *
+ * Serves as a factory for Customize Controls and Settings, and
+ * instantiates default Customize Controls and Settings.
  *
  * @package WordPress
  * @subpackage Customize
  * @since 3.4.0
  */
-
 final class WP_Customize_Manager {
+       /**
+        * An instance of the theme being previewed.
+        *
+        * @var WP_Theme
+        */
        protected $theme;
+
+       /**
+        * The directory name of the previously active theme (within the theme_root).
+        *
+        * @var string
+        */
        protected $original_stylesheet;
 
+       /**
+        * Whether this is a Customizer pageload.
+        *
+        * @var boolean
+        */
        protected $previewing = false;
 
-       protected $settings = array();
-       protected $sections = array();
-       protected $controls = array();
+       /**
+        * Methods and properties deailing with managing widgets in the Customizer.
+        *
+        * @var WP_Customize_Widgets
+        */
+       public $widgets;
+
+       protected $settings   = array();
+       protected $containers = array();
+       protected $panels     = array();
+       protected $sections   = array();
+       protected $controls   = array();
+
+       protected $nonce_tick;
 
        protected $customized;
 
+       /**
+        * Controls that may be rendered from JS templates.
+        *
+        * @since 4.1.0
+        * @access protected
+        * @var array
+        */
+       protected $registered_control_types = array();
+
+       /**
+        * Unsanitized values for Customize Settings parsed from $_POST['customized'].
+        *
+        * @var array|false
+        */
        private $_post_values;
 
        /**
@@ -27,9 +75,13 @@ final class WP_Customize_Manager {
         * @since 3.4.0
         */
        public function __construct() {
-               require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
-               require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
-               require( ABSPATH . WPINC . '/class-wp-customize-control.php' );
+               require_once( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
+               require_once( ABSPATH . WPINC . '/class-wp-customize-panel.php' );
+               require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
+               require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
+               require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
+
+               $this->widgets = new WP_Customize_Widgets( $this );
 
                add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
 
@@ -39,7 +91,7 @@ final class WP_Customize_Manager {
                // Run wp_redirect_status late to make sure we override the status last.
                add_action( 'wp_redirect_status', array( $this, 'wp_redirect_status' ), 1000 );
 
-               // Do not spawn cron (especially the alternate cron) while running the customizer.
+               // Do not spawn cron (especially the alternate cron) while running the Customizer.
                remove_action( 'init', 'wp_cron' );
 
                // Do not run update checks when rendering the controls.
@@ -54,10 +106,12 @@ final class WP_Customize_Manager {
                add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_control_scripts' ) );
        }
 
-       /**
+       /**
         * Return true if it's an AJAX request.
         *
         * @since 3.4.0
+        *
+        * @return bool
         */
        public function doing_ajax() {
                return isset( $_POST['customized'] ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX );
@@ -67,10 +121,10 @@ final class WP_Customize_Manager {
         * Custom wp_die wrapper. Returns either the standard message for UI
         * or the AJAX message.
         *
-        * @param  mixed $ajax_message AJAX return
-        * @param  mixed $message      UI message
-        *
         * @since 3.4.0
+        *
+        * @param mixed $ajax_message AJAX return
+        * @param mixed $message UI message
         */
        protected function wp_die( $ajax_message, $message = null ) {
                if ( $this->doing_ajax() )
@@ -86,6 +140,8 @@ final class WP_Customize_Manager {
         * Return the AJAX wp_die() handler if it's a customized request.
         *
         * @since 3.4.0
+        *
+        * @return string
         */
        public function wp_die_handler() {
                if ( $this->doing_ajax() )
@@ -93,10 +149,11 @@ final class WP_Customize_Manager {
 
                return '_default_wp_die_handler';
        }
+
        /**
-       * Start preview and customize theme.
-       *
-       * Check if customize query variable exist. Init filters to filter the current theme.
+        * Start preview and customize theme.
+        *
+        * Check if customize query variable exist. Init filters to filter the current theme.
         *
         * @since 3.4.0
         */
@@ -110,8 +167,9 @@ final class WP_Customize_Manager {
 
                show_admin_bar( false );
 
-               if ( ! current_user_can( 'edit_theme_options' ) )
+               if ( ! current_user_can( 'customize' ) ) {
                        $this->wp_die( -1 );
+               }
 
                $this->original_stylesheet = get_stylesheet();
 
@@ -121,13 +179,16 @@ final class WP_Customize_Manager {
                        // Once the theme is loaded, we'll validate it.
                        add_action( 'after_setup_theme', array( $this, 'after_setup_theme' ) );
                } else {
+                       // If the requested theme is not the active theme and the user doesn't have the
+                       // switch_themes cap, bail.
                        if ( ! current_user_can( 'switch_themes' ) )
                                $this->wp_die( -1 );
 
-                       // If the theme isn't active, you can't preview it if it is not allowed or has errors.
+                       // If the theme has errors while loading, bail.
                        if ( $this->theme()->errors() )
                                $this->wp_die( -1 );
 
+                       // If the theme isn't allowed per multisite settings, bail.
                        if ( ! $this->theme()->is_allowed() )
                                $this->wp_die( -1 );
                }
@@ -135,7 +196,12 @@ final class WP_Customize_Manager {
                $this->start_previewing_theme();
        }
 
-       function after_setup_theme() {
+       /**
+        * Callback to validate a theme once it is loaded
+        *
+        * @since 3.4.0
+        */
+       public function after_setup_theme() {
                if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
                        wp_redirect( 'themes.php?broken=true' );
                        exit;
@@ -143,9 +209,8 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Start previewing the selected theme.
-        *
-        * Adds filters to change the current theme.
+        * If the theme to be previewed isn't the active theme, add filter callbacks
+        * to swap it out at runtime.
         *
         * @since 3.4.0
         */
@@ -161,7 +226,7 @@ final class WP_Customize_Manager {
                        add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
                        add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 
-                       // @link: http://core.trac.wordpress.org/ticket/20027
+                       // @link: https://core.trac.wordpress.org/ticket/20027
                        add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
                        add_filter( 'pre_option_template', array( $this, 'get_template' ) );
 
@@ -170,6 +235,13 @@ final class WP_Customize_Manager {
                        add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
                }
 
+               /**
+                * Fires once the Customizer theme preview has started.
+                *
+                * @since 3.4.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
                do_action( 'start_previewing_theme', $this );
        }
 
@@ -191,7 +263,7 @@ final class WP_Customize_Manager {
                        remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
                        remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
 
-                       // @link: http://core.trac.wordpress.org/ticket/20027
+                       // @link: https://core.trac.wordpress.org/ticket/20027
                        remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
                        remove_filter( 'pre_option_template', array( $this, 'get_template' ) );
 
@@ -200,6 +272,13 @@ final class WP_Customize_Manager {
                        remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
                }
 
+               /**
+                * Fires once the Customizer theme preview has stopped.
+                *
+                * @since 3.4.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
                do_action( 'stop_previewing_theme', $this );
        }
 
@@ -236,6 +315,17 @@ final class WP_Customize_Manager {
                return $this->controls;
        }
 
+       /**
+        * Get the registered containers.
+        *
+        * @since 4.0.0
+        *
+        * @return array
+        */
+       public function containers() {
+               return $this->containers;
+       }
+
        /**
         * Get the registered sections.
         *
@@ -247,6 +337,18 @@ final class WP_Customize_Manager {
                return $this->sections;
        }
 
+       /**
+        * Get the registered panels.
+        *
+        * @since 4.0.0
+        * @access public
+        *
+        * @return array Panels.
+        */
+       public function panels() {
+               return $this->panels;
+       }
+
        /**
         * Checks if the current theme is active.
         *
@@ -264,6 +366,14 @@ final class WP_Customize_Manager {
         * @since 3.4.0
         */
        public function wp_loaded() {
+
+               /**
+                * Fires once WordPress has loaded, allowing scripts and styles to be initialized.
+                *
+                * @since 3.4.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
                do_action( 'customize_register', $this );
 
                if ( $this->is_preview() && ! is_admin() )
@@ -277,6 +387,9 @@ final class WP_Customize_Manager {
         * Instead, the JS will sniff out the location header.
         *
         * @since 3.4.0
+        *
+        * @param $status
+        * @return int
         */
        public function wp_redirect_status( $status ) {
                if ( $this->is_preview() && ! is_admin() )
@@ -286,31 +399,60 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Decode the $_POST attribute used to override the WP_Customize_Setting values.
+        * Parse the incoming $_POST['customized'] JSON data and store the unsanitized
+        * settings for subsequent post_value() lookups.
         *
-        * @since 3.4.0
+        * @since 4.1.1
+        *
+        * @return array
         */
-       public function post_value( $setting ) {
+       public function unsanitized_post_values() {
                if ( ! isset( $this->_post_values ) ) {
-                       if ( isset( $_POST['customized'] ) )
-                               $this->_post_values = json_decode( stripslashes( $_POST['customized'] ), true );
-                       else
+                       if ( isset( $_POST['customized'] ) ) {
+                               $this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
+                       }
+                       if ( empty( $this->_post_values ) ) { // if not isset or of JSON error
                                $this->_post_values = false;
+                       }
                }
+               if ( empty( $this->_post_values ) ) {
+                       return array();
+               } else {
+                       return $this->_post_values;
+               }
+       }
 
-               if ( isset( $this->_post_values[ $setting->id ] ) )
-                       return $setting->sanitize( $this->_post_values[ $setting->id ] );
+       /**
+        * Return the sanitized value for a given setting from the request's POST data.
+        *
+        * @since 3.4.0
+        * @since 4.1.1 Introduced 'default' parameter.
+        *
+        * @param WP_Customize_Setting $setting A WP_Customize_Setting derived object
+        * @param mixed $default value returned $setting has no post value (added in 4.2.0).
+        * @return string|mixed $post_value Sanitized value or the $default provided
+        */
+       public function post_value( $setting, $default = null ) {
+               $post_values = $this->unsanitized_post_values();
+               if ( array_key_exists( $setting->id, $post_values ) ) {
+                       return $setting->sanitize( $post_values[ $setting->id ] );
+               } else {
+                       return $default;
+               }
        }
 
        /**
-        * Print javascript settings.
+        * Print JavaScript settings.
         *
         * @since 3.4.0
         */
        public function customize_preview_init() {
+               $this->nonce_tick = check_ajax_referer( 'preview-customize_' . $this->get_stylesheet(), 'nonce' );
+
                $this->prepare_controls();
 
                wp_enqueue_script( 'customize-preview' );
+               add_action( 'wp', array( $this, 'customize_preview_override_404_status' ) );
                add_action( 'wp_head', array( $this, 'customize_preview_base' ) );
                add_action( 'wp_head', array( $this, 'customize_preview_html5' ) );
                add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
@@ -321,9 +463,30 @@ final class WP_Customize_Manager {
                        $setting->preview();
                }
 
+               /**
+                * Fires once the Customizer preview has initialized and JavaScript
+                * settings have been printed.
+                *
+                * @since 3.4.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
                do_action( 'customize_preview_init', $this );
        }
 
+       /**
+        * Prevent sending a 404 status when returning the response for the customize
+        * preview, since it causes the jQuery AJAX to fail. Send 200 instead.
+        *
+        * @since 4.0.0
+        * @access public
+        */
+       public function customize_preview_override_404_status() {
+               if ( is_404() ) {
+                       status_header( 200 );
+               }
+       }
+
        /**
         * Print base element for preview frame.
         *
@@ -352,29 +515,51 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Print javascript settings for preview frame.
+        * Print JavaScript settings for preview frame.
         *
         * @since 3.4.0
         */
        public function customize_preview_settings() {
                $settings = array(
                        'values'  => array(),
-                       'channel' => esc_js( $_POST['customize_messenger_channel'] ),
+                       'channel' => wp_unslash( $_POST['customize_messenger_channel'] ),
+                       'activePanels' => array(),
+                       'activeSections' => array(),
+                       'activeControls' => array(),
                );
 
+               if ( 2 == $this->nonce_tick ) {
+                       $settings['nonce'] = array(
+                               'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ),
+                               'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() )
+                       );
+               }
+
                foreach ( $this->settings as $id => $setting ) {
                        $settings['values'][ $id ] = $setting->js_value();
                }
+               foreach ( $this->panels as $id => $panel ) {
+                       $settings['activePanels'][ $id ] = $panel->active();
+                       foreach ( $panel->sections as $id => $section ) {
+                               $settings['activeSections'][ $id ] = $section->active();
+                       }
+               }
+               foreach ( $this->sections as $id => $section ) {
+                       $settings['activeSections'][ $id ] = $section->active();
+               }
+               foreach ( $this->controls as $id => $control ) {
+                       $settings['activeControls'][ $id ] = $control->active();
+               }
 
                ?>
                <script type="text/javascript">
-                       var _wpCustomizeSettings = <?php echo json_encode( $settings ); ?>;
+                       var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
                </script>
                <?php
        }
 
        /**
-        * Prints a signature so we can ensure the customizer was properly executed.
+        * Prints a signature so we can ensure the Customizer was properly executed.
         *
         * @since 3.4.0
         */
@@ -383,7 +568,7 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Removes the signature in case we experience a case where the customizer was not properly executed.
+        * Removes the signature in case we experience a case where the Customizer was not properly executed.
         *
         * @since 3.4.0
         */
@@ -453,6 +638,7 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
+        * @param $current_theme {@internal Parameter is not used}
         * @return string Theme name.
         */
        public function current_theme( $current_theme ) {
@@ -460,7 +646,7 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Switch the theme and trigger the save action of each setting.
+        * Switch the theme and trigger the save() method on each setting.
         *
         * @since 3.4.0
         */
@@ -468,23 +654,41 @@ final class WP_Customize_Manager {
                if ( ! $this->is_preview() )
                        die;
 
-               check_ajax_referer( 'customize_controls-' . $this->get_stylesheet(), 'nonce' );
+               check_ajax_referer( 'save-customize_' . $this->get_stylesheet(), 'nonce' );
 
                // Do we have to switch themes?
                if ( ! $this->is_theme_active() ) {
                        // Temporarily stop previewing the theme to allow switch_themes()
                        // to operate properly.
                        $this->stop_previewing_theme();
-                       switch_theme( $this->get_template(), $this->get_stylesheet() );
+                       switch_theme( $this->get_stylesheet() );
+                       update_option( 'theme_switched_via_customizer', true );
                        $this->start_previewing_theme();
                }
 
+               /**
+                * Fires once the theme has switched in the Customizer, but before settings
+                * have been saved.
+                *
+                * @since 3.4.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
                do_action( 'customize_save', $this );
 
                foreach ( $this->settings as $setting ) {
                        $setting->save();
                }
 
+               /**
+                * Fires after Customize settings have been saved.
+                *
+                * @since 3.6.0
+                *
+                * @param WP_Customize_Manager $this WP_Customize_Manager instance.
+                */
+               do_action( 'customize_save_after', $this );
+
                die;
        }
 
@@ -493,9 +697,9 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the setting. Can be a
-        *                   theme mod or option name.
-        * @param array $args Setting arguments.
+        * @param WP_Customize_Setting|string $id Customize Setting object, or ID.
+        * @param array $args                     Setting arguments; passed to WP_Customize_Setting
+        *                                        constructor.
         */
        public function add_setting( $id, $args = array() ) {
                if ( is_a( $id, 'WP_Customize_Setting' ) )
@@ -511,8 +715,8 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the setting.
-        * @return object The settings object.
+        * @param string $id Customize Setting ID.
+        * @return WP_Customize_Setting
         */
        public function get_setting( $id ) {
                if ( isset( $this->settings[ $id ] ) )
@@ -524,19 +728,66 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the setting.
+        * @param string $id Customize Setting ID.
         */
        public function remove_setting( $id ) {
                unset( $this->settings[ $id ] );
        }
 
+       /**
+        * Add a customize panel.
+        *
+        * @since 4.0.0
+        * @access public
+        *
+        * @param WP_Customize_Panel|string $id   Customize Panel object, or Panel ID.
+        * @param array                     $args Optional. Panel arguments. Default empty array.
+        */
+       public function add_panel( $id, $args = array() ) {
+               if ( is_a( $id, 'WP_Customize_Panel' ) ) {
+                       $panel = $id;
+               }
+               else {
+                       $panel = new WP_Customize_Panel( $this, $id, $args );
+               }
+
+               $this->panels[ $panel->id ] = $panel;
+       }
+
+       /**
+        * Retrieve a customize panel.
+        *
+        * @since 4.0.0
+        * @access public
+        *
+        * @param string $id Panel ID to get.
+        * @return WP_Customize_Panel Requested panel instance.
+        */
+       public function get_panel( $id ) {
+               if ( isset( $this->panels[ $id ] ) ) {
+                       return $this->panels[ $id ];
+               }
+       }
+
+       /**
+        * Remove a customize panel.
+        *
+        * @since 4.0.0
+        * @access public
+        *
+        * @param string $id Panel ID to remove.
+        */
+       public function remove_panel( $id ) {
+               unset( $this->panels[ $id ] );
+       }
+
        /**
         * Add a customize section.
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the section.
-        * @param array $args Section arguments.
+        * @param WP_Customize_Section|string $id   Customize Section object, or Section ID.
+        * @param array                       $args Section arguments.
         */
        public function add_section( $id, $args = array() ) {
                if ( is_a( $id, 'WP_Customize_Section' ) )
@@ -552,8 +803,8 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the section.
-        * @return object The section object.
+        * @param string $id Section ID.
+        * @return WP_Customize_Section
         */
        public function get_section( $id ) {
                if ( isset( $this->sections[ $id ] ) )
@@ -565,7 +816,7 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the section.
+        * @param string $id Section ID.
         */
        public function remove_section( $id ) {
                unset( $this->sections[ $id ] );
@@ -576,8 +827,9 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the control.
-        * @param array $args Setting arguments.
+        * @param WP_Customize_Control|string $id   Customize Control object, or ID.
+        * @param array                       $args Control arguments; passed to WP_Customize_Control
+        *                                          constructor.
         */
        public function add_control( $id, $args = array() ) {
                if ( is_a( $id, 'WP_Customize_Control' ) )
@@ -593,8 +845,8 @@ final class WP_Customize_Manager {
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the control.
-        * @return object The settings object.
+        * @param string $id ID of the control.
+        * @return WP_Customize_Control $control The control object.
         */
        public function get_control( $id ) {
                if ( isset( $this->controls[ $id ] ) )
@@ -602,67 +854,125 @@ final class WP_Customize_Manager {
        }
 
        /**
-        * Remove a customize setting.
+        * Remove a customize control.
         *
         * @since 3.4.0
         *
-        * @param string $id A specific ID of the control.
+        * @param string $id ID of the control.
         */
        public function remove_control( $id ) {
                unset( $this->controls[ $id ] );
        }
 
        /**
-        * Helper function to compare two objects by priority.
+        * Register a customize control type.
+        *
+        * Registered types are eligible to be rendered via JS and created dynamically.
+        *
+        * @since 4.1.0
+        * @access public
+        *
+        * @param string $control Name of a custom control which is a subclass of
+        *                        {@see WP_Customize_Control}.
+        */
+       public function register_control_type( $control ) {
+               $this->registered_control_types[] = $control;
+       }
+
+       /**
+        * Render JS templates for all registered control types.
+        *
+        * @since 4.1.0
+        * @access public
+        */
+       public function render_control_templates() {
+               foreach ( $this->registered_control_types as $control_type ) {
+                       $control = new $control_type( $this, 'temp', array() );
+                       $control->print_template();
+               }
+       }
+
+       /**
+        * Helper function to compare two objects by priority, ensuring sort stability via instance_number.
         *
         * @since 3.4.0
         *
-        * @param object $a Object A.
-        * @param object $b Object B.
+        * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $a Object A.
+        * @param {WP_Customize_Panel|WP_Customize_Section|WP_Customize_Control} $b Object B.
+        * @return int
         */
        protected final function _cmp_priority( $a, $b ) {
-               $ap = $a->priority;
-               $bp = $b->priority;
-
-               if ( $ap == $bp )
-                       return 0;
-               return ( $ap > $bp ) ? 1 : -1;
+               if ( $a->priority === $b->priority ) {
+                       return $a->instance_number - $a->instance_number;
+               } else {
+                       return $a->priority - $b->priority;
+               }
        }
 
        /**
-        * Prepare settings and sections.
+        * Prepare panels, sections, and controls.
+        *
+        * For each, check if required related components exist,
+        * whether the user has the necessary capabilities,
+        * and sort by priority.
         *
         * @since 3.4.0
         */
        public function prepare_controls() {
-               // Prepare controls
-               // Reversing makes uasort sort by time added when conflicts occur.
 
-               $this->controls = array_reverse( $this->controls );
                $controls = array();
+               uasort( $this->controls, array( $this, '_cmp_priority' ) );
 
                foreach ( $this->controls as $id => $control ) {
-                       if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() )
+                       if ( ! isset( $this->sections[ $control->section ] ) || ! $control->check_capabilities() ) {
                                continue;
+                       }
 
                        $this->sections[ $control->section ]->controls[] = $control;
                        $controls[ $id ] = $control;
                }
                $this->controls = $controls;
 
-               // Prepare sections
-               $this->sections = array_reverse( $this->sections );
+               // Prepare sections.
                uasort( $this->sections, array( $this, '_cmp_priority' ) );
                $sections = array();
 
                foreach ( $this->sections as $section ) {
-                       if ( ! $section->check_capabilities() || ! $section->controls )
+                       if ( ! $section->check_capabilities() || ! $section->controls ) {
                                continue;
+                       }
 
                        usort( $section->controls, array( $this, '_cmp_priority' ) );
-                       $sections[] = $section;
+
+                       if ( ! $section->panel ) {
+                               // Top-level section.
+                               $sections[ $section->id ] = $section;
+                       } else {
+                               // This section belongs to a panel.
+                               if ( isset( $this->panels [ $section->panel ] ) ) {
+                                       $this->panels[ $section->panel ]->sections[ $section->id ] = $section;
+                               }
+                       }
                }
                $this->sections = $sections;
+
+               // Prepare panels.
+               uasort( $this->panels, array( $this, '_cmp_priority' ) );
+               $panels = array();
+
+               foreach ( $this->panels as $panel ) {
+                       if ( ! $panel->check_capabilities() || ! $panel->sections ) {
+                               continue;
+                       }
+
+                       uasort( $panel->sections, array( $this, '_cmp_priority' ) );
+                       $panels[ $panel->id ] = $panel;
+               }
+               $this->panels = $panels;
+
+               // Sort panels and top-level sections together.
+               $this->containers = array_merge( $this->panels, $this->sections );
+               uasort( $this->containers, array( $this, '_cmp_priority' ) );
        }
 
        /**
@@ -683,6 +993,12 @@ final class WP_Customize_Manager {
         */
        public function register_controls() {
 
+               /* Control Types (custom control classes) */
+               $this->register_control_type( 'WP_Customize_Color_Control' );
+               $this->register_control_type( 'WP_Customize_Upload_Control' );
+               $this->register_control_type( 'WP_Customize_Image_Control' );
+               $this->register_control_type( 'WP_Customize_Background_Image_Control' );
+
                /* Site Title & Tagline */
 
                $this->add_section( 'title_tagline', array(
@@ -797,7 +1113,7 @@ final class WP_Customize_Manager {
                $this->add_control( new WP_Customize_Background_Image_Control( $this ) );
 
                $this->add_setting( 'background_repeat', array(
-                       'default'        => 'repeat',
+                       'default'        => get_theme_support( 'custom-background', 'default-repeat' ),
                        'theme_supports' => 'custom-background',
                ) );
 
@@ -814,7 +1130,7 @@ final class WP_Customize_Manager {
                ) );
 
                $this->add_setting( 'background_position_x', array(
-                       'default'        => 'left',
+                       'default'        => get_theme_support( 'custom-background', 'default-position-x' ),
                        'theme_supports' => 'custom-background',
                ) );
 
@@ -830,7 +1146,7 @@ final class WP_Customize_Manager {
                ) );
 
                $this->add_setting( 'background_attachment', array(
-                       'default'        => 'fixed',
+                       'default'        => get_theme_support( 'custom-background', 'default-attachment' ),
                        'theme_supports' => 'custom-background',
                ) );
 
@@ -839,8 +1155,8 @@ final class WP_Customize_Manager {
                        'section'    => 'background_image',
                        'type'       => 'radio',
                        'choices'    => array(
-                               'fixed'      => __('Fixed'),
                                'scroll'     => __('Scroll'),
+                               'fixed'      => __('Fixed'),
                        ),
                ) );
 
@@ -856,7 +1172,6 @@ final class WP_Customize_Manager {
 
                $locations      = get_registered_nav_menus();
                $menus          = wp_get_nav_menus();
-               $menu_locations = get_nav_menu_locations();
                $num_locations  = count( array_keys( $locations ) );
 
                $this->add_section( 'nav', array(
@@ -869,9 +1184,7 @@ final class WP_Customize_Manager {
                if ( $menus ) {
                        $choices = array( 0 => __( '&mdash; Select &mdash;' ) );
                        foreach ( $menus as $menu ) {
-                               $truncated_name = wp_html_excerpt( $menu->name, 40 );
-                               $truncated_name = ( $truncated_name == $menu->name ) ? $menu->name : trim( $truncated_name ) . '&hellip;';
-                               $choices[ $menu->term_id ] = $truncated_name;
+                               $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
                        }
 
                        foreach ( $locations as $location => $description ) {
@@ -947,21 +1260,35 @@ final class WP_Customize_Manager {
         * Callback for validating the header_textcolor value.
         *
         * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash().
+        * Returns default text color if hex color is empty.
         *
         * @since 3.4.0
+        *
+        * @param string $color
+        * @return string
         */
        public function _sanitize_header_textcolor( $color ) {
-               return ( 'blank' === $color ) ? 'blank' : sanitize_hex_color_no_hash( $color );
+               if ( 'blank' === $color )
+                       return 'blank';
+
+               $color = sanitize_hex_color_no_hash( $color );
+               if ( empty( $color ) )
+                       $color = get_theme_support( 'custom-header', 'default-text-color' );
+
+               return $color;
        }
-};
+}
 
 /**
- * Validates a hex color.
+ * Sanitizes a hex color.
  *
  * Returns either '', a 3 or 6 digit hex color (with #), or null.
- * For validating values without a #, see sanitize_hex_color_no_hash().
+ * For sanitizing values without a #, see sanitize_hex_color_no_hash().
  *
  * @since 3.4.0
+ *
+ * @param string $color
+ * @return string|null
  */
 function sanitize_hex_color( $color ) {
        if ( '' === $color )
@@ -984,6 +1311,9 @@ function sanitize_hex_color( $color ) {
  * Returns either '', a 3 or 6 digit hex color (without a #), or null.
  *
  * @since 3.4.0
+ *
+ * @param string $color
+ * @return string|null
  */
 function sanitize_hex_color_no_hash( $color ) {
        $color = ltrim( $color, '#' );
@@ -1001,6 +1331,9 @@ function sanitize_hex_color_no_hash( $color ) {
  * This method should only be necessary if using sanitize_hex_color_no_hash().
  *
  * @since 3.4.0
+ *
+ * @param string $color
+ * @return string
  */
 function maybe_hash_hex_color( $color ) {
        if ( $unhashed = sanitize_hex_color_no_hash( $color ) )