WordPress.org

Make WordPress Core

Changeset 27497


Ignore:
Timestamp:
03/11/2014 04:12:17 AM (6 years ago)
Author:
nacin
Message:

Add header image uploads with cropping to the customizer.

props mcsf, ehg, gcorne.
see #21785.

Location:
trunk/src
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r27174 r27497  
    456456}
    457457
     458/** Header control **/
     459
     460#customize-control-header_image .current {
     461    margin-bottom: 8px;
     462}
     463
     464#customize-control-header_image .uploaded {
     465    margin-bottom: 18px;
     466}
     467
     468/* Header control: current image container */
     469
     470#customize-control-header_image .current .container {
     471    overflow: hidden;
     472    border-radius: 2px;
     473}
     474
     475#customize-control-header_image .placeholder {
     476    width: 100%;
     477    position: relative;
     478    background: #262626;
     479    text-align: center;
     480    cursor: default;
     481}
     482
     483#customize-control-header_image .inner {
     484    display: none;
     485    position: absolute;
     486    width: 100%;
     487    height: 18px;
     488    margin-top: -9px;
     489    top: 50%;
     490    color: #eee;
     491}
     492
     493/* Header control: overlay "close" button */
     494
     495#customize-control-header_image .header-view {
     496    position: relative;
     497}
     498
     499#customize-control-header_image .uploaded .header-view .close {
     500    font-size: 2em;
     501    color: grey;
     502    position: absolute;
     503    visibility: hidden;
     504    top: 10px;
     505    right: 10px;
     506    z-index: 1;
     507    width: 20px;
     508    height: 20px;
     509    cursor: pointer;
     510}
     511
     512#customize-control-header_image .uploaded .header-view .close:hover {
     513 color: black;
     514 text-shadow:
     515    -1px -1px 0 #fff,
     516    1px -1px 0 #fff,
     517    -1px 1px 0 #fff,
     518    1px 1px 0 #fff;
     519}
     520
     521#customize-control-header_image .header-view:hover .close {
     522    visibility: visible;
     523}
     524
     525/* Header control: randomiz(s)er */
     526
     527#customize-control-header_image .random.placeholder {
     528    cursor: pointer;
     529    border-radius: 2px;
     530    height: 40px;
     531}
     532
     533#customize-control-header_image .random .inner {
     534    display: block;
     535}
     536
     537#customize-control-header_image .dice {
     538    font-size: 16px;
     539    vertical-align: -1px;
     540}
     541
     542#customize-control-header_image .placeholder:hover .dice {
     543    -webkit-animation: dice-color-change 3s infinite;
     544    -moz-animation: dice-color-change 3s infinite;
     545    -ms-animation: dice-color-change 3s infinite;
     546    animation: dice-color-change 3s infinite;
     547}
     548
     549@-webkit-keyframes dice-color-change {
     550    0% { color: #d4b146; }
     551    50% { color: #ef54b0; }
     552    75% { color: #7190d3; }
     553    100% { color: #d4b146; }
     554}
     555
     556@-moz-keyframes dice-color-change {
     557    0% { color: #d4b146; }
     558    50% { color: #ef54b0; }
     559    75% { color: #7190d3; }
     560    100% { color: #d4b146; }
     561}
     562
     563@-ms-keyframes dice-color-change {
     564    0% { color: #d4b146; }
     565    50% { color: #ef54b0; }
     566    75% { color: #7190d3; }
     567    100% { color: #d4b146; }
     568}
     569
     570@keyframes dice-color-change {
     571    0% { color: #d4b146; }
     572    50% { color: #ef54b0; }
     573    75% { color: #7190d3; }
     574    100% { color: #d4b146; }
     575}
     576
     577/* Header control: actions and choices */
     578
     579#customize-control-header_image .actions {
     580    margin-bottom: 32px;
     581}
     582
     583#customize-control-header_image .choice {
     584    position: relative;
     585    display: block;
     586    margin-bottom: 9px;
     587}
     588
     589#customize-control-header_image .choice.random:before {
     590    position: absolute;
     591    content: attr(data-label);
     592    left: 0;
     593    top: 0;
     594}
     595
     596#customize-control-header_image .uploaded div:last-child > .choice {
     597    margin-bottom: 0;
     598}
     599
     600#customize-control-header_image .choices hr {
     601    visibility: hidden;
     602}
     603
     604#customize-control-header_image img {
     605    width: 100%;
     606    border-radius: 2px;
     607}
     608
     609#customize-control-header_image .remove {
     610    float: left;
     611    margin-right: 3px;
     612}
     613
     614#customize-control-header_image .new {
     615    float: right;
     616}
     617
     618
    458619/** Handle cheaters. */
    459620body.cheatin {
  • trunk/src/wp-admin/custom-header.php

    r27469 r27497  
    4444
    4545    /**
    46      * Holds custom headers uploaded by the user
     46     * Holds custom headers uploaded by the user.
    4747     *
    4848     * @var array
     
    7474
    7575        add_action( 'admin_menu', array( $this, 'init' ) );
     76
     77        add_action( 'customize_save_after',         array( $this, 'customize_set_last_used' ) );
     78        add_action( 'wp_ajax_custom-header-crop',   array( $this, 'ajax_header_crop'        ) );
     79        add_action( 'wp_ajax_custom-header-add',    array( $this, 'ajax_header_add'         ) );
     80        add_action( 'wp_ajax_custom-header-remove', array( $this, 'ajax_header_remove'      ) );
    7681    }
    7782
     
    9499        if ( $this->admin_header_callback )
    95100            add_action("admin_head-$page", $this->admin_header_callback, 51);
     101
    96102    }
    97103
     
    820826        $original = get_attached_file($attachment_id);
    821827
    822 
    823         $max_width = 0;
    824         // For flex, limit size of image displayed to 1500px unless theme says otherwise
    825         if ( current_theme_supports( 'custom-header', 'flex-width' ) )
    826             $max_width = 1500;
    827 
    828         if ( current_theme_supports( 'custom-header', 'max-width' ) )
    829             $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
    830         $max_width = max( $max_width, get_theme_support( 'custom-header', 'width' ) );
    831 
    832         if ( ( current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) || $_POST['width'] > $max_width )
    833             $dst_height = absint( $_POST['height'] * ( $max_width / $_POST['width'] ) );
    834         elseif ( current_theme_supports( 'custom-header', 'flex-height' ) && current_theme_supports( 'custom-header', 'flex-width' ) )
    835             $dst_height = absint( $_POST['height'] );
    836         else
    837             $dst_height = get_theme_support( 'custom-header', 'height' );
    838 
    839         if ( ( current_theme_supports( 'custom-header', 'flex-width' ) && ! current_theme_supports( 'custom-header', 'flex-height' ) ) || $_POST['width'] > $max_width )
    840             $dst_width = absint( $_POST['width'] * ( $max_width / $_POST['width'] ) );
    841         elseif ( current_theme_supports( 'custom-header', 'flex-width' ) && current_theme_supports( 'custom-header', 'flex-height' ) )
    842             $dst_width = absint( $_POST['width'] );
    843         else
    844             $dst_width = get_theme_support( 'custom-header', 'width' );
     828        $dimensions = $this->get_header_dimensions( array(
     829            'height' => $_POST['height'],
     830            'width'  => $_POST['width'],
     831        ) );
     832        $height = $dimensions['dst_height'];
     833        $width = $dimensions['dst_width'];
    845834
    846835        if ( empty( $_POST['skip-cropping'] ) )
    847             $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $dst_width, $dst_height );
     836            $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $width, $height );
    848837        elseif ( ! empty( $_POST['create-new-attachment'] ) )
    849838            $cropped = _copy_image_file( $attachment_id );
     
    857846        $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
    858847
    859         $parent = get_post($attachment_id);
    860         $parent_url = $parent->guid;
    861         $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
    862 
    863         $size = @getimagesize( $cropped );
    864         $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
    865 
    866         // Construct the object array
    867         $object = array(
    868             'ID' => $attachment_id,
    869             'post_title' => basename($cropped),
    870             'post_content' => $url,
    871             'post_mime_type' => $image_type,
    872             'guid' => $url,
    873             'context' => 'custom-header'
    874         );
     848        $object = $this->create_attachment_object( $cropped, $attachment_id );
     849
    875850        if ( ! empty( $_POST['create-new-attachment'] ) )
    876851            unset( $object['ID'] );
    877852
    878853        // Update the attachment
    879         $attachment_id = wp_insert_attachment( $object, $cropped );
    880         wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $cropped ) );
    881 
    882         $width = $dst_width;
    883         $height = $dst_height;
     854        $attachment_id = $this->insert_attachment( $object, $cropped );
     855
     856        $url = $object['guid'];
    884857        $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
    885858
     
    10421015        set_theme_mod( 'header_image_data', (object) $default_data );
    10431016    }
     1017
     1018    /**
     1019     * Calculate width and height based on what the currently selected theme supports.
     1020     *
     1021     * @return array dst_height and dst_width of header image.
     1022     */
     1023    final public function get_header_dimensions( $dimensions ) {
     1024        $max_width = 0;
     1025        $width = absint( $dimensions['width'] );
     1026        $height = absint( $dimensions['height'] );
     1027        $theme_height = get_theme_support( 'custom-header', 'height' );
     1028        $theme_width = get_theme_support( 'custom-header', 'width' );
     1029        $has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
     1030        $has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
     1031        $has_max_width = current_theme_supports( 'custom-header', 'max-width' ) ;
     1032        $dst = array( 'dst_height' => null, 'dst_height' => null );
     1033
     1034        // For flex, limit size of image displayed to 1500px unless theme says otherwise
     1035        if ( $has_flex_width ) {
     1036            $max_width = 1500;
     1037        }
     1038
     1039        if ( $has_max_width ) {
     1040            $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
     1041        }
     1042        $max_width = max( $max_width, $theme_width );
     1043
     1044        if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
     1045            $dst['dst_height'] = absint( $height * ( $max_width / $width ) );
     1046        }
     1047        elseif ( $has_flex_height && $has_flex_width ) {
     1048            $dst['dst_height'] = $height;
     1049        }
     1050        else {
     1051            $dst['dst_height'] = $theme_height;
     1052        }
     1053
     1054        if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
     1055            $dst['dst_width'] = absint( $width * ( $max_width / $width ) );
     1056        }
     1057        elseif ( $has_flex_width && $has_flex_height ) {
     1058            $dst['dst_width'] = $width;
     1059        }
     1060        else {
     1061            $dst['dst_width'] = $theme_width;
     1062        }
     1063
     1064        return $dst;
     1065    }
     1066
     1067    /**
     1068     * Create an attachment 'object'.
     1069     *
     1070     * @param string $cropped Cropped image URL.
     1071     * @param int $parent_attachment_id Attachment ID of parent image.
     1072     *
     1073     * @return array Attachment object.
     1074     */
     1075    final public function create_attachment_object( $cropped, $parent_attachment_id ) {
     1076        $parent = get_post( $parent_attachment_id );
     1077        $parent_url = $parent->guid;
     1078        $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
     1079
     1080        $size = @getimagesize( $cropped );
     1081        $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
     1082
     1083        $object = array(
     1084            'ID' => $parent_attachment_id,
     1085            'post_title' => basename($cropped),
     1086            'post_content' => $url,
     1087            'post_mime_type' => $image_type,
     1088            'guid' => $url,
     1089            'context' => 'custom-header'
     1090        );
     1091
     1092        return $object;
     1093    }
     1094
     1095    /**
     1096     * Insert an attachment & its metadata.
     1097     *
     1098     * @param array $object Attachment object.
     1099     * @param string $cropped Cropped image URL.
     1100     *
     1101     * @return int Attachment ID.
     1102     */
     1103    final public function insert_attachment( $object, $cropped ) {
     1104        $attachment_id = wp_insert_attachment( $object, $cropped );
     1105        $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
     1106        /**
     1107         * Allows us to insert custom meta data for an attachment.
     1108         *
     1109         */
     1110        $metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
     1111        wp_update_attachment_metadata( $attachment_id, $metadata );
     1112        return $attachment_id;
     1113    }
     1114
     1115    /**
     1116     * Gets attachment uploaded by Media Manager, crops it, then saves it as a
     1117     * new object. Returns JSON-encoded object details.
     1118     */
     1119    function ajax_header_crop() {
     1120        check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
     1121
     1122        if ( ! current_user_can( 'edit_theme_options' ) ) {
     1123            wp_send_json_error();
     1124        }
     1125
     1126        if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
     1127            wp_send_json_error();
     1128        }
     1129
     1130        $crop_details = $_POST['cropDetails'];
     1131
     1132        $dimensions = $this->get_header_dimensions( array(
     1133            'height' => $crop_details['height'],
     1134            'width'  => $crop_details['width'],
     1135        ) );
     1136
     1137        $attachment_id = absint( $_POST['id'] );
     1138
     1139        $cropped = wp_crop_image(
     1140            $attachment_id,
     1141            (int) $crop_details['x1'],
     1142            (int) $crop_details['y1'],
     1143            (int) $crop_details['width'],
     1144            (int) $crop_details['height'],
     1145            (int) $dimensions['dst_width'],
     1146            (int) $dimensions['dst_height']
     1147        );
     1148
     1149        if ( ! $cropped || is_wp_error( $cropped ) ) {
     1150            wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
     1151        }
     1152
     1153        $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
     1154
     1155        $object = $this->create_attachment_object( $cropped, $attachment_id );
     1156
     1157        unset( $object['ID'] );
     1158
     1159        $new_attachment_id = $this->insert_attachment( $object, $cropped );
     1160
     1161        $object['attachment_id'] = $new_attachment_id;
     1162        $object['width']         = $dimensions['dst_width'];
     1163        $object['height']        = $dimensions['dst_height'];
     1164
     1165        wp_send_json_success( $object );
     1166    }
     1167
     1168    /**
     1169     * Given an attachment ID for a header image, updates its "last used"
     1170     * timestamp to now.
     1171     *
     1172     * Triggered when the user tries adds a new header image from the
     1173     * Media Manager, even if s/he doesn't save that change.
     1174     */
     1175    function ajax_header_add() {
     1176        check_ajax_referer( 'header-add', 'nonce' );
     1177
     1178        if ( ! current_user_can( 'edit_theme_options' ) ) {
     1179            wp_send_json_error();
     1180        }
     1181
     1182        $attachment_id = absint( $_POST['attachment_id'] );
     1183        if ( $attachment_id < 1 ) {
     1184            wp_send_json_error();
     1185        }
     1186
     1187        $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1188        update_post_meta( $attachment_id, $key, time() );
     1189        update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
     1190
     1191        wp_send_json_success();
     1192    }
     1193
     1194    /**
     1195     * Given an attachment ID for a header image, unsets it as a user-uploaded
     1196     * header image for the current theme.
     1197     *
     1198     * Triggered when the user clicks the overlay "X" button next to each image
     1199     * choice in the Customizer's Header tool.
     1200     */
     1201    function ajax_header_remove() {
     1202        check_ajax_referer( 'header-remove', 'nonce' );
     1203
     1204        if ( ! current_user_can( 'edit_theme_options' ) ) {
     1205            wp_send_json_error();
     1206        }
     1207
     1208        $attachment_id = absint( $_POST['attachment_id'] );
     1209        if ( $attachment_id < 1 ) {
     1210            wp_send_json_error();
     1211        }
     1212
     1213        $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1214        delete_post_meta( $attachment_id, $key );
     1215        delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
     1216
     1217        wp_send_json_success();
     1218    }
     1219
     1220    function customize_set_last_used( $wp_customize ) {
     1221        $data = $wp_customize->get_setting( 'header_image_data' )->post_value();
     1222
     1223        if ( ! isset( $data['attachment_id'] ) ) {
     1224            return;
     1225        }
     1226
     1227        $attachment_id = $data['attachment_id'];
     1228        $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     1229        update_post_meta( $attachment_id, $key, time() );
     1230    }
    10441231}
  • trunk/src/wp-admin/js/customize-controls.js

    r26206 r27497  
     1/* globals _wpCustomizeHeader, _wpMediaViewsL10n */
    12(function( exports, $ ){
    23    var api = wp.customize;
     
    305306                this.thumbnail.hide();
    306307        }
     308    });
     309
     310    api.HeaderControl = api.Control.extend({
     311        ready: function() {
     312            this.btnRemove        = $('.actions .remove');
     313            this.btnNew           = $('.actions .new');
     314
     315            _.bindAll(this, 'openMedia', 'removeImage');
     316
     317            this.btnNew.on( 'click', this.openMedia );
     318            this.btnRemove.on( 'click', this.removeImage );
     319
     320            api.HeaderTool.currentHeader = new api.HeaderTool.ImageModel();
     321
     322            new api.HeaderTool.CurrentView({
     323                model: api.HeaderTool.currentHeader,
     324                el: '.current .container'
     325            });
     326
     327            new api.HeaderTool.ChoiceListView({
     328                collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
     329                el: '.choices .uploaded .list'
     330            });
     331
     332            new api.HeaderTool.ChoiceListView({
     333                collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
     334                el: '.choices .default .list'
     335            });
     336
     337            api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
     338                api.HeaderTool.UploadsList,
     339                api.HeaderTool.DefaultsList
     340            ]);
     341        },
     342
     343        /**
     344         * Returns a set of options, computed from the attached image data and
     345         * theme-specific data, to be fed to the imgAreaSelect plugin in
     346         * wp.media.view.Cropper.
     347         *
     348         * @param {wp.media.model.Attachment} attachment
     349         * @param {wp.media.controller.Cropper} controller
     350         * @returns {Object} Options
     351         */
     352        calculateImageSelectOptions: function(attachment, controller) {
     353            var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
     354                yInit = parseInt(_wpCustomizeHeader.data.height, 10),
     355                flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
     356                flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
     357                ratio, xImg, yImg, realHeight, realWidth,
     358                imgSelectOptions;
     359
     360            realWidth = attachment.get('width');
     361            realHeight = attachment.get('height');
     362
     363            this.headerImage = new api.HeaderTool.ImageModel();
     364            this.headerImage.set({
     365                themeWidth: xInit,
     366                themeHeight: yInit,
     367                themeFlexWidth: flexWidth,
     368                themeFlexHeight: flexHeight,
     369                imageWidth: realWidth,
     370                imageHeight: realHeight
     371            });
     372
     373            controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
     374
     375            ratio = xInit / yInit;
     376            xImg = realWidth;
     377            yImg = realHeight;
     378
     379            if ( xImg / yImg > ratio ) {
     380                yInit = yImg;
     381                xInit = yInit * ratio;
     382            } else {
     383                xInit = xImg;
     384                yInit = xInit / ratio;
     385            }
     386
     387            imgSelectOptions = {
     388                handles: true,
     389                keys: true,
     390                instance: true,
     391                persistent: true,
     392                parent: this.$el,
     393                imageWidth: realWidth,
     394                imageHeight: realHeight,
     395                x1: 0,
     396                y1: 0,
     397                x2: xInit,
     398                y2: yInit
     399            };
     400
     401            if (flexHeight === false && flexWidth === false) {
     402                imgSelectOptions.aspectRatio = xInit + ':' + yInit;
     403            }
     404            if (flexHeight === false ) {
     405                imgSelectOptions.maxHeight = yInit;
     406            }
     407            if (flexWidth === false ) {
     408                imgSelectOptions.maxWidth = xInit;
     409            }
     410
     411            return imgSelectOptions;
     412        },
     413
     414        /**
     415         * Sets up and opens the Media Manager in order to select an image.
     416         * Depending on both the size of the image and the properties of the
     417         * current theme, a cropping step after selection may be required or
     418         * skippable.
     419         *
     420         * @param {event} event
     421         */
     422        openMedia: function(event) {
     423            var title, suggestedWidth, suggestedHeight,
     424                l10n = _wpMediaViewsL10n;
     425
     426            event.preventDefault();
     427
     428            suggestedWidth = l10n.suggestedWidth.replace('%d', _wpCustomizeHeader.data.width);
     429            suggestedHeight = l10n.suggestedHeight.replace('%d', _wpCustomizeHeader.data.height);
     430
     431            /* '<span class="suggested-dimensions">' + suggestedWidth + ' ' + suggestedHeight + '</span>' */
     432
     433            this.frame = wp.media({
     434                title: l10n.chooseImage,
     435                library: {
     436                    type: 'image'
     437                },
     438                button: {
     439                    text: l10n.selectAndCrop,
     440                    close: false
     441                },
     442                multiple: false,
     443                imgSelectOptions: this.calculateImageSelectOptions
     444            });
     445
     446            this.frame.states.add([new wp.media.controller.Cropper()]);
     447
     448            this.frame.on('select', this.onSelect, this);
     449            this.frame.on('cropped', this.onCropped, this);
     450            this.frame.on('skippedcrop', this.onSkippedCrop, this);
     451
     452            this.frame.open();
     453        },
     454
     455        onSelect: function() {
     456            this.frame.setState('cropper');
     457        },
     458        onCropped: function(croppedImage) {
     459            var url = croppedImage.post_content,
     460                attachmentId = croppedImage.attachment_id,
     461                w = croppedImage.width,
     462                h = croppedImage.height;
     463            this.setImageFromURL(url, attachmentId, w, h);
     464        },
     465        onSkippedCrop: function(selection) {
     466            var url = selection.get('url'),
     467                w = selection.get('width'),
     468                h = selection.get('height');
     469            this.setImageFromURL(url, selection.id, w, h);
     470        },
     471
     472        /**
     473         * Creates a new wp.customize.HeaderTool.ImageModel from provided
     474         * header image data and inserts it into the user-uploaded headers
     475         * collection.
     476         *
     477         * @param {String} url
     478         * @param {Number} attachmentId
     479         * @param {Number} width
     480         * @param {Number} height
     481         */
     482        setImageFromURL: function(url, attachmentId, width, height) {
     483            var choice, data = {};
     484
     485            data.url = url;
     486            data.thumbnail_url = url;
     487
     488            if (attachmentId) {
     489                data.attachment_id = attachmentId;
     490            }
     491
     492            if (width) {
     493                data.width = width;
     494            }
     495
     496            if (height) {
     497                data.height = height;
     498            }
     499
     500            choice = new api.HeaderTool.ImageModel({
     501                header: data,
     502                choice: url.split('/').pop()
     503            });
     504            api.HeaderTool.UploadsList.add(choice);
     505            api.HeaderTool.currentHeader.set(choice.toJSON());
     506            choice.save();
     507            choice.importImage();
     508        },
     509
     510        /**
     511         * Triggers the necessary events to deselect an image which was set as
     512         * the currently selected one.
     513         */
     514        removeImage: function() {
     515            api.HeaderTool.currentHeader.trigger('hide');
     516            api.HeaderTool.CombinedList.trigger('control:removeImage');
     517        }
     518
    307519    });
    308520
     
    687899        color:  api.ColorControl,
    688900        upload: api.UploadControl,
    689         image:  api.ImageControl
     901        image:  api.ImageControl,
     902        header: api.HeaderControl
    690903    };
    691904
     
    9621175        });
    9631176
    964         // Handle header image data
    965         api.control( 'header_image', function( control ) {
    966             control.setting.bind( function( to ) {
    967                 if ( to === control.params.removed )
    968                     control.settings.data.set( false );
    969             });
    970 
    971             control.library.on( 'click', 'a', function() {
    972                 control.settings.data.set( $(this).data('customizeHeaderImageData') );
    973             });
    974 
    975             control.uploader.success = function( attachment ) {
    976                 var data;
    977 
    978                 api.ImageControl.prototype.success.call( control, attachment );
    979 
    980                 data = {
    981                     attachment_id: attachment.get('id'),
    982                     url:           attachment.get('url'),
    983                     thumbnail_url: attachment.get('url'),
    984                     height:        attachment.get('height'),
    985                     width:         attachment.get('width')
    986                 };
    987 
    988                 attachment.element.data( 'customizeHeaderImageData', data );
    989                 control.settings.data.set( data );
    990             };
    991         });
    992 
    9931177        api.trigger( 'ready' );
    9941178
  • trunk/src/wp-includes/class-wp-customize-control.php

    r27431 r27497  
    709709}
    710710
    711 /**
    712  * Customize Header Image Control Class
    713  *
    714  * @package WordPress
    715  * @subpackage Customize
    716  * @since 3.4.0
    717  */
    718 class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
    719     /**
    720      * The processed default headers.
    721      * @since 3.4.2
    722      * @var array
    723      */
    724     protected $default_headers;
    725 
    726     /**
    727      * The uploaded headers.
    728      * @since 3.4.2
    729      * @var array
    730      */
    731     protected $uploaded_headers;
    732 
    733     /**
    734      * Constructor.
    735      *
    736      * @since 3.4.0
    737      * @uses WP_Customize_Image_Control::__construct()
    738      * @uses WP_Customize_Image_Control::add_tab()
    739      *
    740      * @param WP_Customize_Manager $manager
    741      */
     711final class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
     712    public $type = 'header';
     713
    742714    public function __construct( $manager ) {
    743715        parent::__construct( $manager, 'header_image', array(
     
    751723            'removed'  => 'remove-header',
    752724            'get_url'  => 'get_header_image',
    753             'statuses' => array(
    754                 ''                      => __('Default'),
    755                 'remove-header'         => __('No Image'),
    756                 'random-default-image'  => __('Random Default Image'),
    757                 'random-uploaded-image' => __('Random Uploaded Image'),
     725        ) );
     726
     727    }
     728
     729    public function to_json() {
     730        parent::to_json();
     731    }
     732
     733    public function enqueue() {
     734        wp_enqueue_media();
     735        wp_enqueue_script( 'customize-views' );
     736
     737        $this->prepare_control();
     738
     739        wp_localize_script( 'customize-views', '_wpCustomizeHeader', array(
     740            'data' => array(
     741                'width' => absint( get_theme_support( 'custom-header', 'width' ) ),
     742                'height' => absint( get_theme_support( 'custom-header', 'height' ) ),
     743                'flex-width' => absint( get_theme_support( 'custom-header', 'flex-width' ) ),
     744                'flex-height' => absint( get_theme_support( 'custom-header', 'flex-height' ) ),
     745                'currentImgSrc' => $this->get_current_image_src(),
     746            ),
     747            'nonces' => array(
     748                'add' => wp_create_nonce( 'header-add' ),
     749                'remove' => wp_create_nonce( 'header-remove' ),
     750            ),
     751            'l10n' => array(
     752                /* translators: header images uploaded by user */
     753                'uploaded' => __( 'uploaded' ),
     754                /* translators: header images suggested by the current theme */
     755                'default' => __( 'suggested' )
     756            ),
     757            'uploads' => $this->uploaded_headers,
     758            'defaults' => $this->default_headers
     759        ) );
     760
     761        parent::enqueue();
     762    }
     763
     764    public function get_default_header_images() {
     765        global $custom_image_header;
     766
     767        // Get *the* default image if there is one
     768        $default = get_theme_support( 'custom-header', 'default-image' );
     769
     770        if ( ! $default ) { // If not,
     771            return $custom_image_header->default_headers; // easy peasy.
     772        }
     773
     774        $default = sprintf( $default,
     775            get_template_directory_uri(),
     776            get_stylesheet_directory_uri() );
     777
     778        $header_images = array();
     779        $already_has_default = false;
     780
     781        // Get the whole set of default images
     782        $default_header_images = $custom_image_header->default_headers;
     783        foreach ( $default_header_images as $k => $h ) {
     784            if ( $h['url'] == $default ) {
     785                $already_has_default = true;
     786                break;
     787            }
     788        }
     789
     790        // If *the one true image* isn't included in the default set, add it in
     791        // first position
     792        if ( ! $already_has_default ) {
     793            $header_images['default'] = array(
     794                'url' => $default,
     795                'thumbnail_url' => $default,
     796                'description' => 'Default'
     797            );
     798        }
     799
     800        // The rest of the set comes after
     801        $header_images = array_merge( $header_images, $default_header_images );
     802
     803        return $header_images;
     804    }
     805
     806    public function get_uploaded_header_images() {
     807        $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
     808        $header_images = array();
     809
     810        $headers_not_dated = get_posts( array(
     811            'post_type' => 'attachment',
     812            'meta_key' => '_wp_attachment_is_custom_header',
     813            'meta_value' => get_option('stylesheet'),
     814            'orderby' => 'none',
     815            'nopaging' => true,
     816            'meta_query' => array(
     817                array(
     818                    'key' => '_wp_attachment_is_custom_header',
     819                    'value' => get_option( 'stylesheet' ),
     820                    'compare' => 'LIKE'
     821                ),
     822                array(
     823                    'key' => $key,
     824                    'value' => 'this string must not be empty',
     825                    'compare' => 'NOT EXISTS'
     826                ),
    758827            )
    759828        ) );
    760829
    761         // Remove the upload tab.
    762         $this->remove_tab( 'upload-new' );
    763     }
    764 
    765     /**
    766      * Prepares the control.
    767      *
    768      * If no tabs exist, removes the control from the manager.
    769      *
    770      * @since 3.4.2
    771      */
     830        $headers_dated = get_posts( array(
     831            'post_type' => 'attachment',
     832            'meta_key' => $key,
     833            'orderby' => 'meta_value_num',
     834            'order' => 'DESC',
     835            'nopaging' => true,
     836            'meta_query' => array(
     837                array(
     838                    'key' => '_wp_attachment_is_custom_header',
     839                    'value' => get_option( 'stylesheet' ),
     840                    'compare' => 'LIKE'
     841                ),
     842            ),
     843        ) );
     844
     845        $limit = apply_filters( 'custom_header_uploaded_limit', 15 );
     846        $headers = array_merge( $headers_dated, $headers_not_dated );
     847        $headers = array_slice( $headers, 0, $limit );
     848
     849        foreach ( (array) $headers as $header ) {
     850            $url = esc_url_raw( $header->guid );
     851            $header_data = wp_get_attachment_metadata( $header->ID );
     852            $timestamp = get_post_meta( $header->ID,
     853                '_wp_attachment_custom_header_last_used_' . get_stylesheet(),
     854                true );
     855
     856            $h = array(
     857                'attachment_id' => $header->ID,
     858                'url'           => $url,
     859                'thumbnail_url' => $url,
     860                'timestamp'     => $timestamp ? $timestamp : 0,
     861            );
     862
     863            if ( isset( $header_data['width'] ) ) {
     864                $h['width'] = $header_data['width'];
     865            }
     866            if ( isset( $header_data['height'] ) ) {
     867                $h['height'] = $header_data['height'];
     868            }
     869
     870            $header_images[] = $h;
     871        }
     872
     873        return $header_images;
     874    }
     875
    772876    public function prepare_control() {
    773877        global $custom_image_header;
    774         if ( empty( $custom_image_header ) )
    775             return parent::prepare_control();
     878        if ( empty( $custom_image_header ) ) {
     879            return;
     880        }
    776881
    777882        // Process default headers and uploaded headers.
    778883        $custom_image_header->process_default_headers();
    779         $this->default_headers = $custom_image_header->default_headers;
    780         $this->uploaded_headers = get_uploaded_header_images();
    781 
    782         if ( $this->default_headers )
    783             $this->add_tab( 'default',  __('Default'),  array( $this, 'tab_default_headers' ) );
    784 
    785         if ( ! $this->uploaded_headers )
    786             $this->remove_tab( 'uploaded' );
    787 
    788         return parent::prepare_control();
    789     }
    790 
    791     /**
    792      * @since 3.4.0
    793      *
    794      * @param mixed $choice Which header image to select. (@see Custom_Image_Header::get_header_image() )
    795      * @param array $header
    796      */
    797     public function print_header_image( $choice, $header ) {
    798         $header['url']           = set_url_scheme( $header['url'] );
    799         $header['thumbnail_url'] = set_url_scheme( $header['thumbnail_url'] );
    800 
    801         $header_image_data = array( 'choice' => $choice );
    802         foreach ( array( 'attachment_id', 'width', 'height', 'url', 'thumbnail_url' ) as $key ) {
    803             if ( isset( $header[ $key ] ) )
    804                 $header_image_data[ $key ] = $header[ $key ];
    805         }
    806 
    807 
     884        $this->default_headers = $this->get_default_header_images();
     885        $this->uploaded_headers = $this->get_uploaded_header_images();
     886    }
     887
     888    function print_header_image_template() {
    808889        ?>
    809         <a href="#" class="thumbnail"
    810             data-customize-image-value="<?php echo esc_url( $header['url'] ); ?>"
    811             data-customize-header-image-data="<?php echo esc_attr( json_encode( $header_image_data ) ); ?>">
    812             <img src="<?php echo esc_url( $header['thumbnail_url'] ); ?>" />
    813         </a>
     890        <script type="text/template" id="tmpl-header-choice">
     891            <# if (data.random) { #>
     892
     893            <div class="placeholder random">
     894                <div class="inner">
     895                    <span><span class="dice">&#9860;</span>
     896                    <# if ( data.type === 'uploaded' ) { #>
     897                        <?php _e( 'Randomize uploaded headers' ); ?>
     898                    <# } else if ( data.type === 'suggested' ) { #>
     899                        <?php _e( 'Randomize suggested headers' ); ?>
     900                    <# } #>
     901                    </span>
     902                </div>
     903            </div>
     904
     905            <# } else { #>
     906
     907            <# if (data.type === 'uploaded') { #>
     908            <div class="dashicons dashicons-no close"></div>
     909            <# } #>
     910
     911            <a href="#" class="choice thumbnail #>"
     912                data-customize-image-value="{{{data.header.url}}}"
     913                data-customize-header-image-data="{{JSON.stringify(data.header)}}">
     914                <img src="{{{data.header.thumbnail_url}}}">
     915            </a>
     916
     917            <# } #>
     918        </script>
     919
     920        <script type="text/template" id="tmpl-header-current">
     921            <# if (data.choice) { #>
     922                <# if (data.random) { #>
     923
     924            <div class="placeholder">
     925                <div class="inner">
     926                    <span><span class="dice">&#9860;</span>
     927                    <# if ( data.type === 'uploaded' ) { #>
     928                        <?php _e( 'Randomizing uploaded headers' ); ?>
     929                    <# } else if ( data.type === 'suggested' ) { #>
     930                        <?php _e( 'Randomizing suggested headers' ); ?>
     931                    <# } #>
     932                    </span>
     933                </div>
     934            </div>
     935
     936                <# } else { #>
     937
     938            <img src="{{{data.header.thumbnail_url}}}" />
     939
     940                <# } #>
     941            <# } else { #>
     942
     943            <div class="placeholder">
     944                <div class="inner">
     945                    <span>
     946                        <?php _e( 'No image set' ); ?>
     947                    </span>
     948                </div>
     949            </div>
     950
     951            <# } #>
     952        </script>
    814953        <?php
    815954    }
    816955
    817     /**
    818      * @since 3.4.0
    819      */
    820     public function tab_uploaded() {
    821         ?><div class="uploaded-target"></div><?php
    822 
    823         foreach ( $this->uploaded_headers as $choice => $header )
    824             $this->print_header_image( $choice, $header );
    825     }
    826 
    827     /**
    828      * @since 3.4.0
    829      */
    830     public function tab_default_headers() {
    831         foreach ( $this->default_headers as $choice => $header )
    832             $this->print_header_image( $choice, $header );
     956    public function get_current_image_src() {
     957        $src = $this->value();
     958        if ( isset( $this->get_url ) ) {
     959            $src = call_user_func( $this->get_url, $src );
     960            return $src;
     961        }
     962        return null;
     963    }
     964
     965    public function render_content() {
     966        $this->print_header_image_template();
     967        $visibility = $this->get_current_image_src() ? '' : ' style="display:none" ';
     968        $width = absint( get_theme_support( 'custom-header', 'width' ) );
     969        $height = absint( get_theme_support( 'custom-header', 'height' ) );
     970        ?>
     971
     972
     973        <div class="customize-control-content">
     974            <p class="customizer-section-intro">
     975                <?php _e( 'Personalize your site with your own header image.' ); ?>
     976                <?php
     977                if ( $width && $height ) {
     978                    printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header size of <strong>%dx%d</strong> pixels.' ),
     979                        _x( 'Add new', 'header image' ), $width, $height );
     980                } else {
     981                    if ( $width ) {
     982                        printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header width of <strong>%d</strong> pixels.' ),
     983                            _x( 'Add new', 'header image' ), $width );
     984                    }
     985                    if ( $height ) {
     986                        printf( __( 'While you can crop images to your liking after clicking <strong>%s</strong>, your theme recommends a header height of <strong>%d</strong> pixels.' ),
     987                            _x( 'Add new', 'header image' ), $height );
     988                    }
     989                }
     990                ?>
     991            </p>
     992            <div class="current">
     993                <span class="customize-control-title">
     994                    <?php _e( 'Current header' ); ?>
     995                </span>
     996                <div class="container">
     997                </div>
     998            </div>
     999            <div class="actions">
     1000                <?php /* translators: Hide as in hide header image via the Customizer */ ?>
     1001                <a href="#" <?php echo $visibility ?> class="button remove"><?php _ex( 'Hide', 'custom header' ); ?></a>
     1002                <?php /* translators: New as in add new header image via the Customizer */ ?>
     1003                <a href="#" class="button new"><?php _ex( 'Add new', 'header image' ); ?></a>
     1004                <div style="clear:both"></div>
     1005            </div>
     1006            <div class="choices">
     1007                <span class="customize-control-title header-previously-uploaded">
     1008                    <?php _ex( 'Previously uploaded', 'custom headers' ); ?>
     1009                </span>
     1010                <div class="uploaded">
     1011                    <div class="list">
     1012                    </div>
     1013                </div>
     1014                <span class="customize-control-title header-default">
     1015                    <?php _ex( 'Suggested', 'custom headers' ); ?>
     1016                </span>
     1017                <div class="default">
     1018                    <div class="list">
     1019                    </div>
     1020                </div>
     1021            </div>
     1022        </div>
     1023        <?php
    8331024    }
    8341025}
  • trunk/src/wp-includes/css/media-views.css

    r27487 r27497  
    601601    line-height: 60px;
    602602    margin: 0;
     603}
     604
     605.media-frame-title .suggested-dimensions {
     606    font-size: 14px;
     607    float: right;
     608    margin-right: 20px;
     609}
     610
     611.media-frame-content .crop-content {
     612    display: block;
     613    margin: auto;
     614    max-width: 100%;
     615    max-height: 100%;
    603616}
    604617
  • trunk/src/wp-includes/js/media-views.js

    r27481 r27497  
    13141314                this.refresh();
    13151315            }
     1316        }
     1317    });
     1318
     1319    /**
     1320     * wp.media.controller.Cropper
     1321     *
     1322     * Allows for a cropping step.
     1323     *
     1324     * @constructor
     1325     * @augments wp.media.controller.State
     1326     * @augments Backbone.Model
     1327     */
     1328    media.controller.Cropper = media.controller.State.extend({
     1329        defaults: {
     1330            id: 'cropper',
     1331            title: l10n.cropImage,
     1332            toolbar: 'crop',
     1333            content: 'crop',
     1334            router: false,
     1335            canSkipCrop: false
     1336        },
     1337
     1338        activate: function() {
     1339            this.frame.on( 'content:create:crop', this.createCropContent, this );
     1340            this.frame.on( 'close', this.removeCropper, this );
     1341            this.set('selection', new Backbone.Collection(this.frame._selection.single));
     1342        },
     1343
     1344        deactivate: function() {
     1345            this.frame.toolbar.mode('browse');
     1346        },
     1347
     1348        createCropContent: function() {
     1349            this.cropperView = new wp.media.view.Cropper({controller: this,
     1350                    attachment: this.get('selection').first() });
     1351            this.cropperView.on('image-loaded', this.createCropToolbar, this);
     1352            this.frame.content.set(this.cropperView);
     1353
     1354        },
     1355        removeCropper: function() {
     1356            this.imgSelect.cancelSelection();
     1357            this.imgSelect.setOptions({remove: true});
     1358            this.imgSelect.update();
     1359            this.cropperView.remove();
     1360        },
     1361        createCropToolbar: function() {
     1362            var canSkipCrop, toolbarOptions;
     1363
     1364            canSkipCrop = this.get('canSkipCrop') || false;
     1365
     1366            toolbarOptions = {
     1367                controller: this.frame,
     1368                items: {
     1369                    insert: {
     1370                        style:    'primary',
     1371                        text:     l10n.cropImage,
     1372                        priority: 80,
     1373                        requires: { library: false, selection: false },
     1374
     1375                        click: function() {
     1376                            var self = this,
     1377                                selection = this.controller.state().get('selection').first();
     1378
     1379                            selection.set({cropDetails: this.controller.state().imgSelect.getSelection()});
     1380
     1381                            this.$el.text(l10n.cropping);
     1382                            this.$el.attr('disabled', true);
     1383                            this.controller.state().doCrop( selection ).done( function( croppedImage ) {
     1384                                console.log( croppedImage );
     1385                                self.controller.trigger('cropped', croppedImage );
     1386                                self.controller.close();
     1387                            });
     1388                        }
     1389                    }
     1390                }
     1391            };
     1392
     1393            if ( canSkipCrop ) {
     1394                _.extend( toolbarOptions.items, {
     1395                    skip: {
     1396                        style:      'secondary',
     1397                        text:       l10n.skipCropping,
     1398                        priority:   70,
     1399                        requires:   { library: false, selection: false },
     1400                        click:      function() {
     1401                            var selection = this.controller.state().get('selection').first();
     1402                            this.controller.state().cropperView.remove();
     1403                            this.controller.trigger('skippedcrop', selection);
     1404                            this.controller.close();
     1405                        }
     1406                    }
     1407                });
     1408            }
     1409
     1410            this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
     1411        },
     1412
     1413        doCrop: function( attachment ) {
     1414            return wp.ajax.post( 'custom-header-crop', {
     1415                nonce: attachment.get('nonces').edit,
     1416                id: attachment.get('id'),
     1417                cropDetails: attachment.get('cropDetails')
     1418            } );
    13161419        }
    13171420    });
     
    63246427    });
    63256428
     6429    /**
     6430     * wp.media.view.Cropper
     6431     *
     6432     * Uses the imgAreaSelect plugin to allow a user to crop an image.
     6433     *
     6434     * Takes imgAreaSelect options from
     6435     * wp.customize.HeaderControl.calculateImageSelectOptions via
     6436     * wp.customize.HeaderControl.openMM.
     6437     *
     6438     * @constructor
     6439     * @augments wp.media.View
     6440     * @augments wp.Backbone.View
     6441     * @augments Backbone.View
     6442     */
     6443    media.view.Cropper = media.View.extend({
     6444        tagName: 'img',
     6445        className: 'crop-content',
     6446        initialize: function() {
     6447            _.bindAll(this, 'onImageLoad');
     6448            this.$el.attr('src', this.options.attachment.get('url'));
     6449        },
     6450        ready: function() {
     6451            this.$el.on('load', this.onImageLoad);
     6452            $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
     6453        },
     6454        remove: function() {
     6455            $(window).off('resize.cropper');
     6456            this.$el.remove();
     6457            this.$el.off();
     6458            wp.media.View.prototype.remove.apply(this, arguments);
     6459        },
     6460        prepare: function() {
     6461            return {
     6462                title: l10n.cropYourImage,
     6463                url: this.options.attachment.get('url')
     6464            };
     6465        },
     6466        onImageLoad: function() {
     6467            var imgOptions = this.controller.frame.options.imgSelectOptions;
     6468            if (typeof imgOptions === 'function') {
     6469                imgOptions = imgOptions(this.options.attachment, this.controller);
     6470            }
     6471            this.trigger('image-loaded');
     6472            this.controller.imgSelect = this.$el.imgAreaSelect(imgOptions);
     6473        }
     6474
     6475    });
    63266476
    63276477    media.view.EditImage = media.View.extend({
  • trunk/src/wp-includes/media.php

    r27488 r27497  
    24772477        'editImage'             => __( 'Edit Image' ),
    24782478
     2479        // Crop Image
     2480        /* translators: title for Media Manager library view */
     2481        'chooseImage' => __( 'Choose Image' ),
     2482        /* translators: button to select an image from the MM library to crop */
     2483        'selectAndCrop' => __( 'Select and Crop' ),
     2484        /* translators: button to choose not to crop the selected image */
     2485        'skipCropping' => __( 'Skip Cropping' ),
     2486        /* translators: button to choose to crop the selected image */
     2487        'cropImage' => __( 'Crop Image' ),
     2488        'cropYourImage' => __( 'Crop your image' ),
     2489        /* translators: button label changes to this while the image is being cropped server-side */
     2490        'cropping' => __( 'Cropping...' ),
     2491        /* translators: suggested width of header image in pixels */
     2492        'suggestedWidth' => __( 'Suggested width is %d pixels.' ),
     2493        /* translators: suggested height of header image in pixels */
     2494        'suggestedHeight' => __( 'Suggested height is %d pixels.' ),
     2495
    24792496        // Edit Audio
    24802497        'audioDetailsTitle'     => __( 'Audio Details' ),
  • trunk/src/wp-includes/script-loader.php

    r27494 r27497  
    364364    $scripts->add( 'customize-loader',   "/wp-includes/js/customize-loader$suffix.js",   array( 'customize-base' ), false, 1 );
    365365    $scripts->add( 'customize-preview',  "/wp-includes/js/customize-preview$suffix.js",  array( 'customize-base' ), false, 1 );
     366    $scripts->add( 'customize-models',   "/wp-includes/js/customize-models.js", array( 'underscore', 'backbone' ), false, 1 );
     367    $scripts->add( 'customize-views',    "/wp-includes/js/customize-views.js",  array( 'jquery', 'underscore', 'imgareaselect', 'customize-models' ), false, 1 );
    366368    $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base' ), false, 1 );
    367369    did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array(
     
    601603    $styles->add( 'install',            "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
    602604    $styles->add( 'wp-color-picker',    "/wp-admin/css/color-picker$suffix.css" );
    603     $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie' ) );
     605    $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
    604606    $styles->add( 'ie',                 "/wp-admin/css/ie$suffix.css" );
    605607
Note: See TracChangeset for help on using the changeset viewer.