Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-admin/custom-header.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * @package WordPress
14
 * @subpackage Administration
15
 */
16
class Custom_Image_Header {
17
18
	/**
19
	 * Callback for administration header.
20
	 *
21
	 * @var callable
22
	 * @since 2.1.0
23
	 */
24
	public $admin_header_callback;
25
26
	/**
27
	 * Callback for header div.
28
	 *
29
	 * @var callable
30
	 * @since 3.0.0
31
	 */
32
	public $admin_image_div_callback;
33
34
	/**
35
	 * Holds default headers.
36
	 *
37
	 * @var array
38
	 * @since 3.0.0
39
	 * @access private
40
	 */
41
	public $default_headers = array();
42
43
	/**
44
	 * Used to trigger a success message when settings updated and set to true.
45
	 *
46
	 * @since 3.0.0
47
	 * @access private
48
	 * @var bool
49
	 */
50
	private $updated;
51
52
	/**
53
	 * Constructor - Register administration header callback.
54
	 *
55
	 * @since 2.1.0
56
	 * @param callable $admin_header_callback
57
	 * @param callable $admin_image_div_callback Optional custom image div output callback.
58
	 */
59
	public function __construct($admin_header_callback, $admin_image_div_callback = '') {
60
		$this->admin_header_callback = $admin_header_callback;
61
		$this->admin_image_div_callback = $admin_image_div_callback;
62
63
		add_action( 'admin_menu', array( $this, 'init' ) );
64
65
		add_action( 'customize_save_after',         array( $this, 'customize_set_last_used' ) );
66
		add_action( 'wp_ajax_custom-header-crop',   array( $this, 'ajax_header_crop'        ) );
67
		add_action( 'wp_ajax_custom-header-add',    array( $this, 'ajax_header_add'         ) );
68
		add_action( 'wp_ajax_custom-header-remove', array( $this, 'ajax_header_remove'      ) );
69
	}
70
71
	/**
72
	 * Set up the hooks for the Custom Header admin page.
73
	 *
74
	 * @since 2.1.0
75
	 */
76
	public function init() {
77
		$page = add_theme_page( __( 'Header' ), __( 'Header' ), 'edit_theme_options', 'custom-header', array( $this, 'admin_page' ) );
78
		if ( ! $page ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $page of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
79
			return;
80
		}
81
82
		add_action( "admin_print_scripts-$page", array( $this, 'js_includes' ) );
83
		add_action( "admin_print_styles-$page", array( $this, 'css_includes' ) );
84
		add_action( "admin_head-$page", array( $this, 'help' ) );
85
		add_action( "admin_head-$page", array( $this, 'take_action' ), 50 );
86
		add_action( "admin_head-$page", array( $this, 'js' ), 50 );
87
		if ( $this->admin_header_callback ) {
88
			add_action( "admin_head-$page", $this->admin_header_callback, 51 );
89
		}
90
	}
91
92
	/**
93
	 * Adds contextual help.
94
	 *
95
	 * @since 3.0.0
96
	 */
97
	public function help() {
98
		get_current_screen()->add_help_tab( array(
99
			'id'      => 'overview',
100
			'title'   => __('Overview'),
101
			'content' =>
102
				'<p>' . __( 'This screen is used to customize the header section of your theme.') . '</p>' .
103
				'<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>'
104
		) );
105
106
		get_current_screen()->add_help_tab( array(
107
			'id'      => 'set-header-image',
108
			'title'   => __('Header Image'),
109
			'content' =>
110
				'<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>' .
111
				'<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>' .
112
				'<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>' .
113
				'<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>'
114
		) );
115
116
		get_current_screen()->add_help_tab( array(
117
			'id'      => 'set-header-text',
118
			'title'   => __('Header Text'),
119
			'content' =>
120
				'<p>' . sprintf( __( 'For most themes, the header text is your Site Title and Tagline, as defined in the <a href="%1$s">General Settings</a> section.' ), admin_url( 'options-general.php' ) ) . '<p>' .
121
				'<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>' .
122
				'<p>' . __( 'Don&#8217;t forget to click &#8220;Save Changes&#8221; when you&#8217;re done!') . '</p>'
123
		) );
124
125
		get_current_screen()->set_help_sidebar(
126
			'<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
127
			'<p>' . __( '<a href="https://codex.wordpress.org/Appearance_Header_Screen">Documentation on Custom Header</a>' ) . '</p>' .
128
			'<p>' . __( '<a href="https://wordpress.org/support/">Support Forums</a>' ) . '</p>'
129
		);
130
	}
131
132
	/**
133
	 * Get the current step.
134
	 *
135
	 * @since 2.6.0
136
	 *
137
	 * @return int Current step
138
	 */
139
	public function step() {
140
		if ( ! isset( $_GET['step'] ) )
141
			return 1;
142
143
		$step = (int) $_GET['step'];
144
		if ( $step < 1 || 3 < $step ||
145
			( 2 == $step && ! wp_verify_nonce( $_REQUEST['_wpnonce-custom-header-upload'], 'custom-header-upload' ) ) ||
146
			( 3 == $step && ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'custom-header-crop-image' ) )
147
		)
148
			return 1;
149
150
		return $step;
151
	}
152
153
	/**
154
	 * Set up the enqueue for the JavaScript files.
155
	 *
156
	 * @since 2.1.0
157
	 */
158
	public function js_includes() {
159
		$step = $this->step();
160
161
		if ( ( 1 == $step || 3 == $step ) ) {
162
			wp_enqueue_media();
163
			wp_enqueue_script( 'custom-header' );
164
			if ( current_theme_supports( 'custom-header', 'header-text' ) )
165
				wp_enqueue_script( 'wp-color-picker' );
166
		} elseif ( 2 == $step ) {
167
			wp_enqueue_script('imgareaselect');
168
		}
169
	}
170
171
	/**
172
	 * Set up the enqueue for the CSS files
173
	 *
174
	 * @since 2.7.0
175
	 */
176
	public function css_includes() {
177
		$step = $this->step();
178
179
		if ( ( 1 == $step || 3 == $step ) && current_theme_supports( 'custom-header', 'header-text' ) )
180
			wp_enqueue_style( 'wp-color-picker' );
181
		elseif ( 2 == $step )
182
			wp_enqueue_style('imgareaselect');
183
	}
184
185
	/**
186
	 * Execute custom header modification.
187
	 *
188
	 * @since 2.6.0
189
	 */
