diff --git a/src/wp-admin/css/site-icon.css b/src/wp-admin/css/site-icon.css new file mode 100644 index 0000000000..8a4d428d8a --- /dev/null +++ b/src/wp-admin/css/site-icon.css @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------------ + 28.0 - Site Icon +------------------------------------------------------------------------------*/ + +.site-icon-image { + float: left; + margin: 0 20px 0 0; +} + +.site-icon-content { + overflow: hidden; + padding: 10px; + position: relative; +} + +.site-icon-crop-shell { + max-width: 720px; +} + +.site-icon-crop-preview-shell { + float: right; + overflow: hidden; +} + +.site-icon-crop-preview-shell h3 { + margin-top: 0; +} + +.site-icon-crop-favicon-preview-shell { + margin-bottom: 20px; + position: relative; +} + +.site-icon-crop-preview-favicon, +.site-icon-browser-title { + height: 16px; + left: 88px; + overflow: hidden; + position: absolute; + top: 16px; +} + +.site-icon-crop-preview-favicon { + width: 16px; +} + +.site-icon-browser-title { + left: 109px; +} + +.site-icon-crop-preview-homeicon { + -webkit-border-radius: 16px; + border-radius: 16px; + height: 64px; + overflow: hidden; + width: 64px; +} diff --git a/src/wp-admin/css/wp-admin.css b/src/wp-admin/css/wp-admin.css index 13d66e20de..14c10f9cd9 100644 --- a/src/wp-admin/css/wp-admin.css +++ b/src/wp-admin/css/wp-admin.css @@ -10,4 +10,5 @@ @import url(about.css); @import url(nav-menus.css); @import url(widgets.css); +@import url(site-icon.css); @import url(l10n.css); diff --git a/src/wp-admin/images/browser.png b/src/wp-admin/images/browser.png new file mode 100644 index 0000000000..0747bdf206 Binary files /dev/null and b/src/wp-admin/images/browser.png differ diff --git a/src/wp-admin/includes/admin.php b/src/wp-admin/includes/admin.php index 426bb412dc..cee0a207bc 100644 --- a/src/wp-admin/includes/admin.php +++ b/src/wp-admin/includes/admin.php @@ -64,6 +64,9 @@ require_once(ABSPATH . 'wp-admin/includes/theme.php'); /** WordPress User Administration API */ require_once(ABSPATH . 'wp-admin/includes/user.php'); +/** WordPress Site Icon API */ +require_once(ABSPATH . 'wp-admin/includes/site-icon.php'); + /** WordPress Update Administration API */ require_once(ABSPATH . 'wp-admin/includes/update.php'); diff --git a/src/wp-admin/includes/site-icon.php b/src/wp-admin/includes/site-icon.php new file mode 100644 index 0000000000..8255b4debf --- /dev/null +++ b/src/wp-admin/includes/site-icon.php @@ -0,0 +1,636 @@ + 'site-icon-upload', + ) ); + } + + /** + * Removes site icon. + * + * @since 4.3.0 + */ + public function remove_site_icon() { + check_admin_referer( 'remove-site-icon' ); + + $this->delete_site_icon(); + + add_settings_error( 'site-icon', 'icon-removed', __( 'Site Icon removed.' ), 'updated' ); + } + + /** + * Uploading a site_icon is a 3 step process + * + * 1. Select the file to upload + * 2. Crop the file + * 3. Confirmation + * + * @since 4.3.0 + */ + public function upload_site_icon_page() { + $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'select_site_icon'; + + switch ( $action ) { + case 'select_site_icon': + $this->select_page(); + break; + + case 'crop_site_icon': + $this->crop_page(); + break; + + default: + wp_safe_redirect( admin_url( 'options-general.php#site-icon' ) ); + exit; + } + } + + /** + * Displays the site_icon form to upload the image. + * + * @since 4.3.0 + */ + public function select_page() { + ?> +
+

+ + +
+ 'site-icon', + 'action' => 'crop_site_icon', + ), wp_nonce_url( admin_url( 'options-general.php' ), 'crop-site-icon' ) ) ); + ?> +

