3 * The custom header image script.
6 * @subpackage Administration
10 * The custom header image class.
14 * @subpackage Administration
16 class Custom_Image_Header {
19 * Callback for administration header.
25 private $admin_header_callback;
28 * Callback for header div.
34 private $admin_image_div_callback;
37 * Holds default headers.
43 private $default_headers = array();
46 * Holds custom headers uploaded by the user.
52 private $uploaded_headers = array();
55 * Holds the page menu hook.
69 * Constructor - Register administration header callback.
72 * @param callback $admin_header_callback
73 * @param callback $admin_image_div_callback Optional custom image div output callback.
74 * @return Custom_Image_Header
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;
80 add_action( 'admin_menu', array( $this, 'init' ) );
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' ) );
89 * Make private properties readable for backwards compatibility.
94 * @param string $name Property to get.
95 * @return mixed Property.
97 public function __get( $name ) {
102 * Make private properties settable for backwards compatibility.
107 * @param string $name Property to set.
108 * @param mixed $value Property value.
109 * @return mixed Newly-set property.
111 public function __set( $name, $value ) {
112 return $this->$name = $value;
116 * Make private properties checkable for backwards compatibility.
121 * @param string $name Property to check if set.
122 * @return bool Whether the property is set.
124 public function __isset( $name ) {
125 return isset( $this->$name );
129 * Make private properties un-settable for backwards compatibility.
134 * @param string $name Property to unset.
136 public function __unset( $name ) {
137 unset( $this->$name );
141 * Set up the hooks for the Custom Header admin page.
145 public function init() {
146 if ( ! current_user_can('edit_theme_options') )
149 $this->page = $page = add_theme_page(__('Header'), __('Header'), 'edit_theme_options', 'custom-header', array($this, 'admin_page'));
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);
162 * Adds contextual help.
166 public function help() {
167 get_current_screen()->add_help_tab( array(
169 'title' => __('Overview'),
171 '<p>' . __( 'This screen is used to customize the header section of your theme.') . '</p>' .
172 '<p>' . __( 'You can choose from the theme’s default header images, or use one of your own. You can also customize how your Site Title and Tagline are displayed.') . '<p>'
175 get_current_screen()->add_help_tab( array(
176 'id' => 'set-header-image',
177 'title' => __('Header Image'),
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 “Choose Image” button.' ) . '</p>' .
180 '<p>' . __( 'Some themes come with additional header images bundled. If you see multiple images displayed, select the one you’d like and click the “Save Changes” 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 “Random” radio button next to the Uploaded Images or Default Images section to enable this feature.') . '</p>' .
182 '<p>' . __( 'If you don’t want a header image to be displayed on your site at all, click the “Remove Header Image” 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 “Save Changes”.') . '</p>'
185 get_current_screen()->add_help_tab( array(
186 'id' => 'set-header-text',
187 'title' => __('Header Text'),
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. “#ff0000” for red, or by choosing a color using the color picker.' ) . '</p>' .
191 '<p>' . __( 'Don’t forget to click “Save Changes” when you’re done!') . '</p>'
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>'
202 * Get the current step.
206 * @return int Current step
208 public function step() {
209 if ( ! isset( $_GET['step'] ) )
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' ) )
223 * Set up the enqueue for the JavaScript files.
227 public function js_includes() {
228 $step = $this->step();
230 if ( ( 1 == $step || 3 == $step ) ) {
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');
241 * Set up the enqueue for the CSS files
245 public function css_includes() {
246 $step = $this->step();
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');
255 * Execute custom header modification.
259 public function take_action() {
260 if ( ! current_user_can('edit_theme_options') )
263 if ( empty( $_POST ) )
266 $this->updated = true;
268 if ( isset( $_POST['resetheader'] ) ) {
269 check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
270 $this->reset_header_image();
274 if ( isset( $_POST['removeheader'] ) ) {
275 check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
276 $this->remove_header_image();
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);
290 set_theme_mod( 'header_textcolor', 'blank' );
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'] );
301 * Process the default headers
305 public function process_default_headers() {
306 global $_wp_default_headers;
308 if ( !isset($_wp_default_headers) )
311 if ( ! empty( $this->default_headers ) ) {
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 );
325 * Display UI for selecting one of several default headers.
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.
332 public function show_header_selector( $type = 'default' ) {
333 if ( 'default' == $type ) {
334 $headers = $this->default_headers;
336 $headers = get_uploaded_header_images();
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.' );
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 ) . ' />';
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>';
361 echo '<div class="clear"></div></div>';
365 * Execute JavaScript depending on step.
369 public function js() {
370 $step = $this->step();
371 if ( ( 1 == $step || 3 == $step ) && current_theme_supports( 'custom-header', 'header-text' ) )
373 elseif ( 2 == $step )
378 * Display JavaScript based on Step 1 and 3.
382 public function js_1() {
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;
392 <script type="text/javascript">
395 var default_color = '<?php echo $default_color; ?>',
398 function pickColor(color) {
399 $('#name').css('color', color);
400 $('#desc').css('color', color);
401 $('#text-color').val(color);
404 function toggle_text() {
405 var checked = $('#display-header-text').prop('checked'),
407 header_text_fields.toggle( checked );
410 text_color = $('#text-color');
411 if ( '' == text_color.val().replace('#', '') ) {
412 text_color.val( default_color );
413 pickColor( default_color );
415 pickColor( text_color.val() );
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') );
430 $('#display-header-text').click( toggle_text );
431 <?php if ( ! display_header_text() ) : ?>
442 * Display JavaScript based on Step 2.
446 public function js_2() { ?>
447 <script type="text/javascript">
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);
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();
463 if ( yimg < yinit || ximg < xinit ) {
464 if ( ximg / yimg > ratio ) {
466 xinit = yinit * ratio;
469 yinit = xinit / ratio;
473 jQuery('img#upload').imgAreaSelect({
482 if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
484 aspectRatio: xinit + ':' + yinit,
487 if ( ! current_theme_supports( 'custom-header', 'flex-height' ) ) {
489 maxHeight: <?php echo get_theme_support( 'custom-header', 'height' ); ?>,
492 if ( ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
494 maxWidth: <?php echo get_theme_support( 'custom-header', 'width' ); ?>,
498 onInit: function () {
499 jQuery('#width').val(xinit);
500 jQuery('#height').val(yinit);
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);
516 * Display first step of custom header image page.
520 public function step_1() {
521 $this->process_default_headers();
525 <h2><?php _e( 'Custom Header' ); ?></h2>
527 <?php if ( current_user_can( 'customize' ) ) { ?>
528 <div class="notice notice-info hide-if-no-customize">
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' )
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>
546 <h3><?php _e( 'Header Image' ); ?></h3>
548 <table class="form-table">
551 <?php if ( get_custom_header() || display_header_text() ) : ?>
553 <th scope="row"><?php _e( 'Preview' ); ?></th>
556 if ( $this->admin_image_div_callback ) {
557 call_user_func( $this->admin_image_div_callback );
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;';
566 <div id="headimg" style="<?php echo $header_image_style; ?>">
568 if ( display_header_text() )
569 $style = ' style="color:#' . get_header_textcolor() . ';"';
571 $style = ' style="display:none;"';
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>
581 <?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
583 <th scope="row"><?php _e( 'Select Image' ); ?></th>
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 />
587 if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
588 printf( __( 'Images of exactly <strong>%1$d × %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' ) );
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' ) );
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 ) ) ?>">
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 ); ?>
612 $modal_update_href = esc_url( add_query_arg( array(
613 'page' => 'custom-header',
615 '_wpnonce-custom-header-upload' => wp_create_nonce('custom-header-upload'),
616 ), admin_url('themes.php') ) );
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>
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">
636 <?php if ( get_uploaded_header_images() ) : ?>
638 <th scope="row"><?php _e( 'Uploaded Images' ); ?></th>
640 <p><?php _e( 'You can choose one of your previously uploaded headers, or show a random one.' ) ?></p>
642 $this->show_header_selector( 'uploaded' );
647 if ( ! empty( $this->default_headers ) ) : ?>
649 <th scope="row"><?php _e( 'Default Images' ); ?></th>
651 <?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
652 <p><?php _e( 'If you don‘t want to upload your own image, you can use one of these cool headers, or show a random one.' ) ?></p>
654 <p><?php _e( 'You can use one of these cool headers or show a random one on each page.' ) ?></p>
657 $this->show_header_selector( 'default' );
662 if ( get_header_image() ) : ?>
664 <th scope="row"><?php _e( 'Remove Image' ); ?></th>
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 ); ?>
672 $default_image = get_theme_support( 'custom-header', 'default-image' );
673 if ( $default_image && get_header_image() != $default_image ) : ?>
675 <th scope="row"><?php _e( 'Reset Image' ); ?></th>
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 ); ?>
685 <?php if ( current_theme_supports( 'custom-header', 'header-text' ) ) : ?>
687 <h3><?php _e( 'Header Text' ); ?></h3>
689 <table class="form-table">
692 <th scope="row"><?php _e( 'Header Text' ); ?></th>
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>
700 <tr class="displaying-header-text">
701 <th scope="row"><?php _e( 'Text Color' ); ?></th>
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;
713 $default_color_attr = $default_color ? ' data-default-color="' . esc_attr( $default_color ) . '"' : '';
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;
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>';
733 * Fires just before the submit button in the custom header options form.
737 do_action( 'custom_header_options' );
739 wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
741 <?php submit_button( null, 'primary', 'save-header-options' ); ?>
748 * Display second step of custom header image page.
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’ uh?' ), 403 );
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' );
762 } elseif ( isset( $_POST ) ) {
763 $data = $this->step_2_manage_upload();
764 $attachment_id = $data['attachment_id'];
765 $file = $data['file'];
769 if ( file_exists( $file ) ) {
770 list( $width, $height, $type, $attr ) = getimagesize( $file );
772 $data = wp_get_attachment_metadata( $attachment_id );
773 $height = isset( $data[ 'height' ] ) ? $data[ 'height' ] : 0;
774 $width = isset( $data[ 'width' ] ) ? $data[ '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' ) )
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' ) );
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' ) )
792 if ( file_exists( $file ) )
793 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
795 $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
798 * Fires after the header image is set or an error is returned.
802 * @param string $file Path to the file.
803 * @param int $attachment_id Attachment ID.
805 do_action( 'wp_create_file_in_uploads', $file, $attachment_id ); // For replication
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' ) );
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
817 $url = str_replace(basename($url), basename($image), $url);
818 $width = $width / $oitar;
819 $height = $height / $oitar;
826 <h2><?php _e( 'Crop Header Image' ); ?></h2>
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>
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; ?>" />
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" />
845 <?php wp_nonce_field( 'custom-header-crop-image' ) ?>
848 <?php submit_button( __( 'Crop and Publish' ), 'primary', 'submit', false ); ?>
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 );
861 * Upload the file to be cropped in the second step.
865 public function step_2_manage_upload() {
866 $overrides = array('test_form' => false);
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.' ) );
873 $file = wp_handle_upload($uploaded_file, $overrides);
875 if ( isset($file['error']) )
876 wp_die( $file['error'], __( 'Image Upload Error' ) );
879 $type = $file['type'];
880 $file = $file['file'];
881 $filename = basename($file);
883 // Construct the object array
885 'post_title' => $filename,
886 'post_content' => $url,
887 'post_mime_type' => $type,
889 'context' => 'custom-header'
893 $attachment_id = wp_insert_attachment( $object, $file );
894 return compact( 'attachment_id', 'file', 'filename', 'url', 'type' );
898 * Display third step of custom header image page.
902 public function step_3() {
903 check_admin_referer( 'custom-header-crop-image' );
905 if ( ! current_theme_supports( 'custom-header', 'uploads' ) )
906 wp_die( __( 'Cheatin’ uh?' ), 403 );
908 if ( ! empty( $_POST['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
909 wp_die( __( 'Cheatin’ uh?' ), 403 );
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'];
918 $attachment_id = absint( $_POST['attachment_id'] );
919 $original = get_attached_file($attachment_id);
921 $dimensions = $this->get_header_dimensions( array(
922 'height' => $_POST['height'],
923 'width' => $_POST['width'],
925 $height = $dimensions['dst_height'];
926 $width = $dimensions['dst_width'];
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 );
933 $cropped = get_attached_file( $attachment_id );
935 if ( ! $cropped || is_wp_error( $cropped ) )
936 wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
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
941 $object = $this->create_attachment_object( $cropped, $attachment_id );
943 if ( ! empty( $_POST['create-new-attachment'] ) )
944 unset( $object['ID'] );
946 // Update the attachment
947 $attachment_id = $this->insert_attachment( $object, $cropped );
949 $url = $object['guid'];
950 $this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
953 $medium = str_replace( basename( $original ), 'midsize-' . basename( $original ), $original );
954 if ( file_exists( $medium ) ) {
956 * Filter the path of the file to delete.
960 * @param string $medium Path to the file to delete.
962 @unlink( apply_filters( 'wp_delete_file', $medium ) );
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 ) );
970 return $this->finished();
974 * Display last step of custom header image page.
978 public function finished() {
979 $this->updated = true;
984 * Display the page based on the current step.
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();
994 elseif ( 3 == $step )
1001 * Unused since 3.5.0.
1005 public function attachment_fields_to_edit( $form_fields ) {
1006 return $form_fields;
1010 * Unused since 3.5.0.
1014 public function filter_upload_tabs( $tabs ) {
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).
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.
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'] ) )
1036 $choice['url'] = esc_url_raw( $choice['url'] );
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'],
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 );
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' );
1058 $uploaded = get_uploaded_header_images();
1059 if ( $uploaded && isset( $uploaded[ $choice ] ) ) {
1060 $header_image_data = $uploaded[ $choice ];
1063 $this->process_default_headers();
1064 if ( isset( $this->default_headers[ $choice ] ) )
1065 $header_image_data = $this->default_headers[ $choice ];
1070 set_theme_mod( 'header_image', esc_url_raw( $header_image_data['url'] ) );
1071 set_theme_mod( 'header_image_data', $header_image_data );
1075 * Remove a header image.
1079 final public function remove_header_image() {
1080 return $this->set_header_image( 'remove-header' );
1084 * Reset a header image to the default image for the theme.
1086 * This method does not do anything if the theme does not have a default header image.
1090 final public function reset_header_image() {
1091 $this->process_default_headers();
1092 $default = get_theme_support( 'custom-header', 'default-image' );
1095 return $this->remove_header_image();
1097 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1099 $default_data = array();
1100 foreach ( $this->default_headers as $header => $details ) {
1101 if ( $details['url'] == $default ) {
1102 $default_data = $details;
1107 set_theme_mod( 'header_image', $default );
1108 set_theme_mod( 'header_image_data', (object) $default_data );
1112 * Calculate width and height based on what the currently selected theme supports.
1114 * @return array dst_height and dst_width of header image.
1116 final public function get_header_dimensions( $dimensions ) {
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 );
1127 // For flex, limit size of image displayed to 1500px unless theme says otherwise
1128 if ( $has_flex_width ) {
1132 if ( $has_max_width ) {
1133 $max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
1135 $max_width = max( $max_width, $theme_width );
1137 if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
1138 $dst['dst_height'] = absint( $height * ( $max_width / $width ) );
1140 elseif ( $has_flex_height && $has_flex_width ) {
1141 $dst['dst_height'] = $height;
1144 $dst['dst_height'] = $theme_height;
1147 if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
1148 $dst['dst_width'] = absint( $width * ( $max_width / $width ) );
1150 elseif ( $has_flex_width && $has_flex_height ) {
1151 $dst['dst_width'] = $width;
1154 $dst['dst_width'] = $theme_width;
1161 * Create an attachment 'object'.
1163 * @param string $cropped Cropped image URL.
1164 * @param int $parent_attachment_id Attachment ID of parent image.
1166 * @return array Attachment object.
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 );
1173 $size = @getimagesize( $cropped );
1174 $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
1177 'ID' => $parent_attachment_id,
1178 'post_title' => basename($cropped),
1179 'post_content' => $url,
1180 'post_mime_type' => $image_type,
1182 'context' => 'custom-header'
1189 * Insert an attachment and its metadata.
1191 * @param array $object Attachment object.
1192 * @param string $cropped Cropped image URL.
1194 * @return int Attachment ID.
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 );
1200 * Filter the header image attachment metadata.
1204 * @see wp_generate_attachment_metadata()
1206 * @param array $metadata Attachment metadata.
1208 $metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
1209 wp_update_attachment_metadata( $attachment_id, $metadata );
1210 return $attachment_id;
1214 * Gets attachment uploaded by Media Manager, crops it, then saves it as a
1215 * new object. Returns JSON-encoded object details.
1217 public function ajax_header_crop() {
1218 check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
1220 if ( ! current_user_can( 'edit_theme_options' ) ) {
1221 wp_send_json_error();
1224 if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
1225 wp_send_json_error();
1228 $crop_details = $_POST['cropDetails'];
1230 $dimensions = $this->get_header_dimensions( array(
1231 'height' => $crop_details['height'],
1232 'width' => $crop_details['width'],
1235 $attachment_id = absint( $_POST['id'] );
1237 $cropped = wp_crop_image(
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']
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.' ) ) );
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
1254 $object = $this->create_attachment_object( $cropped, $attachment_id );
1256 unset( $object['ID'] );
1258 $new_attachment_id = $this->insert_attachment( $object, $cropped );
1260 $object['attachment_id'] = $new_attachment_id;
1261 $object['width'] = $dimensions['dst_width'];
1262 $object['height'] = $dimensions['dst_height'];
1264 wp_send_json_success( $object );
1268 * Given an attachment ID for a header image, updates its "last used"
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.
1274 public function ajax_header_add() {
1275 check_ajax_referer( 'header-add', 'nonce' );
1277 if ( ! current_user_can( 'edit_theme_options' ) ) {
1278 wp_send_json_error();
1281 $attachment_id = absint( $_POST['attachment_id'] );
1282 if ( $attachment_id < 1 ) {
1283 wp_send_json_error();
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() );
1290 wp_send_json_success();
1294 * Given an attachment ID for a header image, unsets it as a user-uploaded
1295 * header image for the current theme.
1297 * Triggered when the user clicks the overlay "X" button next to each image
1298 * choice in the Customizer's Header tool.
1300 public function ajax_header_remove() {
1301 check_ajax_referer( 'header-remove', 'nonce' );
1303 if ( ! current_user_can( 'edit_theme_options' ) ) {
1304 wp_send_json_error();
1307 $attachment_id = absint( $_POST['attachment_id'] );
1308 if ( $attachment_id < 1 ) {
1309 wp_send_json_error();
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() );
1316 wp_send_json_success();
1319 public function customize_set_last_used( $wp_customize ) {
1320 $data = $wp_customize->get_setting( 'header_image_data' )->post_value();
1322 if ( ! isset( $data['attachment_id'] ) ) {
1326 $attachment_id = $data['attachment_id'];
1327 $key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1328 update_post_meta( $attachment_id, $key, time() );
1331 public function get_default_header_images() {
1332 $this->process_default_headers();
1334 // Get the default image if there is one.
1335 $default = get_theme_support( 'custom-header', 'default-image' );
1337 if ( ! $default ) { // If not,
1338 return $this->default_headers; // easy peasy.
1341 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1342 $already_has_default = false;
1344 foreach ( $this->default_headers as $k => $h ) {
1345 if ( $h['url'] === $default ) {
1346 $already_has_default = true;
1351 if ( $already_has_default ) {
1352 return $this->default_headers;
1355 // If the one true image isn't included in the default set, prepend it.
1356 $header_images = array();
1357 $header_images['default'] = array(
1359 'thumbnail_url' => $default,
1360 'description' => 'Default'
1363 // The rest of the set comes after.
1364 $header_images = array_merge( $header_images, $this->default_headers );
1365 return $header_images;
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';
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 ] : '';
1379 return $header_images;