190
	public function take_action() {
191
		if ( ! current_user_can('edit_theme_options') )
192
			return;
193
194
		if ( empty( $_POST ) )
195
			return;
196
197
		$this->updated = true;
198
199
		if ( isset( $_POST['resetheader'] ) ) {
200
			check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
201
			$this->reset_header_image();
202
			return;
203
		}
204
205
		if ( isset( $_POST['removeheader'] ) ) {
206
			check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
207
			$this->remove_header_image();
208
			return;
209
		}
210
211
		if ( isset( $_POST['text-color'] ) && ! isset( $_POST['display-header-text'] ) ) {
212
			check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
213
			set_theme_mod( 'header_textcolor', 'blank' );
214
		} elseif ( isset( $_POST['text-color'] ) ) {
215
			check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
216
			$_POST['text-color'] = str_replace( '#', '', $_POST['text-color'] );
217
			$color = preg_replace('/[^0-9a-fA-F]/', '', $_POST['text-color']);
218 View Code Duplication
			if ( strlen($color) == 6 || strlen($color) == 3 )
219
				set_theme_mod('header_textcolor', $color);
220
			elseif ( ! $color )
221
				set_theme_mod( 'header_textcolor', 'blank' );
222
		}
223
224
		if ( isset( $_POST['default-header'] ) ) {
225
			check_admin_referer( 'custom-header-options', '_wpnonce-custom-header-options' );
226
			$this->set_header_image( $_POST['default-header'] );
227
			return;
228
		}
229
	}
230
231
	/**
232
	 * Process the default headers
233
	 *
234
	 * @since 3.0.0
235
	 *
236
	 * @global array $_wp_default_headers
237
	 */
238
	public function process_default_headers() {
239
		global $_wp_default_headers;
240
241
		if ( !isset($_wp_default_headers) )
242
			return;
243
244
		if ( ! empty( $this->default_headers ) ) {
245
			return;
246
		}
247
248
		$this->default_headers = $_wp_default_headers;
249
		$template_directory_uri = get_template_directory_uri();
250
		$stylesheet_directory_uri = get_stylesheet_directory_uri();
251
		foreach ( array_keys($this->default_headers) as $header ) {
252
			$this->default_headers[$header]['url'] =  sprintf( $this->default_headers[$header]['url'], $template_directory_uri, $stylesheet_directory_uri );
253
			$this->default_headers[$header]['thumbnail_url'] =  sprintf( $this->default_headers[$header]['thumbnail_url'], $template_directory_uri, $stylesheet_directory_uri );
254
		}
255
	}
256
257
	/**
258
	 * Display UI for selecting one of several default headers.
259
	 *
260
	 * Show the random image option if this theme has multiple header images.
261
	 * Random image option is on by default if no header has been set.
262
	 *
263
	 * @since 3.0.0
264
	 *
265
	 * @param string $type The header type. One of 'default' (for the Uploaded Images control)
266
	 *                     or 'uploaded' (for the Uploaded Images control).
267
	 */
268
	public function show_header_selector( $type = 'default' ) {
269
		if ( 'default' == $type ) {
270
			$headers = $this->default_headers;
271
		} else {
272
			$headers = get_uploaded_header_images();
273
			$type = 'uploaded';
274
		}
275
276
		if ( 1 < count( $headers ) ) {
277
			echo '<div class="random-header">';
278
			echo '<label><input name="default-header" type="radio" value="random-' . $type . '-image"' . checked( is_random_header_image( $type ), true, false ) . ' />';
279
			_e( '<strong>Random:</strong> Show a different image on each page.' );
280
			echo '</label>';
281
			echo '</div>';
282
		}
283
284
		echo '<div class="available-headers">';
285
		foreach ( $headers as $header_key => $header ) {
286
			$header_thumbnail = $header['thumbnail_url'];
287
			$header_url = $header['url'];
288
			$header_alt_text = empty( $header['alt_text'] ) ? '' : $header['alt_text'];
289
			echo '<div class="default-header">';
290
			echo '<label><input name="default-header" type="radio" value="' . esc_attr( $header_key ) . '" ' . checked( $header_url, get_theme_mod( 'header_image' ), false ) . ' />';
291
			$width = '';
292
			if ( !empty( $header['attachment_id'] ) )
293
				$width = ' width="230"';
294
			echo '<img src="' . set_url_scheme( $header_thumbnail ) . '" alt="' . esc_attr( $header_alt_text ) .'"' . $width . ' /></label>';
295
			echo '</div>';
296
		}
297
		echo '<div class="clear"></div></div>';
298
	}
299
300
	/**
301
	 * Execute JavaScript depending on step.
302
	 *
303
	 * @since 2.1.0
304
	 */
305
	public function js() {
306
		$step = $this->step();
307
		if ( ( 1 == $step || 3 == $step ) && current_theme_supports( 'custom-header', 'header-text' ) )
308
			$this->js_1();
309
		elseif ( 2 == $step )
310
			$this->js_2();
311
	}
312
313
	/**
314
	 * Display JavaScript based on Step 1 and 3.
315
	 *
316
	 * @since 2.6.0
317
	 */
318
	public function js_1() {
319
		$default_color = '';
320 View Code Duplication
		if ( current_theme_supports( 'custom-header', 'default-text-color' ) ) {
321
			$default_color = get_theme_support( 'custom-header', 'default-text-color' );
322
			if ( $default_color && false === strpos( $default_color, '#' ) ) {
323
				$default_color = '#' . $default_color;
324
			}
325
		}
326
		?>
327
<script type="text/javascript">
328
(function($){
329
	var default_color = '<?php echo $default_color; ?>',
330
		header_text_fields;
331
332
	function pickColor(color) {
333
		$('#name').css('color', color);
334
		$('#desc').css('color', color);
335
		$('#text-color').val(color);
336
	}
337
338
	function toggle_text() {
339
		var checked = $('#display-header-text').prop('checked'),
340
			text_color;
341
		header_text_fields.toggle( checked );
342
		if ( ! checked )
343
			return;
344
		text_color = $('#text-color');
345
		if ( '' == text_color.val().replace('#', '') ) {
346
			text_color.val( default_color );
347
			pickColor( default_color );
348
		} else {
349
			pickColor( text_color.val() );
350
		}
351
	}
352
353
	$(document).ready(function() {
354
		var text_color = $('#text-color');
355
		header_text_fields = $('.displaying-header-text');
356
		text_color.wpColorPicker({
357
			change: function( event, ui ) {
358
				pickColor( text_color.wpColorPicker('color') );
359
			},
360
			clear: function() {
361
				pickColor( '' );
362
			}
363
		});
364
		$('#display-header-text').click( toggle_text );
365
		<?php if ( ! display_header_text() ) : ?>
366
		toggle_text();
367
		<?php endif; ?>
368
	});
369
})(jQuery);
370
</script>
371
<?php
372
	}
373
374
	/**
375
	 * Display JavaScript based on Step 2.
376
	 *
377
	 * @since 2.6.0
378
	 */
