ajax-actions.php ➔ wp_ajax_install_plugin()   F
last analyzed

Complexity

Conditions 20
Paths 1536

Size

Total Lines 91
Code Lines 58

Duplication

Lines 31
Ratio 34.07 %

Importance

Changes 0
Metric Value
cc 20
eloc 58
nc 1536
nop 0
dl 31
loc 91
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Administration API: Core Ajax handlers
4
 *
5
 * @package WordPress
6
 * @subpackage Administration
7
 * @since 2.1.0
8
 */
9
10
//
11
// No-privilege Ajax handlers.
12
//
13
14
/**
15
 * Ajax handler for the Heartbeat API in
16
 * the no-privilege context.
17
 *
18
 * Runs when the user is not logged in.
19
 *
20
 * @since 3.6.0
21
 */
22
function wp_ajax_nopriv_heartbeat() {
23
	$response = array();
24
25
	// screen_id is the same as $current_screen->id and the JS global 'pagenow'.
26 View Code Duplication
	if ( ! empty($_POST['screen_id']) )
27
		$screen_id = sanitize_key($_POST['screen_id']);
28
	else
29
		$screen_id = 'front';
30
31
	if ( ! empty($_POST['data']) ) {
32
		$data = wp_unslash( (array) $_POST['data'] );
33
34
		/**
35
		 * Filters Heartbeat Ajax response in no-privilege environments.
36
		 *
37
		 * @since 3.6.0
38
		 *
39
		 * @param array|object $response  The no-priv Heartbeat response object or array.
40
		 * @param array        $data      An array of data passed via $_POST.
41
		 * @param string       $screen_id The screen id.
42
		 */
43
		$response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id );
44
	}
45
46
	/**
47
	 * Filters Heartbeat Ajax response when no data is passed.
48
	 *
49
	 * @since 3.6.0
50
	 *
51
	 * @param array|object $response  The Heartbeat response object or array.
52
	 * @param string       $screen_id The screen id.
53
	 */
54
	$response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id );
55
56
	/**
57
	 * Fires when Heartbeat ticks in no-privilege environments.
58
	 *
59
	 * Allows the transport to be easily replaced with long-polling.
60
	 *
61
	 * @since 3.6.0
62
	 *
63
	 * @param array|object $response  The no-priv Heartbeat response.
64
	 * @param string       $screen_id The screen id.
65
	 */
66
	do_action( 'heartbeat_nopriv_tick', $response, $screen_id );
67
68
	// Send the current time according to the server.
69
	$response['server_time'] = time();
70
71
	wp_send_json($response);
72
}
73
74
//
75
// GET-based Ajax handlers.
76
//
77
78
/**
79
 * Ajax handler for fetching a list table.
80
 *
81
 * @since 3.1.0
82
 */
83
function wp_ajax_fetch_list() {
84
	$list_class = $_GET['list_args']['class'];
85
	check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
86
87
	$wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
88
	if ( ! $wp_list_table ) {
89
		wp_die( 0 );
90
	}
91
92
	if ( ! $wp_list_table->ajax_user_can() ) {
93
		wp_die( -1 );
94
	}
95
96
	$wp_list_table->ajax_response();
97
98
	wp_die( 0 );
99
}
100
101
/**
102
 * Ajax handler for tag search.
103
 *
104
 * @since 3.1.0
105
 */
106
function wp_ajax_ajax_tag_search() {
107
	if ( ! isset( $_GET['tax'] ) ) {
108
		wp_die( 0 );
109
	}
110
111
	$taxonomy = sanitize_key( $_GET['tax'] );
112
	$tax = get_taxonomy( $taxonomy );
113
	if ( ! $tax ) {
114
		wp_die( 0 );
115
	}
116
117
	if ( ! current_user_can( $tax->cap->assign_terms ) ) {
118
		wp_die( -1 );
119
	}
120
121
	$s = wp_unslash( $_GET['q'] );
122
123
	$comma = _x( ',', 'tag delimiter' );
124
	if ( ',' !== $comma )
125
		$s = str_replace( $comma, ',', $s );
126 View Code Duplication
	if ( false !== strpos( $s, ',' ) ) {
127
		$s = explode( ',', $s );
128
		$s = $s[count( $s ) - 1];
129
	}
130
	$s = trim( $s );
131
132
	/**
133
	 * Filters the minimum number of characters required to fire a tag search via Ajax.
134
	 *
135
	 * @since 4.0.0
136
	 *
137
	 * @param int         $characters The minimum number of characters required. Default 2.
138
	 * @param WP_Taxonomy $tax        The taxonomy object.
139
	 * @param string      $s          The search term.
140
	 */
141
	$term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
142
143
	/*
144
	 * Require $term_search_min_chars chars for matching (default: 2)
145
	 * ensure it's a non-negative, non-zero integer.
146
	 */
147
	if ( ( $term_search_min_chars == 0 ) || ( strlen( $s ) < $term_search_min_chars ) ){
148
		wp_die();
149
	}
150
151
	$results = get_terms( $taxonomy, array( 'name__like' => $s, 'fields' => 'names', 'hide_empty' => false ) );
152
153
	echo join( $results, "\n" );
154
	wp_die();
155
}
156
157
/**
158
 * Ajax handler for compression testing.
159
 *
160
 * @since 3.1.0
161
 */
162
function wp_ajax_wp_compression_test() {
163
	if ( !current_user_can( 'manage_options' ) )
164
		wp_die( -1 );
165
166
	if ( ini_get('zlib.output_compression') || 'ob_gzhandler' == ini_get('output_handler') ) {
167
		update_site_option('can_compress_scripts', 0);
168
		wp_die( 0 );
169
	}
170
171
	if ( isset($_GET['test']) ) {
172
		header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
173
		header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
174
		header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
175
		header('Content-Type: application/javascript; charset=UTF-8');
176
		$force_gzip = ( defined('ENFORCE_GZIP') && ENFORCE_GZIP );
177
		$test_str = '"wpCompressionTest Lorem ipsum dolor sit amet consectetuer mollis sapien urna ut a. Eu nonummy condimentum fringilla tempor pretium platea vel nibh netus Maecenas. Hac molestie amet justo quis pellentesque est ultrices interdum nibh Morbi. Cras mattis pretium Phasellus ante ipsum ipsum ut sociis Suspendisse Lorem. Ante et non molestie. Porta urna Vestibulum egestas id congue nibh eu risus gravida sit. Ac augue auctor Ut et non a elit massa id sodales. Elit eu Nulla at nibh adipiscing mattis lacus mauris at tempus. Netus nibh quis suscipit nec feugiat eget sed lorem et urna. Pellentesque lacus at ut massa consectetuer ligula ut auctor semper Pellentesque. Ut metus massa nibh quam Curabitur molestie nec mauris congue. Volutpat molestie elit justo facilisis neque ac risus Ut nascetur tristique. Vitae sit lorem tellus et quis Phasellus lacus tincidunt nunc Fusce. Pharetra wisi Suspendisse mus sagittis libero lacinia Integer consequat ac Phasellus. Et urna ac cursus tortor aliquam Aliquam amet tellus volutpat Vestibulum. Justo interdum condimentum In augue congue tellus sollicitudin Quisque quis nibh."';
178
179
		 if ( 1 == $_GET['test'] ) {
180
		 	echo $test_str;
181
		 	wp_die();
182
		 } elseif ( 2 == $_GET['test'] ) {
183
			if ( !isset($_SERVER['HTTP_ACCEPT_ENCODING']) )
184
				wp_die( -1 );
185
			if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') && function_exists('gzdeflate') && ! $force_gzip ) {
186
				header('Content-Encoding: deflate');
187
				$out = gzdeflate( $test_str, 1 );
188
			} elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzencode') ) {
189
				header('Content-Encoding: gzip');
190
				$out = gzencode( $test_str, 1 );
191
			} else {
192
				wp_die( -1 );
193
			}
194
			echo $out;
0 ignored issues
show
Bug introduced by
The variable $out 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...
195
			wp_die();
196
		} elseif ( 'no' == $_GET['test'] ) {
197
			check_ajax_referer( 'update_can_compress_scripts' );
198
			update_site_option('can_compress_scripts', 0);
199
		} elseif ( 'yes' == $_GET['test'] ) {
200
			check_ajax_referer( 'update_can_compress_scripts' );
201
			update_site_option('can_compress_scripts', 1);
202
		}
203
	}
204
205
	wp_die( 0 );
206
}
207
208
/**
209
 * Ajax handler for image editor previews.
210
 *
211
 * @since 3.1.0
212
 */
213
function wp_ajax_imgedit_preview() {
214
	$post_id = intval($_GET['postid']);
215
	if ( empty($post_id) || !current_user_can('edit_post', $post_id) )
216
		wp_die( -1 );
217
218
	check_ajax_referer( "image_editor-$post_id" );
219
220
	include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
221
	if ( ! stream_preview_image($post_id) )
222
		wp_die( -1 );
223
224
	wp_die();
225
}
226
227
/**
228
 * Ajax handler for oEmbed caching.
229
 *
230
 * @since 3.1.0
231
 *
232
 * @global WP_Embed $wp_embed
233
 */
234
function wp_ajax_oembed_cache() {
235
	$GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
236
	wp_die( 0 );
237
}
238
239
/**
240
 * Ajax handler for user autocomplete.
241
 *
242
 * @since 3.4.0
243
 */
244
function wp_ajax_autocomplete_user() {
245
	if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) )
246
		wp_die( -1 );
247
248
	/** This filter is documented in wp-admin/user-new.php */
249
	if ( ! current_user_can( 'manage_network_users' ) && ! apply_filters( 'autocomplete_users_for_site_admins', false ) )
250
		wp_die( -1 );
251
252
	$return = array();
253
254
	// Check the type of request
255
	// Current allowed values are `add` and `search`
256
	if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
257
		$type = $_REQUEST['autocomplete_type'];
258
	} else {
259
		$type = 'add';
260
	}
261
262
	// Check the desired field for value
263
	// Current allowed values are `user_email` and `user_login`
264
	if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
265
		$field = $_REQUEST['autocomplete_field'];
266
	} else {
267
		$field = 'user_login';
268
	}
269
270
	// Exclude current users of this blog
271
	if ( isset( $_REQUEST['site_id'] ) ) {
272
		$id = absint( $_REQUEST['site_id'] );
273
	} else {
274
		$id = get_current_blog_id();
275
	}
276
277
	$include_blog_users = ( $type == 'search' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
278
	$exclude_blog_users = ( $type == 'add' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
279
280
	$users = get_users( array(
281
		'blog_id' => false,
282
		'search'  => '*' . $_REQUEST['term'] . '*',
283
		'include' => $include_blog_users,
284
		'exclude' => $exclude_blog_users,
285
		'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
286
	) );
287
288
	foreach ( $users as $user ) {
289
		$return[] = array(
290
			/* translators: 1: user_login, 2: user_email */
291
			'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
292
			'value' => $user->$field,
293
		);
294
	}
295
296
	wp_die( wp_json_encode( $return ) );
0 ignored issues
show
Security Bug introduced by
It seems like wp_json_encode($return) targeting wp_json_encode() can also be of type false; however, wp_die() does only seem to accept string|object<WP_Error>, did you maybe forget to handle an error condition?
Loading history...
297
}
298
299
/**
300
 * Handles AJAX requests for community events
301
 *
302
 * @since 4.8.0
303
 */
304
function wp_ajax_get_community_events() {
305
	require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
306
307
	check_ajax_referer( 'community_events' );
308
309
	$search         = isset( $_POST['location'] ) ? wp_unslash( $_POST['location'] ) : '';
310
	$timezone       = isset( $_POST['timezone'] ) ? wp_unslash( $_POST['timezone'] ) : '';
311
	$user_id        = get_current_user_id();
312
	$saved_location = get_user_option( 'community-events-location', $user_id );
313
	$events_client  = new WP_Community_Events( $user_id, $saved_location );
314
	$events         = $events_client->get_events( $search, $timezone );
315
	$ip_changed     = false;
316
317
	if ( is_wp_error( $events ) ) {
318
		wp_send_json_error( array(
319
			'error' => $events->get_error_message(),
320
		) );
321
	} else {
322
		if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
323
			$ip_changed = true;
324
		} elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
325
			$ip_changed = true;
326
		}
327
328
		/*
329
		 * The location should only be updated when it changes. The API doesn't always return
330
		 * a full location; sometimes it's missing the description or country. The location
331
		 * that was saved during the initial request is known to be good and complete, though.
332
		 * It should be left in tact until the user explicitly changes it (either by manually
333
		 * searching for a new location, or by changing their IP address).
334
		 *
335
		 * If the location were updated with an incomplete response from the API, then it could
336
		 * break assumptions that the UI makes (e.g., that there will always be a description
337
		 * that corresponds to a latitude/longitude location).
338
		 *
339
		 * The location is stored network-wide, so that the user doesn't have to set it on each site.
340
		 */
341
		if ( $ip_changed || $search ) {
342
			update_user_option( $user_id, 'community-events-location', $events['location'], true );
343
		}
344
345
		wp_send_json_success( $events );
346
	}
347
}
348
349
/**
350
 * Ajax handler for dashboard widgets.
351
 *
352
 * @since 3.4.0
353
 */
354
function wp_ajax_dashboard_widgets() {
355
	require_once ABSPATH . 'wp-admin/includes/dashboard.php';
356
357
	$pagenow = $_GET['pagenow'];
358
	if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
359
		set_current_screen( $pagenow );
360
	}
361
362
	switch ( $_GET['widget'] ) {
363
		case 'dashboard_primary' :
364
			wp_dashboard_primary();
365
			break;
366
	}
367
	wp_die();
368
}
369
370
/**
371
 * Ajax handler for Customizer preview logged-in status.
372
 *
373
 * @since 3.4.0
374
 */
375
function wp_ajax_logged_in() {
376
	wp_die( 1 );
377
}
378
379
//
380
// Ajax helpers.
381
//
382
383
/**
384
 * Sends back current comment total and new page links if they need to be updated.
385
 *
386
 * Contrary to normal success Ajax response ("1"), die with time() on success.
387
 *
388
 * @access private
389
 * @since 2.7.0
390
 *
391
 * @param int $comment_id
392
 * @param int $delta
393
 */
394
function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
395
	$total    = isset( $_POST['_total'] )    ? (int) $_POST['_total']    : 0;
396
	$per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
397
	$page     = isset( $_POST['_page'] )     ? (int) $_POST['_page']     : 0;
398
	$url      = isset( $_POST['_url'] )      ? esc_url_raw( $_POST['_url'] ) : '';
399
400
	// JS didn't send us everything we need to know. Just die with success message
401
	if ( ! $total || ! $per_page || ! $page || ! $url ) {
402
		$time           = time();
403
		$comment        = get_comment( $comment_id );
404
		$comment_status = '';
405
		$comment_link   = '';
406
407
		if ( $comment ) {
408
			$comment_status = $comment->comment_approved;
409
		}
410
411
		if ( 1 === (int) $comment_status ) {
412
			$comment_link = get_comment_link( $comment );
413
		}
414
415
		$counts = wp_count_comments();
416
417
		$x = new WP_Ajax_Response( array(
418
			'what' => 'comment',
419
			// Here for completeness - not used.
420
			'id' => $comment_id,
421
			'supplemental' => array(
422
				'status' => $comment_status,
423
				'postId' => $comment ? $comment->comment_post_ID : '',
424
				'time' => $time,
425
				'in_moderation' => $counts->moderated,
426
				'i18n_comments_text' => sprintf(
427
					_n( '%s Comment', '%s Comments', $counts->approved ),
428
					number_format_i18n( $counts->approved )
429
				),
430
				'i18n_moderation_text' => sprintf(
431
					_nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
432
					number_format_i18n( $counts->moderated )
433
				),
434
				'comment_link' => $comment_link,
435
			)
436
		) );
437
		$x->send();
438
	}
439
440
	$total += $delta;
441
	if ( $total < 0 )
442
		$total = 0;
443
444
	// Only do the expensive stuff on a page-break, and about 1 other time per page
445
	if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
446
		$post_id = 0;
447
		// What type of comment count are we looking for?
448
		$status = 'all';
449
		$parsed = parse_url( $url );
