*/
final class WP_Customize_Manager {
/**
- * An instance of the theme that is being customized.
+ * An instance of the theme being previewed.
*
* @var WP_Theme
*/
protected $original_stylesheet;
/**
- * Whether filters have been set to change the active theme to the theme being
- * customized.
+ * Whether this is a Customizer pageload.
*
* @var boolean
*/
protected $previewing = false;
/**
- * Methods and properties deailing with managing widgets in the customizer.
+ * Methods and properties deailing with managing widgets in the Customizer.
*
* @var WP_Customize_Widgets
*/
public $widgets;
- protected $settings = array();
- protected $sections = array();
- protected $controls = array();
+ protected $settings = array();
+ protected $containers = array();
+ protected $panels = array();
+ protected $sections = array();
+ protected $controls = array();
protected $nonce_tick;
protected $customized;
/**
- * $_POST values for Customize Settings.
+ * 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;
/**
* @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( ABSPATH . WPINC . '/class-wp-customize-widgets.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 );
// 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.
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();
$this->wp_die( -1 );
}
- // All good, let's do some internal business to preview the theme.
$this->start_previewing_theme();
}
*
* @since 3.4.0
*/
- function after_setup_theme() {
+ public function after_setup_theme() {
if ( ! $this->doing_ajax() && ! validate_current_theme() ) {
wp_redirect( 'themes.php?broken=true' );
exit;
}
/**
- * Start previewing the selected theme by adding 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
*/
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' ) );
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' ) );
return $this->controls;
}
+ /**
+ * Get the registered containers.
+ *
+ * @since 4.0.0
+ *
+ * @return array
+ */
+ public function containers() {
+ return $this->containers;
+ }
+
/**
* Get the registered sections.
*
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.
*
}
/**
- * Decode the $_POST['customized'] values for a specific Customize Setting.
+ * 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
*
- * @param mixed $setting A WP_Customize_Setting derived object
- * @return string $post_value Sanitized value
+ * @return array
*/
- public function post_value( $setting ) {
+ public function unsanitized_post_values() {
if ( ! isset( $this->_post_values ) ) {
- if ( isset( $_POST['customized'] ) )
+ if ( isset( $_POST['customized'] ) ) {
$this->_post_values = json_decode( wp_unslash( $_POST['customized'] ), true );
- else
+ }
+ 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
*/
$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 );
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.
*
}
/**
- * 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 ) {
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
*/
}
/**
- * 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
*/
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.
*
}
/**
- * 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,
*/
public function prepare_controls() {
- $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.
- // Reversing makes uasort sort by time added when conflicts occur.
- $this->sections = array_reverse( $this->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' ) );
}
/**
*/
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(
$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',
) );
) );
$this->add_setting( 'background_position_x', array(
- 'default' => 'left',
+ 'default' => get_theme_support( 'custom-background', 'default-position-x' ),
'theme_supports' => 'custom-background',
) );
) );
$this->add_setting( 'background_attachment', array(
- 'default' => 'fixed',
+ 'default' => get_theme_support( 'custom-background', 'default-attachment' ),
'theme_supports' => 'custom-background',
) );
'section' => 'background_image',
'type' => 'radio',
'choices' => array(
- 'fixed' => __('Fixed'),
'scroll' => __('Scroll'),
+ 'fixed' => __('Fixed'),
),
) );
$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(
return $color;
}
-};
+}
/**
* Sanitizes a hex color.
* Returns either '', a 3 or 6 digit hex color (without a #), or null.
*
* @since 3.4.0
- * @uses sanitize_hex_color()
*
* @param string $color
* @return string|null