379
	public function js_2() { ?>
380
<script type="text/javascript">
381
	function onEndCrop( coords ) {
382
		jQuery( '#x1' ).val(coords.x);
383
		jQuery( '#y1' ).val(coords.y);
384
		jQuery( '#width' ).val(coords.w);
385
		jQuery( '#height' ).val(coords.h);
386
	}
387
388
	jQuery(document).ready(function() {
389
		var xinit = <?php echo absint( get_theme_support( 'custom-header', 'width' ) ); ?>;
390
		var yinit = <?php echo absint( get_theme_support( 'custom-header', 'height' ) ); ?>;
391
		var ratio = xinit / yinit;
392
		var ximg = jQuery('img#upload').width();
393
		var yimg = jQuery('img#upload').height();
394
395
		if ( yimg < yinit || ximg < xinit ) {
396
			if ( ximg / yimg > ratio ) {
397
				yinit = yimg;
398
				xinit = yinit * ratio;
399
			} else {
400
				xinit = ximg;
401
				yinit = xinit / ratio;
402
			}
403
		}
404
405
		jQuery('img#upload').imgAreaSelect({
406
			handles: true,
407
			keys: true,
408
			show: true,
409
			x1: 0,
410
			y1: 0,
411
			x2: xinit,
412
			y2: yinit,
413
			<?php
414
			if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
415
			?>
416
			aspectRatio: xinit + ':' + yinit,
417
			<?php
418
			}
419
			if ( ! current_theme_supports( 'custom-header', 'flex-height' ) ) {
420
			?>
421
			maxHeight: <?php echo get_theme_support( 'custom-header', 'height' ); ?>,
422
			<?php
423
			}
424
			if ( ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
425
			?>
426
			maxWidth: <?php echo get_theme_support( 'custom-header', 'width' ); ?>,
427
			<?php
428
			}
429
			?>
430
			onInit: function () {
431
				jQuery('#width').val(xinit);
432
				jQuery('#height').val(yinit);
433
			},
434
			onSelectChange: function(img, c) {
435
				jQuery('#x1').val(c.x1);
436
				jQuery('#y1').val(c.y1);
437
				jQuery('#width').val(c.width);
438
				jQuery('#height').val(c.height);
439
			}
440
		});
441
	});
442
</script>
443
<?php
444
	}
445
446
	/**
447
	 * Display first step of custom header image page.
448
	 *
449
	 * @since 2.1.0
450
	 */
451
	public function step_1() {
452
		$this->process_default_headers();
453
?>
454
455
<div class="wrap">
456
<h1><?php _e( 'Custom Header' ); ?></h1>
457
458
<?php if ( current_user_can( 'customize' ) ) { ?>
459
<div class="notice notice-info hide-if-no-customize">
460
	<p>
461
		<?php
462
		printf(
463
			__( 'You can now manage and live-preview Custom Header in the <a href="%1$s">Customizer</a>.' ),
464
			admin_url( 'customize.php?autofocus[control]=header_image' )
465
		);
466
		?>
467
	</p>
468
</div>
469
<?php } ?>
470
471
<?php if ( ! empty( $this->updated ) ) { ?>
472
<div id="message" class="updated">
473
<p><?php printf( __( 'Header updated. <a href="%s">Visit your site</a> to see how it looks.' ), home_url( '/' ) ); ?></p>
474
</div>
475
<?php } ?>
476
477
<h3><?php _e( 'Header Image' ); ?></h3>
478
479
<table class="form-table">
480
<tbody>
481
482
<?php if ( get_custom_header() || display_header_text() ) : ?>
483
<tr>
484
<th scope="row"><?php _e( 'Preview' ); ?></th>
485
<td>
486
	<?php
487
	if ( $this->admin_image_div_callback ) {
488
		call_user_func( $this->admin_image_div_callback );
489
	} else {
490
		$custom_header = get_custom_header();
491
		$header_image = get_header_image();
492
493
		if ( $header_image ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $header_image of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
494
			$header_image_style = 'background-image:url(' . esc_url( $header_image ) . ');';
495
		}  else {
496
			$header_image_style = '';
497
		}
498
499
		if ( $custom_header->width )
500
			$header_image_style .= 'max-width:' . $custom_header->width . 'px;';
501
		if ( $custom_header->height )
502
			$header_image_style .= 'height:' . $custom_header->height . 'px;';
503
	?>
504
	<div id="headimg" style="<?php echo $header_image_style; ?>">
505
		<?php
506
		if ( display_header_text() )
507
			$style = ' style="color:#' . get_header_textcolor() . ';"';
508
		else
509
			$style = ' style="display:none;"';
510
		?>
511
		<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>
512
		<div id="desc" class="displaying-header-text" <?php echo $style; ?>><?php bloginfo( 'description' ); ?></div>
513
	</div>
514
	<?php } ?>
515
</td>
516
</tr>
517
<?php endif; ?>
518
519
<?php if ( current_user_can( 'upload_files' ) && current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
520
<tr>
521
<th scope="row"><?php _e( 'Select Image' ); ?></th>
522
<td>
523
	<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 />
524
	<?php
525
	if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' ) ) {
526
		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' ) );
527 View Code Duplication
	} elseif ( current_theme_supports( 'custom-header', 'flex-height' ) ) {
528
		if ( ! current_theme_supports( 'custom-header', 'flex-width' ) )
529
			printf(
530
				/* translators: %s: size in pixels */
531
				__( 'Images should be at least %s wide.' ) . ' ',
532
				sprintf(
533
					/* translators: %d: custom header width */
534
					'<strong>' . __( '%d pixels' ) . '</strong>',
535
					get_theme_support( 'custom-header', 'width' )
536
				)
537
			);
538
	} elseif ( current_theme_supports( 'custom-header', 'flex-width' ) ) {
539
		if ( ! current_theme_supports( 'custom-header', 'flex-height' ) )
540
			printf(
541
				/* translators: %s: size in pixels */
542
				__( 'Images should be at least %s tall.' ) . ' ',
543
				sprintf(
544
					/* translators: %d: custom header height */
545
					'<strong>' . __( '%d pixels' ) . '</strong>',
546
					get_theme_support( 'custom-header', 'height' )
547
				)
548
			);
549
	}
550
	if ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) {
551 View Code Duplication
		if ( current_theme_supports( 'custom-header', 'width' ) )
552
			printf(
553
				/* translators: %s: size in pixels */
554
				__( 'Suggested width is %s.' ) . ' ',
555
				sprintf(
556
					/* translators: %d: custom header width */
557
					'<strong>' . __( '%d pixels' ) . '</strong>',
558
					get_theme_support( 'custom-header', 'width' )
559
				)
560
			);
561 View Code Duplication
		if ( current_theme_supports( 'custom-header', 'height' ) )
562
			printf(
563
				/* translators: %s: size in pixels */
564
				__( 'Suggested height is %s.' ) . ' ',
565
				sprintf(
566
					/* translators: %d: custom header height */
567
					'<strong>' . __( '%d pixels' ) . '</strong>',
568
					get_theme_support( 'custom-header', 'height' )
569
				)
570
			);
571
	}