450
		if ( isset( $parsed['query'] ) ) {
451
			parse_str( $parsed['query'], $query_vars );
452
			if ( !empty( $query_vars['comment_status'] ) )
453
				$status = $query_vars['comment_status'];
454
			if ( !empty( $query_vars['p'] ) )
455
				$post_id = (int) $query_vars['p'];
456
			if ( ! empty( $query_vars['comment_type'] ) )
457
				$type = $query_vars['comment_type'];
458
		}
459
460
		if ( empty( $type ) ) {
461
			// Only use the comment count if not filtering by a comment_type.
462
			$comment_count = wp_count_comments($post_id);
463
464
			// We're looking for a known type of comment count.
465
			if ( isset( $comment_count->$status ) ) {
466
				$total = $comment_count->$status;
467
			}
468
		}
469
		// Else use the decremented value from above.
470
	}
471
472
	// The time since the last comment count.
473
	$time = time();
474
	$comment = get_comment( $comment_id );
475
476
	$x = new WP_Ajax_Response( array(
477
		'what' => 'comment',
478
		// Here for completeness - not used.
479
		'id' => $comment_id,
480
		'supplemental' => array(
481
			'status' => $comment ? $comment->comment_approved : '',
482
			'postId' => $comment ? $comment->comment_post_ID : '',
483
			'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
484
			'total_pages' => ceil( $total / $per_page ),
485
			'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
486
			'total' => $total,
487
			'time' => $time
488
		)
489
	) );
490
	$x->send();
491
}
492
493
//
494
// POST-based Ajax handlers.
495
//
496
497
/**
498
 * Ajax handler for adding a hierarchical term.
499
 *
500
 * @access private
501
 * @since 3.1.0
502
 */
503
function _wp_ajax_add_hierarchical_term() {
504
	$action = $_POST['action'];
505
	$taxonomy = get_taxonomy(substr($action, 4));
506
	check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
507
	if ( !current_user_can( $taxonomy->cap->edit_terms ) )
508
		wp_die( -1 );
509
	$names = explode(',', $_POST['new'.$taxonomy->name]);
510
	$parent = isset($_POST['new'.$taxonomy->name.'_parent']) ? (int) $_POST['new'.$taxonomy->name.'_parent'] : 0;
511
	if ( 0 > $parent )
512
		$parent = 0;
513
	if ( $taxonomy->name == 'category' )
514
		$post_category = isset($_POST['post_category']) ? (array) $_POST['post_category'] : array();
515
	else
516
		$post_category = ( isset($_POST['tax_input']) && isset($_POST['tax_input'][$taxonomy->name]) ) ? (array) $_POST['tax_input'][$taxonomy->name] : array();
517
	$checked_categories = array_map( 'absint', (array) $post_category );
518
	$popular_ids = wp_popular_terms_checklist($taxonomy->name, 0, 10, false);
519
520
	foreach ( $names as $cat_name ) {
521
		$cat_name = trim($cat_name);
522
		$category_nicename = sanitize_title($cat_name);
523
		if ( '' === $category_nicename )
524
			continue;
525
526
		$cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
527
		if ( ! $cat_id || is_wp_error( $cat_id ) ) {
528
			continue;
529
		} else {
530
			$cat_id = $cat_id['term_id'];
531
		}
532
		$checked_categories[] = $cat_id;
533
		if ( $parent ) // Do these all at once in a second
534
			continue;
535
536
		ob_start();
537
538
		wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids ));
539
540
		$data = ob_get_clean();
541
542
		$add = array(
543
			'what' => $taxonomy->name,
544
			'id' => $cat_id,
545
			'data' => str_replace( array("\n", "\t"), '', $data),
546
			'position' => -1
547
		);
548
	}
549
550
	if ( $parent ) { // Foncy - replace the parent and all its children
551
		$parent = get_term( $parent, $taxonomy->name );
552
		$term_id = $parent->term_id;
553
554 View Code Duplication
		while ( $parent->parent ) { // get the top parent
555
			$parent = get_term( $parent->parent, $taxonomy->name );
556
			if ( is_wp_error( $parent ) )
557
				break;
558
			$term_id = $parent->term_id;
559
		}
560
561
		ob_start();
562
563
		wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids));
564
565
		$data = ob_get_clean();
566
567
		$add = array(
568
			'what' => $taxonomy->name,
569
			'id' => $term_id,
570
			'data' => str_replace( array("\n", "\t"), '', $data),
571
			'position' => -1
572
		);
573
	}
574
575
	ob_start();
576
577
	wp_dropdown_categories( array(
578
		'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name',
579
		'hierarchical' => 1, 'show_option_none' => '&mdash; '.$taxonomy->labels->parent_item.' &mdash;'
580
	) );
581
582
	$sup = ob_get_clean();
583
584
	$add['supplemental'] = array( 'newcat_parent' => $sup );
0 ignored issues
show
Bug introduced by
The variable $add 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...
585
586
	$x = new WP_Ajax_Response( $add );
587
	$x->send();
588
}
589
590
/**
591
 * Ajax handler for deleting a comment.
592
 *
593
 * @since 3.1.0
594
 */
595
function wp_ajax_delete_comment() {
596
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
597
598
	if ( !$comment = get_comment( $id ) )
599
		wp_die( time() );
600
	if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
601
		wp_die( -1 );
602
603
	check_ajax_referer( "delete-comment_$id" );
604
	$status = wp_get_comment_status( $comment );
605
606
	$delta = -1;
607
	if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) {
608
		if ( 'trash' == $status )
609
			wp_die( time() );
610
		$r = wp_trash_comment( $comment );
611 View Code Duplication
	} elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) {
612
		if ( 'trash' != $status )
613
			wp_die( time() );
614
		$r = wp_untrash_comment( $comment );
615
		if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash
616
			$delta = 1;
617
	} elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) {
618
		if ( 'spam' == $status )
619
			wp_die( time() );
620
		$r = wp_spam_comment( $comment );
621 View Code Duplication
	} elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) {
622
		if ( 'spam' != $status )
623
			wp_die( time() );
624
		$r = wp_unspam_comment( $comment );
625
		if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam
626
			$delta = 1;
627
	} elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) {
628
		$r = wp_delete_comment( $comment );
629
	} else {
630
		wp_die( -1 );
631
	}
632
633
	if ( $r ) // Decide if we need to send back '1' or a more complicated response including page links and comment counts
0 ignored issues
show
Bug introduced by
The variable $r 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...
634
		_wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
635
	wp_die( 0 );
636
}
637
638
/**
639
 * Ajax handler for deleting a tag.
640
 *
641
 * @since 3.1.0
642
 */
643
function wp_ajax_delete_tag() {
644
	$tag_id = (int) $_POST['tag_ID'];
645
	check_ajax_referer( "delete-tag_$tag_id" );
646
647
	if ( ! current_user_can( 'delete_term', $tag_id ) ) {
648
		wp_die( -1 );
649
	}
650
651
	$taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
652
	$tag = get_term( $tag_id, $taxonomy );
653
	if ( !$tag || is_wp_error( $tag ) )
654
		wp_die( 1 );
655
656
	if ( wp_delete_term($tag_id, $taxonomy))
657
		wp_die( 1 );
658
	else
659
		wp_die( 0 );
660
}
661
662
/**
663
 * Ajax handler for deleting a link.
664
 *
665
 * @since 3.1.0
666
 */
667 View Code Duplication
function wp_ajax_delete_link() {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
668
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
669
670
	check_ajax_referer( "delete-bookmark_$id" );
671
	if ( !current_user_can( 'manage_links' ) )
672
		wp_die( -1 );
673
674
	$link = get_bookmark( $id );
675
	if ( !$link || is_wp_error( $link ) )
676
		wp_die( 1 );
677
678
	if ( wp_delete_link( $id ) )
679
		wp_die( 1 );
680
	else
681
		wp_die( 0 );
682
}
683
684
/**
685
 * Ajax handler for deleting meta.
686
 *
687
 * @since 3.1.0
688
 */
689
function wp_ajax_delete_meta() {
690
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
691
692
	check_ajax_referer( "delete-meta_$id" );
693
	if ( !$meta = get_metadata_by_mid( 'post', $id ) )
694
		wp_die( 1 );
695
696
	if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta',  $meta->post_id, $meta->meta_key ) )
697
		wp_die( -1 );
698
	if ( delete_meta( $meta->meta_id ) )
699
		wp_die( 1 );
700
	wp_die( 0 );
701
}
702
703
/**
704
 * Ajax handler for deleting a post.
705
 *
706
 * @since 3.1.0
707
 *
708
 * @param string $action Action to perform.
709
 */
710 View Code Duplication
function wp_ajax_delete_post( $action ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
711
	if ( empty( $action ) )
712
		$action = 'delete-post';
713
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
714
715
	check_ajax_referer( "{$action}_$id" );
716
	if ( !current_user_can( 'delete_post', $id ) )
717
		wp_die( -1 );
718
719
	if ( !get_post( $id ) )
720
		wp_die( 1 );
721
722
	if ( wp_delete_post( $id ) )
723
		wp_die( 1 );
724
	else
725
		wp_die( 0 );
726
}
727
728
/**
729
 * Ajax handler for sending a post to the trash.
730
 *
731
 * @since 3.1.0
732
 *
733
 * @param string $action Action to perform.
734
 */
735
function wp_ajax_trash_post( $action ) {
736
	if ( empty( $action ) )
737
		$action = 'trash-post';
738
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
739
740
	check_ajax_referer( "{$action}_$id" );
741
	if ( !current_user_can( 'delete_post', $id ) )
742
		wp_die( -1 );
743
744
	if ( !get_post( $id ) )
745
		wp_die( 1 );
746
747
	if ( 'trash-post' == $action )
748
		$done = wp_trash_post( $id );
749
	else
750
		$done = wp_untrash_post( $id );
751
752
	if ( $done )
753
		wp_die( 1 );
754
755
	wp_die( 0 );
756
}
757
758
/**
759
 * Ajax handler to restore a post from the trash.
760
 *
761
 * @since 3.1.0
762
 *
763
 * @param string $action Action to perform.
764
 */
765
function wp_ajax_untrash_post( $action ) {
766
	if ( empty( $action ) )
767
		$action = 'untrash-post';
768
	wp_ajax_trash_post( $action );
769
}
770
771
/**
772
 * @since 3.1.0
773
 *
774
 * @param string $action
775
 */
776 View Code Duplication
function wp_ajax_delete_page( $action ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
777
	if ( empty( $action ) )
778
		$action = 'delete-page';
779
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
780
781
	check_ajax_referer( "{$action}_$id" );
782
	if ( !current_user_can( 'delete_page', $id ) )
783
		wp_die( -1 );
784
785
	if ( ! get_post( $id ) )
786
		wp_die( 1 );
787
788
	if ( wp_delete_post( $id ) )
789
		wp_die( 1 );
790
	else
791
		wp_die( 0 );
792
}
793
794
/**
795
 * Ajax handler to dim a comment.
796
 *
797
 * @since 3.1.0
798
 */
799
function wp_ajax_dim_comment() {
800
	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
801
802
	if ( !$comment = get_comment( $id ) ) {
803
		$x = new WP_Ajax_Response( array(
804
			'what' => 'comment',
805
			'id' => new WP_Error('invalid_comment', sprintf(__('Comment %d does not exist'), $id))
806
		) );
807
		$x->send();
808
	}
809
810
	if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) )
811
		wp_die( -1 );
812
813
	$current = wp_get_comment_status( $comment );
814
	if ( isset( $_POST['new'] ) && $_POST['new'] == $current )
815
		wp_die( time() );
816
817
	check_ajax_referer( "approve-comment_$id" );
818
	if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) {
819
		$result = wp_set_comment_status( $comment, 'approve', true );
820
	} else {
821
		$result = wp_set_comment_status( $comment, 'hold', true );
822
	}
823
824
	if ( is_wp_error($result) ) {
825
		$x = new WP_Ajax_Response( array(
826
			'what' => 'comment',
827
			'id' => $result
828
		) );
829
		$x->send();
830
	}
831
832
	// Decide if we need to send back '1' or a more complicated response including page links and comment counts
833
	_wp_ajax_delete_comment_response( $comment->comment_ID );
834
	wp_die( 0 );
835
}
836
837
/**
838
 * Ajax handler for adding a link category.
839
 *
840
 * @since 3.1.0
841
 *
842
 * @param string $action Action to perform.
843
 */
844
function wp_ajax_add_link_category( $action ) {
845
	if ( empty( $action ) )
846
		$action = 'add-link-category';
847
	check_ajax_referer( $action );
848
	$tax = get_taxonomy( 'link_category' );
849
	if ( ! current_user_can( $tax->cap->manage_terms ) ) {
850
		wp_die( -1 );
851
	}
852
	$names = explode(',', wp_unslash( $_POST['newcat'] ) );
853
	$x = new WP_Ajax_Response();
854
	foreach ( $names as $cat_name ) {
855
		$cat_name = trim($cat_name);
856
		$slug = sanitize_title($cat_name);
857
		if ( '' === $slug )
858
			continue;
859
860
		$cat_id = wp_insert_term( $cat_name, 'link_category' );
861
		if ( ! $cat_id || is_wp_error( $cat_id ) ) {
862
			continue;
863
		} else {
864
			$cat_id = $cat_id['term_id'];
865
		}
866
		$cat_name = esc_html( $cat_name );
867
		$x->add( array(
868
			'what' => 'link-category',
869
			'id' => $cat_id,
870
			'data' => "<li id='link-category-$cat_id'><label for='in-link-category-$cat_id' class='selectit'><input value='" . esc_attr($cat_id) . "' type='checkbox' checked='checked' name='link_category[]' id='in-link-category-$cat_id'/> $cat_name</label></li>",
871
			'position' => -1
872
		) );
873
	}
874
	$x->send();
875
}
876
877
/**
878
 * Ajax handler to add a tag.
879
 *
880
 * @since 3.1.0
881
 */
882
function wp_ajax_add_tag() {
883
	check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
884
	$taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
885
	$tax = get_taxonomy($taxonomy);
886
887
	if ( !current_user_can( $tax->cap->edit_terms ) )
888
		wp_die( -1 );
889
890
	$x = new WP_Ajax_Response();
891
892
	$tag = wp_insert_term($_POST['tag-name'], $taxonomy, $_POST );
893
894
	if ( !$tag || is_wp_error($tag) || (!$tag = get_term( $tag['term_id'], $taxonomy )) ) {
895
		$message = __('An error has occurred. Please reload the page and try again.');
896
		if ( is_wp_error($tag) && $tag->get_error_message() )
897
			$message = $tag->get_error_message();
0 ignored issues
show
Bug introduced by
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
898
899
		$x->add( array(
900
			'what' => 'taxonomy',
901
			'data' => new WP_Error('error', $message )
902
		) );
903
		$x->send();
904
	}
905
906
	$wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
907
908
	$level = 0;
909
	if ( is_taxonomy_hierarchical($taxonomy) ) {
910
		$level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
911
		ob_start();
912
		$wp_list_table->single_row( $tag, $level );
913
		$noparents = ob_get_clean();
914
	}
915
916
	ob_start();
917
	$wp_list_table->single_row( $tag );
918
	$parents = ob_get_clean();
919
920
	$x->add( array(
921
		'what' => 'taxonomy',
922
		'supplemental' => compact('parents', 'noparents')
923
	) );
924
	$x->add( array(
925
		'what' => 'term',
926
		'position' => $level,
927
		'supplemental' => (array) $tag
928
	) );
929
	$x->send();
930
}
931
932
/**
933
 * Ajax handler for getting a tagcloud.
934
 *
935
 * @since 3.1.0
936
 */
