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