572
	?></p>
573
	<form enctype="multipart/form-data" id="upload-form" class="wp-upload-form" method="post" action="<?php echo esc_url( add_query_arg( 'step', 2 ) ) ?>">
574
	<p>
575
		<label for="upload"><?php _e( 'Choose an image from your computer:' ); ?></label><br />
576
		<input type="file" id="upload" name="import" />
577
		<input type="hidden" name="action" value="save" />
578
		<?php wp_nonce_field( 'custom-header-upload', '_wpnonce-custom-header-upload' ); ?>
579
		<?php submit_button( __( 'Upload' ), '', 'submit', false ); ?>
580
	</p>
581
	<?php
582
		$modal_update_href = esc_url( add_query_arg( array(
583
			'page' => 'custom-header',
584
			'step' => 2,
585
			'_wpnonce-custom-header-upload' => wp_create_nonce('custom-header-upload'),
586
		), admin_url('themes.php') ) );
587
	?>
588
	<p>
589
		<label for="choose-from-library-link"><?php _e( 'Or choose an image from your media library:' ); ?></label><br />
590
		<button id="choose-from-library-link" class="button"
591
			data-update-link="<?php echo esc_attr( $modal_update_href ); ?>"
592
			data-choose="<?php esc_attr_e( 'Choose a Custom Header' ); ?>"
593
			data-update="<?php esc_attr_e( 'Set as header' ); ?>"><?php _e( 'Choose Image' ); ?></button>
594
	</p>
595
	</form>
596
</td>
597
</tr>
598
<?php endif; ?>
599
</tbody>
600
</table>
601
602
<form method="post" action="<?php echo esc_url( add_query_arg( 'step', 1 ) ) ?>">
603
<?php submit_button( null, 'screen-reader-text', 'save-header-options', false ); ?>
604
<table class="form-table">
605
<tbody>
606
	<?php if ( get_uploaded_header_images() ) : ?>
607
<tr>
608
<th scope="row"><?php _e( 'Uploaded Images' ); ?></th>
609
<td>
610
	<p><?php _e( 'You can choose one of your previously uploaded headers, or show a random one.' ) ?></p>
611
	<?php
612
		$this->show_header_selector( 'uploaded' );
613
	?>
614
</td>
615
</tr>
616
	<?php endif;
617
	if ( ! empty( $this->default_headers ) ) : ?>
618
<tr>
619
<th scope="row"><?php _e( 'Default Images' ); ?></th>
620
<td>
621
<?php if ( current_theme_supports( 'custom-header', 'uploads' ) ) : ?>
622
	<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>
623
<?php else: ?>
624
	<p><?php _e( 'You can use one of these cool headers or show a random one on each page.' ) ?></p>
625
<?php endif; ?>
626
	<?php
627
		$this->show_header_selector( 'default' );
628
	?>
629
</td>
630
</tr>
631
	<?php endif;
632 View Code Duplication
	if ( get_header_image() ) : ?>
633
<tr>
634
<th scope="row"><?php _e( 'Remove Image' ); ?></th>
635
<td>
636
	<p><?php _e( 'This will remove the header image. You will not be able to restore any customizations.' ) ?></p>
637
	<?php submit_button( __( 'Remove Header Image' ), '', 'removeheader', false ); ?>
638
</td>
639
</tr>
640
	<?php endif;
641
642
	$default_image = sprintf( get_theme_support( 'custom-header', 'default-image' ), get_template_directory_uri(), get_stylesheet_directory_uri() );
643 View Code Duplication
	if ( $default_image && get_header_image() != $default_image ) : ?>
644
<tr>
645
<th scope="row"><?php _e( 'Reset Image' ); ?></th>
646
<td>
647
	<p><?php _e( 'This will restore the original header image. You will not be able to restore any customizations.' ) ?></p>
648
	<?php submit_button( __( 'Restore Original Header Image' ), '', 'resetheader', false ); ?>
649
</td>
650
</tr>
651
	<?php endif; ?>
652
</tbody>
653
</table>
654
655
<?php if ( current_theme_supports( 'custom-header', 'header-text' ) ) : ?>
656
657
<h3><?php _e( 'Header Text' ); ?></h3>
658
659
<table class="form-table">
660
<tbody>
661
<tr>
662
<th scope="row"><?php _e( 'Header Text' ); ?></th>
663
<td>
664
	<p>
665
	<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>
666
	</p>
667
</td>
668
</tr>
669
670
<tr class="displaying-header-text">
671
<th scope="row"><?php _e( 'Text Color' ); ?></th>
672
<td>
673
	<p>
674
	<?php
675
	$default_color = '';
676 View Code Duplication
	if ( current_theme_supports( 'custom-header', 'default-text-color' ) ) {
677
		$default_color = get_theme_support( 'custom-header', 'default-text-color' );
678
		if ( $default_color && false === strpos( $default_color, '#' ) ) {
679
			$default_color = '#' . $default_color;
680
		}
681
	}
682
683
	$default_color_attr = $default_color ? ' data-default-color="' . esc_attr( $default_color ) . '"' : '';
684
685
	$header_textcolor = display_header_text() ? get_header_textcolor() : get_theme_support( 'custom-header', 'default-text-color' );
686
	if ( $header_textcolor && false === strpos( $header_textcolor, '#' ) ) {
687
		$header_textcolor = '#' . $header_textcolor;
688
	}
689
690
	echo '<input type="text" name="text-color" id="text-color" value="' . esc_attr( $header_textcolor ) . '"' . $default_color_attr . ' />';
691
	if ( $default_color ) {
692
		echo ' <span class="description hide-if-js">' . sprintf( _x( 'Default: %s', 'color' ), esc_html( $default_color ) ) . '</span>';
693
	}
694
	?>
695
	</p>
696
</td>
697
</tr>
698
</tbody>
699
</table>
700
<?php endif;
701
702
/**
703
 * Fires just before the submit button in the custom header options form.
704
 *
705
 * @since 3.1.0
706
 */
707
do_action( 'custom_header_options' );
708
709
wp_nonce_field( 'custom-header-options', '_wpnonce-custom-header-options' ); ?>
710
711
<?php submit_button( null, 'primary', 'save-header-options' ); ?>
712
</form>
713
</div>
714
715
<?php }
716
717
	/**
718
	 * Display second step of custom header image page.
719
	 *
720
	 * @since 2.1.0
721
	 */