+ + +

+
+ + + +

+ $this->min_size" ); ?> +

+ +
+ handle_upload(); + $attachment_id = $upload['attachment_id']; + $file = $upload['file']; + $url = $upload['url']; + } + + $image_size = getimagesize( $file ); + + if ( $image_size[0] < $this->min_size ) { + add_settings_error( 'site-icon', 'too-small', sprintf( __( 'The selected image is smaller than %upx in width.' ), $this->min_size ) ); + + // back to step one + $this->select_page(); + + return; + } + + if ( $image_size[1] < $this->min_size ) { + add_settings_error( 'site-icon', 'too-small', sprintf( __( 'The selected image is smaller than %upx in height.' ), $this->min_size ) ); + + // Back to step one. + $this->select_page(); + + return; + } + + // Let's resize the image so that the user can easier crop a image that in the admin view. + $crop_height = absint( $this->page_crop * $image_size[1] / $image_size[0] ); + $cropped = wp_crop_image( $attachment_id, 0, 0, 0, 0, $this->page_crop, $crop_height ); + if ( ! $cropped || is_wp_error( $cropped ) ) { + wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) ); + } + $cropped_size = getimagesize( $cropped ); + + // set default values (in case of no JS) + $crop_ratio = $image_size[0] / $cropped_size[0]; + if ( $cropped_size[0] < $cropped_size[1] ) { + $crop_x = 0; + $crop_y = absint( ( $cropped_size[1] - $cropped_size[0] ) / 2 ); + $crop_size = $cropped_size[0]; + } elseif ( $cropped_size[0] > $cropped_size[1] ) { + $crop_x = absint( ( $cropped_size[0] - $cropped_size[1] ) / 2 ); + $crop_y = 0; + $crop_size = $cropped_size[1]; + } else { + $crop_x = 0; + $crop_y = 0; + $crop_size = $cropped_size[0]; + } + + wp_delete_file( $cropped ); + + wp_localize_script( 'site-icon-crop', 'wpSiteIconCropData', $this->initial_crop_data( $crop_ratio, $cropped_size ) ); + ?> + +
+

+ + +
+
+

+

+ +
+

+ +
+ <?php esc_attr_e( 'Browser Chrome' ); ?> + +
+ <?php esc_attr_e( 'Preview Favicon' ); ?> +
+ +
+ + +
+ <?php esc_attr_e( 'Preview Home Icon' ); ?> +
+
+ <?php esc_attr_e( 'Image to be cropped' ); ?> + + + + + + + + + + + + + + +

+ + + +

+
+
+
+ delete_site_icon(); + + $attachment_id = absint( $_POST['attachment_id'] ); + + // TODO + if ( empty( $_POST['skip-cropping'] ) ) { + $crop_ratio = (float) $_POST['crop_ratio']; + $crop_data = $this->convert_coordinates_from_resized_to_full( $_POST['crop-x'], $_POST['crop-y'], $_POST['crop-w'], $_POST['crop-h'], $crop_ratio ); + $cropped = wp_crop_image( $attachment_id, $crop_data['crop_x'], $crop_data['crop_y'], $crop_data['crop_width'], $crop_data['crop_height'], $this->min_size, $this->min_size ); + } elseif ( ! empty( $_POST['create-new-attachment'] ) ) { + $cropped = _copy_image_file( $attachment_id ); + } else { + $cropped = get_attached_file( $attachment_id ); + } + + if ( ! $cropped || is_wp_error( $cropped ) ) { + wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) ); + } + + $object = $this->create_attachment_object( $cropped, $attachment_id ); + + if ( ! empty( $_POST['create-new-attachment'] ) ) { + unset( $object['ID'] ); + } + + // Update the attachment + add_filter( 'intermediate_image_sizes_advanced', array( $this, 'additional_sizes' ) ); + $attachment_id = $this->insert_attachment( $object, $cropped ); + remove_filter( 'intermediate_image_sizes_advanced', array( $this, 'additional_sizes' ) ); + + // Save the site_icon data into option + update_option( 'site_icon', $attachment_id ); + + add_settings_error( 'site-icon', 'icon-updated', __( 'Site Icon updated.' ), 'updated' ); + } + + /** + * This function is used to pass data to the localize script + * so that we can center the cropper and also set the minimum + * cropper. + * + * @since 4.3.0 + * + * @param float $ratio + * @param array $cropped_size + * @return array + */ + public function initial_crop_data( $ratio, $cropped_size ) { + $init_x = $init_y = $init_size = 0; + + $min_crop_size = ( $this->min_size / $ratio ); + $resized_width = $cropped_size[0]; + $resized_height = $cropped_size[1]; + + // Landscape format ( width > height ) + if ( $resized_width > $resized_height ) { + $init_x = ( $this->page_crop - $resized_height ) / 2; + $init_size = $resized_height; + } + + // Portrait format ( height > width ) + if ( $resized_width < $resized_height ) { + $init_y = ( $this->page_crop - $resized_width ) / 2; + $init_size = $resized_height; + } + + // Square height == width + if ( $resized_width == $resized_height ) { + $init_size = $resized_height; + } + + return array( + 'init_x' => $init_x, + 'init_y' => $init_y, + 'init_size' => $init_size, + 'min_size' => $min_crop_size, + ); + } + + /** + * Converts the coordinates from the downsized image to the original image for accurate cropping. + * + * @since 4.3.0 + * + * @param int $crop_x + * @param int $crop_y + * @param int $crop_width + * @param int $crop_height + * @param float $ratio + * @return array + */ + public function convert_coordinates_from_resized_to_full( $crop_x, $crop_y, $crop_width, $crop_height, $ratio ) { + return array( + 'crop_x' => floor( $crop_x * $ratio ), + 'crop_y' => floor( $crop_y * $ratio ), + 'crop_width' => floor( $crop_width * $ratio ), + 'crop_height' => floor( $crop_height * $ratio ), + ); + } + + /** + * Upload the file to be cropped in the second step. + * + * @since 4.3.0 + */ + public function handle_upload() { + $uploaded_file = $_FILES['site-icon']; + $file_type = wp_check_filetype_and_ext( $uploaded_file['tmp_name'], $uploaded_file['name'] ); + if ( ! wp_match_mime_types( 'image', $file_type['type'] ) ) { + wp_die( __( 'The uploaded file is not a valid image. Please try again.' ) ); + } + + $file = wp_handle_upload( $uploaded_file, array( 'test_form' => false ) ); + + if ( isset( $file['error'] ) ) { + wp_die( $file['error'], __( 'Image Upload Error' ) ); + } + + $url = $file['url']; + $type = $file['type']; + $file = $file['file']; + $filename = basename( $file ); + + // Construct the object array + $object = array( + 'post_title' => $filename, + 'post_content' => $url, + 'post_mime_type' => $type, + 'guid' => $url, + 'context' => 'site-icon', + ); + + // Save the data + $attachment_id = wp_insert_attachment( $object, $file ); + + return compact( 'attachment_id', 'file', 'filename', 'url', 'type' ); + } + + /** + * Create an attachment 'object'. + * + * @since 4.3.0 + * + * @param string $cropped Cropped image URL. + * @param int $parent_attachment_id Attachment ID of parent image. + * @return array Attachment object. + */ + public function create_attachment_object( $cropped, $parent_attachment_id ) { + $parent = get_post( $parent_attachment_id ); + $parent_url = $parent->guid; + $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url ); + + $size = @getimagesize( $cropped ); + $image_type = ( $size ) ? $size['mime'] : 'image/jpeg'; + + $object = array( + 'ID' => $parent_attachment_id, + 'post_title' => basename( $cropped ), + 'post_content' => $url, + 'post_mime_type' => $image_type, + 'guid' => $url, + 'context' => 'site-icon' + ); + + return $object; + } + + /** + * Insert an attachment and its metadata. + * + * @since 4.3.0 + * + * @param array $object Attachment object. + * @param string $cropped Cropped image URL. + * @return int Attachment ID. + */ + public function insert_attachment( $object, $cropped ) { + $attachment_id = wp_insert_attachment( $object, $cropped ); + $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped ); + + /** + * Filter the site icon attachment metadata. + * + * @since 4.3.0 + * + * @see wp_generate_attachment_metadata() + * + * @param array $metadata Attachment metadata. + */ + $metadata = apply_filters( 'site_icon_attachment_metadata', $metadata ); + wp_update_attachment_metadata( $attachment_id, $metadata ); + + return $attachment_id; + } + + /** + * Add additional sizes to be made when creating the site_icon images. + * + * @since 4.3.0 + * + * @param array $sizes + * @return array + */ + public function additional_sizes( $sizes = array() ) { + $only_crop_sizes = array(); + + /** + * Filter the different dimensions that a site icon is saved in. + * + * @since 4.3.0 + * + * @param array $site_icon_sizes Sizes available for the Site Icon. + */ + $this->site_icon_sizes = apply_filters( 'site_icon_image_sizes', $this->site_icon_sizes ); + + // Use a natural sort of numbers. + natsort( $this->site_icon_sizes ); + $this->site_icon_sizes = array_reverse( $this->site_icon_sizes ); + + // ensure that we only resize the image into + foreach ( $sizes as $name => $size_array ) { + if ( $size_array['crop'] ) { + $only_crop_sizes[ $name ] = $size_array; + } + } + + foreach ( $this->site_icon_sizes as $size ) { + if ( $size < $this->min_size ) { + $only_crop_sizes[ 'site_icon-' . $size ] = array( + 'width ' => $size, + 'height' => $size, + 'crop' => true, + ); + } + } + + return $only_crop_sizes; + } + + /** + * Add Site Icon sizes to the array of image sizes on demand. + * + * @since 4.3.0 + * + * @param array $sizes + * @return array + */ + public function intermediate_image_sizes( $sizes = array() ) { + /** This filter is documented in wp-admin/includes/site-icon.php */ + $this->site_icon_sizes = apply_filters( 'site_icon_image_sizes', $this->site_icon_sizes ); + foreach ( $this->site_icon_sizes as $size ) { + $sizes[] = 'site_icon-' . $size; + } + + return $sizes; + } + + /** + * Deletes all additional image sizes, used for site icons. + * + * @since 4.3.0 + * + * @return bool + */ + public function delete_site_icon() { + // We add the filter to make sure that we also delete all the added images. + add_filter( 'intermediate_image_sizes', array( $this, 'intermediate_image_sizes' ) ); + wp_delete_attachment( get_option( 'site_icon' ), true ); + remove_filter( 'intermediate_image_sizes', array( $this, 'intermediate_image_sizes' ) ); + + return delete_option( 'site_icon' ); + } + + /** + * Deletes the Site Icon when the image file is deleted. + * + * @since 4.3.0 + * + * @param int $post_id Attachment ID. + */ + public function delete_attachment_data( $post_id ) { + $site_icon_id = get_option( 'site_icon' ); + + if ( $site_icon_id && $post_id == $site_icon_id ) { + delete_option( 'site_icon' ); + } + } + + /** + * Adds custom image sizes when meta data for an image is requested, that happens to be used as Site Icon. + * + * @since 4.3.0 + * + * @param null|array|string $value The value get_metadata() should + * return - a single metadata value, + * or an array of values. + * @param int $post_id Post ID. + * @param string $meta_key Meta key. + * @param string|array $single Meta value, or an array of values. + * @return array|null|string + */ + public function get_post_metadata( $value, $post_id, $meta_key, $single ) { + $site_icon_id = get_option( 'site_icon' ); + + if ( $post_id == $site_icon_id && '_wp_attachment_backup_sizes' == $meta_key && $single ) { + add_filter( 'intermediate_image_sizes', array( $this, 'intermediate_image_sizes' ) ); + } + + return $value; + } +} + +$GLOBALS['wp_site_icon'] = new WP_Site_Icon; diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index 82155cfdbc..302e9b48dc 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -1778,13 +1778,17 @@ function _media_states( $post ) { $media_states[] = __( 'Background Image' ); } + if ( $post->ID == get_option( 'site_icon' ) ) { + $media_states[] = __( 'Site Icon' ); + } + /** * 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'. + * 'Background Image', 'Site Icon'. */ $media_states = apply_filters( 'display_media_states', $media_states ); diff --git a/src/wp-admin/js/site-icon-crop.js b/src/wp-admin/js/site-icon-crop.js new file mode 100644 index 0000000000..ebe727454e --- /dev/null +++ b/src/wp-admin/js/site-icon-crop.js @@ -0,0 +1,54 @@ +/* global wpSiteIconCropData, jQuery */ +(function($) { + var jcrop_api = {}, + siteIconCrop = { + + updateCoords : function ( coords ) { + $( '#crop-x' ).val( coords.x ); + $( '#crop-y' ).val( coords.y ); + $( '#crop-width' ).val( coords.w ); + $( '#crop-height' ).val( coords.h ); + + siteIconCrop.showPreview( coords ); + }, + + showPreview : function( coords ){ + var rx = 64 / coords.w, + ry = 64 / coords.h, + preview_rx = 16 / coords.w, + preview_ry = 16 / coords.h, + $cropImage = $( '#crop-image' ), + $homeIcon = $( '#preview-homeicon' ), + $favicon = $( '#preview-favicon' ); + + $homeIcon.css({ + width: Math.round(rx * $cropImage.attr( 'width' ) ) + 'px', + height: Math.round(ry * $cropImage.attr( 'height' ) ) + 'px', + marginLeft: '-' + Math.round(rx * coords.x) + 'px', + marginTop: '-' + Math.round(ry * coords.y) + 'px' + }); + + $favicon.css({ + width: Math.round( preview_rx * $cropImage.attr( 'width' ) ) + 'px', + height: Math.round( preview_ry * $cropImage.attr( 'height' ) ) + 'px', + marginLeft: '-' + Math.round( preview_rx * coords.x ) + 'px', + marginTop: '-' + Math.floor( preview_ry* coords.y ) + 'px' + }); + }, + + ready: function() { + jcrop_api = $.Jcrop( '#crop-image' ); + jcrop_api.setOptions({ + aspectRatio: 1, + onSelect: siteIconCrop.updateCoords, + onChange: siteIconCrop.updateCoords, + minSize: [ wpSiteIconCropData.min_size, wpSiteIconCropData.min_size ] + }); + jcrop_api.animateTo([wpSiteIconCropData.init_x, wpSiteIconCropData.init_y, wpSiteIconCropData.init_size, wpSiteIconCropData.init_size]); + } + + }; + + siteIconCrop.ready(); + +})(jQuery); \ No newline at end of file diff --git a/src/wp-admin/js/site-icon.js b/src/wp-admin/js/site-icon.js new file mode 100644 index 0000000000..2328d529f6 --- /dev/null +++ b/src/wp-admin/js/site-icon.js @@ -0,0 +1,49 @@ +(function($) { + var frame; + + $( function() { + // Build the choose from library frame. + $( '#choose-from-library-link' ).click( function( event ) { + var $el = $(this); + event.preventDefault(); + + // If the media frame already exists, reopen it. + if ( frame ) { + frame.open(); + return; + } + + // Create the media frame. + frame = wp.media.frames.customHeader = wp.media({ + // Set the title of the modal. + title: $el.data('choose'), + + // Tell the modal to show only images. + library: { + type: 'image' + }, + + // Customize the submit button. + button: { + // Set the text of the button. + text: $el.data('update'), + // Tell the button not to close the modal, since we're + // going to refresh the page when the image is selected. + close: false + } + }); + + // When an image is selected, run a callback. + frame.on( 'select', function() { + // Grab the selected attachment. + var attachment = frame.state().get('selection').first(), + link = $el.data('updateLink'); + + // Tell the browser to navigate to the crop step. + window.location = link + '&file=' + attachment.id; + }); + + frame.open(); + }); + }); +}(jQuery)); diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 7f2a14ea4f..b8175fbb09 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -124,6 +124,48 @@ include( ABSPATH . 'wp-admin/admin-header.php' );

+ + + + 'site-icon', + 'action' => 'crop_site_icon', + ), wp_nonce_url( admin_url( 'options-general.php' ), 'crop-site-icon' ) ) ); + + wp_enqueue_media(); + wp_enqueue_script( 'site-icon' ); + + if ( has_site_icon() ) : + $remove_url = add_query_arg( array( + 'action' => 'remove_site_icon', + ), wp_nonce_url( admin_url( 'options-general.php' ), 'remove-site-icon' ) ); + ?> + + +

