Index: src/wp-admin/includes/admin.php =================================================================== --- src/wp-admin/includes/admin.php (revision 36674) +++ src/wp-admin/includes/admin.php (working copy) @@ -72,6 +72,9 @@ /** WordPress Site Icon API */ require_once(ABSPATH . 'wp-admin/includes/class-wp-site-icon.php'); +/** WordPress Site Logo API */ +require_once(ABSPATH . 'wp-admin/includes/class-wp-site-logo.php'); + /** WordPress Update Administration API */ require_once(ABSPATH . 'wp-admin/includes/update.php'); Index: src/wp-admin/includes/class-wp-site-logo.php =================================================================== --- src/wp-admin/includes/class-wp-site-logo.php (revision 0) +++ src/wp-admin/includes/class-wp-site-logo.php (working copy) @@ -0,0 +1,162 @@ +header_text_classes() ); + } + } + + /** + * Get header text classes. If not defined in add_theme_support(), defaults from Underscores will be used. + * + * @since 4.5.0 + * @access public + * + * @return string String of classes to hide + */ + public function header_text_classes() { + $args = get_theme_support( 'site-logo' ); + + if ( isset( $args[0]['header-text'] ) ) { + // Use any classes defined in add_theme_support(). + $classes = $args[0]['header-text']; + } else { + // Otherwise, use these defaults, which will work with any Underscores-based theme. + $classes = array( + 'site-title', + 'site-description', + ); + } + + // If we've got an array, reduce them to a string for output. + if ( is_array( $classes ) ) { + $classes = array_map( 'sanitize_html_class', $classes ); + $classes = (string) '.' . implode( ', .', $classes ); + } else { + $classes = (string) '.' . $classes; + } + + return $classes; + } + + /** + * Hide header text on front-end if necessary. + * + * @since 4.5.0 + * @access public + */ + public function head_text_styles() { + // Bail if our theme supports custom headers. + if ( current_theme_supports( 'custom-header' ) || get_theme_mod( 'site_logo_header_text', true ) ) { + return; + } + + // Is Display Header Text unchecked? If so, we need to hide our header text. + ?> + + + $size ) { + // If the size isn't already in the $sizes array, add it. + if ( ! array_key_exists( $size, $sizes ) ) { + $sizes[ $size ] = $size; + } + } + } + + return $sizes; + } + + /** + * Reset the site logo if the current logo is deleted in the media manager. + * + * @since 4.5.0 + * @access public + * + * @param int $post_id + */ + public function delete_attachment_data( $post_id ) { + $site_logo_id = get_theme_mod( 'site_logo' ); + + if ( $site_logo_id && $site_logo_id == $post_id ) { + remove_theme_mod( 'site_logo' ); + } + } + + /** + * Sanitize our header text Customizer setting. + * + * @since 4.5.0 + * @access public + * + * @param int|string $input Input value. + * @return int|string 1 if checked, empty string if not checked. + */ + public function sanitize_checkbox( $input ) { + return ( 1 == $input ) ? 1 : ''; + } +} + +/** + * WP_Site_Logo instance. + * + * @global WP_Site_Logo $wp_site_logo + */ +$GLOBALS['wp_site_logo'] = new WP_Site_Logo; Index: src/wp-admin/includes/template.php =================================================================== --- src/wp-admin/includes/template.php (revision 36674) +++ src/wp-admin/includes/template.php (working copy) @@ -1750,6 +1750,10 @@ $media_states[] = __( 'Site Icon' ); } + if ( $post->ID == get_theme_mod( 'site_logo' ) ) { + $media_states[] = __( 'Logo' ); + } + /** * Filter the default media display states for items in the Media list table. * @@ -1756,7 +1760,7 @@ * @since 3.2.0 * * @param array $media_states An array of media states. Default 'Header Image', - * 'Background Image', 'Site Icon'. + * 'Background Image', 'Site Icon', 'Logo'. */ $media_states = apply_filters( 'display_media_states', $media_states ); Index: src/wp-admin/js/customize-controls.js =================================================================== --- src/wp-admin/js/customize-controls.js (revision 36674) +++ src/wp-admin/js/customize-controls.js (working copy) @@ -1784,7 +1784,15 @@ }); // Re-render whenever the control's setting changes. - control.setting.bind( function () { control.renderContent(); } ); + control.setting.bind( function ( value ) { + // Send attachment information to the preview for possible use in `postMessage` transport. + wp.media.attachment( value ).fetch().done( function() { + wp.customize.previewer.send( control.id + '-attachment-data', this.attributes ); + } ); + alert( control.id + '-attachment-data' ); + + control.renderContent(); + } ); }, pausePlayer: function () { Index: src/wp-includes/class-wp-customize-manager.php =================================================================== --- src/wp-includes/class-wp-customize-manager.php (revision 36674) +++ src/wp-includes/class-wp-customize-manager.php (working copy) @@ -828,6 +828,9 @@ 'activeSections' => array(), 'activeControls' => array(), 'nonce' => $this->get_nonces(), + 'l10n' => array( + 'shiftClickToEdit' => __( 'Shift-click to edit this element.' ), + ), '_dirty' => array_keys( $this->unsanitized_post_values() ), ); @@ -1932,6 +1935,23 @@ 'section' => 'title_tagline', ) ); + // Add a setting to hide header text if the theme isn't supporting the feature itself. + // @todo + if ( ! current_theme_supports( 'custom-header' ) ) { + $this->add_setting( 'header_text', array( + 'default' => 1, + 'sanitize_callback' => 'absint', + 'transport' => 'postMessage', + ) ); + + $this->add_control( 'header_text', array( + 'label' => __( 'Display Site Title and Tagline' ), + 'section' => 'title_tagline', + 'settings' => 'header_text', + 'type' => 'checkbox', + ) ); + } + $this->add_setting( 'site_icon', array( 'type' => 'option', 'capability' => 'manage_options', @@ -1951,6 +1971,36 @@ 'width' => 512, ) ) ); + $this->add_setting( 'site_logo', array( + 'theme_supports' => array( 'site-logo' ), + 'transport' => 'postMessage', + ) ); + + $this->add_control( new WP_Customize_Media_Control( $this, 'site_logo', array( + 'label' => __( 'Logo' ), + 'section' => 'title_tagline', + 'mime_type' => 'image', + 'priority' => 80, + 'button_labels' => array( + 'select' => __( 'Select logo' ), + 'change' => __( 'Change logo' ), + 'placeholder' => __( 'No logo selected' ), + 'frame_title' => __( 'Select logo' ), + 'frame_button' => __( 'Choose logo' ), + ), + ) ) ); + + if ( isset( $this->selective_refresh ) ) { + $this->selective_refresh->add_partial( 'site_logo', array( + 'settings' => array( 'site_logo' ), + 'selector' => '.site-logo-link', + 'render_callback' => array( $this, '_render_site_logo_partial' ), + 'container_inclusive' => true, + ) ); + } + /* Colors */ $this->add_section( 'colors', array( @@ -2180,6 +2230,26 @@ return $color; } + + /** + * Callback for rendering the site logo, used in the site_logo partial. + * + * This method exists because the partial object and context data are passed + * into a partial's render_callback so we cannot use get_the_site_logo() as + * the render_callback directly since it expects a blog ID as the first + * argument. When WP no longer supports PHP 5.3, this method can be removed + * in favor of an anonymous function. + * + * @see WP_Customize_Manager::register_controls() + * + * @since 4.5.0 + * @access private + * + * @return string Site logo. + */ + public function _render_site_logo_partial() { + return get_the_site_logo(); + } } /** Index: src/wp-includes/customize/class-wp-customize-image-control.php =================================================================== --- src/wp-includes/customize/class-wp-customize-image-control.php (revision 36674) +++ src/wp-includes/customize/class-wp-customize-image-control.php (working copy) @@ -31,7 +31,7 @@ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); - $this->button_labels = array( + $this->button_labels = wp_parse_args( $this->button_labels, array( 'select' => __( 'Select Image' ), 'change' => __( 'Change Image' ), 'remove' => __( 'Remove' ), @@ -39,7 +39,7 @@ 'placeholder' => __( 'No image selected' ), 'frame_title' => __( 'Select Image' ), 'frame_button' => __( 'Choose Image' ), - ); + ) ); } /** Index: src/wp-includes/customize/class-wp-customize-media-control.php =================================================================== --- src/wp-includes/customize/class-wp-customize-media-control.php (revision 36674) +++ src/wp-includes/customize/class-wp-customize-media-control.php (working copy) @@ -55,15 +55,17 @@ public function __construct( $manager, $id, $args = array() ) { parent::__construct( $manager, $id, $args ); - $this->button_labels = array( - 'select' => __( 'Select File' ), - 'change' => __( 'Change File' ), - 'default' => __( 'Default' ), - 'remove' => __( 'Remove' ), - 'placeholder' => __( 'No file selected' ), - 'frame_title' => __( 'Select File' ), - 'frame_button' => __( 'Choose File' ), - ); + if ( ! is_a( $this, 'WP_Customize_Image_Control' ) ) { + $this->button_labels = wp_parse_args( $this->button_labels, array( + 'select' => __( 'Select File' ), + 'change' => __( 'Change File' ), + 'default' => __( 'Default' ), + 'remove' => __( 'Remove' ), + 'placeholder' => __( 'No file selected' ), + 'frame_title' => __( 'Select File' ), + 'frame_button' => __( 'Choose File' ), + ) ); + } } /** Index: src/wp-includes/customize/class-wp-customize-site-icon-control.php =================================================================== --- src/wp-includes/customize/class-wp-customize-site-icon-control.php (revision 36674) +++ src/wp-includes/customize/class-wp-customize-site-icon-control.php (working copy) @@ -41,4 +41,71 @@ parent::__construct( $manager, $id, $args ); add_action( 'customize_controls_print_styles', 'wp_site_icon', 99 ); } + + /** + * Render a JS template for the content of the site icon control. + * + * @since 4.5.0 + */ + public function content_template() { + ?> + + + <# if ( data.attachment && data.attachment.id ) { #> +