937
function wp_ajax_get_tagcloud() {
938
	if ( ! isset( $_POST['tax'] ) ) {
939
		wp_die( 0 );
940
	}
941
942
	$taxonomy = sanitize_key( $_POST['tax'] );
943
	$tax = get_taxonomy( $taxonomy );
944
	if ( ! $tax ) {
945
		wp_die( 0 );
946
	}
947
948
	if ( ! current_user_can( $tax->cap->assign_terms ) ) {
949
		wp_die( -1 );
950
	}
951
952
	$tags = get_terms( $taxonomy, array( 'number' => 45, 'orderby' => 'count', 'order' => 'DESC' ) );
953
954
	if ( empty( $tags ) )
955
		wp_die( $tax->labels->not_found );
956
957
	if ( is_wp_error( $tags ) )
958
		wp_die( $tags->get_error_message() );
959
960
	foreach ( $tags as $key => $tag ) {
0 ignored issues
show
Bug introduced by
The expression $tags of type array|integer|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
961
		$tags[ $key ]->link = '#';
962
		$tags[ $key ]->id = $tag->term_id;
963
	}
964
965
	// We need raw tag names here, so don't filter the output
966
	$return = wp_generate_tag_cloud( $tags, array('filter' => 0) );
967
968
	if ( empty($return) )
969
		wp_die( 0 );
970
971
	echo $return;
972
973
	wp_die();
974
}
975
976
/**
977
 * Ajax handler for getting comments.
978
 *
979
 * @since 3.1.0
980
 *
981
 * @global int           $post_id
982
 *
983
 * @param string $action Action to perform.
984
 */
985
function wp_ajax_get_comments( $action ) {
986
	global $post_id;
987
	if ( empty( $action ) ) {
988
		$action = 'get-comments';
989
	}
990
	check_ajax_referer( $action );
991
992
	if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
993
		$id = absint( $_REQUEST['p'] );
994
		if ( ! empty( $id ) ) {
995
			$post_id = $id;
996
		}
997
	}
998
999
	if ( empty( $post_id ) ) {
1000
		wp_die( -1 );
1001
	}
1002
1003
	$wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1004
1005
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
1006
		wp_die( -1 );
1007
	}
1008
1009
	$wp_list_table->prepare_items();
1010
1011
	if ( ! $wp_list_table->has_items() ) {
1012
		wp_die( 1 );
1013
	}
1014
1015
	$x = new WP_Ajax_Response();
1016
	ob_start();
1017
	foreach ( $wp_list_table->items as $comment ) {
1018
		if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved )
1019
			continue;
1020
		get_comment( $comment );
1021
		$wp_list_table->single_row( $comment );
1022
	}
1023
	$comment_list_item = ob_get_clean();
1024
1025
	$x->add( array(
1026
		'what' => 'comments',
1027
		'data' => $comment_list_item
1028
	) );
1029
	$x->send();
1030
}
1031
1032
/**
1033
 * Ajax handler for replying to a comment.
1034
 *
1035
 * @since 3.1.0
1036
 *
1037
 * @param string $action Action to perform.
1038
 */
1039
function wp_ajax_replyto_comment( $action ) {
1040
	if ( empty( $action ) )
1041
		$action = 'replyto-comment';
1042
1043
	check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
1044
1045
	$comment_post_ID = (int) $_POST['comment_post_ID'];
1046
	$post = get_post( $comment_post_ID );
1047
	if ( ! $post )
1048
		wp_die( -1 );
1049
1050
	if ( !current_user_can( 'edit_post', $comment_post_ID ) )
1051
		wp_die( -1 );
1052
1053
	if ( empty( $post->post_status ) )
1054
		wp_die( 1 );
1055
	elseif ( in_array($post->post_status, array('draft', 'pending', 'trash') ) )
1056
		wp_die( __('ERROR: you are replying to a comment on a draft post.') );
1057
1058
	$user = wp_get_current_user();
1059
	if ( $user->exists() ) {
1060
		$user_ID = $user->ID;
1061
		$comment_author       = wp_slash( $user->display_name );
1062
		$comment_author_email = wp_slash( $user->user_email );
1063
		$comment_author_url   = wp_slash( $user->user_url );
1064
		$comment_content      = trim( $_POST['content'] );
1065
		$comment_type         = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
1066
		if ( current_user_can( 'unfiltered_html' ) ) {
1067
			if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) )
1068
				$_POST['_wp_unfiltered_html_comment'] = '';
1069
1070
			if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
1071
				kses_remove_filters(); // start with a clean slate
1072
				kses_init_filters(); // set up the filters
1073
			}
1074
		}
1075
	} else {
1076
		wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
1077
	}
1078
1079
	if ( '' == $comment_content )
0 ignored issues
show
Bug introduced by
The variable $comment_content 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...
1080
		wp_die( __( 'ERROR: please type a comment.' ) );
1081
1082
	$comment_parent = 0;
1083
	if ( isset( $_POST['comment_ID'] ) )
1084
		$comment_parent = absint( $_POST['comment_ID'] );
1085
	$comment_auto_approved = false;
1086
	$commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
1087
1088
	// Automatically approve parent comment.
1089
	if ( !empty($_POST['approve_parent']) ) {
1090
		$parent = get_comment( $comment_parent );
1091
1092
		if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
1093
			if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
1094
				wp_die( -1 );
1095
			}
1096
1097
			if ( wp_set_comment_status( $parent, 'approve' ) )
1098
				$comment_auto_approved = true;
1099
		}
1100
	}
1101
1102
	$comment_id = wp_new_comment( $commentdata );
1103
	$comment = get_comment($comment_id);
1104
	if ( ! $comment ) wp_die( 1 );
1105
1106
	$position = ( isset($_POST['position']) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1107
1108
	ob_start();
1109
	if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
1110
		require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
1111
		_wp_dashboard_recent_comments_row( $comment );
1112
	} else {
1113
		if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
1114
			$wp_list_table = _get_list_table('WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1115
		} else {
1116
			$wp_list_table = _get_list_table('WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1117
		}
1118
		$wp_list_table->single_row( $comment );
1119
	}
1120
	$comment_list_item = ob_get_clean();
1121
1122
	$response =  array(
1123
		'what' => 'comment',
1124
		'id' => $comment->comment_ID,
1125
		'data' => $comment_list_item,
1126
		'position' => $position
1127
	);
1128
1129
	$counts = wp_count_comments();
1130
	$response['supplemental'] = array(
1131
		'in_moderation' => $counts->moderated,
1132
		'i18n_comments_text' => sprintf(
1133
			_n( '%s Comment', '%s Comments', $counts->approved ),
1134
			number_format_i18n( $counts->approved )
1135
		),
1136
		'i18n_moderation_text' => sprintf(
1137
			_nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
1138
			number_format_i18n( $counts->moderated )
1139
		)
1140
	);
1141
1142
	if ( $comment_auto_approved ) {
1143
		$response['supplemental']['parent_approved'] = $parent->comment_ID;
0 ignored issues
show
Bug introduced by
The variable $parent 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...
1144
		$response['supplemental']['parent_post_id'] = $parent->comment_post_ID;
1145
	}
1146
1147
	$x = new WP_Ajax_Response();
1148
	$x->add( $response );
1149
	$x->send();
1150
}
1151
1152
/**
1153
 * Ajax handler for editing a comment.
1154
 *
1155
 * @since 3.1.0
1156
 */
1157
function wp_ajax_edit_comment() {
1158
	check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1159
1160
	$comment_id = (int) $_POST['comment_ID'];
1161
	if ( ! current_user_can( 'edit_comment', $comment_id ) )
1162
		wp_die( -1 );
1163
1164
	if ( '' == $_POST['content'] )
1165
		wp_die( __( 'ERROR: please type a comment.' ) );
1166
1167
	if ( isset( $_POST['status'] ) )
1168
		$_POST['comment_status'] = $_POST['status'];
1169
	edit_comment();
1170
1171
	$position = ( isset($_POST['position']) && (int) $_POST['position']) ? (int) $_POST['position'] : '-1';
1172
	$checkbox = ( isset($_POST['checkbox']) && true == $_POST['checkbox'] ) ? 1 : 0;
1173
	$wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1174
1175
	$comment = get_comment( $comment_id );
1176
	if ( empty( $comment->comment_ID ) )
1177
		wp_die( -1 );
1178
1179
	ob_start();
1180
	$wp_list_table->single_row( $comment );
1181
	$comment_list_item = ob_get_clean();
1182
1183
	$x = new WP_Ajax_Response();
1184
1185
	$x->add( array(
1186
		'what' => 'edit_comment',
1187
		'id' => $comment->comment_ID,
1188
		'data' => $comment_list_item,
1189
		'position' => $position
1190
	));
1191
1192
	$x->send();
1193
}
1194
1195
/**
1196
 * Ajax handler for adding a menu item.
1197
 *
1198
 * @since 3.1.0
1199
 */
1200
function wp_ajax_add_menu_item() {
1201
	check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1202
1203
	if ( ! current_user_can( 'edit_theme_options' ) )
1204
		wp_die( -1 );
1205
1206
	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1207
1208
	// For performance reasons, we omit some object properties from the checklist.
1209
	// The following is a hacky way to restore them when adding non-custom items.
1210
1211
	$menu_items_data = array();
1212
	foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1213
		if (
1214
			! empty( $menu_item_data['menu-item-type'] ) &&
1215
			'custom' != $menu_item_data['menu-item-type'] &&
1216
			! empty( $menu_item_data['menu-item-object-id'] )
1217
		) {
1218
			switch( $menu_item_data['menu-item-type'] ) {
1219
				case 'post_type' :
1220
					$_object = get_post( $menu_item_data['menu-item-object-id'] );
1221
				break;
1222
1223
				case 'post_type_archive' :
1224
					$_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1225
				break;
1226
1227
				case 'taxonomy' :
1228
					$_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1229
				break;
1230
			}
1231
1232
			$_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
0 ignored issues
show
Bug introduced by
The variable $_object 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...
1233
			$_menu_item = reset( $_menu_items );
1234
1235
			// Restore the missing menu item properties
1236
			$menu_item_data['menu-item-description'] = $_menu_item->description;
1237
		}
1238
1239
		$menu_items_data[] = $menu_item_data;
1240
	}
1241
1242
	$item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1243
	if ( is_wp_error( $item_ids ) )
1244
		wp_die( 0 );
1245
1246
	$menu_items = array();
1247
1248
	foreach ( (array) $item_ids as $menu_item_id ) {
1249
		$menu_obj = get_post( $menu_item_id );
1250
		if ( ! empty( $menu_obj->ID ) ) {
1251
			$menu_obj = wp_setup_nav_menu_item( $menu_obj );
0 ignored issues
show
Bug introduced by
It seems like $menu_obj can also be of type array; however, wp_setup_nav_menu_item() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1252
			$menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1253
			$menu_items[] = $menu_obj;
1254
		}
1255
	}
1256
1257
	/** This filter is documented in wp-admin/includes/nav-menu.php */
1258
	$walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1259
1260
	if ( ! class_exists( $walker_class_name ) )
1261
		wp_die( 0 );
1262
1263
	if ( ! empty( $menu_items ) ) {
1264
		$args = array(
1265
			'after' => '',
1266
			'before' => '',
1267
			'link_after' => '',
1268
			'link_before' => '',
1269
			'walker' => new $walker_class_name,
1270
		);
1271
		echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1272
	}
1273
	wp_die();
1274
}
1275
1276
/**
1277
 * Ajax handler for adding meta.
1278
 *
1279
 * @since 3.1.0
1280
 */
1281
function wp_ajax_add_meta() {
1282
	check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1283
	$c = 0;
1284
	$pid = (int) $_POST['post_id'];
1285
	$post = get_post( $pid );
1286
1287
	if ( isset($_POST['metakeyselect']) || isset($_POST['metakeyinput']) ) {
1288
		if ( !current_user_can( 'edit_post', $pid ) )
1289
			wp_die( -1 );
1290
		if ( isset($_POST['metakeyselect']) && '#NONE#' == $_POST['metakeyselect'] && empty($_POST['metakeyinput']) )
1291
			wp_die( 1 );
1292
1293
		// If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1294
		if ( $post->post_status == 'auto-draft' ) {
1295
			$post_data = array();
1296
			$post_data['action'] = 'draft'; // Warning fix
1297
			$post_data['post_ID'] = $pid;
1298
			$post_data['post_type'] = $post->post_type;
1299
			$post_data['post_status'] = 'draft';
1300
			$now = current_time('timestamp', 1);
1301
			/* translators: 1: Post creation date, 2: Post creation time */
1302
			$post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) );
1303
1304
			$pid = edit_post( $post_data );
1305
			if ( $pid ) {
1306 View Code Duplication
				if ( is_wp_error( $pid ) ) {
1307
					$x = new WP_Ajax_Response( array(
1308
						'what' => 'meta',
1309
						'data' => $pid
1310
					) );
1311
					$x->send();
1312
				}
1313
1314
				if ( !$mid = add_meta( $pid ) )
1315
					wp_die( __( 'Please provide a custom field value.' ) );
1316
			} else {
1317
				wp_die( 0 );
1318
			}
1319
		} elseif ( ! $mid = add_meta( $pid ) ) {
1320
			wp_die( __( 'Please provide a custom field value.' ) );
1321
		}
1322
1323
		$meta = get_metadata_by_mid( 'post', $mid );
0 ignored issues
show
Bug introduced by
The variable $mid 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...
Security Bug introduced by
It seems like $mid can also be of type false; however, get_metadata_by_mid() does only seem to accept integer, did you maybe forget to handle an error condition?
Loading history...
1324
		$pid = (int) $meta->post_id;
1325
		$meta = get_object_vars( $meta );
1326
		$x = new WP_Ajax_Response( array(
1327
			'what' => 'meta',
1328
			'id' => $mid,
1329
			'data' => _list_meta_row( $meta, $c ),
1330
			'position' => 1,
1331
			'supplemental' => array('postid' => $pid)
1332
		) );
1333
	} else { // Update?
1334
		$mid = (int) key( $_POST['meta'] );
1335
		$key = wp_unslash( $_POST['meta'][$mid]['key'] );
1336
		$value = wp_unslash( $_POST['meta'][$mid]['value'] );
1337
		if ( '' == trim($key) )
1338
			wp_die( __( 'Please provide a custom field name.' ) );
1339
		if ( '' == trim($value) )
1340
			wp_die( __( 'Please provide a custom field value.' ) );
1341
		if ( ! $meta = get_metadata_by_mid( 'post', $mid ) )
1342
			wp_die( 0 ); // if meta doesn't exist
1343
		if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1344
			! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1345
			! current_user_can( 'edit_post_meta', $meta->post_id, $key ) )
1346
			wp_die( -1 );
1347
		if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1348
			if ( !$u = update_metadata_by_mid( 'post', $mid, $value, $key ) )
1349
				wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1350
		}
1351
1352
		$x = new WP_Ajax_Response( array(
1353
			'what' => 'meta',
1354
			'id' => $mid, 'old_id' => $mid,
1355
			'data' => _list_meta_row( array(
1356
				'meta_key' => $key,
1357
				'meta_value' => $value,
1358
				'meta_id' => $mid
1359
			), $c ),
1360
			'position' => 0,
1361
			'supplemental' => array('postid' => $meta->post_id)
1362
		) );
1363
	}
1364
	$x->send();
1365
}
1366
1367
/**
1368
 * Ajax handler for adding a user.
1369
 *
1370
 * @since 3.1.0
1371
 *
1372
 * @param string $action Action to perform.
1373
 */
1374
function wp_ajax_add_user( $action ) {
1375
	if ( empty( $action ) ) {
1376
		$action = 'add-user';
1377
	}
1378
1379
	check_ajax_referer( $action );
1380
	if ( ! current_user_can('create_users') )
1381
		wp_die( -1 );
1382
	if ( ! $user_id = edit_user() ) {
1383
		wp_die( 0 );
1384 View Code Duplication
	} elseif ( is_wp_error( $user_id ) ) {
1385
		$x = new WP_Ajax_Response( array(
1386
			'what' => 'user',
1387
			'id' => $user_id
1388
		) );
1389
		$x->send();
1390
	}
1391
	$user_object = get_userdata( $user_id );
1392
1393
	$wp_list_table = _get_list_table('WP_Users_List_Table');
1394
1395
	$role = current( $user_object->roles );
1396
1397
	$x = new WP_Ajax_Response( array(
1398
		'what' => 'user',
1399
		'id' => $user_id,
1400
		'data' => $wp_list_table->single_row( $user_object, '', $role ),
1401
		'supplemental' => array(
1402
			'show-link' => sprintf(
1403
				/* translators: %s: the new user */
1404
				__( 'User %s added' ),
1405
				'<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1406
			),
1407
			'role' => $role,
1408
		)
1409
	) );
1410
	$x->send();
1411
}
1412
1413
/**
1414
 * Ajax handler for closed post boxes.
1415
 *
1416
 * @since 3.1.0
1417
 */
