X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/e059c33afecf52580f31b53393d6a95d08aaef35..a7cd4c052013b423c6301153f68c7fdbaa2a447b:/wp-includes/class-wp-customize-manager.php diff --git a/wp-includes/class-wp-customize-manager.php b/wp-includes/class-wp-customize-manager.php index d0c5fb25..c14d6165 100644 --- a/wp-includes/class-wp-customize-manager.php +++ b/wp-includes/class-wp-customize-manager.php @@ -1,26 +1,72 @@ widgets = new WP_Customize_Widgets( $this ); add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) ); @@ -41,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. @@ -56,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 ); @@ -69,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() ) @@ -88,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() ) @@ -95,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 */ @@ -112,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(); @@ -123,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 ); } @@ -137,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; @@ -145,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 */ @@ -163,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' ) ); @@ -172,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 ); } @@ -193,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' ) ); @@ -202,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 ); } @@ -238,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. * @@ -249,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. * @@ -266,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() ) @@ -279,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() ) @@ -288,24 +399,50 @@ 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 */ @@ -315,6 +452,7 @@ final class WP_Customize_Manager { $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 ); @@ -325,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. * @@ -356,36 +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() ) - ); - } + $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(); + } ?> 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; } @@ -504,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' ) ) @@ -522,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 ] ) ) @@ -535,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' ) ) @@ -563,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 ] ) ) @@ -576,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 ] ); @@ -587,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' ) ) @@ -604,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 ] ) ) @@ -613,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' ) ); } /** @@ -694,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( @@ -808,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', ) ); @@ -825,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', ) ); @@ -841,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', ) ); @@ -850,8 +1155,8 @@ final class WP_Customize_Manager { 'section' => 'background_image', 'type' => 'radio', 'choices' => array( - 'fixed' => __('Fixed'), 'scroll' => __('Scroll'), + 'fixed' => __('Fixed'), ), ) ); @@ -867,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( @@ -880,9 +1184,7 @@ final class WP_Customize_Manager { if ( $menus ) { $choices = array( 0 => __( '— Select —' ) ); foreach ( $menus as $menu ) { - $truncated_name = wp_html_excerpt( $menu->name, 40 ); - $truncated_name = ( $truncated_name == $menu->name ) ? $menu->name : trim( $truncated_name ) . '…'; - $choices[ $menu->term_id ] = $truncated_name; + $choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '…' ); } foreach ( $locations as $location => $description ) { @@ -958,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 ) @@ -995,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, '#' ); @@ -1012,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 ) )