722
	public function step_2() {
723
		check_admin_referer('custom-header-upload', '_wpnonce-custom-header-upload');
724 View Code Duplication
		if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
725
			wp_die(
726
				'<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
727
				'<p>' . __( 'The current theme does not support uploading a custom header image.' ) . '</p>',
728
				403
729
			);
730
		}
731
732
		if ( empty( $_POST ) && isset( $_GET['file'] ) ) {
733
			$attachment_id = absint( $_GET['file'] );
734
			$file = get_attached_file( $attachment_id, true );
735
			$url = wp_get_attachment_image_src( $attachment_id, 'full' );
736
			$url = $url[0];
737
		} elseif ( isset( $_POST ) ) {
738
			$data = $this->step_2_manage_upload();
739
			$attachment_id = $data['attachment_id'];
740
			$file = $data['file'];
741
			$url = $data['url'];
742
		}
743
744
		if ( file_exists( $file ) ) {
745
			list( $width, $height, $type, $attr ) = getimagesize( $file );
0 ignored issues
show
The variable $file does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
746
		} else {
747
			$data = wp_get_attachment_metadata( $attachment_id );
0 ignored issues
show
The variable $attachment_id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
748
			$height = isset( $data[ 'height' ] ) ? $data[ 'height' ] : 0;
749
			$width = isset( $data[ 'width' ] ) ? $data[ 'width' ] : 0;
750
			unset( $data );
751
		}
752
753
		$max_width = 0;
754
		// For flex, limit size of image displayed to 1500px unless theme says otherwise
755
		if ( current_theme_supports( 'custom-header', 'flex-width' ) )
756
			$max_width = 1500;
757
758
		if ( current_theme_supports( 'custom-header', 'max-width' ) )
759
			$max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
760
		$max_width = max( $max_width, get_theme_support( 'custom-header', 'width' ) );
761
762
		// If flexible height isn't supported and the image is the exact right size
763
		if ( ! current_theme_supports( 'custom-header', 'flex-height' ) && ! current_theme_supports( 'custom-header', 'flex-width' )
764
			&& $width == get_theme_support( 'custom-header', 'width' ) && $height == get_theme_support( 'custom-header', 'height' ) )
765
		{
766
			// Add the meta-data
767
			if ( file_exists( $file ) )
768
				wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
769
770
			$this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
771
772
			/**
773
			 * Fires after the header image is set or an error is returned.
774
			 *
775
			 * @since 2.1.0
776
			 *
777
			 * @param string $file          Path to the file.
778
			 * @param int    $attachment_id Attachment ID.
779
			 */
780
			do_action( 'wp_create_file_in_uploads', $file, $attachment_id ); // For replication
781
782
			return $this->finished();
783
		} elseif ( $width > $max_width ) {
784
			$oitar = $width / $max_width;
785
			$image = wp_crop_image($attachment_id, 0, 0, $width, $height, $max_width, $height / $oitar, false, str_replace(basename($file), 'midsize-'.basename($file), $file));
786
			if ( ! $image || is_wp_error( $image ) )
787
				wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
788
789
			/** This filter is documented in wp-admin/custom-header.php */
790
			$image = apply_filters( 'wp_create_file_in_uploads', $image, $attachment_id ); // For replication
791
792
			$url = str_replace(basename($url), basename($image), $url);
0 ignored issues
show
The variable $url does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
793
			$width = $width / $oitar;
794
			$height = $height / $oitar;
795
		} else {
796
			$oitar = 1;
797
		}
798
		?>
799
800
<div class="wrap">
801
<h1><?php _e( 'Crop Header Image' ); ?></h1>
802
803
<form method="post" action="<?php echo esc_url(add_query_arg('step', 3)); ?>">
804
	<p class="hide-if-no-js"><?php _e('Choose the part of the image you want to use as your header.'); ?></p>
805
	<p class="hide-if-js"><strong><?php _e( 'You need JavaScript to choose a part of the image.'); ?></strong></p>
806
807
	<div id="crop_image" style="position: relative">
808
		<img src="<?php echo esc_url( $url ); ?>" id="upload" width="<?php echo $width; ?>" height="<?php echo $height; ?>" alt="" />
809
	</div>
810
811
	<input type="hidden" name="x1" id="x1" value="0"/>
812
	<input type="hidden" name="y1" id="y1" value="0"/>
813
	<input type="hidden" name="width" id="width" value="<?php echo esc_attr( $width ); ?>"/>
814
	<input type="hidden" name="height" id="height" value="<?php echo esc_attr( $height ); ?>"/>
815
	<input type="hidden" name="attachment_id" id="attachment_id" value="<?php echo esc_attr( $attachment_id ); ?>" />
816
	<input type="hidden" name="oitar" id="oitar" value="<?php echo esc_attr( $oitar ); ?>" />
817
	<?php if ( empty( $_POST ) && isset( $_GET['file'] ) ) { ?>
818
	<input type="hidden" name="create-new-attachment" value="true" />
819
	<?php } ?>
820
	<?php wp_nonce_field( 'custom-header-crop-image' ) ?>
821
822
	<p class="submit">
823
	<?php submit_button( __( 'Crop and Publish' ), 'primary', 'submit', false ); ?>
824
	<?php
825
	if ( isset( $oitar ) && 1 == $oitar && ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) )
826
		submit_button( __( 'Skip Cropping, Publish Image as Is' ), '', 'skip-cropping', false );
827
	?>
828
	</p>
829
</form>
830
</div>
831
		<?php
832
	}
833
834
835
	/**
836
	 * Upload the file to be cropped in the second step.
837
	 *
838
	 * @since 3.4.0
839
	 */
840
	public function step_2_manage_upload() {
841
		$overrides = array('test_form' => false);
842
843
		$uploaded_file = $_FILES['import'];
844
		$wp_filetype = wp_check_filetype_and_ext( $uploaded_file['tmp_name'], $uploaded_file['name'] );
845
		if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) )
846
			wp_die( __( 'The uploaded file is not a valid image. Please try again.' ) );
847
848
		$file = wp_handle_upload($uploaded_file, $overrides);
849
850
		if ( isset($file['error']) )
851
			wp_die( $file['error'],  __( 'Image Upload Error' ) );
852
853
		$url = $file['url'];
854
		$type = $file['type'];
855
		$file = $file['file'];
856
		$filename = basename($file);
857
858
		// Construct the object array
859
		$object = array(
860
			'post_title'     => $filename,
861
			'post_content'   => $url,
862
			'post_mime_type' => $type,
863
			'guid'           => $url,
864
			'context'        => 'custom-header'
865
		);
