Add header image uploads with cropping to the customizer.

props mcsf, ehg, gcorne.
see #21785.

Built from https://develop.svn.wordpress.org/trunk@27497


git-svn-id: http://core.svn.wordpress.org/trunk@27339 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Andrew Nacin 2014-03-11 04:13:16 +00:00
parent 45bc43515c
commit a589d9d757
20 changed files with 1737 additions and 175 deletions

View File

@ -455,6 +455,167 @@ body {
-webkit-overflow-scrolling: touch;
}
/** Header control **/
#customize-control-header_image .current {
margin-bottom: 8px;
}
#customize-control-header_image .uploaded {
margin-bottom: 18px;
}
/* Header control: current image container */
#customize-control-header_image .current .container {
overflow: hidden;
border-radius: 2px;
}
#customize-control-header_image .placeholder {
width: 100%;
position: relative;
background: #262626;
text-align: center;
cursor: default;
}
#customize-control-header_image .inner {
display: none;
position: absolute;
width: 100%;
height: 18px;
margin-top: -9px;
top: 50%;
color: #eee;
}
/* Header control: overlay "close" button */
#customize-control-header_image .header-view {
position: relative;
}
#customize-control-header_image .uploaded .header-view .close {
font-size: 2em;
color: grey;
position: absolute;
visibility: hidden;
top: 10px;
left: 10px;
z-index: 1;
width: 20px;
height: 20px;
cursor: pointer;
}
#customize-control-header_image .uploaded .header-view .close:hover {
color: black;
text-shadow:
-1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
}
#customize-control-header_image .header-view:hover .close {
visibility: visible;
}
/* Header control: randomiz(s)er */
#customize-control-header_image .random.placeholder {
cursor: pointer;
border-radius: 2px;
height: 40px;
}
#customize-control-header_image .random .inner {
display: block;
}
#customize-control-header_image .dice {
font-size: 16px;
vertical-align: -1px;
}
#customize-control-header_image .placeholder:hover .dice {
-webkit-animation: dice-color-change 3s infinite;
-moz-animation: dice-color-change 3s infinite;
-ms-animation: dice-color-change 3s infinite;
animation: dice-color-change 3s infinite;
}
@-webkit-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@-moz-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@-ms-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
/* Header control: actions and choices */
#customize-control-header_image .actions {
margin-bottom: 32px;
}
#customize-control-header_image .choice {
position: relative;
display: block;
margin-bottom: 9px;
}
#customize-control-header_image .choice.random:before {
position: absolute;
content: attr(data-label);
right: 0;
top: 0;
}
#customize-control-header_image .uploaded div:last-child > .choice {
margin-bottom: 0;
}
#customize-control-header_image .choices hr {
visibility: hidden;
}
#customize-control-header_image img {
width: 100%;
border-radius: 2px;
}
#customize-control-header_image .remove {
float: right;
margin-left: 3px;
}
#customize-control-header_image .new {
float: left;
}
/** Handle cheaters. */
body.cheatin {
min-width: 0;

File diff suppressed because one or more lines are too long

View File

@ -455,6 +455,167 @@ body {
-webkit-overflow-scrolling: touch;
}
/** Header control **/
#customize-control-header_image .current {
margin-bottom: 8px;
}
#customize-control-header_image .uploaded {
margin-bottom: 18px;
}
/* Header control: current image container */
#customize-control-header_image .current .container {
overflow: hidden;
border-radius: 2px;
}
#customize-control-header_image .placeholder {
width: 100%;
position: relative;
background: #262626;
text-align: center;
cursor: default;
}
#customize-control-header_image .inner {
display: none;
position: absolute;
width: 100%;
height: 18px;
margin-top: -9px;
top: 50%;
color: #eee;
}
/* Header control: overlay "close" button */
#customize-control-header_image .header-view {
position: relative;
}
#customize-control-header_image .uploaded .header-view .close {
font-size: 2em;
color: grey;
position: absolute;
visibility: hidden;
top: 10px;
right: 10px;
z-index: 1;
width: 20px;
height: 20px;
cursor: pointer;
}
#customize-control-header_image .uploaded .header-view .close:hover {
color: black;
text-shadow:
-1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
}
#customize-control-header_image .header-view:hover .close {
visibility: visible;
}
/* Header control: randomiz(s)er */
#customize-control-header_image .random.placeholder {
cursor: pointer;
border-radius: 2px;
height: 40px;
}
#customize-control-header_image .random .inner {
display: block;
}
#customize-control-header_image .dice {
font-size: 16px;
vertical-align: -1px;
}
#customize-control-header_image .placeholder:hover .dice {
-webkit-animation: dice-color-change 3s infinite;
-moz-animation: dice-color-change 3s infinite;
-ms-animation: dice-color-change 3s infinite;
animation: dice-color-change 3s infinite;
}
@-webkit-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@-moz-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@-ms-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
@keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
}
/* Header control: actions and choices */
#customize-control-header_image .actions {
margin-bottom: 32px;
}
#customize-control-header_image .choice {
position: relative;
display: block;
margin-bottom: 9px;
}
#customize-control-header_image .choice.random:before {
position: absolute;
content: attr(data-label);
left: 0;
top: 0;
}
#customize-control-header_image .uploaded div:last-child > .choice {
margin-bottom: 0;
}
#customize-control-header_image .choices hr {
visibility: hidden;
}
#customize-control-header_image img {
width: 100%;
border-radius: 2px;
}
#customize-control-header_image .remove {
float: left;
margin-right: 3px;
}
#customize-control-header_image .new {
float: right;
}
/** Handle cheaters. */
body.cheatin {
min-width: 0;

File diff suppressed because one or more lines are too long

View File

@ -43,7 +43,7 @@ class Custom_Image_Header {
var $default_headers = array();
/**
* Holds custom headers uploaded by the user
* Holds custom headers uploaded by the user.
*
* @var array
* @since 3.2.0
@ -73,6 +73,11 @@ class Custom_Image_Header {
$this->admin_image_div_callback = $admin_image_div_callback;
add_action( 'admin_menu', array( $this, 'init' ) );
add_action( 'customize_save_after', array( $this, 'customize_set_last_used' ) );
add_action( 'wp_ajax_custom-header-crop', array( $this, 'ajax_header_crop' ) );
add_action( 'wp_ajax_custom-header-add', array( $this, 'ajax_header_add' ) );
add_action( 'wp_ajax_custom-header-remove', array( $this, 'ajax_header_remove' ) );
}
/**
@ -93,6 +98,7 @@ class Custom_Image_Header {
add_action("admin_head-$page", array($this, 'js'), 50);
if ( $this->admin_header_callback )
add_action("admin_head-$page", $this->admin_header_callback, 51);
}
/**
@ -819,32 +825,15 @@ wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
$attachment_id = absint( $_POST['attachment_id'] );
$original = get_attached_file($attachment_id);
$max_width = 0;
// For flex, limit size of image displayed to 1500px unless theme says otherwise
if ( current_theme_supports( 'custom-header', 'flex-width' ) )
$max_width = 1500;
if ( current_theme_supports( 'custom-header', 'max-width' ) )
$max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
$max_width = max( $max_width, get_theme_support( 'custom-header', 'width' ) );
if ( ( current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) || $_POST['width'] > $max_width )
$dst_height = absint( $_POST['height'] * ( $max_width / $_POST['width'] ) );
elseif ( current_theme_supports( 'custom-header', 'flex-height' ) && current_theme_supports( 'custom-header', 'flex-width' ) )
$dst_height = absint( $_POST['height'] );
else
$dst_height = get_theme_support( 'custom-header', 'height' );
if ( ( current_theme_supports( 'custom-header', 'flex-width' ) && ! current_theme_supports( 'custom-header', 'flex-height' ) ) || $_POST['width'] > $max_width )
$dst_width = absint( $_POST['width'] * ( $max_width / $_POST['width'] ) );
elseif ( current_theme_supports( 'custom-header', 'flex-width' ) && current_theme_supports( 'custom-header', 'flex-height' ) )
$dst_width = absint( $_POST['width'] );
else
$dst_width = get_theme_support( 'custom-header', 'width' );
$dimensions = $this->get_header_dimensions( array(
'height' => $_POST['height'],
'width' => $_POST['width'],
) );
$height = $dimensions['dst_height'];
$width = $dimensions['dst_width'];
if ( empty( $_POST['skip-cropping'] ) )
$cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $dst_width, $dst_height );
$cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $width, $height );
elseif ( ! empty( $_POST['create-new-attachment'] ) )
$cropped = _copy_image_file( $attachment_id );
else
@ -856,31 +845,15 @@ wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
/** This filter is documented in wp-admin/custom-header.php */
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
$parent = get_post($attachment_id);
$parent_url = $parent->guid;
$url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
$object = $this->create_attachment_object( $cropped, $attachment_id );
$size = @getimagesize( $cropped );
$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
// Construct the object array
$object = array(
'ID' => $attachment_id,
'post_title' => basename($cropped),
'post_content' => $url,
'post_mime_type' => $image_type,
'guid' => $url,
'context' => 'custom-header'
);
if ( ! empty( $_POST['create-new-attachment'] ) )
unset( $object['ID'] );
// Update the attachment
$attachment_id = wp_insert_attachment( $object, $cropped );
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $cropped ) );
$attachment_id = $this->insert_attachment( $object, $cropped );
$width = $dst_width;
$height = $dst_height;
$url = $object['guid'];
$this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
// cleanup
@ -1041,4 +1014,218 @@ wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
set_theme_mod( 'header_image', $default );
set_theme_mod( 'header_image_data', (object) $default_data );
}
/**
* Calculate width and height based on what the currently selected theme supports.
*
* @return array dst_height and dst_width of header image.
*/
final public function get_header_dimensions( $dimensions ) {
$max_width = 0;
$width = absint( $dimensions['width'] );
$height = absint( $dimensions['height'] );
$theme_height = get_theme_support( 'custom-header', 'height' );
$theme_width = get_theme_support( 'custom-header', 'width' );
$has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
$has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
$has_max_width = current_theme_supports( 'custom-header', 'max-width' ) ;
$dst = array( 'dst_height' => null, 'dst_height' => null );
// For flex, limit size of image displayed to 1500px unless theme says otherwise
if ( $has_flex_width ) {
$max_width = 1500;
}
if ( $has_max_width ) {
$max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
}
$max_width = max( $max_width, $theme_width );
if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
$dst['dst_height'] = absint( $height * ( $max_width / $width ) );
}
elseif ( $has_flex_height && $has_flex_width ) {
$dst['dst_height'] = $height;
}
else {
$dst['dst_height'] = $theme_height;
}
if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
$dst['dst_width'] = absint( $width * ( $max_width / $width ) );
}
elseif ( $has_flex_width && $has_flex_height ) {
$dst['dst_width'] = $width;
}
else {
$dst['dst_width'] = $theme_width;
}
return $dst;
}
/**
* Create an attachment 'object'.
*
* @param string $cropped Cropped image URL.
* @param int $parent_attachment_id Attachment ID of parent image.
*
* @return array Attachment object.
*/
final public function create_attachment_object( $cropped, $parent_attachment_id ) {
$parent = get_post( $parent_attachment_id );
$parent_url = $parent->guid;
$url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
$size = @getimagesize( $cropped );
$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
$object = array(
'ID' => $parent_attachment_id,
'post_title' => basename($cropped),
'post_content' => $url,
'post_mime_type' => $image_type,
'guid' => $url,
'context' => 'custom-header'
);
return $object;
}
/**
* Insert an attachment & its metadata.
*
* @param array $object Attachment object.
* @param string $cropped Cropped image URL.
*
* @return int Attachment ID.
*/
final public function insert_attachment( $object, $cropped ) {
$attachment_id = wp_insert_attachment( $object, $cropped );
$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
/**
* Allows us to insert custom meta data for an attachment.
*
*/
$metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
wp_update_attachment_metadata( $attachment_id, $metadata );
return $attachment_id;
}
/**
* Gets attachment uploaded by Media Manager, crops it, then saves it as a
* new object. Returns JSON-encoded object details.
*/
function ajax_header_crop() {
check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error();
}
if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
wp_send_json_error();
}
$crop_details = $_POST['cropDetails'];
$dimensions = $this->get_header_dimensions( array(
'height' => $crop_details['height'],
'width' => $crop_details['width'],
) );
$attachment_id = absint( $_POST['id'] );
$cropped = wp_crop_image(
$attachment_id,
(int) $crop_details['x1'],
(int) $crop_details['y1'],
(int) $crop_details['width'],
(int) $crop_details['height'],
(int) $dimensions['dst_width'],
(int) $dimensions['dst_height']
);
if ( ! $cropped || is_wp_error( $cropped ) ) {
wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
}
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
$object = $this->create_attachment_object( $cropped, $attachment_id );
unset( $object['ID'] );
$new_attachment_id = $this->insert_attachment( $object, $cropped );
$object['attachment_id'] = $new_attachment_id;
$object['width'] = $dimensions['dst_width'];
$object['height'] = $dimensions['dst_height'];
wp_send_json_success( $object );
}
/**
* Given an attachment ID for a header image, updates its "last used"
* timestamp to now.
*
* Triggered when the user tries adds a new header image from the
* Media Manager, even if s/he doesn't save that change.
*/
function ajax_header_add() {
check_ajax_referer( 'header-add', 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error();
}
$attachment_id = absint( $_POST['attachment_id'] );
if ( $attachment_id < 1 ) {
wp_send_json_error();
}
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
update_post_meta( $attachment_id, $key, time() );
update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
wp_send_json_success();
}
/**
* Given an attachment ID for a header image, unsets it as a user-uploaded
* header image for the current theme.
*
* Triggered when the user clicks the overlay "X" button next to each image
* choice in the Customizer's Header tool.
*/
function ajax_header_remove() {
check_ajax_referer( 'header-remove', 'nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error();
}
$attachment_id = absint( $_POST['attachment_id'] );
if ( $attachment_id < 1 ) {
wp_send_json_error();
}
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
delete_post_meta( $attachment_id, $key );
delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
wp_send_json_success();
}
function customize_set_last_used( $wp_customize ) {
$data = $wp_customize->get_setting( 'header_image_data' )->post_value();
if ( ! isset( $data['attachment_id'] ) ) {
return;
}
$attachment_id = $data['attachment_id'];
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
update_post_meta( $attachment_id, $key, time() );
}
}

