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