866
867
		// Save the data
868
		$attachment_id = wp_insert_attachment( $object, $file );
869
		return compact( 'attachment_id', 'file', 'filename', 'url', 'type' );
870
	}
871
872
	/**
873
	 * Display third step of custom header image page.
874
	 *
875
	 * @since 2.1.0
876
	 * @since 4.4.0 Switched to using wp_get_attachment_url() instead of the guid
877
	 *              for retrieving the header image URL.
878
	 */
879
	public function step_3() {
880
		check_admin_referer( 'custom-header-crop-image' );
881
882 View Code Duplication
		if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
883
			wp_die(
884
				'<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
885
				'<p>' . __( 'The current theme does not support uploading a custom header image.' ) . '</p>',
886
				403
887
			);
888
		}
889
890 View Code Duplication
		if ( ! empty( $_POST['skip-cropping'] ) && ! ( current_theme_supports( 'custom-header', 'flex-height' ) || current_theme_supports( 'custom-header', 'flex-width' ) ) ) {
891
			wp_die(
892
				'<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
893
				'<p>' . __( 'The current theme does not support a flexible sized header image.' ) . '</p>',
894
				403
895
			);
896
		}
897
898
		if ( $_POST['oitar'] > 1 ) {
899
			$_POST['x1'] = $_POST['x1'] * $_POST['oitar'];
900
			$_POST['y1'] = $_POST['y1'] * $_POST['oitar'];
901
			$_POST['width'] = $_POST['width'] * $_POST['oitar'];
902
			$_POST['height'] = $_POST['height'] * $_POST['oitar'];
903
		}
904
905
		$attachment_id = absint( $_POST['attachment_id'] );
906
		$original = get_attached_file($attachment_id);
907
908
		$dimensions = $this->get_header_dimensions( array(
909
			'height' => $_POST['height'],
910
			'width'  => $_POST['width'],
911
		) );
912
		$height = $dimensions['dst_height'];
913
		$width = $dimensions['dst_width'];
914
915
		if ( empty( $_POST['skip-cropping'] ) )
916
			$cropped = wp_crop_image( $attachment_id, (int) $_POST['x1'], (int) $_POST['y1'], (int) $_POST['width'], (int) $_POST['height'], $width, $height );
917
		elseif ( ! empty( $_POST['create-new-attachment'] ) )
918
			$cropped = _copy_image_file( $attachment_id );
919
		else
920
			$cropped = get_attached_file( $attachment_id );
921
922
		if ( ! $cropped || is_wp_error( $cropped ) )
923
			wp_die( __( 'Image could not be processed. Please go back and try again.' ), __( 'Image Processing Error' ) );
924
925
		/** This filter is documented in wp-admin/custom-header.php */
926
		$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
927
928
		$object = $this->create_attachment_object( $cropped, $attachment_id );
929
930
		if ( ! empty( $_POST['create-new-attachment'] ) )
931
			unset( $object['ID'] );
932
933
		// Update the attachment
934
		$attachment_id = $this->insert_attachment( $object, $cropped );
935
936
		$url = wp_get_attachment_url( $attachment_id );
937
		$this->set_header_image( compact( 'url', 'attachment_id', 'width', 'height' ) );
938
939
		// Cleanup.
940
		$medium = str_replace( basename( $original ), 'midsize-' . basename( $original ), $original );
941
		if ( file_exists( $medium ) ) {
942
			wp_delete_file( $medium );
943
		}
944
945
		if ( empty( $_POST['create-new-attachment'] ) && empty( $_POST['skip-cropping'] ) ) {
946
			wp_delete_file( $original );
0 ignored issues
show
It seems like $original defined by get_attached_file($attachment_id) on line 906 can also be of type false; however, wp_delete_file() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
947
		}
948
949
		return $this->finished();
950
	}
951
952
	/**
953
	 * Display last step of custom header image page.
954
	 *
955
	 * @since 2.1.0
956
	 */
957
	public function finished() {
958
		$this->updated = true;
959
		$this->step_1();
960
	}
961
962
	/**
963
	 * Display the page based on the current step.
964
	 *
965
	 * @since 2.1.0
966
	 */
967
	public function admin_page() {
968
		if ( ! current_user_can('edit_theme_options') )
969
			wp_die(__('Sorry, you are not allowed to customize headers.'));
970
		$step = $this->step();
971
		if ( 2 == $step )
972
			$this->step_2();
973
		elseif ( 3 == $step )
974
			$this->step_3();
975
		else
976
			$this->step_1();
977
	}
978
979
	/**
980
	 * Unused since 3.5.0.
981
	 *
982
	 * @since 3.4.0
983
	 *
984
	 * @param array $form_fields
985
	 * @return array $form_fields
986
	 */
987
	public function attachment_fields_to_edit( $form_fields ) {
988
		return $form_fields;
989
	}
990
991
	/**
992
	 * Unused since 3.5.0.
993
	 *
994
	 * @since 3.4.0
995
	 *
996
	 * @param array $tabs
997
	 * @return array $tabs
998
	 */
999
	public function filter_upload_tabs( $tabs ) {
1000
		return $tabs;
1001
	}
1002
1003
	/**
1004
	 * Choose a header image, selected from existing uploaded and default headers,
1005
	 * or provide an array of uploaded header data (either new, or from media library).
1006
	 *
1007
	 * @since 3.4.0
1008
	 *
1009
	 * @param mixed $choice Which header image to select. Allows for values of 'random-default-image',
1010
	 * 	for randomly cycling among the default images; 'random-uploaded-image', for randomly cycling
1011
	 * 	among the uploaded images; the key of a default image registered for that theme; and
1012
	 * 	the key of an image uploaded for that theme (the attachment ID of the image).
1013
	 *  Or an array of arguments: attachment_id, url, width, height. All are required.
1014
	 */