View File

@ -1,3 +1,4 @@
/* globals _wpCustomizeHeader, _wpMediaViewsL10n */
(function( exports, $ ){
var api = wp.customize;
@ -306,6 +307,217 @@
}
});
api.HeaderControl = api.Control.extend({
ready: function() {
this.btnRemove = $('.actions .remove');
this.btnNew = $('.actions .new');
_.bindAll(this, 'openMedia', 'removeImage');
this.btnNew.on( 'click', this.openMedia );
this.btnRemove.on( 'click', this.removeImage );
api.HeaderTool.currentHeader = new api.HeaderTool.ImageModel();
new api.HeaderTool.CurrentView({
model: api.HeaderTool.currentHeader,
el: '.current .container'
});
new api.HeaderTool.ChoiceListView({
collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
el: '.choices .uploaded .list'
});
new api.HeaderTool.ChoiceListView({
collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
el: '.choices .default .list'
});
api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
api.HeaderTool.UploadsList,
api.HeaderTool.DefaultsList
]);
},
/**
* Returns a set of options, computed from the attached image data and
* theme-specific data, to be fed to the imgAreaSelect plugin in
* wp.media.view.Cropper.
*
* @param {wp.media.model.Attachment} attachment
* @param {wp.media.controller.Cropper} controller
* @returns {Object} Options
*/
calculateImageSelectOptions: function(attachment, controller) {
var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
yInit = parseInt(_wpCustomizeHeader.data.height, 10),
flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
ratio, xImg, yImg, realHeight, realWidth,
imgSelectOptions;
realWidth = attachment.get('width');
realHeight = attachment.get('height');
this.headerImage = new api.HeaderTool.ImageModel();
this.headerImage.set({
themeWidth: xInit,
themeHeight: yInit,
themeFlexWidth: flexWidth,
themeFlexHeight: flexHeight,
imageWidth: realWidth,
imageHeight: realHeight
});
controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
ratio = xInit / yInit;
xImg = realWidth;
yImg = realHeight;
if ( xImg / yImg > ratio ) {
yInit = yImg;
xInit = yInit * ratio;
} else {
xInit = xImg;
yInit = xInit / ratio;
}
imgSelectOptions = {
handles: true,
keys: true,
instance: true,
persistent: true,
parent: this.$el,
imageWidth: realWidth,
imageHeight: realHeight,
x1: 0,
y1: 0,
x2: xInit,
y2: yInit
};
if (flexHeight === false && flexWidth === false) {
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
}
if (flexHeight === false ) {
imgSelectOptions.maxHeight = yInit;
}
if (flexWidth === false ) {
imgSelectOptions.maxWidth = xInit;
}
return imgSelectOptions;
},
/**
* Sets up and opens the Media Manager in order to select an image.
* Depending on both the size of the image and the properties of the
* current theme, a cropping step after selection may be required or
* skippable.
*
* @param {event} event
*/
openMedia: function(event) {
var title, suggestedWidth, suggestedHeight,
l10n = _wpMediaViewsL10n;
event.preventDefault();
suggestedWidth = l10n.suggestedWidth.replace('%d', _wpCustomizeHeader.data.width);
suggestedHeight = l10n.suggestedHeight.replace('%d', _wpCustomizeHeader.data.height);
/* '<span class="suggested-dimensions">' + suggestedWidth + ' ' + suggestedHeight + '</span>' */
this.frame = wp.media({
title: l10n.chooseImage,
library: {
type: 'image'
},
button: {
text: l10n.selectAndCrop,
close: false
},
multiple: false,
imgSelectOptions: this.calculateImageSelectOptions
});
this.frame.states.add([new wp.media.controller.Cropper()]);
this.frame.on('select', this.onSelect, this);
this.frame.on('cropped', this.onCropped, this);
this.frame.on('skippedcrop', this.onSkippedCrop, this);
this.frame.open();
},
onSelect: function() {
this.frame.setState('cropper');
},
onCropped: function(croppedImage) {
var url = croppedImage.post_content,
attachmentId = croppedImage.attachment_id,
w = croppedImage.width,
h = croppedImage.height;
this.setImageFromURL(url, attachmentId, w, h);
},
onSkippedCrop: function(selection) {
var url = selection.get('url'),
w = selection.get('width'),
h = selection.get('height');
this.setImageFromURL(url, selection.id, w, h);
},
/**
* Creates a new wp.customize.HeaderTool.ImageModel from provided
* header image data and inserts it into the user-uploaded headers
* collection.
*
* @param {String} url
* @param {Number} attachmentId
* @param {Number} width
* @param {Number} height
*/
setImageFromURL: function(url, attachmentId, width, height) {
var choice, data = {};
data.url = url;
data.thumbnail_url = url;
if (attachmentId) {
data.attachment_id = attachmentId;
}
if (width) {
data.width = width;
}
if (height) {
data.height = height;
}
choice = new api.HeaderTool.ImageModel({
header: data,
choice: url.split('/').pop()
});
api.HeaderTool.UploadsList.add(choice);
api.HeaderTool.currentHeader.set(choice.toJSON());
choice.save();
choice.importImage();
},
/**
* Triggers the necessary events to deselect an image which was set as
* the currently selected one.
*/
removeImage: function() {
api.HeaderTool.currentHeader.trigger('hide');
api.HeaderTool.CombinedList.trigger('control:removeImage');
}
});
// Change objects contained within the main customize object to Settings.
api.defaultConstructor = api.Setting;
@ -686,7 +898,8 @@
api.controlConstructor = {
color: api.ColorControl,
upload: api.UploadControl,
image: api.ImageControl
image: api.ImageControl,
header: api.HeaderControl
};
$( function() {
@ -961,35 +1174,6 @@
});
});
// Handle header image data
api.control( 'header_image', function( control ) {
control.setting.bind( function( to ) {
if ( to === control.params.removed )
control.settings.data.set( false );
});
control.library.on( 'click', 'a', function() {
control.settings.data.set( $(this).data('customizeHeaderImageData') );
});
control.uploader.success = function( attachment ) {
var data;
api.ImageControl.prototype.success.call( control, attachment );
data = {
attachment_id: attachment.get('id'),
url: attachment.get('url'),
thumbnail_url: attachment.get('url'),
height: attachment.get('height'),
width: attachment.get('width')
};
attachment.element.data( 'customizeHeaderImageData', data );
control.settings.data.set( data );
};
});
api.trigger( 'ready' );
// Make sure left column gets focus

