<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[27497] trunk/src: Add header image uploads with cropping to the customizer.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/27497">27497</a></dd>
<dt>Author</dt> <dd>nacin</dd>
<dt>Date</dt> <dd>2014-03-11 04:12:17 +0000 (Tue, 11 Mar 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add header image uploads with cropping to the customizer.

props mcsf, ehg, gcorne.
see <a href="http://core.trac.wordpress.org/ticket/21785">#21785</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadmincsscustomizecontrolscss">trunk/src/wp-admin/css/customize-controls.css</a></li>
<li><a href="#trunksrcwpadmincustomheaderphp">trunk/src/wp-admin/custom-header.php</a></li>
<li><a href="#trunksrcwpadminjscustomizecontrolsjs">trunk/src/wp-admin/js/customize-controls.js</a></li>
<li><a href="#trunksrcwpincludesclasswpcustomizecontrolphp">trunk/src/wp-includes/class-wp-customize-control.php</a></li>
<li><a href="#trunksrcwpincludescssmediaviewscss">trunk/src/wp-includes/css/media-views.css</a></li>
<li><a href="#trunksrcwpincludesjsmediaviewsjs">trunk/src/wp-includes/js/media-views.js</a></li>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
<li><a href="#trunksrcwpincludesscriptloaderphp">trunk/src/wp-includes/script-loader.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesjscustomizemodelsjs">trunk/src/wp-includes/js/customize-models.js</a></li>
<li><a href="#trunksrcwpincludesjscustomizeviewsjs">trunk/src/wp-includes/js/customize-views.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadmincsscustomizecontrolscss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-admin/css/customize-controls.css (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/css/customize-controls.css    2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-admin/css/customize-controls.css       2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -455,6 +455,167 @@
</span><span class="cx">  -webkit-overflow-scrolling: touch;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+/** 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;
+}
+
+
</ins><span class="cx"> /** Handle cheaters. */
</span><span class="cx"> body.cheatin {
</span><span class="cx">  min-width: 0;
</span></span></pre></div>
<a id="trunksrcwpadmincustomheaderphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-admin/custom-header.php (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/custom-header.php     2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-admin/custom-header.php        2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -43,7 +43,7 @@
</span><span class="cx">  var $default_headers = array();
</span><span class="cx"> 
</span><span class="cx">  /**
</span><del>-        * Holds custom headers uploaded by the user
</del><ins>+         * Holds custom headers uploaded by the user.
</ins><span class="cx">    *
</span><span class="cx">   * @var array
</span><span class="cx">   * @since 3.2.0
</span><span class="lines">@@ -73,6 +73,11 @@
</span><span class="cx">          $this->admin_image_div_callback = $admin_image_div_callback;
</span><span class="cx"> 
</span><span class="cx">          add_action( 'admin_menu', array( $this, 'init' ) );
</span><ins>+
+               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'      ) );
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">  /**
</span><span class="lines">@@ -93,6 +98,7 @@
</span><span class="cx">          add_action("admin_head-$page", array($this, 'js'), 50);
</span><span class="cx">          if ( $this->admin_header_callback )
</span><span class="cx">                  add_action("admin_head-$page", $this->admin_header_callback, 51);
</span><ins>+
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">  /**
</span><span class="lines">@@ -819,32 +825,15 @@
</span><span class="cx">          $attachment_id = absint( $_POST['attachment_id'] );
</span><span class="cx">          $original = get_attached_file($attachment_id);
</span><span class="cx"> 
</span><ins>+               $dimensions = $this->get_header_dimensions( array(
+                       'height' => $_POST['height'],
+                       'width'  => $_POST['width'],
+               ) );
+               $height = $dimensions['dst_height'];
+               $width = $dimensions['dst_width'];
</ins><span class="cx"> 
</span><del>-               $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' );
-
</del><span class="cx">           if ( empty( $_POST['skip-cropping'] ) )
</span><del>-                       $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $dst_width, $dst_height );
</del><ins>+                        $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $width, $height );
</ins><span class="cx">           elseif ( ! empty( $_POST['create-new-attachment'] ) )
</span><span class="cx">                  $cropped = _copy_image_file( $attachment_id );
</span><span class="cx">          else
</span><span class="lines">@@ -856,31 +845,15 @@
</span><span class="cx">          /** This filter is documented in wp-admin/custom-header.php */
</span><span class="cx">          $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
</span><span class="cx"> 
</span><del>-               $parent = get_post($attachment_id);
-               $parent_url = $parent->guid;
-               $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
</del><ins>+                $object = $this->create_attachment_object( $cropped, $attachment_id );
</ins><span class="cx"> 
</span><del>-               $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'
-               );
</del><span class="cx">           if ( ! empty( $_POST['create-new-attachment'] ) )
</span><span class="cx">                  unset( $object['ID'] );
</span><span class="cx"> 
</span><span class="cx">          // Update the attachment
</span><del>-               $attachment_id = wp_insert_attachment( $object, $cropped );
-               wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $cropped ) );
</del><ins>+                $attachment_id = $this->insert_attachment( $object, $cropped );
</ins><span class="cx"> 
</span><del>-               $width = $dst_width;
-               $height = $dst_height;
</del><ins>+                $url = $object['guid'];
</ins><span class="cx">           $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
</span><span class="cx"> 
</span><span class="cx">          // cleanup
</span><span class="lines">@@ -1041,4 +1014,218 @@
</span><span class="cx">          set_theme_mod( 'header_image', $default );
</span><span class="cx">          set_theme_mod( 'header_image_data', (object) $default_data );
</span><span class="cx">  }
</span><ins>+
+       /**
+        * 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() );
+       }
</ins><span class="cx"> }
</span></span></pre></div>
<a id="trunksrcwpadminjscustomizecontrolsjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-admin/js/customize-controls.js (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-admin/js/customize-controls.js      2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-admin/js/customize-controls.js 2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -1,3 +1,4 @@
</span><ins>+/* globals _wpCustomizeHeader, _wpMediaViewsL10n */
</ins><span class="cx"> (function( exports, $ ){
</span><span class="cx">  var api = wp.customize;
</span><span class="cx"> 
</span><span class="lines">@@ -306,6 +307,217 @@
</span><span class="cx">          }
</span><span class="cx">  });
</span><span class="cx"> 
</span><ins>+       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');
+               }
+
+       });
+
</ins><span class="cx">   // Change objects contained within the main customize object to Settings.
</span><span class="cx">  api.defaultConstructor = api.Setting;
</span><span class="cx"> 
</span><span class="lines">@@ -686,7 +898,8 @@
</span><span class="cx">  api.controlConstructor = {
</span><span class="cx">          color:  api.ColorControl,
</span><span class="cx">          upload: api.UploadControl,
</span><del>-               image:  api.ImageControl
</del><ins>+                image:  api.ImageControl,
+               header: api.HeaderControl
</ins><span class="cx">   };
</span><span class="cx"> 
</span><span class="cx">  $( function() {
</span><span class="lines">@@ -961,35 +1174,6 @@
</span><span class="cx">                  });
</span><span class="cx">          });
</span><span class="cx"> 
</span><del>-               // 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 );
-                       };
-               });
-
</del><span class="cx">           api.trigger( 'ready' );
</span><span class="cx"> 
</span><span class="cx">          // Make sure left column gets focus
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpcustomizecontrolphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/class-wp-customize-control.php (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/class-wp-customize-control.php     2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-includes/class-wp-customize-control.php        2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -708,37 +708,9 @@
</span><span class="cx">  }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-/**
- * 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;
</del><ins>+final class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
+       public $type = 'header';
</ins><span class="cx"> 
</span><del>-       /**
-        * 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
-        */
</del><span class="cx">   public function __construct( $manager ) {
</span><span class="cx">          parent::__construct( $manager, 'header_image', array(
</span><span class="cx">                  'label'    => __( 'Header Image' ),
</span><span class="lines">@@ -750,86 +722,305 @@
</span><span class="cx">                  'context'  => 'custom-header',
</span><span class="cx">                  'removed'  => 'remove-header',
</span><span class="cx">                  'get_url'  => 'get_header_image',
</span><del>-                       'statuses' => array(
-                               ''                      => __('Default'),
-                               'remove-header'         => __('No Image'),
-                               'random-default-image'  => __('Random Default Image'),
-                               'random-uploaded-image' => __('Random Uploaded Image'),
</del><ins>+                ) );
+
+       }
+
+       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'
+                               ),
</ins><span class="cx">                   )
</span><span class="cx">          ) );
</span><span class="cx"> 
</span><del>-               // Remove the upload tab.
-               $this->remove_tab( 'upload-new' );
</del><ins>+                $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;
</ins><span class="cx">   }
</span><span class="cx"> 
</span><del>-       /**
-        * Prepares the control.
-        *
-        * If no tabs exist, removes the control from the manager.
-        *
-        * @since 3.4.2
-        */
</del><span class="cx">   public function prepare_control() {
</span><span class="cx">          global $custom_image_header;
</span><del>-               if ( empty( $custom_image_header ) )
-                       return parent::prepare_control();
</del><ins>+                if ( empty( $custom_image_header ) ) {
+                       return;
+               }
</ins><span class="cx"> 
</span><span class="cx">          // Process default headers and uploaded headers.
</span><span class="cx">          $custom_image_header->process_default_headers();
</span><del>-               $this->default_headers = $custom_image_header->default_headers;
-               $this->uploaded_headers = get_uploaded_header_images();
</del><ins>+                $this->default_headers = $this->get_default_header_images();
+               $this->uploaded_headers = $this->get_uploaded_header_images();
+       }
</ins><span class="cx"> 
</span><del>-               if ( $this->default_headers )
-                       $this->add_tab( 'default',  __('Default'),  array( $this, 'tab_default_headers' ) );
</del><ins>+        function print_header_image_template() {
+               ?>
+               <script type="text/template" id="tmpl-header-choice">
+                       <# if (data.random) { #>
</ins><span class="cx"> 
</span><del>-               if ( ! $this->uploaded_headers )
-                       $this->remove_tab( 'uploaded' );
</del><ins>+                        <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>
</ins><span class="cx"> 
</span><del>-               return parent::prepare_control();
-       }
</del><ins>+                        <# } else { #>
</ins><span class="cx"> 
</span><del>-       /**
-        * @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'] );
</del><ins>+                        <# if (data.type === 'uploaded') { #>
+                       <div class="dashicons dashicons-no close"></div>
+                       <# } #>
</ins><span class="cx"> 
</span><del>-               $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 ];
-               }
</del><ins>+                        <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>
</ins><span class="cx"> 
</span><ins>+                       <# } #>
+               </script>
</ins><span class="cx"> 
</span><del>-               ?>
-               <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>
</del><ins>+                <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>
</ins><span class="cx">           <?php
</span><span class="cx">  }
</span><span class="cx"> 
</span><del>-       /**
-        * @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 );
</del><ins>+        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;
</ins><span class="cx">   }
</span><span class="cx"> 
</span><del>-       /**
-        * @since 3.4.0
-        */
-       public function tab_default_headers() {
-               foreach ( $this->default_headers as $choice => $header )
-                       $this->print_header_image( $choice, $header );
</del><ins>+        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
</ins><span class="cx">   }
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunksrcwpincludescssmediaviewscss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/css/media-views.css (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/css/media-views.css        2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-includes/css/media-views.css   2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -602,6 +602,19 @@
</span><span class="cx">  margin: 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.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%;
+}
+
</ins><span class="cx"> /**
</span><span class="cx">  * Iframes
</span><span class="cx">  */
</span></span></pre></div>
<a id="trunksrcwpincludesjscustomizemodelsjs"></a>
<div class="addfile"><h4>Added: trunk/src/wp-includes/js/customize-models.js (0 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/customize-models.js                             (rev 0)
+++ trunk/src/wp-includes/js/customize-models.js        2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -0,0 +1,249 @@
</span><ins>+/* 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 );
</ins></span></pre></div>
<a id="trunksrcwpincludesjscustomizeviewsjs"></a>
<div class="addfile"><h4>Added: trunk/src/wp-includes/js/customize-views.js (0 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/customize-views.js                              (rev 0)
+++ trunk/src/wp-includes/js/customize-views.js 2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -0,0 +1,232 @@
</span><ins>+/* 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, _ );
</ins></span></pre></div>
<a id="trunksrcwpincludesjsmediaviewsjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/media-views.js (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/media-views.js  2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-includes/js/media-views.js     2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -1317,6 +1317,109 @@
</span><span class="cx">  });
</span><span class="cx"> 
</span><span class="cx">  /**
</span><ins>+        * 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')
+                       } );
+               }
+       });
+
+       /**
</ins><span class="cx">    * ========================================================================
</span><span class="cx">   * VIEWS
</span><span class="cx">   * ========================================================================
</span><span class="lines">@@ -6323,7 +6426,54 @@
</span><span class="cx">          }
</span><span class="cx">  });
</span><span class="cx"> 
</span><ins>+       /**
+        * 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);
+               }
</ins><span class="cx"> 
</span><ins>+       });
+
</ins><span class="cx">   media.view.EditImage = media.View.extend({
</span><span class="cx"> 
</span><span class="cx">          className: 'image-editor',
</span></span></pre></div>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/media.php (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/media.php  2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-includes/media.php     2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -2476,6 +2476,23 @@
</span><span class="cx">          'imageDetailsCancel'    => __( 'Cancel Edit' ),
</span><span class="cx">          'editImage'             => __( 'Edit Image' ),
</span><span class="cx"> 
</span><ins>+               // 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.' ),
+
</ins><span class="cx">           // Edit Audio
</span><span class="cx">          'audioDetailsTitle'     => __( 'Audio Details' ),
</span><span class="cx">          'audioReplaceTitle'     => __( 'Replace Audio' ),
</span></span></pre></div>
<a id="trunksrcwpincludesscriptloaderphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/script-loader.php (27496 => 27497)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/script-loader.php  2014-03-11 03:35:52 UTC (rev 27496)
+++ trunk/src/wp-includes/script-loader.php     2014-03-11 04:12:17 UTC (rev 27497)
</span><span class="lines">@@ -363,6 +363,8 @@
</span><span class="cx">  $scripts->add( 'customize-base',     "/wp-includes/js/customize-base$suffix.js",     array( 'jquery', 'json2' ), false, 1 );
</span><span class="cx">  $scripts->add( 'customize-loader',   "/wp-includes/js/customize-loader$suffix.js",   array( 'customize-base' ), false, 1 );
</span><span class="cx">  $scripts->add( 'customize-preview',  "/wp-includes/js/customize-preview$suffix.js",  array( 'customize-base' ), false, 1 );
</span><ins>+       $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 );
</ins><span class="cx">   $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base' ), false, 1 );
</span><span class="cx">  did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array(
</span><span class="cx">          'activate'  => __( 'Save &amp; Activate' ),
</span><span class="lines">@@ -600,7 +602,7 @@
</span><span class="cx">  $styles->add( 'login',              "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
</span><span class="cx">  $styles->add( 'install',            "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
</span><span class="cx">  $styles->add( 'wp-color-picker',    "/wp-admin/css/color-picker$suffix.css" );
</span><del>-       $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie' ) );
</del><ins>+        $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
</ins><span class="cx">   $styles->add( 'ie',                 "/wp-admin/css/ie$suffix.css" );
</span><span class="cx"> 
</span><span class="cx">  $styles->add_data( 'ie', 'conditional', 'lte IE 7' );
</span></span></pre>
</div>
</div>

</body>
</html>