WordPress 4.1.3
[autoinstalls/wordpress.git] / wp-admin / custom-header.php
1 <?php
2 /**
3  * The custom header image script.
4  *
5  * @package WordPress
6  * @subpackage Administration
7  */
8
9 /**
10  * The custom header image class.
11  *
12  * @since 2.1.0
13  * @package WordPress
14  * @subpackage Administration
15  */
16 class Custom_Image_Header {
17
18         /**
19          * Callback for administration header.
20          *
21          * @var callback
22          * @since 2.1.0
23          * @access private
24          */
25         private $admin_header_callback;
26
27         /**
28          * Callback for header div.
29          *
30          * @var callback
31          * @since 3.0.0
32          * @access private
33          */
34         private $admin_image_div_callback;
35
36         /**
37          * Holds default headers.
38          *
39          * @var array
40          * @since 3.0.0
41          * @access private
42          */
43         private $default_headers = array();
44
45         /**
46          * Holds custom headers uploaded by the user.
47          *
48          * @var array
49          * @since 3.2.0
50          * @access private
51          */
52         private $uploaded_headers = array();
53
54         /**
55          * Holds the page menu hook.
56          *
57          * @var string
58          * @since 3.0.0
59          * @access private
60          */
61         private $page = '';
62
63         /**
64          * @var bool
65          */
66         private $updated;
67
68         /**
69          * Constructor - Register administration header callback.
70          *
71          * @since 2.1.0
72          * @param callback $admin_header_callback
73          * @param callback $admin_image_div_callback Optional custom image div output callback.
74          * @return Custom_Image_Header
75          */
76         public function __construct($admin_header_callback, $admin_image_div_callback = '') {
77                 $this->admin_header_callback = $admin_header_callback;
78                 $this->admin_image_div_callback = $admin_image_div_callback;
79
80                 add_action( 'admin_menu', array( $this, 'init' ) );
81
82                 add_action( 'customize_save_after',         array( $this, 'customize_set_last_used' ) );
83                 add_action( 'wp_ajax_custom-header-crop',   array( $this, 'ajax_header_crop'        ) );
84                 add_action( 'wp_ajax_custom-header-add',    array( $this, 'ajax_header_add'         ) );
85                 add_action( 'wp_ajax_custom-header-remove', array( $this, 'ajax_header_remove'      ) );
86         }
87
88         /**
89          * Make private properties readable for backwards compatibility.
90          *
91          * @since 4.0.0
92          * @access public
93          *
94          * @param string $name Property to get.
95          * @return mixed Property.
96          */
97         public function __get( $name ) {
98                 return $this->$name;
99         }
100
101         /**
102          * Make private properties settable for backwards compatibility.
103          *
104          * @since 4.0.0
105          * @access public
106          *
107          * @param string $name  Property to set.
108          * @param mixed  $value Property value.
109          * @return mixed Newly-set property.
110          */
111         public function __set( $name, $value ) {
112                 return $this->$name = $value;
113         }
114
115         /**
116          * Make private properties checkable for backwards compatibility.
117          *
118          * @since 4.0.0
119          * @access public
120          *
121          * @param string $name Property to check if set.
122          * @return bool Whether the property is set.
123          */
124         public function __isset( $name ) {
125                 return isset( $this->$name );
126         }
127
128         /**
129          * Make private properties un-settable for backwards compatibility.
130          *
131          * @since 4.0.0
132          * @access public
133          *
134          * @param string $name Property to unset.
135          */
136         public function __unset( $name ) {
137                 unset( $this->$name );
138         }
139
140         /**
141          * Set up the hooks for the Custom Header admin page.
142          *
143          * @since 2.1.0
144          */
145         public function init() {
146                 if ( ! current_user_can('edit_theme_options') )
147                         return;
148
149                 $this->page = $page = add_theme_page(__('Header'), __('Header'), 'edit_theme_options', 'custom-header', array($this, 'admin_page'));
150
151                 add_action("admin_print_scripts-$page", array($this, 'js_includes'));
152                 add_action("admin_print_styles-$page", array($this, 'css_includes'));
153                 add_action("admin_head-$page", array($this, 'help') );
154                 add_action("admin_head-$page", array($this, 'take_action'), 50);
155                 add_action("admin_head-$page", array($this, 'js'), 50);
156                 if ( $this->admin_header_callback )
157                         add_action("admin_head-$page", $this->admin_header_callback, 51);
158
159         }
160
161         /**
162          * Adds contextual help.
163          *
164          * @since 3.0.0
165          */
166         public function help() {
167                 get_current_screen()->add_help_tab( array(
168                         'id'      => 'overview',
169                         'title'   => __('Overview'),
170                         'content' =>
171                                 '<p>' . __( 'This screen is used to customize the header section of your theme.') . '</p>' .
172                                 '<p>' . __( 'You can choose from the theme&#8217;s default header images, or use one of your own. You can also customize how your Site Title and Tagline are displayed.') . '<p>'
173                 ) );
174
175                 get_current_screen()->add_help_tab( array(
176                         'id'      => 'set-header-image',
177                         'title'   => __('Header Image'),
178                         'content' =>
179                                 '<p>' . __( 'You can set a custom image header for your site. Simply upload the image and crop it, and the new header will go live immediately. Alternatively, you can use an image that has already been uploaded to your Media Library by clicking the &#8220;Choose Image&#8221; button.' ) . '</p>' .
180                                 '<p>' . __( 'Some themes come with additional header images bundled. If you see multiple images displayed, select the one you&#8217;d like and click the &#8220;Save Changes&#8221; button.' ) . '</p>' .
181                                 '<p>' . __( 'If your theme has more than one default header image, or you have uploaded more than one custom header image, you have the option of having WordPress display a randomly different image on each page of your site. Click the &#8220;Random&#8221; radio button next to the Uploaded Images or Default Images section to enable this feature.') . '</p>' .
182                                 '<p>' . __( 'If you don&#8217;t want a header image to be displayed on your site at all, click the &#8220;Remove Header Image&#8221; button at the bottom of the Header Image section of this page. If you want to re-enable the header image later, you just have to select one of the other image options and click &#8220;Save Changes&#8221;.') . '</p>'
183                 ) );
184
185                 get_current_screen()->add_help_tab( array(
186                         'id'      => 'set-header-text',
187                         'title'   => __('Header Text'),
188                         'content' =>
189                                 '<p>' . sprintf( __( 'For most themes, the header text is your Site Title and Tagline, as defined in the <a href="%1$s">General Settings</a> section.' ), admin_url( 'options-general.php' ) ) . '<p>' .
190                                 '<p>' . __( 'In the Header Text section of this page, you can choose whether to display this text or hide it. You can also choose a color for the text by clicking the Select Color button and either typing in a legitimate HTML hex value, e.g. &#8220;#ff0000&#8221; for red, or by choosing a color using the color picker.' ) . '</p>' .
191                                 '<p>' . __( 'Don&#8217;t forget to click &#8220;Save Changes&#8221; when you&#8217;re done!') . '</p>'
192                 ) );
193
194                 get_current_screen()->set_help_sidebar(
195                         '<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
196                         '<p>' . __( '<a href="http://codex.wordpress.org/Appearance_Header_Screen" target="_blank">Documentation on Custom Header</a>' ) . '</p>' .
197                         '<p>' . __( '<a href="https://wordpress.org/support/" target="_blank">Support Forums</a>' ) . '</p>'
198                 );
199         }
200
201         /**
202          * Get the current step.
203          *
204          * @since 2.6.0
205          *
206          * @return int Current step
207          */
208         public function step() {
209                 if ( ! isset( $_GET['step'] ) )
210                         return 1;
211
212                 $step = (int) $_GET['step'];
213                 if ( $step < 1 || 3 < $step ||
214                         ( 2 == $step && ! wp_verify_nonce( $_REQUEST['_wpnonce-custom-header-upload'], 'custom-header-upload' ) ) ||
215                         ( 3 == $step && ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'custom-header-crop-image' ) )
216                 )
217                         return 1;
218
219                 return $step;
220         }
221
222         /**
223          * Set up the enqueue for the JavaScript files.
224          *
225          * @since 2.1.0
226          */
227         public function js_includes() {
228                 $step = $this->step();
229
230                 if ( ( 1 == $step || 3 == $step ) ) {
231                         wp_enqueue_media();
232                         wp_enqueue_script( 'custom-header' );
233                         if ( current_theme_supports( 'custom-header', 'header-text' ) )
234                                 wp_enqueue_script( 'wp-color-picker' );
235                 } elseif ( 2 == $step ) {
236                         wp_enqueue_script('imgareaselect');
237                 }
238         }
239
240         /**
241          * Set up the enqueue for the CSS files
242          *
243          * @since 2.7.0
244          */
245         public function css_includes() {
246                 $step = $this->step();
247
248                 if ( ( 1 == $step || 3 == $step ) && current_theme_supports( 'custom-header', 'header-text' ) )
249                         wp_enqueue_style( 'wp-color-picker' );
250                 elseif ( 2 == $step )
251                         wp_enqueue_style('imgareaselect');
252         }
253
254         /**
255          * Execute custom header modification.
256          *
257          * @since 2.6.0
258          */
259         public function take_action() {
260                 if ( ! current_user_can('edit_theme_options') )
261                         return;
262
263                 if ( empty( $_POST ) )
264                         return;
265
266                 $this->updated = true;
267
268                 if ( isset( $_POST['resetheader'] ) ) {
269                         check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
270                         $this->reset_header_image();
271                         return;
272                 }
273
274                 if ( isset( $_POST['removeheader'] ) ) {
275                         check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
276                         $this->remove_header_image();
277                         return;
278                 }
279
280                 if ( isset( $_POST['text-color'] ) && ! isset( $_POST['display-header-text'] ) ) {
281                         check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
282                         set_theme_mod( 'header_textcolor', 'blank' );
283                 } elseif ( isset( $_POST['text-color'] ) ) {
284                         check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
285                         $_POST['text-color'] = str_replace( '#', '', $_POST['text-color'] );
286                         $color = preg_replace('/[^0-9a-fA-F]/', '', $_POST['text-color']);
287                         if ( strlen($color) == 6 || strlen($color) == 3 )
288                                 set_theme_mod('header_textcolor', $color);
289                         elseif ( ! $color )
290                                 set_theme_mod( 'header_textcolor', 'blank' );
291                 }
292
293                 if ( isset( $_POST['default-header'] ) ) {
294                         check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
295                         $this->set_header_image( $_POST['default-header'] );
296                         return;
297                 }
298         }
299
300         /**
301          * Process the default headers
302          *
303          * @since 3.0.0
304          */
305         public function process_default_headers() {
306                 global $_wp_default_headers;
307
308                 if ( !isset($_wp_default_headers) )
309                         return;
310
311                 if ( ! empty( $this->default_headers ) ) {
312                         return;
313                 }
314
315                 $this->default_headers = $_wp_default_headers;
316                 $template_directory_uri = get_template_directory_uri();
317                 $stylesheet_directory_uri = get_stylesheet_directory_uri();
318                 foreach ( array_keys($this->default_headers) as $header ) {
319                         $this->default_headers[$header]['url'] =  sprintf( $this->default_headers[$header]['url'], $template_directory_uri, $stylesheet_directory_uri );
320                         $this->default_headers[$header]['thumbnail_url'] =  sprintf( $this->default_headers[$header]['thumbnail_url'], $template_directory_uri, $stylesheet_directory_uri );
321                 }
322         }
323
324         /**
325          * Display UI for selecting one of several default headers.
326          *
327          * Show the random image option if this theme has multiple header images.
328          * Random image option is on by default if no header has been set.
329          *
330          * @since 3.0.0
331          */
332         public function show_header_selector( $type = 'default' ) {
333                 if ( 'default' == $type ) {
334                         $headers = $this->default_headers;
335                 } else {
336                         $headers = get_uploaded_header_images();
337                         $type = 'uploaded';
338                 }
339
340                 if ( 1 < count( $headers ) ) {
341                         echo '<div class="random-header">';
342                         echo '<label><input name="default-header" type="radio" value="random-' . $type . '-image"' . checked( is_random_header_image( $type ), true, false ) . ' />';
343                         echo __( '<strong>Random:</strong> Show a different image on each page.' );
344                         echo '</label>';
345                         echo '</div>';
346                 }
347
348                 echo '<div class="available-headers">';
349                 foreach ( $headers as $header_key => $header ) {
350                         $header_thumbnail = $header['thumbnail_url'];
351                         $header_url = $header['url'];
352                         $header_desc = empty( $header['description'] ) ? '' : $header['description'];
353                         echo '<div class="default-header">';
354                         echo '<label><input name="default-header" type="radio" value="' . esc_attr( $header_key ) . '" ' . checked( $header_url, get_theme_mod( 'header_image' ), false ) . ' />';
355                         $width = '';
356                         if ( !empty( $header['attachment_id'] ) )
357                                 $width = ' width="230"';
358                         echo '<img src="' . set_url_scheme( $header_thumbnail ) . '" alt="' . esc_attr( $header_desc ) .'" title="' . esc_attr( $header_desc ) . '"' . $width . ' /></label>';
359                         echo '</div>';
360                 }
361                 echo '<div class="clear"></div></div>';
362         }
363
364         /**
365          * Execute JavaScript depending on step.
366          *
367          * @since 2.1.0
368          */
369         public function js() {
370                 $step = $this->step();
371                 if ( ( 1 == $step || 3 == $step ) && current_theme_supports( 'custom-header', 'header-text' ) )
372                         $this->js_1();
373                 elseif ( 2 == $step )
374                         $this->js_2();
375         }
376
377         /**
378          * Display JavaScript based on Step 1 and 3.
379          *
380          * @since 2.6.0
381          */
382         public function js_1() {
383                 $default_color = '';
384                 if ( current_theme_supports( 'custom-header', 'default-text-color' ) ) {
385                         $default_color = get_theme_support( 'custom-header', 'default-text-color' );
386                         if ( $default_color && false === strpos( $default_color, '#' ) ) {
387                                 $default_color = '#' . $default_color;
388                         }
389                 }
390                 ?>
391
392 <script type="text/javascript">
393 /* <![CDATA[ */
394 (function($){
395         var default_color = '<?php echo $default_color; ?>',
396                 header_text_fields;
397
398         function pickColor(color) {
399                 $('#name').css('color', color);
400                 $('#desc').css('color', color);
401                 $('#text-color').val(color);
402         }
403
404         function toggle_text() {
405                 var checked = $('#display-header-text').prop('checked'),
406                         text_color;
407                 header_text_fields.toggle( checked );
408                 if ( ! checked )
409                         return;
410                 text_color = $('#text-color');
411                 if ( '' == text_color.val().replace('#', '') ) {
412                         text_color.val( default_color );
413                         pickColor( default_color );
414                 } else {
415                         pickColor( text_color.val() );
416                 }
417         }
418
419         $(document).ready(function() {
420                 var text_color = $('#text-color');
421                 header_text_fields = $('.displaying-header-text');
422                 text_color.wpColorPicker({
423                         change: function( event, ui ) {
424                                 pickColor( text_color.wpColorPicker('color') );
425                         },
426                         clear: function() {
427                                 pickColor( '' );
428                         }
429                 });
430                 $('#display-header-text').click( toggle_text );
431                 <?php if ( ! display_header_text() ) : ?>
432                 toggle_text();
433                 <?php endif; ?>
434         });
435 })(jQuery);
436 /* ]]> */
437 </script>
438 <?php
439         }
440
441         /**
442          * Display JavaScript based on Step 2.
443          *
444          * @since 2.6.0
445          */
446         public function js_2() { ?>
447 <script type="text/javascript">
448 /* <![CDATA[ */
449         function onEndCrop( coords ) {
450                 jQuery( '#x1' ).val(coords.x);
451                 jQuery( '#y1' ).val(coords.y);
452                 jQuery( '#width' ).val(coords.w);
453                 jQuery( '#height' ).val(coords.h);
454         }
455
456         jQuery(document).ready(function() {
457                 var xinit = <?php echo absint( get_theme_support( 'custom-header', 'width' ) ); ?>;
458                 var yinit = <?php echo absint( get_theme_support( 'custom-header', 'height' ) ); ?>;
459                 var ratio = xinit / yinit;
460                 var ximg = jQuery('img#upload').width();
461                 var yimg = jQuery('img#upload').height();
462
463                 if ( yimg < yinit || ximg < xinit ) {
464                         if ( ximg / yimg > ratio ) {
465                                 yinit = yimg;
466                                 xinit = yinit * ratio;
467                         } else {
468                                 xinit = ximg;
469                                 yinit = xinit / ratio;
470                         }
471                 }
472
473                 jQuery('img#upload').imgAreaSelect({
474                         handles: true,
475                         keys: true,
476                         show: true,
477                         x1: 0,
478                         y1: 0,
479                         x2: xinit,
480                         y2: yinit,
481                         <?php
482                         if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
483                         ?>
484                         aspectRatio: xinit + ':' + yinit,
485                         <?php
486                         }
487                         if ( ! current_theme_supports( 'custom-header', 'flex-height' ) ) {
488                         ?>
489                         maxHeight: <?php echo get_theme_support( 'custom-header', 'height' ); ?>,
490                         <?php
491                         }
492                         if ( ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
493                         ?>
494                         maxWidth: <?php echo get_theme_support( 'custom-header', 'width' ); ?>,
495                         <?php
496                         }
497                         ?>
498                         onInit: function () {
499                                 jQuery('#width').val(xinit);
500                                 jQuery('#height').val(yinit);
501                         },
502                         onSelectChange: function(img, c) {
503                                 jQuery('#x1').val(c.x1);
504                                 jQuery('#y1').val(c.y1);
505                                 jQuery('#width').val(c.width);
506                                 jQuery('#height').val(c.height);
507                         }
508                 });
509         });
510 /* ]]> */
511 </script>
512 <?php
513         }
514
515         /**
516          * Display first step of custom header image page.
517          *
518          * @since 2.1.0
519          */
520         public function step_1() {
521                 $this->process_default_headers();
522 ?>
523
524 <div class="wrap">
525 <h2><?php _e( 'Custom Header' ); ?></h2>
526
527 <?php if ( current_user_can( 'customize' ) ) { ?>
528 <div class="notice notice-info hide-if-no-customize">
529         <p>
530                 <?php
531                 printf(
532                         __( 'You can now manage and live-preview Custom Header in the <a href="%1$s">Customizer</a>.' ),
533                         admin_url( 'customize.php?autofocus[control]=header_image' )
534                 );
535                 ?>
536         </p>
537 </div>
538 <?php } ?>
539
540 <?php if ( ! empty( $this->updated ) ) { ?>
541 <div id="message" class="updated">
542 <p><?php printf( __( 'Header updated. <a href="%s">Visit your site</a> to see how it looks.' ), home_url( '/' ) ); ?></p>
543 </div>
544 <?php } ?>
545
546 <h3><?php _e( 'Header Image' ); ?></h3>
547
548 <table class="form-table">
549 <tbody>
550
551 <?php if ( get_custom_header() || display_header_text() ) : ?>
552 <tr>
553 <th scope="row"><?php _e( 'Preview' ); ?></th>
554 <td>
555         <?php
556         if ( $this->admin_image_div_callback ) {
557                 call_user_func( $this->admin_image_div_callback );
558         } else {
559                 $custom_header = get_custom_header();
560                 $header_image_style = 'background-image:url(' . esc_url( get_header_image() ) . ');';
561                 if ( $custom_header->width )
562                         $header_image_style .= 'max-width:' . $custom_header->width . 'px;';
563                 if ( $custom_header->height )
564                         $header_image_style .= 'height:' . $custom_header->height . 'px;';
565         ?>
566         <div id="headimg" style="<?php echo $header_image_style; ?>">
567                 <?php
568                 if ( display_header_text() )
569                         $style = ' style="color:#' . get_header_textcolor() . ';"';
570                 else
571                         $style = ' style="display:none;"';
572                 ?>
573                 <h1><a id="name" class="displaying-header-text" <?php echo $style; ?> onclick="return false;" href="<?php bloginfo('url'); ?>" tabindex="-1"><?php bloginfo( 'name' ); ?></a></h1>
574                 <div id="desc" class="displaying-header-text" <?php echo $style; ?>><?php bloginfo( 'description' ); ?></div>
575         </div>
576         <?php } ?>
577 </td>
578 </tr>
579 <?php endif; ?>
580
581 <?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
582 <tr>
583 <th scope="row"><?php _e( 'Select Image' ); ?></th>
584 <td>
585         <p><?php _e( 'You can select an image to be shown at the top of your site by uploading from your computer or choosing from your media library. After selecting an image you will be able to crop it.' ); ?><br />
586         <?php
587         if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
588                 printf( __( 'Images of exactly <strong>%1$d &times; %2$d pixels</strong> will be used as-is.' ) . '<br />', get_theme_support( 'custom-header', 'width' ), get_theme_support( 'custom-header', 'height' ) );
589         } elseif ( current_theme_supports( 'custom-header', 'flex-height' ) ) {
590                 if ( ! current_theme_supports( 'custom-header', 'flex-width' ) )
591                         printf( __( 'Images should be at least <strong>%1$d pixels</strong> wide.' ) . ' ', get_theme_support( 'custom-header', 'width' ) );
592         } elseif ( current_theme_supports( 'custom-header', 'flex-width' ) ) {
593                 if ( ! current_theme_supports( 'custom-header', 'flex-height' ) )
594                         printf( __( 'Images should be at least <strong>%1$d pixels</strong> tall.' ) . ' ', get_theme_support( 'custom-header', 'height' ) );
595         }
596         if ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) {
597                 if ( current_theme_supports( 'custom-header', 'width' ) )
598                         printf( __( 'Suggested width is <strong>%1$d pixels</strong>.' ) . ' ', get_theme_support( 'custom-header', 'width' ) );
599                 if ( current_theme_supports( 'custom-header', 'height' ) )
600                         printf( __( 'Suggested height is <strong>%1$d pixels</strong>.' ) . ' ', get_theme_support( 'custom-header', 'height' ) );
601         }
602         ?></p>
603         <form enctype="multipart/form-data" id="upload-form" class="wp-upload-form" method="post" action="<?php echo esc_url( add_query_arg( 'step', 2 ) ) ?>">
604         <p>
605                 <label for="upload"><?php _e( 'Choose an image from your computer:' ); ?></label><br />
606                 <input type="file" id="upload" name="import" />
607                 <input type="hidden" name="action" value="save" />
608                 <?php wp_nonce_field( 'custom-header-upload', '_wpnonce-custom-header-upload' ); ?>
609                 <?php submit_button( __( 'Upload' ), 'button', 'submit', false ); ?>
610         </p>
611         <?php
612                 $modal_update_href = esc_url( add_query_arg( array(
613                         'page' => 'custom-header',
614                         'step' => 2,
615                         '_wpnonce-custom-header-upload' => wp_create_nonce('custom-header-upload'),
616                 ), admin_url('themes.php') ) );
617         ?>
618         <p>
619                 <label for="choose-from-library-link"><?php _e( 'Or choose an image from your media library:' ); ?></label><br />
620                 <button id="choose-from-library-link" class="button"
621                         data-update-link="<?php echo esc_attr( $modal_update_href ); ?>"
622                         data-choose="<?php esc_attr_e( 'Choose a Custom Header' ); ?>"
623                         data-update="<?php esc_attr_e( 'Set as header' ); ?>"><?php _e( 'Choose Image' ); ?></button>
624         </p>
625         </form>
626 </td>
627 </tr>
628 <?php endif; ?>
629 </tbody>
630 </table>
631
632 <form method="post" action="<?php echo esc_url( add_query_arg( 'step', 1 ) ) ?>">
633 <?php submit_button( null, 'screen-reader-text', 'save-header-options', false ); ?>
634 <table class="form-table">
635 <tbody>
636         <?php if ( get_uploaded_header_images() ) : ?>
637 <tr>
638 <th scope="row"><?php _e( 'Uploaded Images' ); ?></th>
639 <td>
640         <p><?php _e( 'You can choose one of your previously uploaded headers, or show a random one.' ) ?></p>
641         <?php
642                 $this->show_header_selector( 'uploaded' );
643         ?>
644 </td>
645 </tr>
646         <?php endif;
647         if ( ! empty( $this->default_headers ) ) : ?>
648 <tr>
649 <th scope="row"><?php _e( 'Default Images' ); ?></th>
650 <td>
651 <?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
652         <p><?php _e( 'If you don&lsquo;t want to upload your own image, you can use one of these cool headers, or show a random one.' ) ?></p>
653 <?php else: ?>
654         <p><?php _e( 'You can use one of these cool headers or show a random one on each page.' ) ?></p>
655 <?php endif; ?>
656         <?php
657                 $this->show_header_selector( 'default' );
658         ?>
659 </td>
660 </tr>
661         <?php endif;
662         if ( get_header_image() ) : ?>
663 <tr>
664 <th scope="row"><?php _e( 'Remove Image' ); ?></th>
665 <td>
666         <p><?php _e( 'This will remove the header image. You will not be able to restore any customizations.' ) ?></p>
667         <?php submit_button( __( 'Remove Header Image' ), 'button', 'removeheader', false ); ?>
668 </td>
669 </tr>
670         <?php endif;
671
672         $default_image = get_theme_support( 'custom-header', 'default-image' );
673         if ( $default_image && get_header_image() != $default_image ) : ?>
674 <tr>
675 <th scope="row"><?php _e( 'Reset Image' ); ?></th>
676 <td>
677         <p><?php _e( 'This will restore the original header image. You will not be able to restore any customizations.' ) ?></p>
678         <?php submit_button( __( 'Restore Original Header Image' ), 'button', 'resetheader', false ); ?>
679 </td>
680 </tr>
681         <?php endif; ?>
682 </tbody>
683 </table>
684
685 <?php if ( current_theme_supports( 'custom-header', 'header-text' ) ) : ?>
686
687 <h3><?php _e( 'Header Text' ); ?></h3>
688
689 <table class="form-table">
690 <tbody>
691 <tr>
692 <th scope="row"><?php _e( 'Header Text' ); ?></th>
693 <td>
694         <p>
695         <label><input type="checkbox" name="display-header-text" id="display-header-text"<?php checked( display_header_text() ); ?> /> <?php _e( 'Show header text with your image.' ); ?></label>
696         </p>
697 </td>
698 </tr>
699
700 <tr class="displaying-header-text">
701 <th scope="row"><?php _e( 'Text Color' ); ?></th>
702 <td>
703         <p>
704         <?php
705         $default_color = '';
706         if ( current_theme_supports( 'custom-header', 'default-text-color' ) ) {
707                 $default_color = get_theme_support( 'custom-header', 'default-text-color' );
708                 if ( $default_color && false === strpos( $default_color, '#' ) ) {
709                         $default_color = '#' . $default_color;
710                 }
711         }
712
713         $default_color_attr = $default_color ? ' data-default-color="' . esc_attr( $default_color ) . '"' : '';
714
715         $header_textcolor = display_header_text() ? get_header_textcolor() : get_theme_support( 'custom-header', 'default-text-color' );
716         if ( $header_textcolor && false === strpos( $header_textcolor, '#' ) ) {
717                 $header_textcolor = '#' . $header_textcolor;
718         }
719
720         echo '<input type="text" name="text-color" id="text-color" value="' . esc_attr( $header_textcolor ) . '"' . $default_color_attr . ' />';
721         if ( $default_color ) {
722                 echo ' <span class="description hide-if-js">' . sprintf( _x( 'Default: %s', 'color' ), esc_html( $default_color ) ) . '</span>';
723         }
724         ?>
725         </p>
726 </td>
727 </tr>
728 </tbody>
729 </table>
730 <?php endif;
731
732 /**
733  * Fires just before the submit button in the custom header options form.
734  *
735  * @since 3.1.0
736  */
737 do_action( 'custom_header_options' );
738
739 wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
740
741 <?php submit_button( null, 'primary', 'save-header-options' ); ?>
742 </form>
743 </div>
744
745 <?php }
746
747         /**
748          * Display second step of custom header image page.
749          *
750          * @since 2.1.0
751          */
752         public function step_2() {
753                 check_admin_referer('custom-header-upload', '_wpnonce-custom-header-upload');
754                 if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
755                         wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
756
757                 if ( empty( $_POST ) && isset( $_GET['file'] ) ) {
758                         $attachment_id = absint( $_GET['file'] );
759                         $file = get_attached_file( $attachment_id, true );
760                         $url = wp_get_attachment_image_src( $attachment_id, 'full' );
761                         $url = $url[0];
762                 } elseif ( isset( $_POST ) ) {
763                         $data = $this->step_2_manage_upload();
764                         $attachment_id = $data['attachment_id'];
765                         $file = $data['file'];
766                         $url = $data['url'];
767                 }
768
769                 if ( file_exists( $file ) ) {
770                         list( $width, $height, $type, $attr ) = getimagesize( $file );
771                 } else {
772                         $data = wp_get_attachment_metadata( $attachment_id );
773                         $height = isset( $data[ 'height' ] ) ? $data[ 'height' ] : 0;
774                         $width = isset( $data[ 'width' ] ) ? $data[ 'width' ] : 0;
775                         unset( $data );
776                 }
777
778                 $max_width = 0;
779                 // For flex, limit size of image displayed to 1500px unless theme says otherwise
780                 if ( current_theme_supports( 'custom-header', 'flex-width' ) )
781                         $max_width = 1500;
782
783                 if ( current_theme_supports( 'custom-header', 'max-width' ) )
784                         $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
785                 $max_width = max( $max_width, get_theme_support( 'custom-header', 'width' ) );
786
787                 // If flexible height isn't supported and the image is the exact right size
788                 if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' )
789                         && $width == get_theme_support( 'custom-header', 'width' ) && $height == get_theme_support( 'custom-header', 'height' ) )
790                 {
791                         // Add the meta-data
792                         if ( file_exists( $file ) )
793                                 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
794
795                         $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
796
797                         /**
798                          * Fires after the header image is set or an error is returned.
799                          *
800                          * @since 2.1.0
801                          *
802                          * @param string $file          Path to the file.
803                          * @param int    $attachment_id Attachment ID.
804                          */
805                         do_action( 'wp_create_file_in_uploads', $file, $attachment_id ); // For replication
806
807                         return $this->finished();
808                 } elseif ( $width > $max_width ) {
809                         $oitar = $width / $max_width;
810                         $image = wp_crop_image($attachment_id, 0, 0, $width, $height, $max_width, $height / $oitar, false, str_replace(basename($file), 'midsize-'.basename($file), $file));
811                         if ( ! $image || is_wp_error( $image ) )
812                                 wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
813
814                         /** This filter is documented in wp-admin/custom-header.php */
815                         $image = apply_filters( 'wp_create_file_in_uploads', $image, $attachment_id ); // For replication
816
817                         $url = str_replace(basename($url), basename($image), $url);
818                         $width = $width / $oitar;
819                         $height = $height / $oitar;
820                 } else {
821                         $oitar = 1;
822                 }
823                 ?>
824
825 <div class="wrap">
826 <h2><?php _e( 'Crop Header Image' ); ?></h2>
827
828 <form method="post" action="<?php echo esc_url(add_query_arg('step', 3)); ?>">
829         <p class="hide-if-no-js"><?php _e('Choose the part of the image you want to use as your header.'); ?></p>
830         <p class="hide-if-js"><strong><?php _e( 'You need Javascript to choose a part of the image.'); ?></strong></p>
831
832         <div id="crop_image" style="position: relative">
833                 <img src="<?php echo esc_url( $url ); ?>" id="upload" width="<?php echo $width; ?>" height="<?php echo $height; ?>" />
834         </div>
835
836         <input type="hidden" name="x1" id="x1" value="0"/>
837         <input type="hidden" name="y1" id="y1" value="0"/>
838         <input type="hidden" name="width" id="width" value="<?php echo esc_attr( $width ); ?>"/>
839         <input type="hidden" name="height" id="height" value="<?php echo esc_attr( $height ); ?>"/>
840         <input type="hidden" name="attachment_id" id="attachment_id" value="<?php echo esc_attr( $attachment_id ); ?>" />
841         <input type="hidden" name="oitar" id="oitar" value="<?php echo esc_attr( $oitar ); ?>" />
842         <?php if ( empty( $_POST ) && isset( $_GET['file'] ) ) { ?>
843         <input type="hidden" name="create-new-attachment" value="true" />
844         <?php } ?>
845         <?php wp_nonce_field( 'custom-header-crop-image' ) ?>
846
847         <p class="submit">
848         <?php submit_button( __( 'Crop and Publish' ), 'primary', 'submit', false ); ?>
849         <?php
850         if ( isset( $oitar ) && 1 == $oitar && ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
851                 submit_button( __( 'Skip Cropping, Publish Image as Is' ), 'secondary', 'skip-cropping', false );
852         ?>
853         </p>
854 </form>
855 </div>
856                 <?php
857         }
858
859
860         /**
861          * Upload the file to be cropped in the second step.
862          *
863          * @since 3.4.0
864          */
865         public function step_2_manage_upload() {
866                 $overrides = array('test_form' => false);
867
868                 $uploaded_file = $_FILES['import'];
869                 $wp_filetype = wp_check_filetype_and_ext( $uploaded_file['tmp_name'], $uploaded_file['name'], false );
870                 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) )
871                         wp_die( __( 'The uploaded file is not a valid image. Please try again.' ) );
872
873                 $file = wp_handle_upload($uploaded_file, $overrides);
874
875                 if ( isset($file['error']) )
876                         wp_die( $file['error'],  __( 'Image Upload Error' ) );
877
878                 $url = $file['url'];
879                 $type = $file['type'];
880                 $file = $file['file'];
881                 $filename = basename($file);
882
883                 // Construct the object array
884                 $object = array(
885                         'post_title'     => $filename,
886                         'post_content'   => $url,
887                         'post_mime_type' => $type,
888                         'guid'           => $url,
889                         'context'        => 'custom-header'
890                 );
891
892                 // Save the data
893                 $attachment_id = wp_insert_attachment( $object, $file );
894                 return compact( 'attachment_id', 'file', 'filename', 'url', 'type' );
895         }
896
897         /**
898          * Display third step of custom header image page.
899          *
900          * @since 2.1.0
901          */
902         public function step_3() {
903                 check_admin_referer( 'custom-header-crop-image' );
904
905                 if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
906                         wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
907
908                 if ( ! empty( $_POST['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
909                         wp_die( __( 'Cheatin&#8217; uh?' ), 403 );
910
911                 if ( $_POST['oitar'] > 1 ) {
912                         $_POST['x1'] = $_POST['x1'] * $_POST['oitar'];
913                         $_POST['y1'] = $_POST['y1'] * $_POST['oitar'];
914                         $_POST['width'] = $_POST['width'] * $_POST['oitar'];
915                         $_POST['height'] = $_POST['height'] * $_POST['oitar'];
916                 }
917
918                 $attachment_id = absint( $_POST['attachment_id'] );
919                 $original = get_attached_file($attachment_id);
920
921                 $dimensions = $this->get_header_dimensions( array(
922                         'height' => $_POST['height'],
923                         'width'  => $_POST['width'],
924                 ) );
925                 $height = $dimensions['dst_height'];
926                 $width = $dimensions['dst_width'];
927
928                 if ( empty( $_POST['skip-cropping'] ) )
929                         $cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $width, $height );
930                 elseif ( ! empty( $_POST['create-new-attachment'] ) )
931                         $cropped = _copy_image_file( $attachment_id );
932                 else
933                         $cropped = get_attached_file( $attachment_id );
934
935                 if ( ! $cropped || is_wp_error( $cropped ) )
936                         wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
937
938                 /** This filter is documented in wp-admin/custom-header.php */
939                 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
940
941                 $object = $this->create_attachment_object( $cropped, $attachment_id );
942
943                 if ( ! empty( $_POST['create-new-attachment'] ) )
944                         unset( $object['ID'] );
945
946                 // Update the attachment
947                 $attachment_id = $this->insert_attachment( $object, $cropped );
948
949                 $url = $object['guid'];
950                 $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
951
952                 // Cleanup.
953                 $medium = str_replace( basename( $original ), 'midsize-' . basename( $original ), $original );
954                 if ( file_exists( $medium ) ) {
955                         /**
956                          * Filter the path of the file to delete.
957                          *
958                          * @since 2.1.0
959                          *
960                          * @param string $medium Path to the file to delete.
961                          */
962                         @unlink( apply_filters( 'wp_delete_file', $medium ) );
963                 }
964
965                 if ( empty( $_POST['create-new-attachment'] ) && empty( $_POST['skip-cropping'] ) ) {
966                         /** This filter is documented in wp-admin/custom-header.php */
967                         @unlink( apply_filters( 'wp_delete_file', $original ) );
968                 }
969
970                 return $this->finished();
971         }
972
973         /**
974          * Display last step of custom header image page.
975          *
976          * @since 2.1.0
977          */
978         public function finished() {
979                 $this->updated = true;
980                 $this->step_1();
981         }
982
983         /**
984          * Display the page based on the current step.
985          *
986          * @since 2.1.0
987          */
988         public function admin_page() {
989                 if ( ! current_user_can('edit_theme_options') )
990                         wp_die(__('You do not have permission to customize headers.'));
991                 $step = $this->step();
992                 if ( 2 == $step )
993                         $this->step_2();
994                 elseif ( 3 == $step )
995                         $this->step_3();
996                 else
997                         $this->step_1();
998         }
999
1000         /**
1001          * Unused since 3.5.0.
1002          *
1003          * @since 3.4.0
1004          */
1005         public function attachment_fields_to_edit( $form_fields ) {
1006                 return $form_fields;
1007         }
1008
1009         /**
1010          * Unused since 3.5.0.
1011          *
1012          * @since 3.4.0
1013          */
1014         public function filter_upload_tabs( $tabs ) {
1015                 return $tabs;
1016         }
1017
1018         /**
1019          * Choose a header image, selected from existing uploaded and default headers,
1020          * or provide an array of uploaded header data (either new, or from media library).
1021          *
1022          * @param mixed $choice Which header image to select. Allows for values of 'random-default-image',
1023          *      for randomly cycling among the default images; 'random-uploaded-image', for randomly cycling
1024          *      among the uploaded images; the key of a default image registered for that theme; and
1025          *      the key of an image uploaded for that theme (the basename of the URL).
1026          *  Or an array of arguments: attachment_id, url, width, height. All are required.
1027          *
1028          * @since 3.4.0
1029          */
1030         final public function set_header_image( $choice ) {
1031                 if ( is_array( $choice ) || is_object( $choice ) ) {
1032                         $choice = (array) $choice;
1033                         if ( ! isset( $choice['attachment_id'] ) || ! isset( $choice['url'] ) )
1034                                 return;
1035
1036                         $choice['url'] = esc_url_raw( $choice['url'] );
1037
1038                         $header_image_data = (object) array(
1039                                 'attachment_id' => $choice['attachment_id'],
1040                                 'url'           => $choice['url'],
1041                                 'thumbnail_url' => $choice['url'],
1042                                 'height'        => $choice['height'],
1043                                 'width'         => $choice['width'],
1044                         );
1045
1046                         update_post_meta( $choice['attachment_id'], '_wp_attachment_is_custom_header', get_stylesheet() );
1047                         set_theme_mod( 'header_image', $choice['url'] );
1048                         set_theme_mod( 'header_image_data', $header_image_data );
1049                         return;
1050                 }
1051
1052                 if ( in_array( $choice, array( 'remove-header', 'random-default-image', 'random-uploaded-image' ) ) ) {
1053                         set_theme_mod( 'header_image', $choice );
1054                         remove_theme_mod( 'header_image_data' );
1055                         return;
1056                 }
1057
1058                 $uploaded = get_uploaded_header_images();
1059                 if ( $uploaded && isset( $uploaded[ $choice ] ) ) {
1060                         $header_image_data = $uploaded[ $choice ];
1061
1062                 } else {
1063                         $this->process_default_headers();
1064                         if ( isset( $this->default_headers[ $choice ] ) )
1065                                 $header_image_data = $this->default_headers[ $choice ];
1066                         else
1067                                 return;
1068                 }
1069
1070                 set_theme_mod( 'header_image', esc_url_raw( $header_image_data['url'] ) );
1071                 set_theme_mod( 'header_image_data', $header_image_data );
1072         }
1073
1074         /**
1075          * Remove a header image.
1076          *
1077          * @since 3.4.0
1078          */
1079         final public function remove_header_image() {
1080                 return $this->set_header_image( 'remove-header' );
1081         }
1082
1083         /**
1084          * Reset a header image to the default image for the theme.
1085          *
1086          * This method does not do anything if the theme does not have a default header image.
1087          *
1088          * @since 3.4.0
1089          */
1090         final public function reset_header_image() {
1091                 $this->process_default_headers();
1092                 $default = get_theme_support( 'custom-header', 'default-image' );
1093
1094                 if ( ! $default )
1095                         return $this->remove_header_image();
1096
1097                 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1098
1099                 $default_data = array();
1100                 foreach ( $this->default_headers as $header => $details ) {
1101                         if ( $details['url'] == $default ) {
1102                                 $default_data = $details;
1103                                 break;
1104                         }
1105                 }
1106
1107                 set_theme_mod( 'header_image', $default );
1108                 set_theme_mod( 'header_image_data', (object) $default_data );
1109         }
1110
1111         /**
1112          * Calculate width and height based on what the currently selected theme supports.
1113          *
1114          * @return array dst_height and dst_width of header image.
1115          */
1116         final public function get_header_dimensions( $dimensions ) {
1117                 $max_width = 0;
1118                 $width = absint( $dimensions['width'] );
1119                 $height = absint( $dimensions['height'] );
1120                 $theme_height = get_theme_support( 'custom-header', 'height' );
1121                 $theme_width = get_theme_support( 'custom-header', 'width' );
1122                 $has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
1123                 $has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
1124                 $has_max_width = current_theme_supports( 'custom-header', 'max-width' ) ;
1125                 $dst = array( 'dst_height' => null, 'dst_width' => null );
1126
1127                 // For flex, limit size of image displayed to 1500px unless theme says otherwise
1128                 if ( $has_flex_width ) {
1129                         $max_width = 1500;
1130                 }
1131
1132                 if ( $has_max_width ) {
1133                         $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
1134                 }
1135                 $max_width = max( $max_width, $theme_width );
1136
1137                 if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
1138                         $dst['dst_height'] = absint( $height * ( $max_width / $width ) );
1139                 }
1140                 elseif ( $has_flex_height && $has_flex_width ) {
1141                         $dst['dst_height'] = $height;
1142                 }
1143                 else {
1144                         $dst['dst_height'] = $theme_height;
1145                 }
1146
1147                 if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
1148                         $dst['dst_width'] = absint( $width * ( $max_width / $width ) );
1149                 }
1150                 elseif ( $has_flex_width && $has_flex_height ) {
1151                         $dst['dst_width'] = $width;
1152                 }
1153                 else {
1154                         $dst['dst_width'] = $theme_width;
1155                 }
1156
1157                 return $dst;
1158         }
1159
1160         /**
1161          * Create an attachment 'object'.
1162          *
1163          * @param string $cropped Cropped image URL.
1164          * @param int $parent_attachment_id Attachment ID of parent image.
1165          *
1166          * @return array Attachment object.
1167          */
1168         final public function create_attachment_object( $cropped, $parent_attachment_id ) {
1169                 $parent = get_post( $parent_attachment_id );
1170                 $parent_url = $parent->guid;
1171                 $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
1172
1173                 $size = @getimagesize( $cropped );
1174                 $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
1175
1176                 $object = array(
1177                         'ID' => $parent_attachment_id,
1178                         'post_title' => basename($cropped),
1179                         'post_content' => $url,
1180                         'post_mime_type' => $image_type,
1181                         'guid' => $url,
1182                         'context' => 'custom-header'
1183                 );
1184
1185                 return $object;
1186         }
1187
1188         /**
1189          * Insert an attachment and its metadata.
1190          *
1191          * @param array $object Attachment object.
1192          * @param string $cropped Cropped image URL.
1193          *
1194          * @return int Attachment ID.
1195          */
1196         final public function insert_attachment( $object, $cropped ) {
1197                 $attachment_id = wp_insert_attachment( $object, $cropped );
1198                 $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
1199                 /**
1200                  * Filter the header image attachment metadata.
1201                  *
1202                  * @since 3.9.0
1203                  *
1204                  * @see wp_generate_attachment_metadata()
1205                  *
1206                  * @param array $metadata Attachment metadata.
1207                  */
1208                 $metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
1209                 wp_update_attachment_metadata( $attachment_id, $metadata );
1210                 return $attachment_id;
1211         }
1212
1213         /**
1214          * Gets attachment uploaded by Media Manager, crops it, then saves it as a
1215          * new object. Returns JSON-encoded object details.
1216          */
1217         public function ajax_header_crop() {
1218                 check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
1219
1220                 if ( ! current_user_can( 'edit_theme_options' ) ) {
1221                         wp_send_json_error();
1222                 }
1223
1224                 if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
1225                         wp_send_json_error();
1226                 }
1227
1228                 $crop_details = $_POST['cropDetails'];
1229
1230                 $dimensions = $this->get_header_dimensions( array(
1231                         'height' => $crop_details['height'],
1232                         'width'  => $crop_details['width'],
1233                 ) );
1234
1235                 $attachment_id = absint( $_POST['id'] );
1236
1237                 $cropped = wp_crop_image(
1238                         $attachment_id,
1239                         (int) $crop_details['x1'],
1240                         (int) $crop_details['y1'],
1241                         (int) $crop_details['width'],
1242                         (int) $crop_details['height'],
1243                         (int) $dimensions['dst_width'],
1244                         (int) $dimensions['dst_height']
1245                 );
1246
1247                 if ( ! $cropped || is_wp_error( $cropped ) ) {
1248                         wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
1249                 }
1250
1251                 /** This filter is documented in wp-admin/custom-header.php */
1252                 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
1253
1254                 $object = $this->create_attachment_object( $cropped, $attachment_id );
1255
1256                 unset( $object['ID'] );
1257
1258                 $new_attachment_id = $this->insert_attachment( $object, $cropped );
1259
1260                 $object['attachment_id'] = $new_attachment_id;
1261                 $object['width']         = $dimensions['dst_width'];
1262                 $object['height']        = $dimensions['dst_height'];
1263
1264                 wp_send_json_success( $object );
1265         }
1266
1267         /**
1268          * Given an attachment ID for a header image, updates its "last used"
1269          * timestamp to now.
1270          *
1271          * Triggered when the user tries adds a new header image from the
1272          * Media Manager, even if s/he doesn't save that change.
1273          */
1274         public function ajax_header_add() {
1275                 check_ajax_referer( 'header-add', 'nonce' );
1276
1277                 if ( ! current_user_can( 'edit_theme_options' ) ) {
1278                         wp_send_json_error();
1279                 }
1280
1281                 $attachment_id = absint( $_POST['attachment_id'] );
1282                 if ( $attachment_id < 1 ) {
1283                         wp_send_json_error();
1284                 }
1285
1286                 $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1287                 update_post_meta( $attachment_id, $key, time() );
1288                 update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
1289
1290                 wp_send_json_success();
1291         }
1292
1293         /**
1294          * Given an attachment ID for a header image, unsets it as a user-uploaded
1295          * header image for the current theme.
1296          *
1297          * Triggered when the user clicks the overlay "X" button next to each image
1298          * choice in the Customizer's Header tool.
1299          */
1300         public function ajax_header_remove() {
1301                 check_ajax_referer( 'header-remove', 'nonce' );
1302
1303                 if ( ! current_user_can( 'edit_theme_options' ) ) {
1304                         wp_send_json_error();
1305                 }
1306
1307                 $attachment_id = absint( $_POST['attachment_id'] );
1308                 if ( $attachment_id < 1 ) {
1309                         wp_send_json_error();
1310                 }
1311
1312                 $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1313                 delete_post_meta( $attachment_id, $key );
1314                 delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
1315
1316                 wp_send_json_success();
1317         }
1318
1319         public function customize_set_last_used( $wp_customize ) {
1320                 $data = $wp_customize->get_setting( 'header_image_data' )->post_value();
1321
1322                 if ( ! isset( $data['attachment_id'] ) ) {
1323                         return;
1324                 }
1325
1326                 $attachment_id = $data['attachment_id'];
1327                 $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1328                 update_post_meta( $attachment_id, $key, time() );
1329         }
1330
1331         public function get_default_header_images() {
1332                 $this->process_default_headers();
1333
1334                 // Get the default image if there is one.
1335                 $default = get_theme_support( 'custom-header', 'default-image' );
1336
1337                 if ( ! $default ) { // If not,
1338                         return $this->default_headers; // easy peasy.
1339                 }
1340
1341                 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1342                 $already_has_default = false;
1343
1344                 foreach ( $this->default_headers as $k => $h ) {
1345                         if ( $h['url'] === $default ) {
1346                                 $already_has_default = true;
1347                                 break;
1348                         }
1349                 }
1350
1351                 if ( $already_has_default ) {
1352                         return $this->default_headers;
1353                 }
1354
1355                 // If the one true image isn't included in the default set, prepend it.
1356                 $header_images = array();
1357                 $header_images['default'] = array(
1358                         'url'           => $default,
1359                         'thumbnail_url' => $default,
1360                         'description'   => 'Default'
1361                 );
1362
1363                 // The rest of the set comes after.
1364                 $header_images = array_merge( $header_images, $this->default_headers );
1365                 return $header_images;
1366         }
1367
1368         public function get_uploaded_header_images() {
1369                 $header_images = get_uploaded_header_images();
1370                 $timestamp_key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1371                 $alt_text_key = '_wp_attachment_image_alt';
1372
1373                 foreach ( $header_images as &$header_image ) {
1374                         $header_meta = get_post_meta( $header_image['attachment_id'] );
1375                         $header_image['timestamp'] = isset( $header_meta[ $timestamp_key ] ) ? $header_meta[ $timestamp_key ] : '';
1376                         $header_image['alt_text'] = isset( $header_meta[ $alt_text_key ] ) ? $header_meta[ $alt_text_key ] : '';
1377                 }
1378
1379                 return $header_images;
1380         }
1381 }