+ + + +

+

+ + +

+ + + +

+ + +

+ + + +

+ + diff --git a/src/wp-admin/options.php b/src/wp-admin/options.php index 752d7b46ff..3dc5d2dd9b 100644 --- a/src/wp-admin/options.php +++ b/src/wp-admin/options.php @@ -72,7 +72,7 @@ if ( is_multisite() && !is_super_admin() && 'update' != $action ) wp_die( __( 'Cheatin’ uh?' ), 403 ); $whitelist_options = array( - 'general' => array( 'blogname', 'blogdescription', 'gmt_offset', 'date_format', 'time_format', 'start_of_week', 'timezone_string', 'WPLANG' ), + 'general' => array( 'blogname', 'blogdescription', 'site_icon', 'gmt_offset', 'date_format', 'time_format', 'start_of_week', 'timezone_string', 'WPLANG' ), 'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'page_comments', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ), 'media' => array( 'thumbnail_size_w', 'thumbnail_size_h', 'thumbnail_crop', 'medium_size_w', 'medium_size_h', 'large_size_w', 'large_size_h', 'image_default_size', 'image_default_align', 'image_default_link_type' ), 'reading' => array( 'posts_per_page', 'posts_per_rss', 'rss_use_excerpt', 'show_on_front', 'page_on_front', 'page_for_posts', 'blog_public' ), diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 257cbfa3ad..b1f69ea98c 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -222,8 +222,9 @@ add_action( 'wp_head', 'wp_print_styles', 8 ); add_action( 'wp_head', 'wp_print_head_scripts', 9 ); add_action( 'wp_head', 'wp_generator' ); add_action( 'wp_head', 'rel_canonical' ); -add_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); add_action( 'wp_head', 'wp_shortlink_wp_head', 10, 0 ); +add_action( 'wp_head', 'wp_site_icon', 99 ); +add_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 ); add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); add_action( 'init', 'check_theme_switched', 99 ); @@ -243,6 +244,11 @@ foreach ( array( 'rss2_head', 'commentsrss2_head', 'rss_head', 'rdf_header', 'at add_action( $action, 'the_generator' ); } +// Feed Site Icon +add_action( 'atom_head', 'atom_site_icon' ); +add_action( 'rss2_head', 'rss2_site_icon' ); + + // WP Cron if ( !defined( 'DOING_CRON' ) ) add_action( 'init', 'wp_cron' ); diff --git a/src/wp-includes/feed.php b/src/wp-includes/feed.php index 4554d0e461..93a6ddf268 100644 --- a/src/wp-includes/feed.php +++ b/src/wp-includes/feed.php @@ -587,6 +587,42 @@ function prep_atom_text_construct($data) { } } +/** + * Display Site Icon in atom feeds. + * + * @since 4.3.0 + */ +function atom_site_icon() { + $url = get_site_icon_url( null, 32 ); + if ( $url ) { + echo "$url\n"; + } +} + +/** + * Display Site Icon in RSS2. + * + * @since 4.3.0 + */ +function rss2_site_icon() { + $rss_title = get_wp_title_rss(); + if ( empty( $rss_title ) ) { + $rss_title = get_bloginfo_rss( 'name' ); + } + + $url = get_site_icon_url( null, 32 ); + if ( $url ) { + echo ' + + ' . convert_chars( $url ) . ' + ' . $rss_title . ' + ' . get_bloginfo_rss( 'url' ) . ' + 32 + 32 + ' . "\n"; + } +} + /** * Display the link for the currently displayed feed in a XSS safe way. * diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index aa8f398e22..8578ad1e6c 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -3421,6 +3421,7 @@ function sanitize_option( $option, $value ) { case 'thread_comments_depth': case 'users_can_register': case 'start_of_week': + case 'site_icon': $value = absint( $value ); break; diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 3c3f88f02c..a95da70f79 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -721,6 +721,58 @@ function get_bloginfo( $show = '', $filter = 'raw' ) { return $output; } +/** + * Returns the Site Icon URL. + * + * @param null|int $blog_id Id of the blog to get the site icon for. + * @param int $size Size of the site icon. + * @param string $url Fallback url if no site icon is found. + * @return string Site Icon URL. + */ +function get_site_icon_url( $blog_id = null, $size = 512, $url = '' ) { + if ( function_exists( 'get_blog_option' ) ) { + if ( ! is_int( $blog_id ) ) { + $blog_id = get_current_blog_id(); + } + $site_icon_id = get_blog_option( $blog_id, 'site_icon' ); + } else { + $site_icon_id = get_option( 'site_icon' ); + } + + if ( $site_icon_id ) { + if ( $size >= 512 ) { + $size_data = 'full'; + } else { + $size_data = array( $size, $size ); + } + $url_data = wp_get_attachment_image_src( $site_icon_id, $size_data ); + $url = $url_data[0]; + } + + return $url; +} + +/** + * Displays the Site Icon URL. + * + * @param null|int $blog_id Id of the blog to get the site icon for. + * @param int $size Size of the site icon. + * @param string $url Fallback url if no site icon is found. + */ +function site_icon_url( $blog_id = null, $size = 512, $url = '' ) { + echo esc_url( get_site_icon_url( $blog_id, $size, $url ) ); +} + +/** + * Whether the site has a Site Icon. + * + * @param int|null $blog_id Optional. Blog ID. Default: Current blog. + * @return bool + */ +function has_site_icon( $blog_id = null ) { + return !! get_site_icon_url( $blog_id, 512 ); +} + /** * Display title tag with contents. * @@ -2385,6 +2437,39 @@ function wp_no_robots() { echo "\n"; } +/** + * Display site icon meta tags. + * + * @since 4.3.0 + * + * @link http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon HTML5 specification link icon. + */ +function wp_site_icon() { + if ( ! has_site_icon() ) { + return; + } + + $meta_tags = array( + sprintf( '', esc_url( get_site_icon_url( null, 32 ) ) ), + sprintf( '', esc_url( get_site_icon_url( null, 180 ) ) ), + sprintf( '', esc_url( get_site_icon_url( null, 270 ) ) ), + ); + + /** + * Filters the site icon meta tags, so Plugins can add their own. + * + * @since 4.3.0 + * + * @param array $meta_tags Site Icon meta elements. + */ + $meta_tags = apply_filters( 'site_icon_meta_tags', $meta_tags ); + $meta_tags = array_filter( $meta_tags ); + + foreach ( $meta_tags as $meta_tag ) { + echo "$meta_tag\n"; + } +} + /** * Whether the user should have a WYSIWIG editor. * diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 287a23ed06..79d060136d 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -612,6 +612,9 @@ function wp_default_scripts( &$scripts ) { 'untitled' => _x( '(no label)', 'missing menu item navigation label' ) ) ); + $scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery' ), false, 1 ); + $scripts->add( 'site-icon-crop', '/wp-admin/js/site-icon-crop.js', array( 'jcrop' ), false, 1 ); + $scripts->add( 'custom-header', "/wp-admin/js/custom-header.js", array( 'jquery-masonry' ), false, 1 ); $scripts->add( 'custom-background', "/wp-admin/js/custom-background$suffix.js", array( 'wp-color-picker', 'media-views' ), false, 1 ); $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array('jquery'), false, 1 );