1015
	final public function set_header_image( $choice ) {
1016
		if ( is_array( $choice ) || is_object( $choice ) ) {
1017
			$choice = (array) $choice;
1018
			if ( ! isset( $choice['attachment_id'] ) || ! isset( $choice['url'] ) )
1019
				return;
1020
1021
			$choice['url'] = esc_url_raw( $choice['url'] );
1022
1023
			$header_image_data = (object) array(
1024
				'attachment_id' => $choice['attachment_id'],
1025
				'url'           => $choice['url'],
1026
				'thumbnail_url' => $choice['url'],
1027
				'height'        => $choice['height'],
1028
				'width'         => $choice['width'],
1029
			);
1030
1031
			update_post_meta( $choice['attachment_id'], '_wp_attachment_is_custom_header', get_stylesheet() );
1032
			set_theme_mod( 'header_image', $choice['url'] );
1033
			set_theme_mod( 'header_image_data', $header_image_data );
1034
			return;
1035
		}
1036
1037
		if ( in_array( $choice, array( 'remove-header', 'random-default-image', 'random-uploaded-image' ) ) ) {
1038
			set_theme_mod( 'header_image', $choice );
1039
			remove_theme_mod( 'header_image_data' );
1040
			return;
1041
		}
1042
1043
		$uploaded = get_uploaded_header_images();
1044
		if ( $uploaded && isset( $uploaded[ $choice ] ) ) {
1045
			$header_image_data = $uploaded[ $choice ];
1046
1047
		} else {
1048
			$this->process_default_headers();
1049
			if ( isset( $this->default_headers[ $choice ] ) )
1050
				$header_image_data = $this->default_headers[ $choice ];
1051
			else
1052
				return;
1053
		}
1054
1055
		set_theme_mod( 'header_image', esc_url_raw( $header_image_data['url'] ) );
1056
		set_theme_mod( 'header_image_data', $header_image_data );
1057
	}
1058
1059
	/**
1060
	 * Remove a header image.
1061
	 *
1062
	 * @since 3.4.0
1063
	 */
1064
	final public function remove_header_image() {
1065
		$this->set_header_image( 'remove-header' );
1066
	}
1067
1068
	/**
1069
	 * Reset a header image to the default image for the theme.
1070
	 *
1071
	 * This method does not do anything if the theme does not have a default header image.
1072
	 *
1073
	 * @since 3.4.0
1074
	 */
1075
	final public function reset_header_image() {
1076
		$this->process_default_headers();
1077
		$default = get_theme_support( 'custom-header', 'default-image' );
1078
1079
		if ( ! $default ) {
1080
			$this->remove_header_image();
1081
			return;
1082
		}
1083
		$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1084
1085
		$default_data = array();
1086
		foreach ( $this->default_headers as $header => $details ) {
1087
			if ( $details['url'] == $default ) {
1088
				$default_data = $details;
1089
				break;
1090
			}
1091
		}
1092
1093
		set_theme_mod( 'header_image', $default );
1094
		set_theme_mod( 'header_image_data', (object) $default_data );
1095
	}
1096
1097
	/**
1098
	 * Calculate width and height based on what the currently selected theme supports.
1099
	 *
1100
	 * @since 3.9.0
1101
	 *
1102
	 * @param array $dimensions
1103
	 * @return array dst_height and dst_width of header image.
1104
	 */
1105
	final public function get_header_dimensions( $dimensions ) {
1106
		$max_width = 0;
1107
		$width = absint( $dimensions['width'] );
1108
		$height = absint( $dimensions['height'] );
1109
		$theme_height = get_theme_support( 'custom-header', 'height' );
1110
		$theme_width = get_theme_support( 'custom-header', 'width' );
1111
		$has_flex_width = current_theme_supports( 'custom-header', 'flex-width' );
1112
		$has_flex_height = current_theme_supports( 'custom-header', 'flex-height' );
1113
		$has_max_width = current_theme_supports( 'custom-header', 'max-width' ) ;
1114
		$dst = array( 'dst_height' => null, 'dst_width' => null );
1115
1116
		// For flex, limit size of image displayed to 1500px unless theme says otherwise
1117
		if ( $has_flex_width ) {
1118
			$max_width = 1500;
1119
		}
1120
1121
		if ( $has_max_width ) {
1122
			$max_width = max( $max_width, get_theme_support( 'custom-header', 'max-width' ) );
1123
		}
1124
		$max_width = max( $max_width, $theme_width );
1125
1126 View Code Duplication
		if ( $has_flex_height && ( ! $has_flex_width || $width > $max_width ) ) {
1127
			$dst['dst_height'] = absint( $height * ( $max_width / $width ) );
1128
		}
1129
		elseif ( $has_flex_height && $has_flex_width ) {
1130
			$dst['dst_height'] = $height;
1131
		}
1132
		else {
1133
			$dst['dst_height'] = $theme_height;
1134
		}
1135
1136 View Code Duplication
		if ( $has_flex_width && ( ! $has_flex_height || $width > $max_width ) ) {
1137
			$dst['dst_width'] = absint( $width * ( $max_width / $width ) );
1138
		}
1139
		elseif ( $has_flex_width && $has_flex_height ) {
1140
			$dst['dst_width'] = $width;
1141
		}
1142
		else {
1143
			$dst['dst_width'] = $theme_width;
1144
		}
1145
1146
		return $dst;
1147
	}
1148
1149
	/**
1150
	 * Create an attachment 'object'.
1151
	 *
1152
	 * @since 3.9.0
1153
	 *
1154
	 * @param string $cropped              Cropped image URL.
1155
	 * @param int    $parent_attachment_id Attachment ID of parent image.
1156
	 * @return array Attachment object.
1157
	 */
1158 View Code Duplication
	final public function create_attachment_object( $cropped, $parent_attachment_id ) {
1159
		$parent = get_post( $parent_attachment_id );
1160
		$parent_url = wp_get_attachment_url( $parent->ID );
1161
		$url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
1162
1163
		$size = @getimagesize( $cropped );
1164
		$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
1165
1166
		$object = array(
1167
			'ID' => $parent_attachment_id,
1168
			'post_title' => basename($cropped),
1169
			'post_mime_type' => $image_type,
1170
			'guid' => $url,
1171
			'context' => 'custom-header'
1172
		);
1173
1174
		return $object;
1175
	}
1176
1177
	/**
1178
	 * Insert an attachment and its metadata.
1179
	 *
1180
	 * @since 3.9.0
1181
	 *
1182
	 * @param array  $object  Attachment object.
1183
	 * @param string $cropped Cropped image URL.
1184
	 * @return int Attachment ID.
1185
	 */
1186 View Code Duplication
	final public function insert_attachment( $object, $cropped ) {
1187
		$attachment_id = wp_insert_attachment( $object, $cropped );
1188
		$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
1189
		/**
1190
		 * Filters the header image attachment metadata.
1191
		 *
1192
		 * @since 3.9.0
1193
		 *
1194
		 * @see wp_generate_attachment_metadata()
1195
		 *
1196
		 * @param array $metadata Attachment metadata.
1197
		 */
1198
		$metadata = apply_filters( 'wp_header_image_attachment_metadata', $metadata );
1199
		wp_update_attachment_metadata( $attachment_id, $metadata );
1200
		return $attachment_id;
1201
	}
1202
1203
	/**
1204
	 * Gets attachment uploaded by Media Manager, crops it, then saves it as a
1205
	 * new object. Returns JSON-encoded object details.
1206
	 *
1207
	 * @since 3.9.0
1208
	 */