File diff suppressed because one or more lines are too long

View File

@ -708,37 +708,9 @@ class WP_Customize_Background_Image_Control extends WP_Customize_Image_Control {
}
}
/**
* Customize Header Image Control Class
*
* @package WordPress
* @subpackage Customize
* @since 3.4.0
*/
class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
/**
* The processed default headers.
* @since 3.4.2
* @var array
*/
protected $default_headers;
final class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
public $type = 'header';
/**
* The uploaded headers.
* @since 3.4.2
* @var array
*/
protected $uploaded_headers;
/**
* Constructor.
*
* @since 3.4.0
* @uses WP_Customize_Image_Control::__construct()
* @uses WP_Customize_Image_Control::add_tab()
*
* @param WP_Customize_Manager $manager
*/
public function __construct( $manager ) {
parent::__construct( $manager, 'header_image', array(
'label' => __( 'Header Image' ),
@ -750,86 +722,305 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
'context' => 'custom-header',
'removed' => 'remove-header',
'get_url' => 'get_header_image',
'statuses' => array(
'' => __('Default'),
'remove-header' => __('No Image'),
'random-default-image' => __('Random Default Image'),
'random-uploaded-image' => __('Random Uploaded Image'),
) );
}
public function to_json() {
parent::to_json();
}
public function enqueue() {
wp_enqueue_media();
wp_enqueue_script( 'customize-views' );
$this->prepare_control();
wp_localize_script( 'customize-views', '_wpCustomizeHeader', array(
'data' => array(
'width' => absint( get_theme_support( 'custom-header', 'width' ) ),
'height' => absint( get_theme_support( 'custom-header', 'height' ) ),
'flex-width' => absint( get_theme_support( 'custom-header', 'flex-width' ) ),
'flex-height' => absint( get_theme_support( 'custom-header', 'flex-height' ) ),
'currentImgSrc' => $this->get_current_image_src(),
),
'nonces' => array(
'add' => wp_create_nonce( 'header-add' ),
'remove' => wp_create_nonce( 'header-remove' ),
),
'l10n' => array(
/* translators: header images uploaded by user */
'uploaded' => __( 'uploaded' ),
/* translators: header images suggested by the current theme */
'default' => __( 'suggested' )
),
'uploads' => $this->uploaded_headers,
'defaults' => $this->default_headers
) );
parent::enqueue();
}
public function get_default_header_images() {
global $custom_image_header;
// Get *the* default image if there is one
$default = get_theme_support( 'custom-header', 'default-image' );
if ( ! $default ) { // If not,
return $custom_image_header->default_headers; // easy peasy.
}
$default = sprintf( $default,
get_template_directory_uri(),
get_stylesheet_directory_uri() );
$header_images = array();
$already_has_default = false;
// Get the whole set of default images
$default_header_images = $custom_image_header->default_headers;
foreach ( $default_header_images as $k => $h ) {
if ( $h['url'] == $default ) {
$already_has_default = true;
break;
}
}
// If *the one true image* isn't included in the default set, add it in
// first position
if ( ! $already_has_default ) {
$header_images['default'] = array(
'url' => $default,
'thumbnail_url' => $default,
'description' => 'Default'
);
}
// The rest of the set comes after
$header_images = array_merge( $header_images, $default_header_images );
return $header_images;
}
public function get_uploaded_header_images() {
$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
$header_images = array();
$headers_not_dated = get_posts( array(
'post_type' => 'attachment',
'meta_key' => '_wp_attachment_is_custom_header',
'meta_value' => get_option('stylesheet'),
'orderby' => 'none',
'nopaging' => true,
'meta_query' => array(
array(
'key' => '_wp_attachment_is_custom_header',
'value' => get_option( 'stylesheet' ),
'compare' => 'LIKE'
),
array(
'key' => $key,
'value' => 'this string must not be empty',
'compare' => 'NOT EXISTS'
),
)
) );
// Remove the upload tab.
$this->remove_tab( 'upload-new' );
$headers_dated = get_posts( array(
'post_type' => 'attachment',
'meta_key' => $key,
'orderby' => 'meta_value_num',
'order' => 'DESC',
'nopaging' => true,
'meta_query' => array(
array(
'key' => '_wp_attachment_is_custom_header',
'value' => get_option( 'stylesheet' ),
'compare' => 'LIKE'
),
),
) );
$limit = apply_filters( 'custom_header_uploaded_limit', 15 );
$headers = array_merge( $headers_dated, $headers_not_dated );
$headers = array_slice( $headers, 0, $limit );
foreach ( (array) $headers as $header ) {
$url = esc_url_raw( $header->guid );
$header_data = wp_get_attachment_metadata( $header->ID );
$timestamp = get_post_meta( $header->ID,
'_wp_attachment_custom_header_last_used_' . get_stylesheet(),
true );
$h = array(
'attachment_id' => $header->ID,
'url' => $url,
'thumbnail_url' => $url,
'timestamp' => $timestamp ? $timestamp : 0,
);
if ( isset( $header_data['width'] ) ) {
$h['width'] = $header_data['width'];
}
if ( isset( $header_data['height'] ) ) {
$h['height'] = $header_data['height'];
}
$header_images[] = $h;
}
return $header_images;
}
/**
* Prepares the control.
*
* If no tabs exist, removes the control from the manager.
*
* @since 3.4.2
*/
public function prepare_control() {
global $custom_image_header;
if ( empty( $custom_image_header ) )
return parent::prepare_control();
if ( empty( $custom_image_header ) ) {
return;
}
// Process default headers and uploaded headers.
$custom_image_header->process_default_headers();
$this->default_headers = $custom_image_header->default_headers;
$this->uploaded_headers = get_uploaded_header_images();
if ( $this->default_headers )
$this->add_tab( 'default', __('Default'), array( $this, 'tab_default_headers' ) );
if ( ! $this->uploaded_headers )
$this->remove_tab( 'uploaded' );
return parent::prepare_control();
$this->default_headers = $this->get_default_header_images();
$this->uploaded_headers = $this->get_uploaded_header_images();
}
/**
* @since 3.4.0
*
* @param mixed $choice Which header image to select. (@see Custom_Image_Header::get_header_image() )
* @param array $header
*/
public function print_header_image( $choice, $header ) {
$header['url'] = set_url_scheme( $header['url'] );
$header['thumbnail_url'] = set_url_scheme( $header['thumbnail_url'] );
$header_image_data = array( 'choice' => $choice );
foreach ( array( 'attachment_id', 'width', 'height', 'url', 'thumbnail_url' ) as $key ) {
if ( isset( $header[ $key ] ) )
$header_image_data[ $key ] = $header[ $key ];
}
function print_header_image_template() {
?>
<a href="#" class="thumbnail"
data-customize-image-value="<?php echo esc_url( $header['url'] ); ?>"
data-customize-header-image-data="<?php echo esc_attr( json_encode( $header_image_data ) ); ?>">
<img src="<?php echo esc_url( $header['thumbnail_url'] ); ?>" />
</a>
<script type="text/template" id="tmpl-header-choice">
<# if (data.random) { #>
<div class="placeholder random">
<div class="inner">
<span><span class="dice">&#9860;</span>
<# if ( data.type === 'uploaded' ) { #>
<?php _e( 'Randomize uploaded headers' ); ?>
<# } else if ( data.type === 'suggested' ) { #>
<?php _e( 'Randomize suggested headers' ); ?>
<# } #>
</span>
</div>
</div>
<# } else { #>
<# if (data.type === 'uploaded') { #>
<div class="dashicons dashicons-no close"></div>
<# } #>
<a href="#" class="choice thumbnail #>"
data-customize-image-value="{{{data.header.url}}}"
data-customize-header-image-data="{{JSON.stringify(data.header)}}">
<img src="{{{data.header.thumbnail_url}}}">
</a>
<# } #>
</script>
<script type="text/template" id="tmpl-header-current">
<# if (data.choice) { #>
<# if (data.random) { #>
<div class="placeholder">
<div class="inner">
<span><span class="dice">&#9860;</span>
<# if ( data.type === 'uploaded' ) { #>
<?php _e( 'Randomizing uploaded headers' ); ?>
<# } else if ( data.type === 'suggested' ) { #>
<?php _e( 'Randomizing suggested headers' ); ?>
<# } #>
</span>
</div>
</div>
<# } else { #>
<img src="{{{data.header.thumbnail_url}}}" />
<# } #>
<# } else { #>
<div class="placeholder">
<div class="inner">
<span>
<?php _e( 'No image set' ); ?>
</span>
</div>
</div>
<# } #>
</script>
<?php
}
/**
* @since 3.4.0
*/
public function tab_uploaded() {
?><div class="uploaded-target"></div><?php
foreach ( $this->uploaded_headers as $choice => $header )
$this->print_header_image( $choice, $header );
public function get_current_image_src() {
$src = $this->value();
if ( isset( $this->get_url ) ) {
$src = call_user_func( $this->get_url, $src );
return $src;
}
return null;
}
/**
* @since 3.4.0
*/
public function tab_default_headers() {
foreach ( $this->default_headers as $choice => $header )
$this->print_header_image( $choice, $header );
public function render_content() {
$this->print_header_image_template();
$visibility = $this->get_current_image_src() ? '' : ' style="display:none" ';
$width = absint( get_theme_support( 'custom-header', 'width' ) );
$height = absint( get_theme_support( 'custom-header', 'height' ) );
?>
<div class="customize-control-content">
<p class="customizer-section-intro">
<?php _e( 'Personalize your site with your own header image.' ); ?>
<?php
if ( $width && $height ) {
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.' ),
_x( 'Add new', 'header image' ), $width, $height );
} else {
if ( $width ) {
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.' ),
_x( 'Add new', 'header image' ), $width );
}
if ( $height ) {
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.' ),
_x( 'Add new', 'header image' ), $height );
}
}
?>
</p>
<div class="current">
<span class="customize-control-title">
<?php _e( 'Current header' ); ?>
</span>
<div class="container">
</div>
</div>
<div class="actions">
<?php /* translators: Hide as in hide header image via the Customizer */ ?>
<a href="#" <?php echo $visibility ?> class="button remove"><?php _ex( 'Hide', 'custom header' ); ?></a>
<?php /* translators: New as in add new header image via the Customizer */ ?>
<a href="#" class="button new"><?php _ex( 'Add new', 'header image' ); ?></a>
<div style="clear:both"></div>
</div>
<div class="choices">
<span class="customize-control-title header-previously-uploaded">
<?php _ex( 'Previously uploaded', 'custom headers' ); ?>
</span>
<div class="uploaded">
<div class="list">
</div>
</div>
<span class="customize-control-title header-default">
<?php _ex( 'Suggested', 'custom headers' ); ?>
</span>
<div class="default">
<div class="list">
</div>
</div>
</div>
</div>
<?php
}
}

