angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-custom-image-header.php (48141B)


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