[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> class-custom-image-header.php (source)

   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&#8217;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 &#8220;Choose Image&#8221; 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 &#8220;Save Changes&#8221; 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 &#8220;Random&#8221; 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 &#8220;Remove Header Image&#8221; button at the bottom of the Header Image section of this page. If you want to re-enable the header image later, you just have to select one of the other image options and click &#8220;Save Changes&#8221;.' ) . '</p>',
 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. &#8220;#ff0000&#8221; for red, or by choosing a color using the color picker.' ) . '</p>' .
 147                      '<p>' . __( 'Do not forget to click &#8220;Save Changes&#8221; 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 &times; %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  }


Generated : Tue Nov 3 08:20:36 2026 Cross-referenced by PHPXref