1418
function wp_ajax_closed_postboxes() {
1419
	check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1420
	$closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
1421
	$closed = array_filter($closed);
1422
1423
	$hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden']) : array();
1424
	$hidden = array_filter($hidden);
1425
1426
	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1427
1428
	if ( $page != sanitize_key( $page ) )
1429
		wp_die( 0 );
1430
1431
	if ( ! $user = wp_get_current_user() )
1432
		wp_die( -1 );
1433
1434
	if ( is_array($closed) )
1435
		update_user_option($user->ID, "closedpostboxes_$page", $closed, true);
1436
1437
	if ( is_array($hidden) ) {
1438
		$hidden = array_diff( $hidden, array('submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu') ); // postboxes that are always shown
1439
		update_user_option($user->ID, "metaboxhidden_$page", $hidden, true);
1440
	}
1441
1442
	wp_die( 1 );
1443
}
1444
1445
/**
1446
 * Ajax handler for hidden columns.
1447
 *
1448
 * @since 3.1.0
1449
 */
1450
function wp_ajax_hidden_columns() {
1451
	check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1452
	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1453
1454
	if ( $page != sanitize_key( $page ) )
1455
		wp_die( 0 );
1456
1457
	if ( ! $user = wp_get_current_user() )
1458
		wp_die( -1 );
1459
1460
	$hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1461
	update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true );
1462
1463
	wp_die( 1 );
1464
}
1465
1466
/**
1467
 * Ajax handler for updating whether to display the welcome panel.
1468
 *
1469
 * @since 3.1.0
1470
 */
1471
function wp_ajax_update_welcome_panel() {
1472
	check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1473
1474
	if ( ! current_user_can( 'edit_theme_options' ) )
1475
		wp_die( -1 );
1476
1477
	update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1478
1479
	wp_die( 1 );
1480
}
1481
1482
/**
1483
 * Ajax handler for retrieving menu meta boxes.
1484
 *
1485
 * @since 3.1.0
1486
 */
1487
function wp_ajax_menu_get_metabox() {
1488
	if ( ! current_user_can( 'edit_theme_options' ) )
1489
		wp_die( -1 );
1490
1491
	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1492
1493
	if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1494
		$type = 'posttype';
1495
		$callback = 'wp_nav_menu_item_post_type_meta_box';
1496
		$items = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1497
	} elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1498
		$type = 'taxonomy';
1499
		$callback = 'wp_nav_menu_item_taxonomy_meta_box';
1500
		$items = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1501
	}
1502
1503
	if ( ! empty( $_POST['item-object'] ) && isset( $items[$_POST['item-object']] ) ) {
1504
		$menus_meta_box_object = $items[ $_POST['item-object'] ];
1505
1506
		/** This filter is documented in wp-admin/includes/nav-menu.php */
1507
		$item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1508
		ob_start();
1509
		call_user_func_array($callback, array(
0 ignored issues
show
Bug introduced by
The variable $callback 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...
1510
			null,
1511
			array(
1512
				'id' => 'add-' . $item->name,
1513
				'title' => $item->labels->name,
1514
				'callback' => $callback,
1515
				'args' => $item,
1516
			)
1517
		));
1518
1519
		$markup = ob_get_clean();
1520
1521
		echo wp_json_encode(array(
1522
			'replace-id' => $type . '-' . $item->name,
0 ignored issues
show
Bug introduced by
The variable $type 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...
1523
			'markup' => $markup,
1524
		));
1525
	}
1526
1527
	wp_die();
1528
}
1529
1530
/**
1531
 * Ajax handler for internal linking.
1532
 *
1533
 * @since 3.1.0
1534
 */
1535
function wp_ajax_wp_link_ajax() {
1536
	check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1537
1538
	$args = array();
1539
1540
	if ( isset( $_POST['search'] ) ) {
1541
		$args['s'] = wp_unslash( $_POST['search'] );
1542
	}
1543
1544
	if ( isset( $_POST['term'] ) ) {
1545
		$args['s'] = wp_unslash( $_POST['term'] );
1546
	}
1547
1548
	$args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1549
1550
	if ( ! class_exists( '_WP_Editors', false ) ) {
1551
		require( ABSPATH . WPINC . '/class-wp-editor.php' );
1552
	}
1553
1554
	$results = _WP_Editors::wp_link_query( $args );
1555
1556
	if ( ! isset( $results ) )
1557
		wp_die( 0 );
1558
1559
	echo wp_json_encode( $results );
1560
	echo "\n";
1561
1562
	wp_die();
1563
}
1564
1565
/**
1566
 * Ajax handler for menu locations save.
1567
 *
1568
 * @since 3.1.0
1569
 */
1570
function wp_ajax_menu_locations_save() {
1571
	if ( ! current_user_can( 'edit_theme_options' ) )
1572
		wp_die( -1 );
1573
	check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1574
	if ( ! isset( $_POST['menu-locations'] ) )
1575
		wp_die( 0 );
1576
	set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1577
	wp_die( 1 );
1578
}
1579
1580
/**
1581
 * Ajax handler for saving the meta box order.
1582
 *
1583
 * @since 3.1.0
1584
 */
1585
function wp_ajax_meta_box_order() {
1586
	check_ajax_referer( 'meta-box-order' );
1587
	$order = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1588
	$page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1589
1590
	if ( $page_columns != 'auto' )
1591
		$page_columns = (int) $page_columns;
1592
1593
	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1594
1595
	if ( $page != sanitize_key( $page ) )
1596
		wp_die( 0 );
1597
1598
	if ( ! $user = wp_get_current_user() )
1599
		wp_die( -1 );
1600
1601
	if ( $order )
1602
		update_user_option($user->ID, "meta-box-order_$page", $order, true);
1603
1604
	if ( $page_columns )
1605
		update_user_option($user->ID, "screen_layout_$page", $page_columns, true);
1606
1607
	wp_die( 1 );
1608
}
1609
1610
/**
1611
 * Ajax handler for menu quick searching.
1612
 *
1613
 * @since 3.1.0
1614
 */
1615
function wp_ajax_menu_quick_search() {
1616
	if ( ! current_user_can( 'edit_theme_options' ) )
1617
		wp_die( -1 );
1618
1619
	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1620
1621
	_wp_ajax_menu_quick_search( $_POST );
1622
1623
	wp_die();
1624
}
1625
1626
/**
1627
 * Ajax handler to retrieve a permalink.
1628
 *
1629
 * @since 3.1.0
1630
 */
1631
function wp_ajax_get_permalink() {
1632
	check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1633
	$post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1634
	wp_die( get_preview_post_link( $post_id ) );
1635
}
1636
1637
/**
1638
 * Ajax handler to retrieve a sample permalink.
1639
 *
1640
 * @since 3.1.0
1641
 */
1642
function wp_ajax_sample_permalink() {
1643
	check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1644
	$post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1645
	$title = isset($_POST['new_title'])? $_POST['new_title'] : '';
1646
	$slug = isset($_POST['new_slug'])? $_POST['new_slug'] : null;
1647
	wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1648
}
1649
1650
/**
1651
 * Ajax handler for Quick Edit saving a post from a list table.
1652
 *
1653
 * @since 3.1.0
1654
 *
1655
 * @global string $mode List table view mode.
1656
 */
1657
function wp_ajax_inline_save() {
1658
	global $mode;
1659
1660
	check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1661
1662
	if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
1663
		wp_die();
1664
1665 View Code Duplication
	if ( 'page' == $_POST['post_type'] ) {
1666
		if ( ! current_user_can( 'edit_page', $post_ID ) )
0 ignored issues
show
Bug introduced by
The variable $post_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...
1667
			wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
1668
	} else {
1669
		if ( ! current_user_can( 'edit_post', $post_ID ) )
1670
			wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1671
	}
1672
1673
	if ( $last = wp_check_post_lock( $post_ID ) ) {
1674
		$last_user = get_userdata( $last );
1675
		$last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1676
		printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ),	esc_html( $last_user_name ) );
1677
		wp_die();
1678
	}
1679
1680
	$data = &$_POST;
1681
1682
	$post = get_post( $post_ID, ARRAY_A );
1683
1684
	// Since it's coming from the database.
1685
	$post = wp_slash($post);
0 ignored issues
show
Bug introduced by
It seems like $post can also be of type null or object<WP_Post>; however, wp_slash() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1686
1687
	$data['content'] = $post['post_content'];
1688
	$data['excerpt'] = $post['post_excerpt'];
1689
1690
	// Rename.
1691
	$data['user_ID'] = get_current_user_id();
1692
1693
	if ( isset($data['post_parent']) )
1694
		$data['parent_id'] = $data['post_parent'];
1695
1696
	// Status.
1697
	if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1698
		$data['visibility']  = 'private';
1699
		$data['post_status'] = 'private';
1700
	} else {
1701
		$data['post_status'] = $data['_status'];
1702
	}
1703
1704
	if ( empty($data['comment_status']) )
1705
		$data['comment_status'] = 'closed';
1706
	if ( empty($data['ping_status']) )
1707
		$data['ping_status'] = 'closed';
1708
1709
	// Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1710
	if ( ! empty( $data['tax_input'] ) ) {
1711
		foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1712
			$tax_object = get_taxonomy( $taxonomy );
1713
			/** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1714
			if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1715
				unset( $data['tax_input'][ $taxonomy ] );
1716
			}
1717
		}
1718
	}
1719
1720
	// Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1721
	if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1722
		$post['post_status'] = 'publish';
1723
		$data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1724
	}
1725
1726
	// Update the post.
1727
	edit_post();
1728
1729
	$wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1730
1731
	$mode = $_POST['post_view'] === 'excerpt' ? 'excerpt' : 'list';
1732
1733
	$level = 0;
1734
	if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
1735
		$request_post = array( get_post( $_POST['post_ID'] ) );
1736
		$parent       = $request_post[0]->post_parent;
1737
1738
		while ( $parent > 0 ) {
1739
			$parent_post = get_post( $parent );
1740
			$parent      = $parent_post->post_parent;
1741
			$level++;
1742
		}
1743
	}
1744
1745
	$wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1746
1747
	wp_die();
1748
}
1749
1750
/**
1751
 * Ajax handler for quick edit saving for a term.
1752
 *
1753
 * @since 3.1.0
1754
 */
1755
function wp_ajax_inline_save_tax() {
1756
	check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1757
1758
	$taxonomy = sanitize_key( $_POST['taxonomy'] );
1759
	$tax = get_taxonomy( $taxonomy );
1760
	if ( ! $tax )
1761
		wp_die( 0 );
1762
1763
	if ( ! isset( $_POST['tax_ID'] ) || ! ( $id = (int) $_POST['tax_ID'] ) ) {
1764
		wp_die( -1 );
1765
	}
1766
1767
	if ( ! current_user_can( 'edit_term', $id ) ) {
0 ignored issues
show
Bug introduced by
The variable $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...
1768
		wp_die( -1 );
1769
	}
1770
1771
	$wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1772
1773
	$tag = get_term( $id, $taxonomy );
1774
	$_POST['description'] = $tag->description;
1775
1776
	$updated = wp_update_term($id, $taxonomy, $_POST);
1777
	if ( $updated && !is_wp_error($updated) ) {
1778
		$tag = get_term( $updated['term_id'], $taxonomy );
1779
		if ( !$tag || is_wp_error( $tag ) ) {
1780
			if ( is_wp_error($tag) && $tag->get_error_message() )
1781
				wp_die( $tag->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_message does only exist in WP_Error, but not in WP_Term.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1782
			wp_die( __( 'Item not updated.' ) );
1783
		}
1784
	} else {
1785
		if ( is_wp_error($updated) && $updated->get_error_message() )
1786
			wp_die( $updated->get_error_message() );
1787
		wp_die( __( 'Item not updated.' ) );
1788
	}
1789
	$level = 0;
1790
	$parent = $tag->parent;
1791
	while ( $parent > 0 ) {
1792
		$parent_tag = get_term( $parent, $taxonomy );
1793
		$parent = $parent_tag->parent;
1794
		$level++;
1795
	}
1796
	$wp_list_table->single_row( $tag, $level );
1797
	wp_die();
1798
}
1799
1800
/**
1801
 * Ajax handler for querying posts for the Find Posts modal.
1802
 *
1803
 * @see window.findPosts
1804
 *
1805
 * @since 3.1.0
1806
 */
1807
function wp_ajax_find_posts() {
1808
	check_ajax_referer( 'find-posts' );
1809
1810
	$post_types = get_post_types( array( 'public' => true ), 'objects' );
1811
	unset( $post_types['attachment'] );
1812
1813
	$s = wp_unslash( $_POST['ps'] );
1814
	$args = array(
1815
		'post_type' => array_keys( $post_types ),
1816
		'post_status' => 'any',
1817
		'posts_per_page' => 50,
1818
	);
1819
	if ( '' !== $s )
1820
		$args['s'] = $s;
1821
1822
	$posts = get_posts( $args );
1823
1824
	if ( ! $posts ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $posts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1825
		wp_send_json_error( __( 'No items found.' ) );
1826
	}
1827
1828
	$html = '<table class="widefat"><thead><tr><th class="found-radio"><br /></th><th>'.__('Title').'</th><th class="no-break">'.__('Type').'</th><th class="no-break">'.__('Date').'</th><th class="no-break">'.__('Status').'</th></tr></thead><tbody>';
1829
	$alt = '';
1830
	foreach ( $posts as $post ) {
1831
		$title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1832
		$alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1833
1834
		switch ( $post->post_status ) {
1835
			case 'publish' :
1836
			case 'private' :
1837
				$stat = __('Published');
1838
				break;
1839
			case 'future' :
1840
				$stat = __('Scheduled');
1841
				break;
1842
			case 'pending' :
1843
				$stat = __('Pending Review');
1844
				break;
1845
			case 'draft' :
1846
				$stat = __('Draft');
1847
				break;
1848
		}
1849
1850
		if ( '0000-00-00 00:00:00' == $post->post_date ) {
1851
			$time = '';
1852
		} else {
1853
			/* translators: date format in table columns, see https://secure.php.net/date */
1854
			$time = mysql2date(__('Y/m/d'), $post->post_date);
1855
		}
1856
1857
		$html .= '<tr class="' . trim( 'found-posts ' . $alt ) . '"><td class="found-radio"><input type="radio" id="found-'.$post->ID.'" name="found_post_id" value="' . esc_attr($post->ID) . '"></td>';
1858
		$html .= '<td><label for="found-'.$post->ID.'">' . esc_html( $title ) . '</label></td><td class="no-break">' . esc_html( $post_types[$post->post_type]->labels->singular_name ) . '</td><td class="no-break">'.esc_html( $time ) . '</td><td class="no-break">' . esc_html( $stat ). ' </td></tr>' . "\n\n";
0 ignored issues
show
Bug introduced by
The variable $stat 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...
1859
	}
1860
1861
	$html .= '</tbody></table>';
1862
1863
	wp_send_json_success( $html );
1864
}
1865
1866
/**
1867
 * Ajax handler for saving the widgets order.
1868
 *
1869
 * @since 3.1.0
1870
 */
1871
function wp_ajax_widgets_order() {
1872
	check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1873
1874
	if ( !current_user_can('edit_theme_options') )
1875
		wp_die( -1 );
1876
1877
	unset( $_POST['savewidgets'], $_POST['action'] );
1878
1879
	// Save widgets order for all sidebars.
1880
	if ( is_array($_POST['sidebars']) ) {
1881
		$sidebars = array();
1882
		foreach ( $_POST['sidebars'] as $key => $val ) {
1883
			$sb = array();
1884
			if ( !empty($val) ) {
1885
				$val = explode(',', $val);
1886
				foreach ( $val as $k => $v ) {
1887
					if ( strpos($v, 'widget-') === false )
1888
						continue;
1889
1890
					$sb[$k] = substr($v, strpos($v, '_') + 1);
1891
				}
1892
			}
1893
			$sidebars[$key] = $sb;
1894
		}
1895
		wp_set_sidebars_widgets($sidebars);
1896
		wp_die( 1 );
1897
	}
1898
1899
	wp_die( -1 );
1900
}
1901
1902
/**
1903
 * Ajax handler for saving a widget.
1904
 *
1905
 * @since 3.1.0
1906
 *
1907
 * @global array $wp_registered_widgets
1908
 * @global array $wp_registered_widget_controls
1909
 * @global array $wp_registered_widget_updates
1910
 */
1911
function wp_ajax_save_widget() {
1912
	global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1913
1914
	check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1915
1916
	if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1917
		wp_die( -1 );
1918
1919
	unset( $_POST['savewidgets'], $_POST['action'] );
1920
1921
	/**
1922
	 * Fires early when editing the widgets displayed in sidebars.
1923
	 *
1924
	 * @since 2.8.0
1925
	 */
1926
	do_action( 'load-widgets.php' );
1927
1928
	/**
1929
	 * Fires early when editing the widgets displayed in sidebars.
1930
	 *
1931
	 * @since 2.8.0
1932
	 */
1933
	do_action( 'widgets.php' );
1934
1935
	/** This action is documented in wp-admin/widgets.php */
1936
	do_action( 'sidebar_admin_setup' );
1937
1938
	$id_base = $_POST['id_base'];
1939
	$widget_id = $_POST['widget-id'];
1940
	$sidebar_id = $_POST['sidebar'];
1941
	$multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1942
	$settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1943
	$error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1944
1945
	$sidebars = wp_get_sidebars_widgets();
1946
	$sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1947
1948
	// Delete.
1949
	if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1950
1951
		if ( !isset($wp_registered_widgets[$widget_id]) )
1952
			wp_die( $error );
1953
1954
		$sidebar = array_diff( $sidebar, array($widget_id) );
1955
		$_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1956
1957
		/** This action is documented in wp-admin/widgets.php */
1958
		do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
1959
1960
	} elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1961
		if ( !$multi_number )
1962
			wp_die( $error );
1963
1964
		$_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1965
		$widget_id = $id_base . '-' . $multi_number;
1966
		$sidebar[] = $widget_id;
1967
	}
1968
	$_POST['widget-id'] = $sidebar;
1969
1970 View Code Duplication
	foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1971
1972
		if ( $name == $id_base ) {
1973
			if ( !is_callable( $control['callback'] ) )
1974
				continue;
1975
1976
			ob_start();
1977
				call_user_func_array( $control['callback'], $control['params'] );
1978
			ob_end_clean();
1979
			break;
1980
		}
1981
	}