View File

@ -602,6 +602,19 @@
margin: 0;
}
.media-frame-title .suggested-dimensions {
font-size: 14px;
float: left;
margin-left: 20px;
}
.media-frame-content .crop-content {
display: block;
margin: auto;
max-width: 100%;
max-height: 100%;
}
/**
* Iframes
*/

File diff suppressed because one or more lines are too long

View File

@ -602,6 +602,19 @@
margin: 0;
}
.media-frame-title .suggested-dimensions {
font-size: 14px;
float: right;
margin-right: 20px;
}
.media-frame-content .crop-content {
display: block;
margin: auto;
max-width: 100%;
max-height: 100%;
}
/**
* Iframes
*/

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,249 @@
/* globals _wpCustomizeHeader, _wpCustomizeSettings */
(function( $, wp ) {
var api = wp.customize;
api.HeaderTool = {};
/**
* wp.customize.HeaderTool.ImageModel
*
* A header image. This is where saves via the Customizer API are
* abstracted away, plus our own AJAX calls to add images to and remove
* images from the user's recently uploaded images setting on the server.
* These calls are made regardless of whether the user actually saves new
* Customizer settings.
*
* @constructor
* @augments Backbone.Model
*/
api.HeaderTool.ImageModel = Backbone.Model.extend({
defaults: function() {
return {
header: {
attachment_id: 0,
url: '',
timestamp: Date.now(),
thumbnail_url: ''
},
choice: '',
hidden: false,
random: false
};
},
initialize: function() {
this.on('hide', this.hide, this);
},
hide: function() {
this.set('choice', '');
api('header_image').set('remove-header');
api('header_image_data').set('remove-header');
},
destroy: function() {
var data = this.get('header'),
curr = api.HeaderTool.currentHeader.get('header').attachment_id;
// If the image we're removing is also the current header, unset
// the latter
if (curr && data.attachment_id === curr) {
api.HeaderTool.currentHeader.trigger('hide');
}
wp.ajax.post( 'custom-header-remove', {
nonce: _wpCustomizeHeader.nonces.remove,
wp_customize: 'on',
theme: api.settings.theme.stylesheet,
attachment_id: data.attachment_id
});
this.trigger('destroy', this, this.collection);
},
save: function() {
if (this.get('random')) {
api('header_image').set(this.get('header').random);
api('header_image_data').set(this.get('header').random);
} else {
if (this.get('header').defaultName) {
api('header_image').set(this.get('header').url);
api('header_image_data').set(this.get('header').defaultName);
} else {
api('header_image').set(this.get('header').url);
api('header_image_data').set(this.get('header'));
}
}
api.HeaderTool.combinedList.trigger('control:setImage', this);
},
importImage: function() {
var data = this.get('header');
if (data.attachment_id === undefined) {
return;
}
wp.ajax.post( 'custom-header-add', {
nonce: _wpCustomizeHeader.nonces.add,
wp_customize: 'on',
theme: api.settings.theme.stylesheet,
attachment_id: data.attachment_id,
} );
},
shouldBeCropped: function() {
if (this.get('themeFlexWidth') === true &&
this.get('themeFlexHeight') === true) {
return false;
}
if (this.get('themeFlexWidth') === true &&
this.get('themeHeight') === this.get('imageHeight')) {
return false;
}
if (this.get('themeFlexHeight') === true &&
this.get('themeWidth') === this.get('imageWidth')) {
return false;
}
if (this.get('themeWidth') === this.get('imageWidth') &&
this.get('themeHeight') === this.get('imageHeight')) {
return false;
}
return true;
}
});
/**
* wp.customize.HeaderTool.ChoiceList
*
* @constructor
* @augments Backbone.Collection
*/
api.HeaderTool.ChoiceList = Backbone.Collection.extend({
model: api.HeaderTool.ImageModel,
// Ordered from most recently used to least
comparator: function(model) {
return -model.get('header').timestamp;
},
initialize: function() {
var current = api.HeaderTool.currentHeader.get('choice').replace(/^https?:\/\//, ''),
isRandom = this.isRandomChoice(api.get().header_image);
// Overridable by an extending class
if (!this.type) {
this.type = 'uploaded';
}
// Overridable by an extending class
if (!this.data) {
this.data = _wpCustomizeHeader.uploads;
}
if (isRandom) {
// So that when adding data we don't hide regular images
current = api.get().header_image;
}
this.on('control:setImage', this.setImage, this);
this.on('control:removeImage', this.removeImage, this);
this.on('add', this.maybeAddRandomChoice, this);
_.each(this.data, function(elt, index) {
if (!elt.attachment_id) {
elt.defaultName = index;
}
this.add({
header: elt,
choice: elt.url.split('/').pop(),
hidden: current === elt.url.replace(/^https?:\/\//, '')
}, { silent: true });
}, this);
if (this.size() > 0) {
this.addRandomChoice(current);
}
},
maybeAddRandomChoice: function() {
if (this.size() === 1) {
this.addRandomChoice();
}
},
addRandomChoice: function(initialChoice) {
var isRandomSameType = RegExp(this.type).test(initialChoice),
randomChoice = 'random-' + this.type + '-image';
this.add({
header: {
timestamp: 0,
random: randomChoice,
width: 245,
height: 41
},
choice: randomChoice,
random: true,
hidden: isRandomSameType
});
},
isRandomChoice: function(choice) {
return (/^random-(uploaded|default)-image$/).test(choice);
},
shouldHideTitle: function() {
return _.every(this.pluck('hidden'));
},
setImage: function(model) {
this.each(function(m) {
m.set('hidden', false);
});
if (model) {
model.set('hidden', true);
// Bump images to top except for special "Randomize" images
if (!model.get('random')) {
model.get('header').timestamp = Date.now();
this.sort();
}
}
},
removeImage: function() {
this.each(function(m) {
m.set('hidden', false);
});
},
shown: function() {
var filtered = this.where({ hidden: false });
return new api.HeaderTool.ChoiceList( filtered );
}
});
/**
* wp.customize.HeaderTool.DefaultsList
*
* @constructor
* @augments wp.customize.HeaderTool.ChoiceList
* @augments Backbone.Collection
*/
api.HeaderTool.DefaultsList = api.HeaderTool.ChoiceList.extend({
initialize: function() {
this.type = 'default';
this.data = _wpCustomizeHeader.defaults;
api.HeaderTool.ChoiceList.prototype.initialize.apply(this);
}
});
})( jQuery, window.wp );

View File

@ -0,0 +1 @@
!function(a,b){var c=b.customize;c.HeaderTool={},c.HeaderTool.ImageModel=Backbone.Model.extend({defaults:function(){return{header:{attachment_id:0,url:"",timestamp:Date.now(),thumbnail_url:""},choice:"",hidden:!1,random:!1}},initialize:function(){this.on("hide",this.hide,this)},hide:function(){this.set("choice",""),c("header_image").set("remove-header"),c("header_image_data").set("remove-header")},destroy:function(){var a=this.get("header"),d=c.HeaderTool.currentHeader.get("header").attachment_id;d&&a.attachment_id===d&&c.HeaderTool.currentHeader.trigger("hide"),b.ajax.post("custom-header-remove",{nonce:_wpCustomizeHeader.nonces.remove,wp_customize:"on",theme:c.settings.theme.stylesheet,attachment_id:a.attachment_id}),this.trigger("destroy",this,this.collection)},save:function(){this.get("random")?(c("header_image").set(this.get("header").random),c("header_image_data").set(this.get("header").random)):this.get("header").defaultName?(c("header_image").set(this.get("header").url),c("header_image_data").set(this.get("header").defaultName)):(c("header_image").set(this.get("header").url),c("header_image_data").set(this.get("header"))),c.HeaderTool.combinedList.trigger("control:setImage",this)},importImage:function(){var a=this.get("header");void 0!==a.attachment_id&&b.ajax.post("custom-header-add",{nonce:_wpCustomizeHeader.nonces.add,wp_customize:"on",theme:c.settings.theme.stylesheet,attachment_id:a.attachment_id})},shouldBeCropped:function(){return this.get("themeFlexWidth")===!0&&this.get("themeFlexHeight")===!0?!1:this.get("themeFlexWidth")===!0&&this.get("themeHeight")===this.get("imageHeight")?!1:this.get("themeFlexHeight")===!0&&this.get("themeWidth")===this.get("imageWidth")?!1:this.get("themeWidth")===this.get("imageWidth")&&this.get("themeHeight")===this.get("imageHeight")?!1:!0}}),c.HeaderTool.ChoiceList=Backbone.Collection.extend({model:c.HeaderTool.ImageModel,comparator:function(a){return-a.get("header").timestamp},initialize:function(){var a=c.HeaderTool.currentHeader.get("choice").replace(/^https?:\/\//,""),b=this.isRandomChoice(c.get().header_image);this.type||(this.type="uploaded"),this.data||(this.data=_wpCustomizeHeader.uploads),b&&(a=c.get().header_image),this.on("control:setImage",this.setImage,this),this.on("control:removeImage",this.removeImage,this),this.on("add",this.maybeAddRandomChoice,this),_.each(this.data,function(b,c){b.attachment_id||(b.defaultName=c),this.add({header:b,choice:b.url.split("/").pop(),hidden:a===b.url.replace(/^https?:\/\//,"")},{silent:!0})},this),this.size()>0&&this.addRandomChoice(a)},maybeAddRandomChoice:function(){1===this.size()&&this.addRandomChoice()},addRandomChoice:function(a){var b=RegExp(this.type).test(a),c="random-"+this.type+"-image";this.add({header:{timestamp:0,random:c,width:245,height:41},choice:c,random:!0,hidden:b})},isRandomChoice:function(a){return/^random-(uploaded|default)-image$/.test(a)},shouldHideTitle:function(){return _.every(this.pluck("hidden"))},setImage:function(a){this.each(function(a){a.set("hidden",!1)}),a&&(a.set("hidden",!0),a.get("random")||(a.get("header").timestamp=Date.now(),this.sort()))},removeImage:function(){this.each(function(a){a.set("hidden",!1)})},shown:function(){var a=this.where({hidden:!1});return new c.HeaderTool.ChoiceList(a)}}),c.HeaderTool.DefaultsList=c.HeaderTool.ChoiceList.extend({initialize:function(){this.type="default",this.data=_wpCustomizeHeader.defaults,c.HeaderTool.ChoiceList.prototype.initialize.apply(this)}})}(jQuery,window.wp);

View File

@ -0,0 +1,232 @@
/* globals _wpCustomizeHeader */
(function( $, wp, _ ) {
if ( ! wp || ! wp.customize ) { return; }
var api = wp.customize;
/**
* wp.customize.HeaderTool.CurrentView
*
* Displays the currently selected header image, or a placeholder in lack
* thereof.
*
* Instantiate with model wp.customize.HeaderTool.currentHeader.
*
* @constructor
* @augments wp.Backbone.View
*/
api.HeaderTool.CurrentView = wp.Backbone.View.extend({
template: wp.template('header-current'),
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.setPlaceholder();
this.setButtons();
return this;
},
getHeight: function() {
var image = this.$el.find('img'),
saved = this.model.get('savedHeight'),
height = image.height() || saved,
headerImageData;
if (image.length) {
this.$el.find('.inner').hide();
} else {
this.$el.find('.inner').show();
}
// happens at ready
if (!height) {
headerImageData = api.get().header_image_data;
if (headerImageData && headerImageData.width && headerImageData.height) {
// hardcoded container width
height = 260 / headerImageData.width * headerImageData.height;
}
else {
// fallback for when no image is set
height = 40;
}
}
return height;
},
setPlaceholder: function(_height) {
var height = _height || this.getHeight();
this.model.set('savedHeight', height);
this.$el
.add(this.$el.find('.placeholder'))
.height(height);
},
setButtons: function() {
var elements = $('.actions .remove');
if (this.model.get('choice')) {
elements.show();
} else {
elements.hide();
}
}
});
/**
* wp.customize.HeaderTool.ChoiceView
*
* Represents a choosable header image, be it user-uploaded,
* theme-suggested or a special Randomize choice.
*
* Takes a wp.customize.HeaderTool.ImageModel.
*
* Manually changes model wp.customize.HeaderTool.currentHeader via the
* `select` method.
*
* @constructor
* @augments wp.Backbone.View
*/
(function () { // closures FTW
var lastHeight = 0;
api.HeaderTool.ChoiceView = wp.Backbone.View.extend({
template: wp.template('header-choice'),
className: 'header-view',
events: {
'click .choice,.random': 'select',
'click .close': 'removeImage'
},
initialize: function() {
var properties = [
this.model.get('header').url,
this.model.get('choice')
];
this.listenTo(this.model, 'change', this.render);
if (_.contains(properties, api.get().header_image)) {
api.HeaderTool.currentHeader.set(this.extendedModel());
}
},
render: function() {
var model = this.model;
this.$el.html(this.template(this.extendedModel()));
if (model.get('random')) {
this.setPlaceholder(40);
}
else {
lastHeight = this.getHeight();
}
this.$el.toggleClass('hidden', model.get('hidden'));
return this;
},
extendedModel: function() {
var c = this.model.get('collection'),
t = _wpCustomizeHeader.l10n[c.type] || '';
return _.extend(this.model.toJSON(), {
// -1 to exclude the randomize button
nImages: c.size() - 1,
type: t
});
},
getHeight: api.HeaderTool.CurrentView.prototype.getHeight,
setPlaceholder: api.HeaderTool.CurrentView.prototype.setPlaceholder,
select: function() {
this.model.save();
api.HeaderTool.currentHeader.set(this.extendedModel());
},
removeImage: function(e) {
e.stopPropagation();
this.model.destroy();
this.remove();
}
});
})();
/**
* wp.customize.HeaderTool.ChoiceListView
*
* A container for ChoiceViews. These choices should be of one same type:
* user-uploaded headers or theme-defined ones.
*
* Takes a wp.customize.HeaderTool.ChoiceList.
*
* @constructor
* @augments wp.Backbone.View
*/
api.HeaderTool.ChoiceListView = wp.Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'remove', this.render);
this.listenTo(this.collection, 'sort', this.render);
this.listenTo(this.collection, 'change:hidden', this.toggleTitle);
this.listenTo(this.collection, 'change:hidden', this.setMaxListHeight);
this.render();
},
render: function() {
this.$el.empty();
this.collection.each(this.addOne, this);
this.toggleTitle();
},
addOne: function(choice) {
var view;
choice.set({ collection: this.collection });
view = new api.HeaderTool.ChoiceView({ model: choice });
this.$el.append(view.render().el);
},
toggleTitle: function() {
var title = this.$el.parents().prev('.customize-control-title');
if (this.collection.shouldHideTitle()) {
title.hide();
} else {
title.show();
}
}
});
/**
* wp.customize.HeaderTool.CombinedList
*
* Aggregates wp.customize.HeaderTool.ChoiceList collections (or any
* Backbone object, really) and acts as a bus to feed them events.
*
* @constructor
* @augments wp.Backbone.View
*/
api.HeaderTool.CombinedList = wp.Backbone.View.extend({
initialize: function(collections) {
this.collections = collections;
this.on('all', this.propagate, this);
},
propagate: function(event, arg) {
_.each(this.collections, function(collection) {
collection.trigger(event, arg);
});
}
});
})( jQuery, window.wp, _ );

1
wp-includes/js/customize-views.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(a,b,c){if(b&&b.customize){var d=b.customize;d.HeaderTool.CurrentView=b.Backbone.View.extend({template:b.template("header-current"),initialize:function(){this.listenTo(this.model,"change",this.render),this.render()},render:function(){return this.$el.html(this.template(this.model.toJSON())),this.setPlaceholder(),this.setButtons(),this},getHeight:function(){var a,b=this.$el.find("img"),c=this.model.get("savedHeight"),e=b.height()||c;return b.length?this.$el.find(".inner").hide():this.$el.find(".inner").show(),e||(a=d.get().header_image_data,e=a&&a.width&&a.height?260/a.width*a.height:40),e},setPlaceholder:function(a){var b=a||this.getHeight();this.model.set("savedHeight",b),this.$el.add(this.$el.find(".placeholder")).height(b)},setButtons:function(){var b=a(".actions .remove");this.model.get("choice")?b.show():b.hide()}}),function(){var a=0;d.HeaderTool.ChoiceView=b.Backbone.View.extend({template:b.template("header-choice"),className:"header-view",events:{"click .choice,.random":"select","click .close":"removeImage"},initialize:function(){var a=[this.model.get("header").url,this.model.get("choice")];this.listenTo(this.model,"change",this.render),c.contains(a,d.get().header_image)&&d.HeaderTool.currentHeader.set(this.extendedModel())},render:function(){var b=this.model;return this.$el.html(this.template(this.extendedModel())),b.get("random")?this.setPlaceholder(40):a=this.getHeight(),this.$el.toggleClass("hidden",b.get("hidden")),this},extendedModel:function(){var a=this.model.get("collection"),b=_wpCustomizeHeader.l10n[a.type]||"";return c.extend(this.model.toJSON(),{nImages:a.size()-1,type:b})},getHeight:d.HeaderTool.CurrentView.prototype.getHeight,setPlaceholder:d.HeaderTool.CurrentView.prototype.setPlaceholder,select:function(){this.model.save(),d.HeaderTool.currentHeader.set(this.extendedModel())},removeImage:function(a){a.stopPropagation(),this.model.destroy(),this.remove()}})}(),d.HeaderTool.ChoiceListView=b.Backbone.View.extend({initialize:function(){this.listenTo(this.collection,"add",this.addOne),this.listenTo(this.collection,"remove",this.render),this.listenTo(this.collection,"sort",this.render),this.listenTo(this.collection,"change:hidden",this.toggleTitle),this.listenTo(this.collection,"change:hidden",this.setMaxListHeight),this.render()},render:function(){this.$el.empty(),this.collection.each(this.addOne,this),this.toggleTitle()},addOne:function(a){var b;a.set({collection:this.collection}),b=new d.HeaderTool.ChoiceView({model:a}),this.$el.append(b.render().el)},toggleTitle:function(){var a=this.$el.parents().prev(".customize-control-title");this.collection.shouldHideTitle()?a.hide():a.show()}}),d.HeaderTool.CombinedList=b.Backbone.View.extend({initialize:function(a){this.collections=a,this.on("all",this.propagate,this)},propagate:function(a,b){c.each(this.collections,function(c){c.trigger(a,b)})}})}}(jQuery,window.wp,_);

View File

@ -1316,6 +1316,109 @@
}
});
/**
* wp.media.controller.Cropper
*
* Allows for a cropping step.
*
* @constructor
* @augments wp.media.controller.State
* @augments Backbone.Model
*/
media.controller.Cropper = media.controller.State.extend({
defaults: {
id: 'cropper',
title: l10n.cropImage,
toolbar: 'crop',
content: 'crop',
router: false,
canSkipCrop: false
},
activate: function() {
this.frame.on( 'content:create:crop', this.createCropContent, this );
this.frame.on( 'close', this.removeCropper, this );
this.set('selection', new Backbone.Collection(this.frame._selection.single));
},
deactivate: function() {
this.frame.toolbar.mode('browse');
},
createCropContent: function() {
this.cropperView = new wp.media.view.Cropper({controller: this,
attachment: this.get('selection').first() });
this.cropperView.on('image-loaded', this.createCropToolbar, this);
this.frame.content.set(this.cropperView);
},
removeCropper: function() {
this.imgSelect.cancelSelection();
this.imgSelect.setOptions({remove: true});
this.imgSelect.update();
this.cropperView.remove();
},
createCropToolbar: function() {
var canSkipCrop, toolbarOptions;
canSkipCrop = this.get('canSkipCrop') || false;
toolbarOptions = {
controller: this.frame,
items: {
insert: {
style: 'primary',
text: l10n.cropImage,
priority: 80,
requires: { library: false, selection: false },
click: function() {
var self = this,
selection = this.controller.state().get('selection').first();
selection.set({cropDetails: this.controller.state().imgSelect.getSelection()});
this.$el.text(l10n.cropping);
this.$el.attr('disabled', true);
this.controller.state().doCrop( selection ).done( function( croppedImage ) {
console.log( croppedImage );
self.controller.trigger('cropped', croppedImage );
self.controller.close();
});
}
}
}
};
if ( canSkipCrop ) {
_.extend( toolbarOptions.items, {
skip: {
style: 'secondary',
text: l10n.skipCropping,
priority: 70,
requires: { library: false, selection: false },
click: function() {
var selection = this.controller.state().get('selection').first();
this.controller.state().cropperView.remove();
this.controller.trigger('skippedcrop', selection);
this.controller.close();
}
}
});
}
this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
},
doCrop: function( attachment ) {
return wp.ajax.post( 'custom-header-crop', {
nonce: attachment.get('nonces').edit,
id: attachment.get('id'),
cropDetails: attachment.get('cropDetails')
} );
}
});
/**
* ========================================================================
* VIEWS
@ -6323,6 +6426,53 @@
}
});
/**
* wp.media.view.Cropper
*
* Uses the imgAreaSelect plugin to allow a user to crop an image.
*
* Takes imgAreaSelect options from
* wp.customize.HeaderControl.calculateImageSelectOptions via
* wp.customize.HeaderControl.openMM.
*
* @constructor
* @augments wp.media.View
* @augments wp.Backbone.View
* @augments Backbone.View
*/
media.view.Cropper = media.View.extend({
tagName: 'img',
className: 'crop-content',
initialize: function() {
_.bindAll(this, 'onImageLoad');
this.$el.attr('src', this.options.attachment.get('url'));
},
ready: function() {
this.$el.on('load', this.onImageLoad);
$(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
},
remove: function() {
$(window).off('resize.cropper');
this.$el.remove();
this.$el.off();
wp.media.View.prototype.remove.apply(this, arguments);
},
prepare: function() {
return {
title: l10n.cropYourImage,
url: this.options.attachment.get('url')
};
},
onImageLoad: function() {
var imgOptions = this.controller.frame.options.imgSelectOptions;
if (typeof imgOptions === 'function') {
imgOptions = imgOptions(this.options.attachment, this.controller);
}
this.trigger('image-loaded');
this.controller.imgSelect = this.$el.imgAreaSelect(imgOptions);
}
});
media.view.EditImage = media.View.extend({

File diff suppressed because one or more lines are too long

View File

@ -2476,6 +2476,23 @@ function wp_enqueue_media( $args = array() ) {
'imageDetailsCancel' => __( 'Cancel Edit' ),
'editImage' => __( 'Edit Image' ),
// Crop Image
/* translators: title for Media Manager library view */
'chooseImage' => __( 'Choose Image' ),
/* translators: button to select an image from the MM library to crop */
'selectAndCrop' => __( 'Select and Crop' ),
/* translators: button to choose not to crop the selected image */
'skipCropping' => __( 'Skip Cropping' ),
/* translators: button to choose to crop the selected image */
'cropImage' => __( 'Crop Image' ),
'cropYourImage' => __( 'Crop your image' ),
/* translators: button label changes to this while the image is being cropped server-side */
'cropping' => __( 'Cropping...' ),
/* translators: suggested width of header image in pixels */
'suggestedWidth' => __( 'Suggested width is %d pixels.' ),
/* translators: suggested height of header image in pixels */
'suggestedHeight' => __( 'Suggested height is %d pixels.' ),
// Edit Audio
'audioDetailsTitle' => __( 'Audio Details' ),
'audioReplaceTitle' => __( 'Replace Audio' ),

View File

@ -363,6 +363,8 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'customize-base', "/wp-includes/js/customize-base$suffix.js", array( 'jquery', 'json2' ), false, 1 );
$scripts->add( 'customize-loader', "/wp-includes/js/customize-loader$suffix.js", array( 'customize-base' ), false, 1 );
$scripts->add( 'customize-preview', "/wp-includes/js/customize-preview$suffix.js", array( 'customize-base' ), false, 1 );
$scripts->add( 'customize-models', "/wp-includes/js/customize-models.js", array( 'underscore', 'backbone' ), false, 1 );
$scripts->add( 'customize-views', "/wp-includes/js/customize-views.js", array( 'jquery', 'underscore', 'imgareaselect', 'customize-models' ), false, 1 );
$scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base' ), false, 1 );
did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array(
'activate' => __( 'Save &amp; Activate' ),
@ -600,7 +602,7 @@ function wp_default_styles( &$styles ) {
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
$styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
$styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie' ) );
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
$styles->add( 'ie', "/wp-admin/css/ie$suffix.css" );
$styles->add_data( 'ie', 'conditional', 'lte IE 7' );