Changeset 27497
- Timestamp:
- 03/11/2014 04:12:17 AM (11 years ago)
- Location:
- trunk/src
- Files:
-
- 2 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/customize-controls.css
r27174 r27497 456 456 } 457 457 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 458 619 /** Handle cheaters. */ 459 620 body.cheatin { -
trunk/src/wp-admin/custom-header.php
r27469 r27497 44 44 45 45 /** 46 * Holds custom headers uploaded by the user 46 * Holds custom headers uploaded by the user. 47 47 * 48 48 * @var array … … 74 74 75 75 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' ) ); 76 81 } 77 82 … … 94 99 if ( $this->admin_header_callback ) 95 100 add_action("admin_head-$page", $this->admin_header_callback, 51); 101 96 102 } 97 103 … … 820 826 $original = get_attached_file($attachment_id); 821 827 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']; 845 834 846 835 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 ); 848 837 elseif ( ! empty( $_POST['create-new-attachment'] ) ) 849 838 $cropped = _copy_image_file( $attachment_id ); … … 857 846 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication 858 847 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 875 850 if ( ! empty( $_POST['create-new-attachment'] ) ) 876 851 unset( $object['ID'] ); 877 852 878 853 // 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']; 884 857 $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) ); 885 858 … … 1042 1015 set_theme_mod( 'header_image_data', (object) $default_data ); 1043 1016 } 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 } 1044 1231 } -
trunk/src/wp-admin/js/customize-controls.js
r26206 r27497 1 /* globals _wpCustomizeHeader, _wpMediaViewsL10n */ 1 2 (function( exports, $ ){ 2 3 var api = wp.customize; … … 305 306 this.thumbnail.hide(); 306 307 } 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 307 519 }); 308 520 … … 687 899 color: api.ColorControl, 688 900 upload: api.UploadControl, 689 image: api.ImageControl 901 image: api.ImageControl, 902 header: api.HeaderControl 690 903 }; 691 904 … … 962 1175 }); 963 1176 964 // Handle header image data965 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 993 1177 api.trigger( 'ready' ); 994 1178 -
trunk/src/wp-includes/class-wp-customize-control.php
r27431 r27497 709 709 } 710 710 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 */ 711 final class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control { 712 public $type = 'header'; 713 742 714 public function __construct( $manager ) { 743 715 parent::__construct( $manager, 'header_image', array( … … 751 723 'removed' => 'remove-header', 752 724 '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 ), 758 827 ) 759 828 ) ); 760 829 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 772 876 public function prepare_control() { 773 877 global $custom_image_header; 774 if ( empty( $custom_image_header ) ) 775 return parent::prepare_control(); 878 if ( empty( $custom_image_header ) ) { 879 return; 880 } 776 881 777 882 // Process default headers and uploaded headers. 778 883 $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() { 808 889 ?> 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">⚄</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">⚄</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> 814 953 <?php 815 954 } 816 955 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 833 1024 } 834 1025 } -
trunk/src/wp-includes/css/media-views.css
r27487 r27497 601 601 line-height: 60px; 602 602 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%; 603 616 } 604 617 -
trunk/src/wp-includes/js/media-views.js
r27481 r27497 1314 1314 this.refresh(); 1315 1315 } 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 } ); 1316 1419 } 1317 1420 }); … … 6324 6427 }); 6325 6428 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 }); 6326 6476 6327 6477 media.view.EditImage = media.View.extend({ -
trunk/src/wp-includes/media.php
r27488 r27497 2477 2477 'editImage' => __( 'Edit Image' ), 2478 2478 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 2479 2496 // Edit Audio 2480 2497 'audioDetailsTitle' => __( 'Audio Details' ), -
trunk/src/wp-includes/script-loader.php
r27494 r27497 364 364 $scripts->add( 'customize-loader', "/wp-includes/js/customize-loader$suffix.js", array( 'customize-base' ), false, 1 ); 365 365 $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 ); 366 368 $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base' ), false, 1 ); 367 369 did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array( … … 601 603 $styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) ); 602 604 $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' ) ); 604 606 $styles->add( 'ie', "/wp-admin/css/ie$suffix.css" ); 605 607
Note: See TracChangeset
for help on using the changeset viewer.