1982
1983
	if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1984
		$sidebars[$sidebar_id] = $sidebar;
1985
		wp_set_sidebars_widgets($sidebars);
1986
		echo "deleted:$widget_id";
1987
		wp_die();
1988
	}
1989
1990
	if ( !empty($_POST['add_new']) )
1991
		wp_die();
1992
1993
	if ( $form = $wp_registered_widget_controls[$widget_id] )
1994
		call_user_func_array( $form['callback'], $form['params'] );
1995
1996
	wp_die();
1997
}
1998
1999
/**
2000
 * Ajax handler for saving a widget.
2001
 *
2002
 * @since 3.9.0
2003
 *
2004
 * @global WP_Customize_Manager $wp_customize
2005
 */
2006
function wp_ajax_update_widget() {
2007
	global $wp_customize;
2008
	$wp_customize->widgets->wp_ajax_update_widget();
2009
}
2010
2011
/**
2012
 * Ajax handler for removing inactive widgets.
2013
 *
2014
 * @since 4.4.0
2015
 */
2016
function wp_ajax_delete_inactive_widgets() {
2017
	check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
2018
2019
	if ( ! current_user_can( 'edit_theme_options' ) ) {
2020
		wp_die( -1 );
2021
	}
2022
2023
	unset( $_POST['removeinactivewidgets'], $_POST['action'] );
2024
	/** This action is documented in wp-admin/includes/ajax-actions.php */
2025
	do_action( 'load-widgets.php' );
2026
	/** This action is documented in wp-admin/includes/ajax-actions.php */
2027
	do_action( 'widgets.php' );
2028
	/** This action is documented in wp-admin/widgets.php */
2029
	do_action( 'sidebar_admin_setup' );
2030
2031
	$sidebars_widgets = wp_get_sidebars_widgets();
2032
2033 View Code Duplication
	foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
2034
		$pieces = explode( '-', $widget_id );
2035
		$multi_number = array_pop( $pieces );
2036
		$id_base = implode( '-', $pieces );
2037
		$widget = get_option( 'widget_' . $id_base );
2038
		unset( $widget[$multi_number] );
2039
		update_option( 'widget_' . $id_base, $widget );
2040
		unset( $sidebars_widgets['wp_inactive_widgets'][$key] );
2041
	}
2042
2043
	wp_set_sidebars_widgets( $sidebars_widgets );
2044
2045
	wp_die();
2046
}
2047
2048
/**
2049
 * Ajax handler for uploading attachments
2050
 *
2051
 * @since 3.3.0
2052
 */
2053
function wp_ajax_upload_attachment() {
2054
	check_ajax_referer( 'media-form' );
2055
	/*
2056
	 * This function does not use wp_send_json_success() / wp_send_json_error()
2057
	 * as the html4 Plupload handler requires a text/html content-type for older IE.
2058
	 * See https://core.trac.wordpress.org/ticket/31037
2059
	 */
2060
2061
	if ( ! current_user_can( 'upload_files' ) ) {
2062
		echo wp_json_encode( array(
2063
			'success' => false,
2064
			'data'    => array(
2065
				'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2066
				'filename' => $_FILES['async-upload']['name'],
2067
			)
2068
		) );
2069
2070
		wp_die();
2071
	}
2072
2073
	if ( isset( $_REQUEST['post_id'] ) ) {
2074
		$post_id = $_REQUEST['post_id'];
2075 View Code Duplication
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
2076
			echo wp_json_encode( array(
2077
				'success' => false,
2078
				'data'    => array(
2079
					'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2080
					'filename' => $_FILES['async-upload']['name'],
2081
				)
2082
			) );
2083
2084
			wp_die();
2085
		}
2086
	} else {
2087
		$post_id = null;
2088
	}
2089
2090
	$post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
2091
2092
	// If the context is custom header or background, make sure the uploaded file is an image.
2093
	if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
2094
		$wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2095 View Code Duplication
		if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2096
			echo wp_json_encode( array(
2097
				'success' => false,
2098
				'data'    => array(
2099
					'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2100
					'filename' => $_FILES['async-upload']['name'],
2101
				)
2102
			) );
2103
2104
			wp_die();
2105
		}
2106
	}
2107
2108
	$attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2109
2110
	if ( is_wp_error( $attachment_id ) ) {
2111
		echo wp_json_encode( array(
2112
			'success' => false,
2113
			'data'    => array(
2114
				'message'  => $attachment_id->get_error_message(),
2115
				'filename' => $_FILES['async-upload']['name'],
2116
			)
2117
		) );
2118
2119
		wp_die();
2120
	}
2121
2122
	if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2123
		if ( 'custom-background' === $post_data['context'] )
2124
			update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2125
2126
		if ( 'custom-header' === $post_data['context'] )
2127
			update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2128
	}
2129
2130
	if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
2131
		wp_die();
2132
2133
	echo wp_json_encode( array(
2134
		'success' => true,
2135
		'data'    => $attachment,
2136
	) );
2137
2138
	wp_die();
2139
}
2140
2141
/**
2142
 * Ajax handler for image editing.
2143
 *
2144
 * @since 3.1.0
2145
 */
2146
function wp_ajax_image_editor() {
2147
	$attachment_id = intval($_POST['postid']);
2148
	if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
2149
		wp_die( -1 );
2150
2151
	check_ajax_referer( "image_editor-$attachment_id" );
2152
	include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
2153
2154
	$msg = false;
2155
	switch ( $_POST['do'] ) {
2156
		case 'save' :
2157
			$msg = wp_save_image($attachment_id);
2158
			$msg = wp_json_encode($msg);
2159
			wp_die( $msg );
0 ignored issues
show
Security Bug introduced by
It seems like $msg defined by wp_json_encode($msg) on line 2158 can also be of type false; however, wp_die() does only seem to accept string|object<WP_Error>, 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...
2160
			break;
2161
		case 'scale' :
2162
			$msg = wp_save_image($attachment_id);
2163
			break;
2164
		case 'restore' :
2165
			$msg = wp_restore_image($attachment_id);
2166
			break;
2167
	}
2168
2169
	wp_image_editor($attachment_id, $msg);
2170
	wp_die();
2171
}
2172
2173
/**
2174
 * Ajax handler for setting the featured image.
2175
 *
2176
 * @since 3.1.0
2177
 */
2178
function wp_ajax_set_post_thumbnail() {
2179
	$json = ! empty( $_REQUEST['json'] ); // New-style request
2180
2181
	$post_ID = intval( $_POST['post_id'] );
2182
	if ( ! current_user_can( 'edit_post', $post_ID ) )
2183
		wp_die( -1 );
2184
2185
	$thumbnail_id = intval( $_POST['thumbnail_id'] );
2186
2187
	if ( $json )
2188
		check_ajax_referer( "update-post_$post_ID" );
2189
	else
2190
		check_ajax_referer( "set_post_thumbnail-$post_ID" );
2191
2192
	if ( $thumbnail_id == '-1' ) {
2193
		if ( delete_post_thumbnail( $post_ID ) ) {
2194
			$return = _wp_post_thumbnail_html( null, $post_ID );
2195
			$json ? wp_send_json_success( $return ) : wp_die( $return );
2196
		} else {
2197
			wp_die( 0 );
2198
		}
2199
	}
2200
2201
	if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2202
		$return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2203
		$json ? wp_send_json_success( $return ) : wp_die( $return );
2204
	}
2205
2206
	wp_die( 0 );
2207
}
2208
2209
/**
2210
 * Ajax handler for retrieving HTML for the featured image.
2211
 *
2212
 * @since 4.6.0
2213
 */
2214
function wp_ajax_get_post_thumbnail_html() {
2215
	$post_ID = intval( $_POST['post_id'] );
2216
2217
	check_ajax_referer( "update-post_$post_ID" );
2218
2219
	if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2220
		wp_die( -1 );
2221
	}
2222
2223
	$thumbnail_id = intval( $_POST['thumbnail_id'] );
2224
2225
	// For backward compatibility, -1 refers to no featured image.
2226
	if ( -1 === $thumbnail_id ) {
2227
		$thumbnail_id = null;
2228
	}
2229
2230
	$return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2231
	wp_send_json_success( $return );
2232
}
2233
2234
/**
2235
 * Ajax handler for setting the featured image for an attachment.
2236
 *
2237
 * @since 4.0.0
2238
 *
2239
 * @see set_post_thumbnail()
2240
 */
2241
function wp_ajax_set_attachment_thumbnail() {
2242
	if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2243
		wp_send_json_error();
2244
	}
2245
2246
	$thumbnail_id = (int) $_POST['thumbnail_id'];
2247
	if ( empty( $thumbnail_id ) ) {
2248
		wp_send_json_error();
2249
	}
2250
2251
	$post_ids = array();
2252
	// For each URL, try to find its corresponding post ID.
2253
	foreach ( $_POST['urls'] as $url ) {
2254
		$post_id = attachment_url_to_postid( $url );
2255
		if ( ! empty( $post_id ) ) {
2256
			$post_ids[] = $post_id;
2257
		}
2258
	}
2259
2260
	if ( empty( $post_ids ) ) {
2261
		wp_send_json_error();
2262
	}
2263
2264
	$success = 0;
2265
	// For each found attachment, set its thumbnail.
2266
	foreach ( $post_ids as $post_id ) {
2267
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
2268
			continue;
2269
		}
2270
2271
		if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2272
			$success++;
2273
		}
2274
	}
2275
2276
	if ( 0 === $success ) {
2277
		wp_send_json_error();
2278
	} else {
2279
		wp_send_json_success();
2280
	}
2281
2282
	wp_send_json_error();
2283
}
2284
2285
/**
2286
 * Ajax handler for date formatting.
2287
 *
2288
 * @since 3.1.0
2289
 */
2290
function wp_ajax_date_format() {
2291
	wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['date']) targeting wp_unslash() can also be of type array; however, sanitize_option() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2292
}
2293
2294
/**
2295
 * Ajax handler for time formatting.
2296
 *
2297
 * @since 3.1.0
2298
 */
2299
function wp_ajax_time_format() {
2300
	wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['date']) targeting wp_unslash() can also be of type array; however, sanitize_option() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2301
}
2302
2303
/**
2304
 * Ajax handler for saving posts from the fullscreen editor.
2305
 *
2306
 * @since 3.1.0
2307
 * @deprecated 4.3.0
2308
 */
2309
function wp_ajax_wp_fullscreen_save_post() {
2310
	$post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2311
2312
	$post = null;
2313
2314
	if ( $post_id )
2315
		$post = get_post( $post_id );
2316
2317
	check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2318
2319
	$post_id = edit_post();
2320
2321
	if ( is_wp_error( $post_id ) ) {
2322
		wp_send_json_error();
2323
	}
2324
2325
	if ( $post ) {
2326
		$last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2327
		$last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2328
	} else {
2329
		$last_date = date_i18n( __( 'F j, Y' ) );
2330
		$last_time = date_i18n( __( 'g:i a' ) );
2331
	}
2332
2333
	if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2334
		$last_user = get_userdata( $last_id );
2335
		$last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2336
	} else {
2337
		$last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2338
	}
2339
2340
	wp_send_json_success( array( 'last_edited' => $last_edited ) );
2341
}
2342
2343
/**
2344
 * Ajax handler for removing a post lock.
2345
 *
2346
 * @since 3.1.0
2347
 */
2348
function wp_ajax_wp_remove_post_lock() {
2349
	if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2350
		wp_die( 0 );
2351
	$post_id = (int) $_POST['post_ID'];
2352
	if ( ! $post = get_post( $post_id ) )
2353
		wp_die( 0 );
2354
2355
	check_ajax_referer( 'update-post_' . $post_id );
2356
2357
	if ( ! current_user_can( 'edit_post', $post_id ) )
2358
		wp_die( -1 );
2359
2360
	$active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2361
	if ( $active_lock[1] != get_current_user_id() )
2362
		wp_die( 0 );
2363
2364
	/**
2365
	 * Filters the post lock window duration.
2366
	 *
2367
	 * @since 3.3.0
2368
	 *
2369
	 * @param int $interval The interval in seconds the post lock duration
2370
	 *                      should last, plus 5 seconds. Default 150.
2371
	 */
2372
	$new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2373
	update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2374
	wp_die( 1 );
2375
}
2376
2377
/**
2378
 * Ajax handler for dismissing a WordPress pointer.
2379
 *
2380
 * @since 3.1.0
2381
 */
2382
function wp_ajax_dismiss_wp_pointer() {
2383
	$pointer = $_POST['pointer'];
2384
	if ( $pointer != sanitize_key( $pointer ) )
2385
		wp_die( 0 );
2386
2387
//	check_ajax_referer( 'dismiss-pointer_' . $pointer );
2388
2389
	$dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2390
2391
	if ( in_array( $pointer, $dismissed ) )
2392
		wp_die( 0 );
2393
2394
	$dismissed[] = $pointer;
2395
	$dismissed = implode( ',', $dismissed );
2396
2397
	update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2398
	wp_die( 1 );
2399
}
2400
2401
/**
2402
 * Ajax handler for getting an attachment.
2403
 *
2404
 * @since 3.5.0
2405
 */
2406
function wp_ajax_get_attachment() {
2407
	if ( ! isset( $_REQUEST['id'] ) )
2408
		wp_send_json_error();
2409
2410
	if ( ! $id = absint( $_REQUEST['id'] ) )
2411
		wp_send_json_error();
2412
2413
	if ( ! $post = get_post( $id ) )
2414
		wp_send_json_error();
2415
2416
	if ( 'attachment' != $post->post_type )
2417
		wp_send_json_error();
2418
2419
	if ( ! current_user_can( 'upload_files' ) )
2420
		wp_send_json_error();
2421
2422
	if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2423
		wp_send_json_error();
2424
2425
	wp_send_json_success( $attachment );
2426
}
2427
2428
/**
2429
 * Ajax handler for querying attachments.
2430
 *
2431
 * @since 3.5.0
2432
 */
