From b73a1c9aa942eed1ec13bbed59404cd9768d3a1a Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Wed, 24 Feb 2016 22:09:54 +0000 Subject: [PATCH] Customize: Introduce Logo support for themes. Allows a common theme feature to have a common implementation provided by core and available in a consistent location for users. See https://make.wordpress.org/core/2016/02/24/theme-logo-support/ Props kwight, enejb, jeherve, bhubbard, samhotchkiss, zinigor, eliorivero, adamsilverstein, melchoyce, ryan, mikeschroder, westonruter, pento, karmatosed, celloexpressions, obenland. See #33755. git-svn-id: https://develop.svn.wordpress.org/trunk@36698 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/customize-controls.css | 12 ++ src/wp-admin/includes/admin.php | 3 + src/wp-admin/includes/class-wp-site-logo.php | 136 ++++++++++++++++++ src/wp-admin/includes/template.php | 6 +- src/wp-admin/js/customize-controls.js | 38 +++++ .../class-wp-customize-manager.php | 62 ++++++++ .../class-wp-customize-site-icon-control.php | 67 +++++++++ .../class-wp-customize-site-logo-control.php | 53 +++++++ src/wp-includes/general-template.php | 87 +++++++++++ src/wp-includes/js/customize-preview.js | 56 ++++++++ src/wp-includes/post-template.php | 4 + 11 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 src/wp-admin/includes/class-wp-site-logo.php create mode 100644 src/wp-includes/customize/class-wp-customize-site-logo-control.php diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index 1a2d34602d..967a02f734 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -725,6 +725,7 @@ p.customize-section-description { .customize-control-background .current, .customize-control-cropped_image .current, .customize-control-site_icon .current, +.customize-control-site_logo .current, .customize-control-header .current { margin-bottom: 8px; } @@ -766,6 +767,9 @@ p.customize-section-description { .customize-control-site_icon .remove-button, .customize-control-site_icon .default-button, .customize-control-site_icon .upload-button, +.customize-control-site_logo .remove-button, +.customize-control-site_logo .default-button, +.customize-control-site_logo .upload-button, .customize-control-header button.new, .customize-control-header button.remove { white-space: normal; @@ -779,6 +783,7 @@ p.customize-section-description { .customize-control-background .current .container, .customize-control-cropped_image .current .container, .customize-control-site_icon .current .container, +.customize-control-site_logo .current .container, .customize-control-header .current .container { overflow: hidden; -webkit-border-radius: 2px; @@ -792,6 +797,7 @@ p.customize-section-description { .customize-control-background .current .container, .customize-control-cropped_image .current .container, .customize-control-site_icon .current .container, +.customize-control-site_logo .current .container, .customize-control-image .current .container { min-height: 40px; } @@ -820,6 +826,7 @@ p.customize-section-description { .customize-control-background .inner, .customize-control-cropped_image .inner, .customize-control-site_icon .inner, +.customize-control-site_logo .inner, .customize-control-header .inner { display: none; position: absolute; @@ -835,6 +842,7 @@ p.customize-section-description { .customize-control-background .inner, .customize-control-cropped_image .inner, .customize-control-site_icon .inner, +.customize-control-site_logo .inner, .customize-control-image .inner { display: block; min-height: 40px; @@ -846,6 +854,7 @@ p.customize-section-description { .customize-control-background .inner, .customize-control-cropped_image .inner, .customize-control-site_icon .inner, +.customize-control-site_logo.inner, .customize-control-header .inner, .customize-control-header .inner .dashicons { line-height: 20px; @@ -989,6 +998,8 @@ p.customize-section-description { .customize-control-cropped_image .default-button, .customize-control-site_icon .remove-button, .customize-control-site_icon .default-button, +.customize-control-site_logo .remove-button, +.customize-control-site_logo .default-button, .customize-control-header .remove { float: left; margin-right: 3px; @@ -1000,6 +1011,7 @@ p.customize-section-description { .customize-control-background .upload-button, .customize-control-cropped_image .upload-button, .customize-control-site_icon .upload-button, +.customize-control-site_logo .upload-button, .customize-control-header .new { float: right; } diff --git a/src/wp-admin/includes/admin.php b/src/wp-admin/includes/admin.php index a44abba917..5b749f966e 100644 --- a/src/wp-admin/includes/admin.php +++ b/src/wp-admin/includes/admin.php @@ -72,6 +72,9 @@ require_once(ABSPATH . 'wp-admin/includes/user.php'); /** 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'); diff --git a/src/wp-admin/includes/class-wp-site-logo.php b/src/wp-admin/includes/class-wp-site-logo.php new file mode 100644 index 0000000000..08eb3eb9a9 --- /dev/null +++ b/src/wp-admin/includes/class-wp-site-logo.php @@ -0,0 +1,136 @@ + + + + $size ) { + + // If the size isn't already in the $sizes array, add it. + if ( ! array_key_exists( $size, $sizes ) ) { + $sizes[ $size ] = $size; + } + } + } + + return $sizes; + } + + /** + * Get header text classes. If not defined in add_theme_support(), defaults from Underscores will be used. + * + * @since 4.5.0 + * @access protected + * + * @return string String of classes to hide + */ + protected 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; + } +} + +/** + * WP_Site_Logo instance. + * + * @global WP_Site_Logo $wp_site_logo + */ +$GLOBALS['wp_site_logo'] = new WP_Site_Logo; diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index 3587556f4f..bc103faa71 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -1750,13 +1750,17 @@ function _media_states( $post ) { $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. * * @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 ); diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index e1021a3e2e..0e10f2cf25 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -2302,6 +2302,43 @@ } }); + /** + * A control for selecting Site Logos. + * + * @class + * @augments wp.customize.MediaControl + * @augments wp.customize.Control + * @augments wp.customize.Class + */ + api.SiteLogoControl = api.MediaControl.extend({ + + /** + * When the control's DOM structure is ready, + * set up internal event bindings. + */ + ready: function() { + var control = this; + + // Shortcut so that we don't have to use _.bind every time we add a callback. + _.bindAll( control, 'restoreDefault', 'removeFile', 'openFrame', 'select' ); + + // Bind events, with delegation to facilitate re-rendering. + control.container.on( 'click keydown', '.upload-button', control.openFrame ); + control.container.on( 'click keydown', '.thumbnail-image img', control.openFrame ); + control.container.on( 'click keydown', '.default-button', control.restoreDefault ); + control.container.on( 'click keydown', '.remove-button', control.removeFile ); + + control.setting.bind( function( attachmentId ) { + wp.media.attachment( attachmentId ).fetch().done( function() { + wp.customize.previewer.send( 'site-logo-attachment-data', this.attributes ); + } ); + + // Re-render whenever the control's setting changes. + control.renderContent(); + } ); + } + }); + /** * @class * @augments wp.customize.Control @@ -3208,6 +3245,7 @@ image: api.ImageControl, cropped_image: api.CroppedImageControl, site_icon: api.SiteIconControl, + site_logo: api.SiteLogoControl, header: api.HeaderControl, background: api.BackgroundControl, theme: api.ThemeControl diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index ffc3ca5c5f..41adbee7b1 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -217,6 +217,7 @@ final class WP_Customize_Manager { require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' ); + require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-logo-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' ); @@ -828,6 +829,9 @@ final class WP_Customize_Manager { 'activeSections' => array(), 'activeControls' => array(), 'nonce' => $this->get_nonces(), + 'l10n' => array( + 'shiftClickToEdit' => __( 'Shift-click to edit this element.' ), + ), '_dirty' => array_keys( $this->unsanitized_post_values() ), ); @@ -1857,6 +1861,7 @@ final class WP_Customize_Manager { $this->register_control_type( 'WP_Customize_Background_Image_Control' ); $this->register_control_type( 'WP_Customize_Cropped_Image_Control' ); $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); + $this->register_control_type( 'WP_Customize_Site_Logo_Control' ); $this->register_control_type( 'WP_Customize_Theme_Control' ); /* Themes */ @@ -1932,6 +1937,23 @@ final class WP_Customize_Manager { '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 +1973,26 @@ final class WP_Customize_Manager { 'width' => 512, ) ) ); + $this->add_setting( 'site_logo', array( + 'theme_supports' => array( 'site-logo' ), + 'transport' => 'postMessage', + ) ); + + $this->add_control( new WP_Customize_Site_Logo_Control( $this, 'site_logo', array( + 'label' => __( 'Logo' ), + 'section' => 'title_tagline', + 'priority' => 0, + ) ) ); + + 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 +2222,26 @@ final class WP_Customize_Manager { 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(); + } } /** diff --git a/src/wp-includes/customize/class-wp-customize-site-icon-control.php b/src/wp-includes/customize/class-wp-customize-site-icon-control.php index fe8ac37114..4d20f8f817 100644 --- a/src/wp-includes/customize/class-wp-customize-site-icon-control.php +++ b/src/wp-includes/customize/class-wp-customize-site-icon-control.php @@ -41,4 +41,71 @@ class WP_Customize_Site_Icon_Control extends WP_Customize_Cropped_Image_Control 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 ) { #> +
+
+
+ +
+ + +
+ <?php esc_attr_e( 'Preview as a browser icon' ); ?> +
+ +
+ + +

