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