2433
function wp_ajax_query_attachments() {
2434
	if ( ! current_user_can( 'upload_files' ) )
2435
		wp_send_json_error();
2436
2437
	$query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2438
	$keys = array(
2439
		's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2440
		'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2441
	);
2442
	foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2443
		if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2444
			$keys[] = $t->query_var;
2445
		}
2446
	}
2447
2448
	$query = array_intersect_key( $query, array_flip( $keys ) );
2449
	$query['post_type'] = 'attachment';
2450
	if ( MEDIA_TRASH
2451
		&& ! empty( $_REQUEST['query']['post_status'] )
2452
		&& 'trash' === $_REQUEST['query']['post_status'] ) {
2453
		$query['post_status'] = 'trash';
2454
	} else {
2455
		$query['post_status'] = 'inherit';
2456
	}
2457
2458
	if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2459
		$query['post_status'] .= ',private';
2460
2461
	// Filter query clauses to include filenames.
2462
	if ( isset( $query['s'] ) ) {
2463
		add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
2464
	}
2465
2466
	/**
2467
	 * Filters the arguments passed to WP_Query during an Ajax
2468
	 * call for querying attachments.
2469
	 *
2470
	 * @since 3.7.0
2471
	 *
2472
	 * @see WP_Query::parse_query()
2473
	 *
2474
	 * @param array $query An array of query variables.
2475
	 */
2476
	$query = apply_filters( 'ajax_query_attachments_args', $query );
2477
	$query = new WP_Query( $query );
2478
2479
	$posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2480
	$posts = array_filter( $posts );
2481
2482
	wp_send_json_success( $posts );
2483
}
2484
2485
/**
2486
 * Ajax handler for updating attachment attributes.
2487
 *
2488
 * @since 3.5.0
2489
 */
2490
function wp_ajax_save_attachment() {
2491
	if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2492
		wp_send_json_error();
2493
2494
	if ( ! $id = absint( $_REQUEST['id'] ) )
2495
		wp_send_json_error();
2496
2497
	check_ajax_referer( 'update-post_' . $id, 'nonce' );
2498
2499
	if ( ! current_user_can( 'edit_post', $id ) )
2500
		wp_send_json_error();
2501
2502
	$changes = $_REQUEST['changes'];
2503
	$post    = get_post( $id, ARRAY_A );
2504
2505
	if ( 'attachment' != $post['post_type'] )
2506
		wp_send_json_error();
2507
2508
	if ( isset( $changes['parent'] ) )
2509
		$post['post_parent'] = $changes['parent'];
2510
2511
	if ( isset( $changes['title'] ) )
2512
		$post['post_title'] = $changes['title'];
2513
2514
	if ( isset( $changes['caption'] ) )
2515
		$post['post_excerpt'] = $changes['caption'];
2516
2517
	if ( isset( $changes['description'] ) )
2518
		$post['post_content'] = $changes['description'];
2519
2520
	if ( MEDIA_TRASH && isset( $changes['status'] ) )
2521
		$post['post_status'] = $changes['status'];
2522
2523 View Code Duplication
	if ( isset( $changes['alt'] ) ) {
2524
		$alt = wp_unslash( $changes['alt'] );
2525
		if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2526
			$alt = wp_strip_all_tags( $alt, true );
0 ignored issues
show
Bug introduced by
It seems like $alt can also be of type array; however, wp_strip_all_tags() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2527
			update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2528
		}
2529
	}
2530
2531
	if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2532
		$changed = false;
2533
		$id3data = wp_get_attachment_metadata( $post['ID'] );
2534
		if ( ! is_array( $id3data ) ) {
2535
			$changed = true;
2536
			$id3data = array();
2537
		}
2538
		foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2539
			if ( isset( $changes[ $key ] ) ) {
2540
				$changed = true;
2541
				$id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($changes[$key]) targeting wp_unslash() can also be of type array; however, sanitize_text_field() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2542
			}
2543
		}
2544
2545
		if ( $changed ) {
2546
			wp_update_attachment_metadata( $id, $id3data );
2547
		}
2548
	}
2549
2550
	if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2551
		wp_delete_post( $id );
2552
	} else {
2553
		wp_update_post( $post );
2554
	}
2555
2556
	wp_send_json_success();
2557
}
2558
2559
/**
2560
 * Ajax handler for saving backward compatible attachment attributes.
2561
 *
2562
 * @since 3.5.0
2563
 */
2564
function wp_ajax_save_attachment_compat() {
2565
	if ( ! isset( $_REQUEST['id'] ) )
2566
		wp_send_json_error();
2567
2568
	if ( ! $id = absint( $_REQUEST['id'] ) )
2569
		wp_send_json_error();
2570
2571
	if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2572
		wp_send_json_error();
2573
	$attachment_data = $_REQUEST['attachments'][ $id ];
2574
2575
	check_ajax_referer( 'update-post_' . $id, 'nonce' );
2576
2577
	if ( ! current_user_can( 'edit_post', $id ) )
2578
		wp_send_json_error();
2579
2580
	$post = get_post( $id, ARRAY_A );
2581
2582
	if ( 'attachment' != $post['post_type'] )
2583
		wp_send_json_error();
2584
2585
	/** This filter is documented in wp-admin/includes/media.php */
2586
	$post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2587
2588
	if ( isset( $post['errors'] ) ) {
2589
		$errors = $post['errors']; // @todo return me and display me!
2590
		unset( $post['errors'] );
2591
	}
2592
2593
	wp_update_post( $post );
2594
2595 View Code Duplication
	foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2596
		if ( isset( $attachment_data[ $taxonomy ] ) )
2597
			wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2598
	}
2599
2600
	if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2601
		wp_send_json_error();
2602
2603
	wp_send_json_success( $attachment );
2604
}
2605
2606
/**
2607
 * Ajax handler for saving the attachment order.
2608
 *
2609
 * @since 3.5.0
2610
 */
2611
function wp_ajax_save_attachment_order() {
2612
	if ( ! isset( $_REQUEST['post_id'] ) )
2613
		wp_send_json_error();
2614
2615
	if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2616
		wp_send_json_error();
2617
2618
	if ( empty( $_REQUEST['attachments'] ) )
2619
		wp_send_json_error();
2620
2621
	check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2622
2623
	$attachments = $_REQUEST['attachments'];
2624
2625
	if ( ! current_user_can( 'edit_post', $post_id ) )
2626
		wp_send_json_error();
2627
2628
	foreach ( $attachments as $attachment_id => $menu_order ) {
2629
		if ( ! current_user_can( 'edit_post', $attachment_id ) )
2630
			continue;
2631
		if ( ! $attachment = get_post( $attachment_id ) )
2632
			continue;
2633
		if ( 'attachment' != $attachment->post_type )
2634
			continue;
2635
2636
		wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2637
	}
2638
2639
	wp_send_json_success();
2640
}
2641
2642
/**
2643
 * Ajax handler for sending an attachment to the editor.
2644
 *
2645
 * Generates the HTML to send an attachment to the editor.
2646
 * Backward compatible with the {@see 'media_send_to_editor'} filter
2647
 * and the chain of filters that follow.
2648
 *
2649
 * @since 3.5.0
2650
 */
2651
function wp_ajax_send_attachment_to_editor() {
2652
	check_ajax_referer( 'media-send-to-editor', 'nonce' );
2653
2654
	$attachment = wp_unslash( $_POST['attachment'] );
2655
2656
	$id = intval( $attachment['id'] );
2657
2658
	if ( ! $post = get_post( $id ) )
2659
		wp_send_json_error();
2660
2661
	if ( 'attachment' != $post->post_type )
2662
		wp_send_json_error();
2663
2664
	if ( current_user_can( 'edit_post', $id ) ) {
2665
		// If this attachment is unattached, attach it. Primarily a back compat thing.
2666
		if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2667
			wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2668
		}
2669
	}
2670
2671
	$url = empty( $attachment['url'] ) ? '' : $attachment['url'];
2672
	$rel = ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url );
2673
2674
	remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2675
2676
	if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2677
		$align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2678
		$size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2679
		$alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2680
2681
		// No whitespace-only captions.
2682
		$caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2683
		if ( '' === trim( $caption ) ) {
2684
			$caption = '';
2685
		}
2686
2687
		$title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2688
		$html = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
2689
	} elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post )  ) {
2690
		$html = stripslashes_deep( $_POST['html'] );
2691
	} else {
2692
		$html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2693
		$rel = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized
2694
2695
		if ( ! empty( $url ) ) {
2696
			$html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2697
		}
2698
	}
2699
2700
	/** This filter is documented in wp-admin/includes/media.php */
2701
	$html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2702
2703
	wp_send_json_success( $html );
2704
}
2705
2706
/**
2707
 * Ajax handler for sending a link to the editor.
2708
 *
2709
 * Generates the HTML to send a non-image embed link to the editor.
2710
 *
2711
 * Backward compatible with the following filters:
2712
 * - file_send_to_editor_url
2713
 * - audio_send_to_editor_url
2714
 * - video_send_to_editor_url
2715
 *
2716
 * @since 3.5.0
2717
 *
2718
 * @global WP_Post  $post
2719
 * @global WP_Embed $wp_embed
2720
 */
2721
function wp_ajax_send_link_to_editor() {
2722
	global $post, $wp_embed;
2723
2724
	check_ajax_referer( 'media-send-to-editor', 'nonce' );
2725
2726
	if ( ! $src = wp_unslash( $_POST['src'] ) )
2727
		wp_send_json_error();
2728
2729
	if ( ! strpos( $src, '://' ) )
2730
		$src = 'http://' . $src;
2731
2732
	if ( ! $src = esc_url_raw( $src ) )
0 ignored issues
show
Bug introduced by
It seems like $src can also be of type array; however, esc_url_raw() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2733
		wp_send_json_error();
2734
2735
	if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2736
		$link_text = wp_basename( $src );
2737
2738
	$post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2739
2740
	// Ping WordPress for an embed.
2741
	$check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2742
2743
	// Fallback that WordPress creates when no oEmbed was found.
2744
	$fallback = $wp_embed->maybe_make_link( $src );
2745
2746
	if ( $check_embed !== $fallback ) {
2747
		// TinyMCE view for [embed] will parse this
2748
		$html = '[embed]' . $src . '[/embed]';
2749
	} elseif ( $link_text ) {
2750
		$html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2751
	} else {
2752
		$html = '';
2753
	}
2754
2755
	// Figure out what filter to run:
2756
	$type = 'file';
2757 View Code Duplication
	if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2758
		&& ( 'audio' == $ext_type || 'video' == $ext_type ) )
2759
			$type = $ext_type;
2760
2761
	/** This filter is documented in wp-admin/includes/media.php */
2762
	$html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
2763
2764
	wp_send_json_success( $html );
2765
}
2766
2767
/**
2768
 * Ajax handler for the Heartbeat API.
2769
 *
2770
 * Runs when the user is logged in.
2771
 *
2772
 * @since 3.6.0
2773
 */
2774
function wp_ajax_heartbeat() {
2775
	if ( empty( $_POST['_nonce'] ) ) {
2776
		wp_send_json_error();
2777
	}
2778
2779
	$response = $data = array();
2780
	$nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
2781
2782
	// screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2783 View Code Duplication
	if ( ! empty( $_POST['screen_id'] ) ) {
2784
		$screen_id = sanitize_key($_POST['screen_id']);
2785
	} else {
2786
		$screen_id = 'front';
2787
	}
2788
2789
	if ( ! empty( $_POST['data'] ) ) {
2790
		$data = wp_unslash( (array) $_POST['data'] );
2791
	}
2792
2793
	if ( 1 !== $nonce_state ) {
2794
		$response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
2795
2796
		if ( false === $nonce_state ) {
2797
			// User is logged in but nonces have expired.
2798
			$response['nonces_expired'] = true;
2799
			wp_send_json( $response );
2800
		}
2801
	}
2802
2803
	if ( ! empty( $data ) ) {
2804
		/**
2805
		 * Filters the Heartbeat response received.
2806
		 *
2807
		 * @since 3.6.0
2808
		 *
2809
		 * @param array  $response  The Heartbeat response.
2810
		 * @param array  $data      The $_POST data sent.
2811
		 * @param string $screen_id The screen id.
2812
		 */
2813
		$response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2814
	}
2815
2816
	/**
2817
	 * Filters the Heartbeat response sent.
2818
	 *
2819
	 * @since 3.6.0
2820
	 *
2821
	 * @param array  $response  The Heartbeat response.
2822
	 * @param string $screen_id The screen id.
2823
	 */
2824
	$response = apply_filters( 'heartbeat_send', $response, $screen_id );
2825
2826
	/**
2827
	 * Fires when Heartbeat ticks in logged-in environments.
2828
	 *
2829
	 * Allows the transport to be easily replaced with long-polling.
2830
	 *
2831
	 * @since 3.6.0
2832
	 *
2833
	 * @param array  $response  The Heartbeat response.
2834
	 * @param string $screen_id The screen id.
2835
	 */
2836
	do_action( 'heartbeat_tick', $response, $screen_id );
2837
2838
	// Send the current time according to the server
2839
	$response['server_time'] = time();
2840
2841
	wp_send_json( $response );
2842
}
2843
2844
/**
2845
 * Ajax handler for getting revision diffs.
2846
 *
2847
 * @since 3.6.0
2848
 */
2849
function wp_ajax_get_revision_diffs() {
2850
	require ABSPATH . 'wp-admin/includes/revision.php';
2851
2852
	if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2853
		wp_send_json_error();
2854
2855
	if ( ! current_user_can( 'edit_post', $post->ID ) )
2856
		wp_send_json_error();
2857
2858
	// Really just pre-loading the cache here.
2859
	if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2860
		wp_send_json_error();
2861
2862
	$return = array();
2863
	@set_time_limit( 0 );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2864
2865
	foreach ( $_REQUEST['compare'] as $compare_key ) {
2866
		list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2867
2868
		$return[] = array(
2869
			'id' => $compare_key,
2870
			'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2871
		);
2872
	}
2873
	wp_send_json_success( $return );
2874
}
2875
2876
/**
2877
 * Ajax handler for auto-saving the selected color scheme for
2878
 * a user's own profile.
2879
 *
2880
 * @since 3.8.0
2881
 *
2882
 * @global array $_wp_admin_css_colors
2883
 */
2884
function wp_ajax_save_user_color_scheme() {
2885
	global $_wp_admin_css_colors;
2886
2887
	check_ajax_referer( 'save-color-scheme', 'nonce' );
2888
2889
	$color_scheme = sanitize_key( $_POST['color_scheme'] );
2890
2891
	if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2892
		wp_send_json_error();
2893
	}
2894
2895
	$previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2896
	update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2897
2898
	wp_send_json_success( array(
2899
		'previousScheme' => 'admin-color-' . $previous_color_scheme,
2900
		'currentScheme'  => 'admin-color-' . $color_scheme
2901
	) );
2902
}
2903
2904
/**
2905
 * Ajax handler for getting themes from themes_api().
2906
 *
2907
 * @since 3.9.0
2908
 *
2909
 * @global array $themes_allowedtags
2910
 * @global array $theme_field_defaults
2911
 */
2912
function wp_ajax_query_themes() {
2913
	global $themes_allowedtags, $theme_field_defaults;
2914
2915
	if ( ! current_user_can( 'install_themes' ) ) {
2916
		wp_send_json_error();
2917
	}
2918
2919
	$args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2920
		'per_page' => 20,
2921
		'fields'   => $theme_field_defaults
2922
	) );
2923
2924
	if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
2925
		$user = get_user_option( 'wporg_favorites' );
2926
		if ( $user ) {
2927
			$args['user'] = $user;
2928
		}
2929
	}
2930
2931
	$old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2932
