"Fossies" - the Fresh Open Source Software Archive

Member "wordpress/wp-admin/includes/class-custom-image-header.php" (2 Feb 2021, 47915 Bytes) of package /linux/www/wordpress-5.7-RC1.tar.gz:


The requested HTML page contains a <FORM> tag that is unusable on "Fossies" in "automatic" (rendered) mode so that page is shown as HTML source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "class-custom-image-header.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6.2_vs_5.7-RC1.

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