1209
	public function ajax_header_crop() {
1210
		check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
1211
1212
		if ( ! current_user_can( 'edit_theme_options' ) ) {
1213
			wp_send_json_error();
1214
		}
1215
1216
		if ( ! current_theme_supports( 'custom-header', 'uploads' ) ) {
1217
			wp_send_json_error();
1218
		}
1219
1220
		$crop_details = $_POST['cropDetails'];
1221
1222
		$dimensions = $this->get_header_dimensions( array(
1223
			'height' => $crop_details['height'],
1224
			'width'  => $crop_details['width'],
1225
		) );
1226
1227
		$attachment_id = absint( $_POST['id'] );
1228
1229
		$cropped = wp_crop_image(
1230
			$attachment_id,
1231
			(int) $crop_details['x1'],
1232
			(int) $crop_details['y1'],
1233
			(int) $crop_details['width'],
1234
			(int) $crop_details['height'],
1235
			(int) $dimensions['dst_width'],
1236
			(int) $dimensions['dst_height']
1237
		);
1238
1239
		if ( ! $cropped || is_wp_error( $cropped ) ) {
1240
			wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
1241
		}
1242
1243
		/** This filter is documented in wp-admin/custom-header.php */
1244
		$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
1245
1246
		$object = $this->create_attachment_object( $cropped, $attachment_id );
1247
1248
		unset( $object['ID'] );
1249
1250
		$new_attachment_id = $this->insert_attachment( $object, $cropped );
1251
1252
		$object['attachment_id'] = $new_attachment_id;
1253
		$object['url']           = wp_get_attachment_url( $new_attachment_id );;
1254
		$object['width']         = $dimensions['dst_width'];
1255
		$object['height']        = $dimensions['dst_height'];
1256
1257
		wp_send_json_success( $object );
1258
	}
1259
1260
	/**
1261
	 * Given an attachment ID for a header image, updates its "last used"
1262
	 * timestamp to now.
1263
	 *
1264
	 * Triggered when the user tries adds a new header image from the
1265
	 * Media Manager, even if s/he doesn't save that change.
1266
	 *
1267
	 * @since 3.9.0
1268
	 */
1269 View Code Duplication
	public function ajax_header_add() {
1270
		check_ajax_referer( 'header-add', 'nonce' );
1271
1272
		if ( ! current_user_can( 'edit_theme_options' ) ) {
1273
			wp_send_json_error();
1274
		}
1275
1276
		$attachment_id = absint( $_POST['attachment_id'] );
1277
		if ( $attachment_id < 1 ) {
1278
			wp_send_json_error();
1279
		}
1280
1281
		$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1282
		update_post_meta( $attachment_id, $key, time() );
1283
		update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
1284
1285
		wp_send_json_success();
1286
	}
1287
1288
	/**
1289
	 * Given an attachment ID for a header image, unsets it as a user-uploaded
1290
	 * header image for the current theme.
1291
	 *
1292
	 * Triggered when the user clicks the overlay "X" button next to each image
1293
	 * choice in the Customizer's Header tool.
1294
	 *
1295
	 * @since 3.9.0
1296
	 */
1297 View Code Duplication
	public function ajax_header_remove() {
1298
		check_ajax_referer( 'header-remove', 'nonce' );
1299
1300
		if ( ! current_user_can( 'edit_theme_options' ) ) {
1301
			wp_send_json_error();
1302
		}
1303
1304
		$attachment_id = absint( $_POST['attachment_id'] );
1305
		if ( $attachment_id < 1 ) {
1306
			wp_send_json_error();
1307
		}
1308
1309
		$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1310
		delete_post_meta( $attachment_id, $key );
1311
		delete_post_meta( $attachment_id, '_wp_attachment_is_custom_header', get_stylesheet() );
1312
1313
		wp_send_json_success();
1314
	}
1315
1316
	/**
1317
	 * Updates the last-used postmeta on a header image attachment after saving a new header image via the Customizer.
1318
	 *
1319
	 * @since 3.9.0
1320
	 *
1321
	 * @param WP_Customize_Manager $wp_customize Customize manager.
1322
	 */
1323
	public function customize_set_last_used( $wp_customize ) {
1324
		$data = $wp_customize->get_setting( 'header_image_data' )->post_value();
1325
1326
		if ( ! isset( $data['attachment_id'] ) ) {
1327
			return;
1328
		}
1329
1330
		$attachment_id = $data['attachment_id'];
1331
		$key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1332
		update_post_meta( $attachment_id, $key, time() );
1333
	}
1334
1335
	/**
1336
	 * Gets the details of default header images if defined.
1337
	 *
1338
	 * @since 3.9.0
1339
	 *
1340
	 * @return array Default header images.
1341
	 */
1342
	public function get_default_header_images() {
1343
		$this->process_default_headers();
1344
1345
		// Get the default image if there is one.
1346
		$default = get_theme_support( 'custom-header', 'default-image' );
1347
1348
		if ( ! $default ) { // If not,
1349
			return $this->default_headers; // easy peasy.
1350
		}
1351
1352
		$default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1353
		$already_has_default = false;
1354
1355
		foreach ( $this->default_headers as $k => $h ) {
1356
			if ( $h['url'] === $default ) {
1357
				$already_has_default = true;
1358
				break;
1359
			}
1360
		}
1361
1362
		if ( $already_has_default ) {
1363
			return $this->default_headers;
1364
		}
1365
1366
		// If the one true image isn't included in the default set, prepend it.
1367
		$header_images = array();
1368
		$header_images['default'] = array(
1369
			'url'           => $default,
1370
			'thumbnail_url' => $default,
1371
			'description'   => 'Default'
1372
		);
1373
1374
		// The rest of the set comes after.
1375
		return array_merge( $header_images, $this->default_headers );
1376
	}
1377
1378
	/**
1379
	 * Gets the previously uploaded header images.
1380
	 *
1381
	 * @since 3.9.0
1382
	 *
1383
	 * @return array Uploaded header images.
1384
	 */
1385
	public function get_uploaded_header_images() {
1386
		$header_images = get_uploaded_header_images();
1387
		$timestamp_key = '_wp_attachment_custom_header_last_used_' . get_stylesheet();
1388
		$alt_text_key = '_wp_attachment_image_alt';
1389
1390
		foreach ( $header_images as &$header_image ) {
1391
			$header_meta = get_post_meta( $header_image['attachment_id'] );
1392
			$header_image['timestamp'] = isset( $header_meta[ $timestamp_key ] ) ? $header_meta[ $timestamp_key ] : '';
1393
			$header_image['alt_text'] = isset( $header_meta[ $alt_text_key ] ) ? $header_meta[ $alt_text_key ] : '';
1394
		}
1395
1396
		return $header_images;
1397
	}
1398
}
1399