2933
	/** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2934
	$args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2935
2936
	$api = themes_api( 'query_themes', $args );
2937
2938
	if ( is_wp_error( $api ) ) {
2939
		wp_send_json_error();
2940
	}
2941
2942
	$update_php = network_admin_url( 'update.php?action=install-theme' );
2943
	foreach ( $api->themes as &$theme ) {
2944
		$theme->install_url = add_query_arg( array(
2945
			'theme'    => $theme->slug,
2946
			'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2947
		), $update_php );
2948
2949
		if ( current_user_can( 'switch_themes' ) ) {
2950
			if ( is_multisite() ) {
2951
				$theme->activate_url = add_query_arg( array(
2952
					'action'   => 'enable',
2953
					'_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
2954
					'theme'    => $theme->slug,
2955
				), network_admin_url( 'themes.php' ) );
2956
			} else {
2957
				$theme->activate_url = add_query_arg( array(
2958
					'action'     => 'activate',
2959
					'_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
2960
					'stylesheet' => $theme->slug,
2961
				), admin_url( 'themes.php' ) );
2962
			}
2963
		}
2964
2965 View Code Duplication
		if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
2966
			$theme->customize_url = add_query_arg( array(
2967
				'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
2968
			), wp_customize_url( $theme->slug ) );
2969
		}
2970
2971
		$theme->name        = wp_kses( $theme->name, $themes_allowedtags );
2972
		$theme->author      = wp_kses( $theme->author, $themes_allowedtags );
2973
		$theme->version     = wp_kses( $theme->version, $themes_allowedtags );
2974
		$theme->description = wp_kses( $theme->description, $themes_allowedtags );
2975
		$theme->stars       = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) );
2976
		$theme->num_ratings = number_format_i18n( $theme->num_ratings );
2977
		$theme->preview_url = set_url_scheme( $theme->preview_url );
2978
	}
2979
2980
	wp_send_json_success( $api );
2981
}
2982
2983
/**
2984
 * Apply [embed] Ajax handlers to a string.
2985
 *
2986
 * @since 4.0.0
2987
 *
2988
 * @global WP_Post    $post       Global $post.
2989
 * @global WP_Embed   $wp_embed   Embed API instance.
2990
 * @global WP_Scripts $wp_scripts
2991
 */
2992
function wp_ajax_parse_embed() {
2993
	global $post, $wp_embed;
2994
2995
	if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2996
		wp_send_json_error();
2997
	}
2998
2999
	if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
3000
		wp_send_json_error();
3001
	}
3002
3003
	$shortcode = wp_unslash( $_POST['shortcode'] );
3004
3005
	preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3006
	$atts = shortcode_parse_atts( $matches[3] );
3007
	if ( ! empty( $matches[5] ) ) {
3008
		$url = $matches[5];
3009
	} elseif ( ! empty( $atts['src'] ) ) {
3010
		$url = $atts['src'];
3011
	} else {
3012
		$url = '';
3013
	}
3014
3015
	$parsed = false;
3016
	setup_postdata( $post );
3017
3018
	$wp_embed->return_false_on_fail = true;
3019
3020
	if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
3021
		// Admin is ssl and the user pasted non-ssl URL.
3022
		// Check if the provider supports ssl embeds and use that for the preview.
3023
		$ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3024
		$parsed = $wp_embed->run_shortcode( $ssl_shortcode );
3025
3026
		if ( ! $parsed ) {
3027
			$no_ssl_support = true;
3028
		}
3029
	}
3030
3031
	if ( $url && ! $parsed ) {
3032
		$parsed = $wp_embed->run_shortcode( $shortcode );
3033
	}
3034
3035
	if ( ! $parsed ) {
3036
		wp_send_json_error( array(
3037
			'type' => 'not-embeddable',
3038
			'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3039
		) );
3040
	}
3041
3042
	if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3043
		$styles = '';
3044
		$mce_styles = wpview_media_sandbox_styles();
3045
		foreach ( $mce_styles as $style ) {
3046
			$styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
3047
		}
3048
3049
		$html = do_shortcode( $parsed );
3050
3051
		global $wp_scripts;
3052
		if ( ! empty( $wp_scripts ) ) {
3053
			$wp_scripts->done = array();
3054
		}
3055
		ob_start();
3056
		wp_print_scripts( 'wp-mediaelement' );
3057
		$scripts = ob_get_clean();
3058
3059
		$parsed = $styles . $html . $scripts;
3060
	}
3061
3062
	if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3063
		preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3064
		// Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3065
		wp_send_json_error( array(
3066
			'type' => 'not-ssl',
3067
			'message' => __( 'This preview is unavailable in the editor.' ),
3068
		) );
3069
	}
3070
3071
	$return = array(
3072
		'body' => $parsed,
3073
		'attr' => $wp_embed->last_attr
3074
	);
3075
3076
	if ( strpos( $parsed, 'class="wp-embedded-content' ) ) {
3077
		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3078
			$script_src = includes_url( 'js/wp-embed.js' );
3079
		} else {
3080
			$script_src = includes_url( 'js/wp-embed.min.js' );
3081
		}
3082
3083
		$return['head'] = '<script src="' . $script_src . '"></script>';
3084
		$return['sandbox'] = true;
3085
	}
3086
3087
	wp_send_json_success( $return );
3088
}
3089
3090
/**
3091
 * @since 4.0.0
3092
 *
3093
 * @global WP_Post    $post
3094
 * @global WP_Scripts $wp_scripts
3095
 */
3096
function wp_ajax_parse_media_shortcode() {
3097
	global $post, $wp_scripts;
3098
3099
	if ( empty( $_POST['shortcode'] ) ) {
3100
		wp_send_json_error();
3101
	}
3102
3103
	$shortcode = wp_unslash( $_POST['shortcode'] );
3104
3105
	if ( ! empty( $_POST['post_ID'] ) ) {
3106
		$post = get_post( (int) $_POST['post_ID'] );
3107
	}
3108
3109
	// the embed shortcode requires a post
3110
	if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3111
		if ( 'embed' === $shortcode ) {
3112
			wp_send_json_error();
3113
		}
3114
	} else {
3115
		setup_postdata( $post );
3116
	}
3117
3118
	$parsed = do_shortcode( $shortcode  );
3119
3120
	if ( empty( $parsed ) ) {
3121
		wp_send_json_error( array(
3122
			'type' => 'no-items',
3123
			'message' => __( 'No items found.' ),
3124
		) );
3125
	}
3126
3127
	$head = '';
3128
	$styles = wpview_media_sandbox_styles();
3129
3130
	foreach ( $styles as $style ) {
3131
		$head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3132
	}
3133
3134
	if ( ! empty( $wp_scripts ) ) {
3135
		$wp_scripts->done = array();
3136
	}
3137
3138
	ob_start();
3139
3140
	echo $parsed;
3141
3142
	if ( 'playlist' === $_REQUEST['type'] ) {
3143
		wp_underscore_playlist_templates();
3144
3145
		wp_print_scripts( 'wp-playlist' );
3146
	} else {
3147
		wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
3148
	}
3149
3150
	wp_send_json_success( array(
3151
		'head' => $head,
3152
		'body' => ob_get_clean()
3153
	) );
3154
}
3155
3156
/**
3157
 * Ajax handler for destroying multiple open sessions for a user.
3158
 *
3159
 * @since 4.1.0
3160
 */
3161
function wp_ajax_destroy_sessions() {
3162
	$user = get_userdata( (int) $_POST['user_id'] );
3163
	if ( $user ) {
3164
		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3165
			$user = false;
3166
		} elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3167
			$user = false;
3168
		}
3169
	}
3170
3171
	if ( ! $user ) {
3172
		wp_send_json_error( array(
3173
			'message' => __( 'Could not log out user sessions. Please try again.' ),
3174
		) );
3175
	}
3176
3177
	$sessions = WP_Session_Tokens::get_instance( $user->ID );
3178
3179
	if ( $user->ID === get_current_user_id() ) {
3180
		$sessions->destroy_others( wp_get_session_token() );
3181
		$message = __( 'You are now logged out everywhere else.' );
3182
	} else {
3183
		$sessions->destroy_all();
3184
		/* translators: %s: User's display name. */
3185
		$message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3186
	}
3187
3188
	wp_send_json_success( array( 'message' => $message ) );
3189
}
3190
3191
/**
3192
 * Ajax handler for saving a post from Press This.
3193
 *
3194
 * @since 4.2.0
3195
 */
3196
function wp_ajax_press_this_save_post() {
3197
	include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3198
	$wp_press_this = new WP_Press_This();
3199
	$wp_press_this->save_post();
3200
}
3201
3202
/**
3203
 * Ajax handler for creating new category from Press This.
3204
 *
3205
 * @since 4.2.0
3206
 */
3207
function wp_ajax_press_this_add_category() {
3208
	include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3209
	$wp_press_this = new WP_Press_This();
3210
	$wp_press_this->add_category();
3211
}
3212
3213
/**
3214
 * Ajax handler for cropping an image.
3215
 *
3216
 * @since 4.3.0
3217
 */
3218
function wp_ajax_crop_image() {
3219
	$attachment_id = absint( $_POST['id'] );
3220
3221
	check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3222
	if ( ! current_user_can( 'customize' ) ) {
3223
		wp_send_json_error();
3224
	}
3225
3226
	$context = str_replace( '_', '-', $_POST['context'] );
3227
	$data    = array_map( 'absint', $_POST['cropDetails'] );
3228
	$cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3229
3230
	if ( ! $cropped || is_wp_error( $cropped ) ) {
3231
		wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3232
	}
3233
3234
	switch ( $context ) {
3235
		case 'site-icon':
3236
			require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
3237
			$wp_site_icon = new WP_Site_Icon();
3238
3239
			// Skip creating a new attachment if the attachment is a Site Icon.
3240
			if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3241
3242
				// Delete the temporary cropped file, we don't need it.
3243
				wp_delete_file( $cropped );
3244
3245
				// Additional sizes in wp_prepare_attachment_for_js().
3246
				add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3247
				break;
3248
			}
3249
3250
			/** This filter is documented in wp-admin/custom-header.php */
3251
			$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3252
			$object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3253
			unset( $object['ID'] );
3254
3255
			// Update the attachment.
3256
			add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3257
			$attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3258
			remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3259
3260
			// Additional sizes in wp_prepare_attachment_for_js().
3261
			add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3262
			break;
3263
3264
		default:
3265
3266
			/**
3267
			 * Fires before a cropped image is saved.
3268
			 *
3269
			 * Allows to add filters to modify the way a cropped image is saved.
3270
			 *
3271
			 * @since 4.3.0
3272
			 *
3273
			 * @param string $context       The Customizer control requesting the cropped image.
3274
			 * @param int    $attachment_id The attachment ID of the original image.
3275
			 * @param string $cropped       Path to the cropped image file.
3276
			 */
3277
			do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3278
3279
			/** This filter is documented in wp-admin/custom-header.php */
3280
			$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3281
3282
			$parent_url = wp_get_attachment_url( $attachment_id );
3283
			$url        = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
3284
3285
			$size       = @getimagesize( $cropped );
3286
			$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3287
3288
			$object = array(
3289
				'post_title'     => basename( $cropped ),
3290
				'post_content'   => $url,
3291
				'post_mime_type' => $image_type,
3292
				'guid'           => $url,
3293
				'context'        => $context,
3294
			);
3295
3296
			$attachment_id = wp_insert_attachment( $object, $cropped );
3297
			$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
3298
3299
			/**
3300
			 * Filters the cropped image attachment metadata.
3301
			 *
3302
			 * @since 4.3.0
3303
			 *
3304
			 * @see wp_generate_attachment_metadata()
3305
			 *
3306
			 * @param array $metadata Attachment metadata.
3307
			 */
3308
			$metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3309
			wp_update_attachment_metadata( $attachment_id, $metadata );
3310
3311
			/**
3312
			 * Filters the attachment ID for a cropped image.
3313
			 *
3314
			 * @since 4.3.0
3315
			 *
3316
			 * @param int    $attachment_id The attachment ID of the cropped image.
3317
			 * @param string $context       The Customizer control requesting the cropped image.
3318
			 */
3319
			$attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3320
	}
3321
3322
	wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
3323
}
3324
3325
/**
3326
 * Ajax handler for generating a password.
3327
 *
3328
 * @since 4.4.0
3329
 */
3330
function wp_ajax_generate_password() {
3331
	wp_send_json_success( wp_generate_password( 24 ) );
3332
}
3333
3334
/**
3335
 * Ajax handler for saving the user's WordPress.org username.
3336
 *
3337
 * @since 4.4.0
3338
 */
3339
function wp_ajax_save_wporg_username() {
3340
	if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
3341
		wp_send_json_error();
3342
	}
3343
3344
	check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
3345
3346
	$username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
3347
3348
	if ( ! $username ) {
3349
		wp_send_json_error();
3350
	}
3351
3352
	wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
3353
}
3354
3355
/**
3356
 * Ajax handler for installing a theme.
3357
 *
3358
 * @since 4.6.0
3359
 *
3360
 * @see Theme_Upgrader
3361
 */
3362
function wp_ajax_install_theme() {
3363
	check_ajax_referer( 'updates' );
3364
3365
	if ( empty( $_POST['slug'] ) ) {
3366
		wp_send_json_error( array(
3367
			'slug'         => '',
3368
			'errorCode'    => 'no_theme_specified',
3369
			'errorMessage' => __( 'No theme specified.' ),
3370
		) );
3371
	}
3372
3373
	$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['slug']) targeting wp_unslash() can also be of type array; however, sanitize_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3374
3375
	$status = array(
3376
		'install' => 'theme',
3377
		'slug'    => $slug,
3378
	);
3379
3380
	if ( ! current_user_can( 'install_themes' ) ) {
3381
		$status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
3382
		wp_send_json_error( $status );
3383
	}
3384
3385
	include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3386
	include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3387
3388
	$api = themes_api( 'theme_information', array(
3389
		'slug'   => $slug,
3390
		'fields' => array( 'sections' => false ),
3391
	) );
3392
3393
	if ( is_wp_error( $api ) ) {
3394
		$status['errorMessage'] = $api->get_error_message();
3395
		wp_send_json_error( $status );
3396
	}
3397
3398
	$skin     = new WP_Ajax_Upgrader_Skin();
3399
	$upgrader = new Theme_Upgrader( $skin );
3400
	$result   = $upgrader->install( $api->download_link );
3401
3402
	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3403
		$status['debug'] = $skin->get_upgrade_messages();
3404
	}
3405
3406 View Code Duplication
	if ( is_wp_error( $result ) ) {
3407
		$status['errorCode']    = $result->get_error_code();
3408
		$status['errorMessage'] = $result->get_error_message();
3409
		wp_send_json_error( $status );
3410
	} elseif ( is_wp_error( $skin->result ) ) {
3411
		$status['errorCode']    = $skin->result->get_error_code();
3412
		$status['errorMessage'] = $skin->result->get_error_message();
3413
		wp_send_json_error( $status );
3414
	} elseif ( $skin->get_errors()->get_error_code() ) {
3415
		$status['errorMessage'] = $skin->get_error_messages();
3416
		wp_send_json_error( $status );
3417
	} elseif ( is_null( $result ) ) {
3418
		global $wp_filesystem;
3419
3420
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3421
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3422
3423
		// Pass through the error from WP_Filesystem if one was raised.
3424
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3425
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3426
		}
3427
3428
		wp_send_json_error( $status );
3429
	}
3430
3431
	$status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
3432
3433
	if ( current_user_can( 'switch_themes' ) ) {
3434
		if ( is_multisite() ) {
3435
			$status['activateUrl'] = add_query_arg( array(
3436
				'action'   => 'enable',
3437
				'_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
3438
				'theme'    => $slug,
3439
			), network_admin_url( 'themes.php' ) );
3440
		} else {
3441
			$status['activateUrl'] = add_query_arg( array(
3442
				'action'     => 'activate',
3443
				'_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
3444
				'stylesheet' => $slug,
3445
			), admin_url( 'themes.php' ) );
3446
		}
3447
	}
3448
3449 View Code Duplication
	if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3450
		$status['customizeUrl'] = add_query_arg( array(
3451
			'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3452
		), wp_customize_url( $slug ) );
3453
	}
3454
3455
	/*
3456
	 * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
3457
	 * on post-install status.
3458
	 */
3459
	wp_send_json_success( $status );
3460
}
3461
3462
/**
3463
 * Ajax handler for updating a theme.
3464
 *
3465
 * @since 4.6.0
3466
 *
3467
 * @see Theme_Upgrader
3468
 */