+ <?php esc_attr_e( 'Preview as an app icon' ); ?> +

+
+
+
+
+ <# if ( data.canUpload ) { #> + + +
+ <# } #> +
+ <# } else { #> +
+
+
+
+ button_labels['placeholder']; ?> +
+
+
+
+
+ <# if ( data.defaultAttachment ) { #> + + <# } #> + <# if ( data.canUpload ) { #> + + <# } #> +
+
+ <# } #> + button_labels = array( + 'select' => __( 'Select logo' ), + 'change' => __( 'Change logo' ), + 'remove' => __( 'Remove' ), + 'default' => __( 'Default' ), + 'placeholder' => __( 'No logo selected' ), + 'frame_title' => __( 'Select logo' ), + 'frame_button' => __( 'Choose logo' ), + ); + } +} diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 3c9462f9af..ff2f0987f2 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -831,6 +831,93 @@ function has_site_icon( $blog_id = 0 ) { return (bool) get_site_icon_url( 512, '', $blog_id ); } +/** + * Whether the site has a Site Logo. + * + * @since 4.5.0 + * + * @param int $blog_id Optional. ID of the blog in question. Default current blog. + * @return bool Whether the site has a site logo or not. + */ +function has_site_logo( $blog_id = 0 ) { + if ( is_multisite() && (int) $blog_id !== get_current_blog_id() ) { + switch_to_blog( $blog_id ); + } + + $site_logo_id = get_theme_mod( 'site_logo' ); + + if ( is_multisite() && ms_is_switched() ) { + restore_current_blog(); + } + + return (bool) $site_logo_id; +} + +/** + * Returns a Site Logo, linked to home. + * + * @since 4.5.0 + * + * @param int $blog_id Optional. ID of the blog in question. Default current blog. + * @return string Site logo markup. + */ +function get_the_site_logo( $blog_id = 0 ) { + $html = ''; + + if ( is_multisite() && (int) $blog_id !== get_current_blog_id() ) { + switch_to_blog( $blog_id ); + } + + $site_logo_id = get_theme_mod( 'site_logo' ); + + if ( is_multisite() && ms_is_switched() ) { + restore_current_blog(); + } + $size = get_theme_support( 'site-logo' ); + $size = $size[0]['size']; + + // We have a logo. Logo is go. + if ( $site_logo_id ) { + $html = sprintf( '', + esc_url( home_url( '/' ) ), + wp_get_attachment_image( $site_logo_id, $size, false, array( + 'class' => "site-logo attachment-$size", + 'data-size' => $size, + 'itemprop' => 'logo', + ) ) + ); + } + + // If no logo is set but we're in the Customizer, leave a placeholder (needed for the live preview). + elseif ( is_customize_preview() ) { + $html = sprintf( '', + esc_url( home_url( '/' ) ), + esc_attr( $size ) + ); + } + + /** + * Filter the Site Logo output. + * + * @since 4.5.0 + * + * @param string $html Site Logo HTML output. + * @param string $size Size specified in add_theme_support declaration, or 'thumbnail' default. + */ + return apply_filters( 'get_the_site_logo', $html, $size ); +} + +/** + * Displays a Site Logo, linked to home. + * + * @since 4.5.0 + * + * @param int $blog_id Optional. ID of the blog in question. Default current blog. + */ +function the_site_logo( $blog_id = 0 ) { + echo get_the_site_logo( $blog_id ); +} + /** * Returns document title for the current page. * diff --git a/src/wp-includes/js/customize-preview.js b/src/wp-includes/js/customize-preview.js index b6da7759b2..053c309056 100644 --- a/src/wp-includes/js/customize-preview.js +++ b/src/wp-includes/js/customize-preview.js @@ -223,6 +223,62 @@ }); }); + /** + * Site Logo + * + * The site logo setting only contains the attachment ID. To avoid having to send an AJAX request to get more + * data, we send a separate message with the attachment data we get from the Customizer's media modal. + * Therefore first callback handles only the event of a new logo being selected. + * + * We don't need any information about a removed logo, so the second callback only handles that. + * + * @since 4.5.0 + */ + api.preview.bind( 'site-logo-attachment-data', function( attachment ) { + var $logo = $( '.site-logo' ), + size = $logo.data( 'size' ), + srcset = []; + + // If the source was smaller than the size required by the theme, give the biggest we've got. + if ( ! attachment.sizes[ size ] ) { + size = 'full'; + } + + _.each( attachment.sizes, function( size ) { + srcset.push( size.url + ' ' + size.width + 'w' ); + } ); + + $logo.attr( { + height: attachment.sizes[ size ].height, + width: attachment.sizes[ size ].width, + src: attachment.sizes[ size ].url, + srcset: srcset + } ); + + $( '.site-logo-link' ).show(); + $( 'body' ).addClass( 'wp-site-logo' ); + } ); + + api( 'site_logo', function( setting ) { + setting.bind( function( newValue ) { + if ( ! newValue ) { + $( '.site-logo-link' ).hide(); + $( 'body' ).removeClass( 'wp-site-logo' ); + } + } ); + + // Focus on the control when the logo is clicked, if there is no site_logo partial. + if ( ! api.selectiveRefresh || ! api.selectiveRefresh.partial.has( 'site_logo' ) ) { + $( document.body ).on( 'click', '.site-logo-link', function( e ) { + if ( ! e.shiftKey ) { + return; + } + api.preview.send( 'focus-control-for-setting', 'site_logo' ); + } ); + $( '.site-logo-link' ).attr( 'title', api.settings.l10n.shiftClickToEdit ); + } + } ); + api.trigger( 'preview-ready' ); }); diff --git a/src/wp-includes/post-template.php b/src/wp-includes/post-template.php index c162ca1c6d..d2a61dcf08 100644 --- a/src/wp-includes/post-template.php +++ b/src/wp-includes/post-template.php @@ -706,6 +706,10 @@ function get_body_class( $class = '' ) { if ( get_background_color() !== get_theme_support( 'custom-background', 'default-color' ) || get_background_image() ) $classes[] = 'custom-background'; + if ( has_site_logo() ) { + $classes[] = 'wp-site-logo'; + } + $page = $wp_query->get( 'page' ); if ( ! $page || $page < 2 )