WordPress.org

Make WordPress Core

Changeset 33154


Ignore:
Timestamp:
07/10/2015 09:32:23 PM (3 years ago)
Author:
obenland
Message:

Site Icon: Add Customizer UI.

Second part of the Site Icon feature after [32994] introduced it for Settings.

Props celloexpressions.
See #16434.

Location:
trunk/src
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/admin-ajax.php

    r32677 r33154  
    6363    'save-user-color-scheme', 'update-widget', 'query-themes', 'parse-embed', 'set-attachment-thumbnail',
    6464    'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'press-this-save-post',
    65     'press-this-add-category',
     65    'press-this-add-category', 'crop-image',
    6666);
    6767
  • trunk/src/wp-admin/css/customize-controls.css

    r33153 r33154  
    752752.customize-control-image .current,
    753753.customize-control-background .current,
     754.customize-control-cropped_image .current,
     755.customize-control-site_icon .current,
    754756.customize-control-header .current {
    755757    margin-bottom: 8px;
     
    787789.customize-control-background .default-button,
    788790.customize-control-background .upload-button,
     791.customize-control-cropped_image .remove-button,
     792.customize-control-cropped_image .default-button,
     793.customize-control-cropped_image .upload-button,
     794.customize-control-site_icon .remove-button,
     795.customize-control-site_icon .default-button,
     796.customize-control-site_icon .upload-button,
    789797.customize-control-header button.new,
    790798.customize-control-header button.remove {
     
    798806.customize-control-image .current .container,
    799807.customize-control-background .current .container,
     808.customize-control-cropped_image .current .container,
     809.customize-control-site_icon .current .container,
    800810.customize-control-header .current .container {
    801811    overflow: hidden;
     
    809819.customize-control-upload .current .container,
    810820.customize-control-background .current .container,
     821.customize-control-cropped_image .current .container,
     822.customize-control-site_icon .current .container,
    811823.customize-control-image .current .container {
    812824    min-height: 40px;
     
    817829.customize-control-image .placeholder,
    818830.customize-control-background .placeholder,
     831.customize-control-cropped_image .placeholder,
     832.customize-control-site_icon .placeholder,
    819833.customize-control-header .placeholder {
    820834    width: 100%;
     
    828842.customize-control-image .inner,
    829843.customize-control-background .inner,
     844.customize-control-cropped_image .inner,
     845.customize-control-site_icon .inner,
    830846.customize-control-header .inner {
    831847    display: none;
     
    841857.customize-control-upload .inner,
    842858.customize-control-background .inner,
     859.customize-control-cropped_image .inner,
     860.customize-control-site_icon .inner,
    843861.customize-control-image .inner {
    844862    display: block;
     
    850868.customize-control-image .inner,
    851869.customize-control-background .inner,
     870.customize-control-cropped_image .inner,
     871.customize-control-site_icon .inner,
    852872.customize-control-header .inner,
    853873.customize-control-header .inner .dashicons {
     
    953973.customize-control-image .actions,
    954974.customize-control-background .actions,
     975.customize-control-cropped_image .actions,
     976.customize-control-site_icon .actions,
    955977.customize-control-header .actions {
    956978    margin-bottom: 32px;
     
    971993.customize-control-image img,
    972994.customize-control-background img,
     995.customize-control-cropped_image img,
     996.customize-control-site_icon img,
    973997.customize-control-header img {
    974998    width: 100%;
     
    9851009.customize-control-background .remove-button,
    9861010.customize-control-background .default-button,
     1011.customize-control-cropped_image .remove-button,
     1012.customize-control-cropped_image .default-button,
     1013.customize-control-site_icon .remove-button,
     1014.customize-control-site_icon .default-button,
    9871015.customize-control-header .remove {
    9881016    float: left;
     
    9941022.customize-control-image .upload-button,
    9951023.customize-control-background .upload-button,
     1024.customize-control-cropped_image .upload-button,
     1025.customize-control-site_icon .upload-button,
    9961026.customize-control-header .new {
    9971027    float: right;
  • trunk/src/wp-admin/includes/ajax-actions.php

    r32965 r33154  
    30533053    $GLOBALS['wp_press_this']->add_category();
    30543054}
     3055
     3056/**
     3057 * AJAX handler for cropping an image.
     3058 *
     3059 * @since 4.3.0
     3060 *
     3061 * @global WP_Site_Icon $wp_site_icon
     3062 */
     3063function wp_ajax_crop_image() {
     3064    $attachment_id = absint( $_POST['id'] );
     3065
     3066    check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
     3067    if ( ! current_user_can( 'customize' ) ) {
     3068        wp_send_json_error();
     3069    }
     3070
     3071    $context = str_replace( '_', '-', $_POST['context'] );
     3072    $data    = array_map( 'absint', $_POST['cropDetails'] );
     3073    $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
     3074
     3075    if ( ! $cropped || is_wp_error( $cropped ) ) {
     3076        wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
     3077    }
     3078
     3079    switch ( $context ) {
     3080        case 'site-icon':
     3081            require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
     3082            global $wp_site_icon;
     3083
     3084            /** This filter is documented in wp-admin/custom-header.php */
     3085            $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
     3086            $object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
     3087            unset( $object['ID'] );
     3088
     3089            // Update the attachment.
     3090            add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
     3091            $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
     3092            remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
     3093
     3094            // Additional sizes in wp_prepare_attachment_for_js().
     3095            add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
     3096            break;
     3097
     3098        default:
     3099
     3100            /**
     3101             * Filters the attachment id for a cropped image.
     3102             *
     3103             * @since 4.3.0
     3104             *
     3105             * @param int    $attachment_id The ID of the cropped image.
     3106             * @param string $context       The feature requesting the cropped image.
     3107             */
     3108            $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', 0, $context );
     3109
     3110            if ( ! $attachment_id ) {
     3111                wp_send_json_error();
     3112            }
     3113    }
     3114
     3115    wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
     3116}
  • trunk/src/wp-admin/includes/class-wp-site-icon.php

    r33084 r33154  
    275275                    <div class="site-icon-crop-preview-shell hide-if-no-js">
    276276                        <h3><?php _e( 'Preview' ); ?></h3>
    277                         <strong><?php _e( 'As your favicon' ); ?></strong>
     277                        <strong><?php _e( 'As a browser icon' ); ?></strong>
    278278                        <div class="site-icon-crop-favicon-preview-shell">
    279279                            <img src="images/browser.png" class="site-icon-browser-preview" width="182" height="" alt="<?php esc_attr_e( 'Browser Chrome' ); ?>"/>
     
    285285                        </div>
    286286
    287                         <strong><?php _e( 'As a mobile icon' ); ?></strong>
     287                        <strong><?php _e( 'As an app icon' ); ?></strong>
    288288                        <div class="site-icon-crop-preview-homeicon">
    289289                            <img src="<?php echo esc_url( $url ); ?>" id="preview-homeicon" alt="<?php esc_attr_e( 'Preview Home Icon' ); ?>"/>
     
    506506        // ensure that we only resize the image into
    507507        foreach ( $sizes as $name => $size_array ) {
    508             if ( $size_array['crop'] ) {
     508            if ( isset( $size_array['crop'] ) ) {
    509509                $only_crop_sizes[ $name ] = $size_array;
    510510            }
  • trunk/src/wp-admin/js/customize-controls.js

    r33153 r33154  
    18371837
    18381838    /**
     1839     * A control for selecting and cropping an image.
     1840     *
     1841     * @class
     1842     * @augments wp.customize.MediaControl
     1843     * @augments wp.customize.Control
     1844     * @augments wp.customize.Class
     1845     */
     1846    api.CroppedImageControl = api.MediaControl.extend({
     1847
     1848        /**
     1849         * Open the media modal to the library state.
     1850         */
     1851        openFrame: function( event ) {
     1852            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1853                return;
     1854            }
     1855
     1856            this.initFrame();
     1857            this.frame.setState( 'library' ).open();
     1858        },
     1859
     1860        /**
     1861         * Create a media modal select frame, and store it so the instance can be reused when needed.
     1862         */
     1863        initFrame: function() {
     1864            var l10n = _wpMediaViewsL10n;
     1865
     1866            this.frame = wp.media({
     1867                button: {
     1868                    text: l10n.select,
     1869                    close: false
     1870                },
     1871                states: [
     1872                    new wp.media.controller.Library({
     1873                        title: this.params.button_labels.frame_title,
     1874                        library: wp.media.query({ type: 'image' }),
     1875                        multiple: false,
     1876                        date: false,
     1877                        priority: 20,
     1878                        suggestedWidth: this.params.width,
     1879                        suggestedHeight: this.params.height
     1880                    }),
     1881                    new wp.media.controller.customizeImageCropper({
     1882                        imgSelectOptions: this.calculateImageSelectOptions,
     1883                        control: this
     1884                    })
     1885                ]
     1886            });
     1887
     1888            this.frame.on( 'select', this.onSelect, this );
     1889            this.frame.on( 'cropped', this.onCropped, this );
     1890            this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
     1891        },
     1892
     1893        /**
     1894         * After an image is selected in the media modal, switch to the cropper
     1895         * state if the image isn't the right size.
     1896         */
     1897        onSelect: function() {
     1898            var attachment = this.frame.state().get( 'selection' ).first().toJSON();
     1899
     1900            if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
     1901                this.setImageFromAttachment( attachment );
     1902                this.frame.close();
     1903            } else {
     1904                this.frame.setState( 'cropper' );
     1905            }
     1906        },
     1907
     1908        /**
     1909         * After the image has been cropped, apply the cropped image data to the setting.
     1910         *
     1911         * @param {object} croppedImage Cropped attachment data.
     1912         */
     1913        onCropped: function( croppedImage ) {
     1914            this.setImageFromAttachment( croppedImage );
     1915        },
     1916
     1917        /**
     1918         * Returns a set of options, computed from the attached image data and
     1919         * control-specific data, to be fed to the imgAreaSelect plugin in
     1920         * wp.media.view.Cropper.
     1921         *
     1922         * @param {wp.media.model.Attachment} attachment
     1923         * @param {wp.media.controller.Cropper} controller
     1924         * @returns {Object} Options
     1925         */
     1926        calculateImageSelectOptions: function( attachment, controller ) {
     1927            var control    = controller.get( 'control' ),
     1928                flexWidth  = !! parseInt( control.params.flex_width, 10 ),
     1929                flexHeight = !! parseInt( control.params.flex_height, 10 ),
     1930                realWidth  = attachment.get( 'width' ),
     1931                realHeight = attachment.get( 'height' ),
     1932                xInit = parseInt( control.params.width, 10 ),
     1933                yInit = parseInt( control.params.height, 10 ),
     1934                ratio = xInit / yInit,
     1935                xImg  = realWidth,
     1936                yImg  = realHeight,
     1937                imgSelectOptions;
     1938
     1939            controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
     1940
     1941            if ( xImg / yImg > ratio ) {
     1942                yInit = yImg;
     1943                xInit = yInit * ratio;
     1944            } else {
     1945                xInit = xImg;
     1946                yInit = xInit / ratio;
     1947            }
     1948
     1949            imgSelectOptions = {
     1950                handles: true,
     1951                keys: true,
     1952                instance: true,
     1953                persistent: true,
     1954                imageWidth: realWidth,
     1955                imageHeight: realHeight,
     1956                x1: 0,
     1957                y1: 0,
     1958                x2: xInit,
     1959                y2: yInit
     1960            };
     1961
     1962            if ( flexHeight === false && flexWidth === false ) {
     1963                imgSelectOptions.aspectRatio = xInit + ':' + yInit;
     1964            }
     1965            if ( flexHeight === false ) {
     1966                imgSelectOptions.maxHeight = yInit;
     1967            }
     1968            if ( flexWidth === false ) {
     1969                imgSelectOptions.maxWidth = xInit;
     1970            }
     1971
     1972            return imgSelectOptions;
     1973        },
     1974
     1975        /**
     1976         * Return whether the image must be cropped, based on required dimensions.
     1977         *
     1978         * @param {bool} flexW
     1979         * @param {bool} flexH
     1980         * @param {int}  dstW
     1981         * @param {int}  dstH
     1982         * @param {int}  imgW
     1983         * @param {int}  imgH
     1984         * @return {bool}
     1985         */
     1986        mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
     1987            if ( true === flexW && true === flexH ) {
     1988                return false;
     1989            }
     1990
     1991            if ( true === flexW && dstH === imgH ) {
     1992                return false;
     1993            }
     1994
     1995            if ( true === flexH && dstW === imgW ) {
     1996                return false;
     1997            }
     1998
     1999            if ( dstW === imgW && dstH === imgH ) {
     2000                return false;
     2001            }
     2002
     2003            if ( imgW <= dstW ) {
     2004                return false;
     2005            }
     2006
     2007            return true;
     2008        },
     2009
     2010        /**
     2011         * If cropping was skipped, apply the image data directly to the setting.
     2012         */
     2013        onSkippedCrop: function() {
     2014            var attachment = this.frame.state().get( 'selection' ).first().toJSON();
     2015            this.setImageFromAttachment( attachment );
     2016        },
     2017
     2018        /**
     2019         * Updates the setting and re-renders the control UI.
     2020         *
     2021         * @param {object} attachment
     2022         */
     2023        setImageFromAttachment: function( attachment ) {
     2024            this.params.attachment = attachment;
     2025
     2026            // Set the Customizer setting; the callback takes care of rendering.
     2027            this.setting( attachment.id );
     2028        }
     2029    });
     2030
     2031    /**
     2032     * A control for selecting and cropping Site Icons.
     2033     *
     2034     * @class
     2035     * @augments wp.customize.CroppedImageControl
     2036     * @augments wp.customize.MediaControl
     2037     * @augments wp.customize.Control
     2038     * @augments wp.customize.Class
     2039     */
     2040    api.SiteIconControl = api.CroppedImageControl.extend({
     2041        /**
     2042         * Updates the setting and re-renders the control UI.
     2043         *
     2044         * @param {object} attachment
     2045         */
     2046        setImageFromAttachment: function( attachment ) {
     2047            var icon = typeof attachment.sizes['site_icon-32'] !== 'undefined' ? attachment.sizes['site_icon-32'] : attachment.sizes.thumbnail;
     2048
     2049            this.params.attachment = attachment;
     2050
     2051            // Set the Customizer setting; the callback takes care of rendering.
     2052            this.setting( attachment.id );
     2053
     2054
     2055            // Update the icon in-browser.
     2056            $( 'link[rel="icon"]' ).attr( 'href', icon.url );
     2057        },
     2058
     2059        /**
     2060         * Called when the "Remove" link is clicked. Empties the setting.
     2061         *
     2062         * @param {object} event jQuery Event object
     2063         */
     2064        removeFile: function( event ) {
     2065            if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     2066                return;
     2067            }
     2068            event.preventDefault();
     2069
     2070            this.params.attachment = {};
     2071            this.setting( '' );
     2072            this.renderContent(); // Not bound to setting change when emptying.
     2073            $( 'link[rel="icon"]' ).attr( 'href', '' );
     2074        }
     2075    });
     2076
     2077    /**
    18392078     * @class
    18402079     * @augments wp.customize.Control
     
    26962935
    26972936    api.controlConstructor = {
    2698         color:      api.ColorControl,
    2699         media:      api.MediaControl,
    2700         upload:     api.UploadControl,
    2701         image:      api.ImageControl,
    2702         header:     api.HeaderControl,
    2703         background: api.BackgroundControl,
    2704         theme:      api.ThemeControl
     2937        color:         api.ColorControl,
     2938        media:         api.MediaControl,
     2939        upload:        api.UploadControl,
     2940        image:         api.ImageControl,
     2941        cropped_image: api.CroppedImageControl,
     2942        site_icon:     api.SiteIconControl,
     2943        header:        api.HeaderControl,
     2944        background:    api.BackgroundControl,
     2945        theme:         api.ThemeControl
    27052946    };
    27062947    api.panelConstructor = {};
  • trunk/src/wp-admin/options-general.php

    r33075 r33154  
    164164
    165165    <?php endif; ?>
    166     <p class="description"><?php _e( 'Site Icon creates a favicon and app icons for your site.' ) ?></p>
     166    <p class="description"><?php _e( 'The Site Icon is used as a browser and app icon for your site.' ); ?></p>
    167167</td>
    168168</tr>
  • trunk/src/wp-includes/class-wp-customize-control.php

    r33090 r33154  
    10021002
    10031003/**
     1004 * Customize Cropped Image Control class.
     1005 *
     1006 * @since 4.3.0
     1007 *
     1008 * @see WP_Customize_Image_Control
     1009 */
     1010class WP_Customize_Cropped_Image_Control extends WP_Customize_Image_Control {
     1011
     1012    /**
     1013     * Control type.
     1014     *
     1015     * @since 4.3.0
     1016     *
     1017     * @access public
     1018     * @var string
     1019     */
     1020    public $type = 'cropped_image';
     1021
     1022    /**
     1023     * Suggested width for cropped image.
     1024     *
     1025     * @since 4.3.0
     1026     *
     1027     * @access public
     1028     * @var int
     1029     */
     1030    public $width = 150;
     1031
     1032    /**
     1033     * Suggested height for cropped image.
     1034     *
     1035     * @since 4.3.0
     1036     *
     1037     * @access public
     1038     * @var int
     1039     */
     1040    public $height = 150;
     1041
     1042    /**
     1043     * Whether the width is flexible.
     1044     *
     1045     * @since 4.3.0
     1046     *
     1047     * @access public
     1048     * @var bool
     1049     */
     1050    public $flex_width = false;
     1051
     1052    /**
     1053     * Whether the height is flexible.
     1054     *
     1055     * @since 4.3.0
     1056     *
     1057     * @access public
     1058     * @var bool
     1059     */
     1060    public $flex_height = false;
     1061
     1062    /**
     1063     * Enqueue control related scripts/styles.
     1064     *
     1065     * @since 4.3.0
     1066     *
     1067     * @access public
     1068     */
     1069    public function enqueue() {
     1070        wp_enqueue_script( 'customize-views' );
     1071
     1072        parent::enqueue();
     1073    }
     1074
     1075    /**
     1076     * Refresh the parameters passed to the JavaScript via JSON.
     1077     *
     1078     * @since 4.3.0
     1079     *
     1080     * @access public
     1081     * @uses WP_Customize_Image_Control::to_json()
     1082     * @see WP_Customize_Control::to_json()
     1083     */
     1084    public function to_json() {
     1085        parent::to_json();
     1086
     1087        $this->json['width']       = absint( $this->width );
     1088        $this->json['height']      = absint( $this->height );
     1089        $this->json['flex_width']  = absint( $this->flex_width );
     1090        $this->json['flex_height'] = absint( $this->flex_height );
     1091    }
     1092
     1093}
     1094
     1095/**
     1096 * Customize Site Icon control class.
     1097 *
     1098 * Used only for custom functionality in JavaScript.
     1099 *
     1100 * @since 4.3.0
     1101 *
     1102 * @see WP_Customize_Cropped_Image_Control
     1103 */
     1104class WP_Customize_Site_Icon_Control extends WP_Customize_Cropped_Image_Control {
     1105
     1106    /**
     1107     * Control type.
     1108     *
     1109     * @since 4.3.0
     1110     *
     1111     * @access public
     1112     * @var string
     1113     */
     1114    public $type = 'site_icon';
     1115
     1116    /**
     1117     * Constructor.
     1118     *
     1119     * @since 4.3.0
     1120     *
     1121     * @param WP_Customize_Manager $manager
     1122     * @param string               $id
     1123     * @param array                $args
     1124     */
     1125    public function __construct( $manager, $id, $args = array() ) {
     1126        parent::__construct( $manager, $id, $args );
     1127        add_action( 'customize_controls_print_styles', 'wp_site_icon', 99 );
     1128    }
     1129}
     1130
     1131/**
    10041132 * Customize Header Image Control class.
    10051133 *
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r32964 r33154  
    12791279        $this->register_control_type( 'WP_Customize_Image_Control' );
    12801280        $this->register_control_type( 'WP_Customize_Background_Image_Control' );
     1281        $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
     1282        $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
    12811283        $this->register_control_type( 'WP_Customize_Theme_Control' );
    12821284
     
    13251327        }
    13261328
    1327         /* Site Title & Tagline */
     1329        /* Site Identity */
    13281330
    13291331        $this->add_section( 'title_tagline', array(
    1330             'title'    => __( 'Site Title & Tagline' ),
     1332            'title'    => __( 'Site Identity' ),
    13311333            'priority' => 20,
    13321334        ) );
     
    13531355            'section'    => 'title_tagline',
    13541356        ) );
     1357
     1358        $icon = wp_get_attachment_image_src( absint( get_option( 'site_icon' ) ), 'full' );
     1359        $this->add_setting( 'site_icon', array(
     1360            'default'    => $icon[0] ? $icon[0] : '',
     1361            'type'       => 'option',
     1362            'capability' => 'manage_options',
     1363            'transport'  => 'postMessage', // Previewed with JS in the Customizer controls window.
     1364        ) );
     1365
     1366        $this->add_control( new WP_Customize_Site_Icon_Control( $this, 'site_icon', array(
     1367            'label'       => __( 'Site Icon' ),
     1368            'description' => __( 'The Site Icon is used as a browser and app icon for your site. Icons must be square, and at least 512px wide and tall.' ),
     1369            'section'     => 'title_tagline',
     1370            'priority'    => 60,
     1371            'height'      => 512,
     1372            'width'       => 512,
     1373        ) ) );
    13551374
    13561375        /* Colors */
     
    13761395            'section'  => 'title_tagline',
    13771396            'type'     => 'checkbox',
     1397            'priority' => 40,
    13781398        ) );
    13791399
  • trunk/src/wp-includes/general-template.php

    r32994 r33154  
    24462446 */
    24472447function wp_site_icon() {
    2448     if ( ! has_site_icon() ) {
     2448    if ( ! has_site_icon() && ! is_customize_preview() ) {
    24492449        return;
    24502450    }
  • trunk/src/wp-includes/js/customize-views.js

    r31471 r33154  
    44    var api = wp.customize;
    55
     6    /**
     7     * Use a custom ajax action for cropped image controls.
     8     */
     9    wp.media.controller.customizeImageCropper = wp.media.controller.Cropper.extend( {
     10        doCrop: function( attachment ) {
     11            var cropDetails = attachment.get( 'cropDetails' ),
     12                control = this.get( 'control' );
     13
     14            cropDetails.dst_width  = control.params.width;
     15            cropDetails.dst_height = control.params.height;
     16
     17            return wp.ajax.post( 'crop-image', {
     18                wp_customize: 'on',
     19                nonce: attachment.get( 'nonces' ).edit,
     20                id: attachment.get( 'id' ),
     21                context: control.id,
     22                cropDetails: cropDetails
     23            } );
     24        }
     25    } );
    626
    727    /**
Note: See TracChangeset for help on using the changeset viewer.