3469
function wp_ajax_update_theme() {
3470
	check_ajax_referer( 'updates' );
3471
3472
	if ( empty( $_POST['slug'] ) ) {
3473
		wp_send_json_error( array(
3474
			'slug'         => '',
3475
			'errorCode'    => 'no_theme_specified',
3476
			'errorMessage' => __( 'No theme specified.' ),
3477
		) );
3478
	}
3479
3480
	$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3481
	$status     = array(
3482
		'update'     => 'theme',
3483
		'slug'       => $stylesheet,
3484
		'newVersion' => '',
3485
	);
3486
3487
	if ( ! current_user_can( 'update_themes' ) ) {
3488
		$status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
3489
		wp_send_json_error( $status );
3490
	}
3491
3492
	include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3493
3494
	$current = get_site_transient( 'update_themes' );
3495
	if ( empty( $current ) ) {
3496
		wp_update_themes();
3497
	}
3498
3499
	$skin     = new WP_Ajax_Upgrader_Skin();
3500
	$upgrader = new Theme_Upgrader( $skin );
3501
	$result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
3502
3503
	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3504
		$status['debug'] = $skin->get_upgrade_messages();
3505
	}
3506
3507
	if ( is_wp_error( $skin->result ) ) {
3508
		$status['errorCode']    = $skin->result->get_error_code();
3509
		$status['errorMessage'] = $skin->result->get_error_message();
3510
		wp_send_json_error( $status );
3511
	} elseif ( $skin->get_errors()->get_error_code() ) {
3512
		$status['errorMessage'] = $skin->get_error_messages();
3513
		wp_send_json_error( $status );
3514
	} elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
3515
3516
		// Theme is already at the latest version.
3517
		if ( true === $result[ $stylesheet ] ) {
3518
			$status['errorMessage'] = $upgrader->strings['up_to_date'];
3519
			wp_send_json_error( $status );
3520
		}
3521
3522
		$theme = wp_get_theme( $stylesheet );
3523
		if ( $theme->get( 'Version' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $theme->get('Version') of type string|false 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...
3524
			$status['newVersion'] = $theme->get( 'Version' );
3525
		}
3526
3527
		wp_send_json_success( $status );
3528
	} elseif ( false === $result ) {
3529
		global $wp_filesystem;
3530
3531
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3532
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3533
3534
		// Pass through the error from WP_Filesystem if one was raised.
3535
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3536
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3537
		}
3538
3539
		wp_send_json_error( $status );
3540
	}
3541
3542
	// An unhandled error occurred.
3543
	$status['errorMessage'] = __( 'Update failed.' );
3544
	wp_send_json_error( $status );
3545
}
3546
3547
/**
3548
 * Ajax handler for deleting a theme.
3549
 *
3550
 * @since 4.6.0
3551
 *
3552
 * @see delete_theme()
3553
 */
3554
function wp_ajax_delete_theme() {
3555
	check_ajax_referer( 'updates' );
3556
3557
	if ( empty( $_POST['slug'] ) ) {
3558
		wp_send_json_error( array(
3559
			'slug'         => '',
3560
			'errorCode'    => 'no_theme_specified',
3561
			'errorMessage' => __( 'No theme specified.' ),
3562
		) );
3563
	}
3564
3565
	$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3566
	$status     = array(
3567
		'delete' => 'theme',
3568
		'slug'   => $stylesheet,
3569
	);
3570
3571
	if ( ! current_user_can( 'delete_themes' ) ) {
3572
		$status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
3573
		wp_send_json_error( $status );
3574
	}
3575
3576
	if ( ! wp_get_theme( $stylesheet )->exists() ) {
3577
		$status['errorMessage'] = __( 'The requested theme does not exist.' );
3578
		wp_send_json_error( $status );
3579
	}
3580
3581
	// Check filesystem credentials. `delete_theme()` will bail otherwise.
3582
	$url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
3583
	ob_start();
3584
	$credentials = request_filesystem_credentials( $url );
3585
	ob_end_clean();
3586
	if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3587
		global $wp_filesystem;
3588
3589
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3590
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3591
3592
		// Pass through the error from WP_Filesystem if one was raised.
3593
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3594
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3595
		}
3596
3597
		wp_send_json_error( $status );
3598
	}
3599
3600
	include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3601
3602
	$result = delete_theme( $stylesheet );
3603
3604 View Code Duplication
	if ( is_wp_error( $result ) ) {
3605
		$status['errorMessage'] = $result->get_error_message();
3606
		wp_send_json_error( $status );
3607
	} elseif ( false === $result ) {
3608
		$status['errorMessage'] = __( 'Theme could not be deleted.' );
3609
		wp_send_json_error( $status );
3610
	}
3611
3612
	wp_send_json_success( $status );
3613
}
3614
3615
/**
3616
 * Ajax handler for installing a plugin.
3617
 *
3618
 * @since 4.6.0
3619
 *
3620
 * @see Plugin_Upgrader
3621
 */
3622
function wp_ajax_install_plugin() {
3623
	check_ajax_referer( 'updates' );
3624
3625 View Code Duplication
	if ( empty( $_POST['slug'] ) ) {
3626
		wp_send_json_error( array(
3627
			'slug'         => '',
3628
			'errorCode'    => 'no_plugin_specified',
3629
			'errorMessage' => __( 'No plugin specified.' ),
3630
		) );
3631
	}
3632
3633
	$status = array(
3634
		'install' => 'plugin',
3635
		'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['slug']) targeting wp_unslash() can also be of type array; however, sanitize_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3636
	);
3637
3638
	if ( ! current_user_can( 'install_plugins' ) ) {
3639
		$status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
3640
		wp_send_json_error( $status );
3641
	}
3642
3643
	include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3644
	include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
3645
3646
	$api = plugins_api( 'plugin_information', array(
3647
		'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['slug']) targeting wp_unslash() can also be of type array; however, sanitize_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3648
		'fields' => array(
3649
			'sections' => false,
3650
		),
3651
	) );
3652
3653
	if ( is_wp_error( $api ) ) {
3654
		$status['errorMessage'] = $api->get_error_message();
3655
		wp_send_json_error( $status );
3656
	}
3657
3658
	$status['pluginName'] = $api->name;
3659
3660
	$skin     = new WP_Ajax_Upgrader_Skin();
3661
	$upgrader = new Plugin_Upgrader( $skin );
3662
	$result   = $upgrader->install( $api->download_link );
3663
3664
	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3665
		$status['debug'] = $skin->get_upgrade_messages();
3666
	}
3667
3668 View Code Duplication
	if ( is_wp_error( $result ) ) {
3669
		$status['errorCode']    = $result->get_error_code();
3670
		$status['errorMessage'] = $result->get_error_message();
3671
		wp_send_json_error( $status );
3672
	} elseif ( is_wp_error( $skin->result ) ) {
3673
		$status['errorCode']    = $skin->result->get_error_code();
3674
		$status['errorMessage'] = $skin->result->get_error_message();
3675
		wp_send_json_error( $status );
3676
	} elseif ( $skin->get_errors()->get_error_code() ) {
3677
		$status['errorMessage'] = $skin->get_error_messages();
3678
		wp_send_json_error( $status );
3679
	} elseif ( is_null( $result ) ) {
3680
		global $wp_filesystem;
3681
3682
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3683
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3684
3685
		// Pass through the error from WP_Filesystem if one was raised.
3686
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3687
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3688
		}
3689
3690
		wp_send_json_error( $status );
3691
	}
3692
3693
	$install_status = install_plugin_install_status( $api );
3694
	$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3695
3696
	// If install request is coming from import page, do not return network activation link.
3697
	$plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
3698
3699
	if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) {
3700
		$status['activateUrl'] = add_query_arg( array(
3701
			'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
3702
			'action'   => 'activate',
3703
			'plugin'   => $install_status['file'],
3704
		), $plugins_url );
3705
	}
3706
3707
	if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
3708
		$status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
3709
	}
3710
3711
	wp_send_json_success( $status );
3712
}
3713
3714
/**
3715
 * Ajax handler for updating a plugin.
3716
 *
3717
 * @since 4.2.0
3718
 *
3719
 * @see Plugin_Upgrader
3720
 */
3721
function wp_ajax_update_plugin() {
3722
	check_ajax_referer( 'updates' );
3723
3724 View Code Duplication
	if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
3725
		wp_send_json_error( array(
3726
			'slug'         => '',
3727
			'errorCode'    => 'no_plugin_specified',
3728
			'errorMessage' => __( 'No plugin specified.' ),
3729
		) );
3730
	}
3731
3732
	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['plugin']) targeting wp_unslash() can also be of type array; however, sanitize_text_field() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3733
3734
	$status = array(
3735
		'update'     => 'plugin',
3736
		'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['slug']) targeting wp_unslash() can also be of type array; however, sanitize_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3737
		'oldVersion' => '',
3738
		'newVersion' => '',
3739
	);
3740
3741 View Code Duplication
	if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
3742
		$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
3743
		wp_send_json_error( $status );
3744
	}
3745
3746
	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3747
	$status['plugin']     = $plugin;
3748
	$status['pluginName'] = $plugin_data['Name'];
3749
3750 View Code Duplication
	if ( $plugin_data['Version'] ) {
3751
		/* translators: %s: Plugin version */
3752
		$status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3753
	}
3754
3755
	include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3756
3757
	wp_update_plugins();
3758
3759
	$skin     = new WP_Ajax_Upgrader_Skin();
3760
	$upgrader = new Plugin_Upgrader( $skin );
3761
	$result   = $upgrader->bulk_upgrade( array( $plugin ) );
3762
3763
	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3764
		$status['debug'] = $skin->get_upgrade_messages();
3765
	}
3766
3767
	if ( is_wp_error( $skin->result ) ) {
3768
		$status['errorCode']    = $skin->result->get_error_code();
3769
		$status['errorMessage'] = $skin->result->get_error_message();
3770
		wp_send_json_error( $status );
3771
	} elseif ( $skin->get_errors()->get_error_code() ) {
3772
		$status['errorMessage'] = $skin->get_error_messages();
3773
		wp_send_json_error( $status );
3774
	} elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
3775
		$plugin_update_data = current( $result );
3776
3777
		/*
3778
		 * If the `update_plugins` site transient is empty (e.g. when you update
3779
		 * two plugins in quick succession before the transient repopulates),
3780
		 * this may be the return.
3781
		 *
3782
		 * Preferably something can be done to ensure `update_plugins` isn't empty.
3783
		 * For now, surface some sort of error here.
3784
		 */
3785
		if ( true === $plugin_update_data ) {
3786
			$status['errorMessage'] = __( 'Plugin update failed.' );
3787
			wp_send_json_error( $status );
3788
		}
3789
3790
		$plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
3791
		$plugin_data = reset( $plugin_data );
3792
3793 View Code Duplication
		if ( $plugin_data['Version'] ) {
3794
			/* translators: %s: Plugin version */
3795
			$status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3796
		}
3797
		wp_send_json_success( $status );
3798
	} elseif ( false === $result ) {
3799
		global $wp_filesystem;
3800
3801
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3802
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3803
3804
		// Pass through the error from WP_Filesystem if one was raised.
3805
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3806
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3807
		}
3808
3809
		wp_send_json_error( $status );
3810
	}
3811
3812
	// An unhandled error occurred.
3813
	$status['errorMessage'] = __( 'Plugin update failed.' );
3814
	wp_send_json_error( $status );
3815
}
3816
3817
/**
3818
 * Ajax handler for deleting a plugin.
3819
 *
3820
 * @since 4.6.0
3821
 *
3822
 * @see delete_plugins()
3823
 */
3824
function wp_ajax_delete_plugin() {
3825
	check_ajax_referer( 'updates' );
3826
3827 View Code Duplication
	if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
3828
		wp_send_json_error( array(
3829
			'slug'         => '',
3830
			'errorCode'    => 'no_plugin_specified',
3831
			'errorMessage' => __( 'No plugin specified.' ),
3832
		) );
3833
	}
3834
3835
	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['plugin']) targeting wp_unslash() can also be of type array; however, sanitize_text_field() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3836
3837
	$status = array(
3838
		'delete' => 'plugin',
3839
		'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_POST['slug']) targeting wp_unslash() can also be of type array; however, sanitize_key() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3840
	);
3841
3842 View Code Duplication
	if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
3843
		$status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
3844
		wp_send_json_error( $status );
3845
	}
3846
3847
	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3848
	$status['plugin']     = $plugin;
3849
	$status['pluginName'] = $plugin_data['Name'];
3850
3851
	if ( is_plugin_active( $plugin ) ) {
3852
		$status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
3853
		wp_send_json_error( $status );
3854
	}
3855
3856
	// Check filesystem credentials. `delete_plugins()` will bail otherwise.
3857
	$url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
3858
	ob_start();
3859
	$credentials = request_filesystem_credentials( $url );
3860
	ob_end_clean();
3861
	if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3862
		global $wp_filesystem;
3863
3864
		$status['errorCode']    = 'unable_to_connect_to_filesystem';
3865
		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3866
3867
		// Pass through the error from WP_Filesystem if one was raised.
3868
		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3869
			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3870
		}
3871
3872
		wp_send_json_error( $status );
3873
	}
3874
3875
	$result = delete_plugins( array( $plugin ) );
3876
3877 View Code Duplication
	if ( is_wp_error( $result ) ) {
3878
		$status['errorMessage'] = $result->get_error_message();
3879
		wp_send_json_error( $status );
3880
	} elseif ( false === $result ) {
3881
		$status['errorMessage'] = __( 'Plugin could not be deleted.' );
3882
		wp_send_json_error( $status );
3883
	}
3884
3885
	wp_send_json_success( $status );
3886
}
3887
3888
/**
3889
 * Ajax handler for searching plugins.
3890
 *
3891
 * @since 4.6.0
3892
 *
3893
 * @global string $s Search term.
3894
 */
3895
function wp_ajax_search_plugins() {
3896
	check_ajax_referer( 'updates' );
3897
3898
	$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3899
	if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
3900
		set_current_screen( $pagenow );
3901
	}
3902
3903
	/** @var WP_Plugins_List_Table $wp_list_table */
3904
	$wp_list_table = _get_list_table( 'WP_Plugins_List_Table', array(
3905
		'screen' => get_current_screen(),
3906
	) );
3907
3908
	$status = array();
3909
3910
	if ( ! $wp_list_table->ajax_user_can() ) {
3911
		$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3912
		wp_send_json_error( $status );
3913
	}
3914
3915
	// Set the correct requester, so pagination works.
3916
	$_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3917
		'_ajax_nonce' => null,
3918
		'action'      => null,
3919
	) ), network_admin_url( 'plugins.php', 'relative' ) );
3920
3921
	$GLOBALS['s'] = wp_unslash( $_POST['s'] );
3922
3923
	$wp_list_table->prepare_items();
3924
3925
	ob_start();
3926
	$wp_list_table->display();
3927
	$status['count'] = count( $wp_list_table->items );
3928
	$status['items'] = ob_get_clean();
3929
3930
	wp_send_json_success( $status );
3931
}
3932
3933
/**
3934
 * Ajax handler for searching plugins to install.
3935
 *
3936
 * @since 4.6.0
3937
 */
3938
function wp_ajax_search_install_plugins() {
3939
	check_ajax_referer( 'updates' );
3940
3941
	$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3942
	if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
3943
		set_current_screen( $pagenow );
3944
	}
3945
3946
	/** @var WP_Plugin_Install_List_Table $wp_list_table */
3947
	$wp_list_table = _get_list_table( 'WP_Plugin_Install_List_Table', array(
3948
		'screen' => get_current_screen(),
3949
	) );
3950
3951
	$status = array();
3952
3953
	if ( ! $wp_list_table->ajax_user_can() ) {
3954
		$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3955
		wp_send_json_error( $status );
3956
	}
3957
3958
	// Set the correct requester, so pagination works.
3959
	$_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3960
		'_ajax_nonce' => null,
3961
		'action'      => null,
3962
	) ), network_admin_url( 'plugin-install.php', 'relative' ) );
3963
3964
	$wp_list_table->prepare_items();
3965
3966
	ob_start();
3967
	$wp_list_table->display();
3968
	$status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
3969
	$status['items'] = ob_get_clean();
3970
3971
	wp_send_json_success( $status );
3972
}
3973