angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

ajax-actions.php (146713B)


      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 the no-privilege context.
     16  *
     17  * Runs when the user is not logged in.
     18  *
     19  * @since 3.6.0
     20  */
     21 function wp_ajax_nopriv_heartbeat() {
     22 	$response = array();
     23 
     24 	// 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
     25 	if ( ! empty( $_POST['screen_id'] ) ) {
     26 		$screen_id = sanitize_key( $_POST['screen_id'] );
     27 	} else {
     28 		$screen_id = 'front';
     29 	}
     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  $response  The no-priv Heartbeat response.
     40 		 * @param array  $data      The $_POST data sent.
     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 in no-privilege environments when no data is passed.
     48 	 *
     49 	 * @since 3.6.0
     50 	 *
     51 	 * @param array  $response  The no-priv Heartbeat response.
     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  $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 
    114 	if ( ! $tax ) {
    115 		wp_die( 0 );
    116 	}
    117 
    118 	if ( ! current_user_can( $tax->cap->assign_terms ) ) {
    119 		wp_die( -1 );
    120 	}
    121 
    122 	$s = wp_unslash( $_GET['q'] );
    123 
    124 	$comma = _x( ',', 'tag delimiter' );
    125 	if ( ',' !== $comma ) {
    126 		$s = str_replace( $comma, ',', $s );
    127 	}
    128 
    129 	if ( false !== strpos( $s, ',' ) ) {
    130 		$s = explode( ',', $s );
    131 		$s = $s[ count( $s ) - 1 ];
    132 	}
    133 
    134 	$s = trim( $s );
    135 
    136 	/**
    137 	 * Filters the minimum number of characters required to fire a tag search via Ajax.
    138 	 *
    139 	 * @since 4.0.0
    140 	 *
    141 	 * @param int         $characters The minimum number of characters required. Default 2.
    142 	 * @param WP_Taxonomy $tax        The taxonomy object.
    143 	 * @param string      $s          The search term.
    144 	 */
    145 	$term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
    146 
    147 	/*
    148 	 * Require $term_search_min_chars chars for matching (default: 2)
    149 	 * ensure it's a non-negative, non-zero integer.
    150 	 */
    151 	if ( ( 0 == $term_search_min_chars ) || ( strlen( $s ) < $term_search_min_chars ) ) {
    152 		wp_die();
    153 	}
    154 
    155 	$results = get_terms(
    156 		array(
    157 			'taxonomy'   => $taxonomy,
    158 			'name__like' => $s,
    159 			'fields'     => 'names',
    160 			'hide_empty' => false,
    161 		)
    162 	);
    163 
    164 	echo implode( "\n", $results );
    165 	wp_die();
    166 }
    167 
    168 /**
    169  * Ajax handler for compression testing.
    170  *
    171  * @since 3.1.0
    172  */
    173 function wp_ajax_wp_compression_test() {
    174 	if ( ! current_user_can( 'manage_options' ) ) {
    175 		wp_die( -1 );
    176 	}
    177 
    178 	if ( ini_get( 'zlib.output_compression' ) || 'ob_gzhandler' === ini_get( 'output_handler' ) ) {
    179 		update_site_option( 'can_compress_scripts', 0 );
    180 		wp_die( 0 );
    181 	}
    182 
    183 	if ( isset( $_GET['test'] ) ) {
    184 		header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
    185 		header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
    186 		header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
    187 		header( 'Content-Type: application/javascript; charset=UTF-8' );
    188 		$force_gzip = ( defined( 'ENFORCE_GZIP' ) && ENFORCE_GZIP );
    189 		$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."';
    190 
    191 		if ( 1 == $_GET['test'] ) {
    192 			echo $test_str;
    193 			wp_die();
    194 		} elseif ( 2 == $_GET['test'] ) {
    195 			if ( ! isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
    196 				wp_die( -1 );
    197 			}
    198 
    199 			if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate' ) && function_exists( 'gzdeflate' ) && ! $force_gzip ) {
    200 				header( 'Content-Encoding: deflate' );
    201 				$out = gzdeflate( $test_str, 1 );
    202 			} elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) && function_exists( 'gzencode' ) ) {
    203 				header( 'Content-Encoding: gzip' );
    204 				$out = gzencode( $test_str, 1 );
    205 			} else {
    206 				wp_die( -1 );
    207 			}
    208 
    209 			echo $out;
    210 			wp_die();
    211 		} elseif ( 'no' === $_GET['test'] ) {
    212 			check_ajax_referer( 'update_can_compress_scripts' );
    213 			update_site_option( 'can_compress_scripts', 0 );
    214 		} elseif ( 'yes' === $_GET['test'] ) {
    215 			check_ajax_referer( 'update_can_compress_scripts' );
    216 			update_site_option( 'can_compress_scripts', 1 );
    217 		}
    218 	}
    219 
    220 	wp_die( 0 );
    221 }
    222 
    223 /**
    224  * Ajax handler for image editor previews.
    225  *
    226  * @since 3.1.0
    227  */
    228 function wp_ajax_imgedit_preview() {
    229 	$post_id = (int) $_GET['postid'];
    230 	if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) {
    231 		wp_die( -1 );
    232 	}
    233 
    234 	check_ajax_referer( "image_editor-$post_id" );
    235 
    236 	include_once ABSPATH . 'wp-admin/includes/image-edit.php';
    237 
    238 	if ( ! stream_preview_image( $post_id ) ) {
    239 		wp_die( -1 );
    240 	}
    241 
    242 	wp_die();
    243 }
    244 
    245 /**
    246  * Ajax handler for oEmbed caching.
    247  *
    248  * @since 3.1.0
    249  *
    250  * @global WP_Embed $wp_embed
    251  */
    252 function wp_ajax_oembed_cache() {
    253 	$GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
    254 	wp_die( 0 );
    255 }
    256 
    257 /**
    258  * Ajax handler for user autocomplete.
    259  *
    260  * @since 3.4.0
    261  */
    262 function wp_ajax_autocomplete_user() {
    263 	if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) ) {
    264 		wp_die( -1 );
    265 	}
    266 
    267 	/** This filter is documented in wp-admin/user-new.php */
    268 	if ( ! current_user_can( 'manage_network_users' ) && ! apply_filters( 'autocomplete_users_for_site_admins', false ) ) {
    269 		wp_die( -1 );
    270 	}
    271 
    272 	$return = array();
    273 
    274 	// Check the type of request.
    275 	// Current allowed values are `add` and `search`.
    276 	if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
    277 		$type = $_REQUEST['autocomplete_type'];
    278 	} else {
    279 		$type = 'add';
    280 	}
    281 
    282 	// Check the desired field for value.
    283 	// Current allowed values are `user_email` and `user_login`.
    284 	if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
    285 		$field = $_REQUEST['autocomplete_field'];
    286 	} else {
    287 		$field = 'user_login';
    288 	}
    289 
    290 	// Exclude current users of this blog.
    291 	if ( isset( $_REQUEST['site_id'] ) ) {
    292 		$id = absint( $_REQUEST['site_id'] );
    293 	} else {
    294 		$id = get_current_blog_id();
    295 	}
    296 
    297 	$include_blog_users = ( 'search' === $type ? get_users(
    298 		array(
    299 			'blog_id' => $id,
    300 			'fields'  => 'ID',
    301 		)
    302 	) : array() );
    303 
    304 	$exclude_blog_users = ( 'add' === $type ? get_users(
    305 		array(
    306 			'blog_id' => $id,
    307 			'fields'  => 'ID',
    308 		)
    309 	) : array() );
    310 
    311 	$users = get_users(
    312 		array(
    313 			'blog_id'        => false,
    314 			'search'         => '*' . $_REQUEST['term'] . '*',
    315 			'include'        => $include_blog_users,
    316 			'exclude'        => $exclude_blog_users,
    317 			'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
    318 		)
    319 	);
    320 
    321 	foreach ( $users as $user ) {
    322 		$return[] = array(
    323 			/* translators: 1: User login, 2: User email address. */
    324 			'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
    325 			'value' => $user->$field,
    326 		);
    327 	}
    328 
    329 	wp_die( wp_json_encode( $return ) );
    330 }
    331 
    332 /**
    333  * Handles Ajax requests for community events
    334  *
    335  * @since 4.8.0
    336  */
    337 function wp_ajax_get_community_events() {
    338 	require_once ABSPATH . 'wp-admin/includes/class-wp-community-events.php';
    339 
    340 	check_ajax_referer( 'community_events' );
    341 
    342 	$search         = isset( $_POST['location'] ) ? wp_unslash( $_POST['location'] ) : '';
    343 	$timezone       = isset( $_POST['timezone'] ) ? wp_unslash( $_POST['timezone'] ) : '';
    344 	$user_id        = get_current_user_id();
    345 	$saved_location = get_user_option( 'community-events-location', $user_id );
    346 	$events_client  = new WP_Community_Events( $user_id, $saved_location );
    347 	$events         = $events_client->get_events( $search, $timezone );
    348 	$ip_changed     = false;
    349 
    350 	if ( is_wp_error( $events ) ) {
    351 		wp_send_json_error(
    352 			array(
    353 				'error' => $events->get_error_message(),
    354 			)
    355 		);
    356 	} else {
    357 		if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
    358 			$ip_changed = true;
    359 		} elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
    360 			$ip_changed = true;
    361 		}
    362 
    363 		/*
    364 		 * The location should only be updated when it changes. The API doesn't always return
    365 		 * a full location; sometimes it's missing the description or country. The location
    366 		 * that was saved during the initial request is known to be good and complete, though.
    367 		 * It should be left intact until the user explicitly changes it (either by manually
    368 		 * searching for a new location, or by changing their IP address).
    369 		 *
    370 		 * If the location was updated with an incomplete response from the API, then it could
    371 		 * break assumptions that the UI makes (e.g., that there will always be a description
    372 		 * that corresponds to a latitude/longitude location).
    373 		 *
    374 		 * The location is stored network-wide, so that the user doesn't have to set it on each site.
    375 		 */
    376 		if ( $ip_changed || $search ) {
    377 			update_user_meta( $user_id, 'community-events-location', $events['location'] );
    378 		}
    379 
    380 		wp_send_json_success( $events );
    381 	}
    382 }
    383 
    384 /**
    385  * Ajax handler for dashboard widgets.
    386  *
    387  * @since 3.4.0
    388  */
    389 function wp_ajax_dashboard_widgets() {
    390 	require_once ABSPATH . 'wp-admin/includes/dashboard.php';
    391 
    392 	$pagenow = $_GET['pagenow'];
    393 	if ( 'dashboard-user' === $pagenow || 'dashboard-network' === $pagenow || 'dashboard' === $pagenow ) {
    394 		set_current_screen( $pagenow );
    395 	}
    396 
    397 	switch ( $_GET['widget'] ) {
    398 		case 'dashboard_primary':
    399 			wp_dashboard_primary();
    400 			break;
    401 	}
    402 	wp_die();
    403 }
    404 
    405 /**
    406  * Ajax handler for Customizer preview logged-in status.
    407  *
    408  * @since 3.4.0
    409  */
    410 function wp_ajax_logged_in() {
    411 	wp_die( 1 );
    412 }
    413 
    414 //
    415 // Ajax helpers.
    416 //
    417 
    418 /**
    419  * Sends back current comment total and new page links if they need to be updated.
    420  *
    421  * Contrary to normal success Ajax response ("1"), die with time() on success.
    422  *
    423  * @since 2.7.0
    424  * @access private
    425  *
    426  * @param int $comment_id
    427  * @param int $delta
    428  */
    429 function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
    430 	$total    = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
    431 	$per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
    432 	$page     = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
    433 	$url      = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : '';
    434 
    435 	// JS didn't send us everything we need to know. Just die with success message.
    436 	if ( ! $total || ! $per_page || ! $page || ! $url ) {
    437 		$time           = time();
    438 		$comment        = get_comment( $comment_id );
    439 		$comment_status = '';
    440 		$comment_link   = '';
    441 
    442 		if ( $comment ) {
    443 			$comment_status = $comment->comment_approved;
    444 		}
    445 
    446 		if ( 1 === (int) $comment_status ) {
    447 			$comment_link = get_comment_link( $comment );
    448 		}
    449 
    450 		$counts = wp_count_comments();
    451 
    452 		$x = new WP_Ajax_Response(
    453 			array(
    454 				'what'         => 'comment',
    455 				// Here for completeness - not used.
    456 				'id'           => $comment_id,
    457 				'supplemental' => array(
    458 					'status'               => $comment_status,
    459 					'postId'               => $comment ? $comment->comment_post_ID : '',
    460 					'time'                 => $time,
    461 					'in_moderation'        => $counts->moderated,
    462 					'i18n_comments_text'   => sprintf(
    463 						/* translators: %s: Number of comments. */
    464 						_n( '%s Comment', '%s Comments', $counts->approved ),
    465 						number_format_i18n( $counts->approved )
    466 					),
    467 					'i18n_moderation_text' => sprintf(
    468 						/* translators: %s: Number of comments. */
    469 						_n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
    470 						number_format_i18n( $counts->moderated )
    471 					),
    472 					'comment_link'         => $comment_link,
    473 				),
    474 			)
    475 		);
    476 		$x->send();
    477 	}
    478 
    479 	$total += $delta;
    480 	if ( $total < 0 ) {
    481 		$total = 0;
    482 	}
    483 
    484 	// Only do the expensive stuff on a page-break, and about 1 other time per page.
    485 	if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
    486 		$post_id = 0;
    487 		// What type of comment count are we looking for?
    488 		$status = 'all';
    489 		$parsed = parse_url( $url );
    490 
    491 		if ( isset( $parsed['query'] ) ) {
    492 			parse_str( $parsed['query'], $query_vars );
    493 
    494 			if ( ! empty( $query_vars['comment_status'] ) ) {
    495 				$status = $query_vars['comment_status'];
    496 			}
    497 
    498 			if ( ! empty( $query_vars['p'] ) ) {
    499 				$post_id = (int) $query_vars['p'];
    500 			}
    501 
    502 			if ( ! empty( $query_vars['comment_type'] ) ) {
    503 				$type = $query_vars['comment_type'];
    504 			}
    505 		}
    506 
    507 		if ( empty( $type ) ) {
    508 			// Only use the comment count if not filtering by a comment_type.
    509 			$comment_count = wp_count_comments( $post_id );
    510 
    511 			// We're looking for a known type of comment count.
    512 			if ( isset( $comment_count->$status ) ) {
    513 				$total = $comment_count->$status;
    514 			}
    515 		}
    516 		// Else use the decremented value from above.
    517 	}
    518 
    519 	// The time since the last comment count.
    520 	$time    = time();
    521 	$comment = get_comment( $comment_id );
    522 	$counts  = wp_count_comments();
    523 
    524 	$x = new WP_Ajax_Response(
    525 		array(
    526 			'what'         => 'comment',
    527 			'id'           => $comment_id,
    528 			'supplemental' => array(
    529 				'status'               => $comment ? $comment->comment_approved : '',
    530 				'postId'               => $comment ? $comment->comment_post_ID : '',
    531 				/* translators: %s: Number of comments. */
    532 				'total_items_i18n'     => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
    533 				'total_pages'          => ceil( $total / $per_page ),
    534 				'total_pages_i18n'     => number_format_i18n( ceil( $total / $per_page ) ),
    535 				'total'                => $total,
    536 				'time'                 => $time,
    537 				'in_moderation'        => $counts->moderated,
    538 				'i18n_moderation_text' => sprintf(
    539 					/* translators: %s: Number of comments. */
    540 					_n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
    541 					number_format_i18n( $counts->moderated )
    542 				),
    543 			),
    544 		)
    545 	);
    546 	$x->send();
    547 }
    548 
    549 //
    550 // POST-based Ajax handlers.
    551 //
    552 
    553 /**
    554  * Ajax handler for adding a hierarchical term.
    555  *
    556  * @since 3.1.0
    557  * @access private
    558  */
    559 function _wp_ajax_add_hierarchical_term() {
    560 	$action   = $_POST['action'];
    561 	$taxonomy = get_taxonomy( substr( $action, 4 ) );
    562 	check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
    563 
    564 	if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
    565 		wp_die( -1 );
    566 	}
    567 
    568 	$names  = explode( ',', $_POST[ 'new' . $taxonomy->name ] );
    569 	$parent = isset( $_POST[ 'new' . $taxonomy->name . '_parent' ] ) ? (int) $_POST[ 'new' . $taxonomy->name . '_parent' ] : 0;
    570 
    571 	if ( 0 > $parent ) {
    572 		$parent = 0;
    573 	}
    574 
    575 	if ( 'category' === $taxonomy->name ) {
    576 		$post_category = isset( $_POST['post_category'] ) ? (array) $_POST['post_category'] : array();
    577 	} else {
    578 		$post_category = ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input'][ $taxonomy->name ] ) ) ? (array) $_POST['tax_input'][ $taxonomy->name ] : array();
    579 	}
    580 
    581 	$checked_categories = array_map( 'absint', (array) $post_category );
    582 	$popular_ids        = wp_popular_terms_checklist( $taxonomy->name, 0, 10, false );
    583 
    584 	foreach ( $names as $cat_name ) {
    585 		$cat_name          = trim( $cat_name );
    586 		$category_nicename = sanitize_title( $cat_name );
    587 
    588 		if ( '' === $category_nicename ) {
    589 			continue;
    590 		}
    591 
    592 		$cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
    593 
    594 		if ( ! $cat_id || is_wp_error( $cat_id ) ) {
    595 			continue;
    596 		} else {
    597 			$cat_id = $cat_id['term_id'];
    598 		}
    599 
    600 		$checked_categories[] = $cat_id;
    601 
    602 		if ( $parent ) { // Do these all at once in a second.
    603 			continue;
    604 		}
    605 
    606 		ob_start();
    607 
    608 		wp_terms_checklist(
    609 			0,
    610 			array(
    611 				'taxonomy'             => $taxonomy->name,
    612 				'descendants_and_self' => $cat_id,
    613 				'selected_cats'        => $checked_categories,
    614 				'popular_cats'         => $popular_ids,
    615 			)
    616 		);
    617 
    618 		$data = ob_get_clean();
    619 
    620 		$add = array(
    621 			'what'     => $taxonomy->name,
    622 			'id'       => $cat_id,
    623 			'data'     => str_replace( array( "\n", "\t" ), '', $data ),
    624 			'position' => -1,
    625 		);
    626 	}
    627 
    628 	if ( $parent ) { // Foncy - replace the parent and all its children.
    629 		$parent  = get_term( $parent, $taxonomy->name );
    630 		$term_id = $parent->term_id;
    631 
    632 		while ( $parent->parent ) { // Get the top parent.
    633 			$parent = get_term( $parent->parent, $taxonomy->name );
    634 			if ( is_wp_error( $parent ) ) {
    635 				break;
    636 			}
    637 			$term_id = $parent->term_id;
    638 		}
    639 
    640 		ob_start();
    641 
    642 		wp_terms_checklist(
    643 			0,
    644 			array(
    645 				'taxonomy'             => $taxonomy->name,
    646 				'descendants_and_self' => $term_id,
    647 				'selected_cats'        => $checked_categories,
    648 				'popular_cats'         => $popular_ids,
    649 			)
    650 		);
    651 
    652 		$data = ob_get_clean();
    653 
    654 		$add = array(
    655 			'what'     => $taxonomy->name,
    656 			'id'       => $term_id,
    657 			'data'     => str_replace( array( "\n", "\t" ), '', $data ),
    658 			'position' => -1,
    659 		);
    660 	}
    661 
    662 	ob_start();
    663 
    664 	wp_dropdown_categories(
    665 		array(
    666 			'taxonomy'         => $taxonomy->name,
    667 			'hide_empty'       => 0,
    668 			'name'             => 'new' . $taxonomy->name . '_parent',
    669 			'orderby'          => 'name',
    670 			'hierarchical'     => 1,
    671 			'show_option_none' => '&mdash; ' . $taxonomy->labels->parent_item . ' &mdash;',
    672 		)
    673 	);
    674 
    675 	$sup = ob_get_clean();
    676 
    677 	$add['supplemental'] = array( 'newcat_parent' => $sup );
    678 
    679 	$x = new WP_Ajax_Response( $add );
    680 	$x->send();
    681 }
    682 
    683 /**
    684  * Ajax handler for deleting a comment.
    685  *
    686  * @since 3.1.0
    687  */
    688 function wp_ajax_delete_comment() {
    689 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    690 
    691 	$comment = get_comment( $id );
    692 
    693 	if ( ! $comment ) {
    694 		wp_die( time() );
    695 	}
    696 
    697 	if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
    698 		wp_die( -1 );
    699 	}
    700 
    701 	check_ajax_referer( "delete-comment_$id" );
    702 	$status = wp_get_comment_status( $comment );
    703 	$delta  = -1;
    704 
    705 	if ( isset( $_POST['trash'] ) && 1 == $_POST['trash'] ) {
    706 		if ( 'trash' === $status ) {
    707 			wp_die( time() );
    708 		}
    709 
    710 		$r = wp_trash_comment( $comment );
    711 	} elseif ( isset( $_POST['untrash'] ) && 1 == $_POST['untrash'] ) {
    712 		if ( 'trash' !== $status ) {
    713 			wp_die( time() );
    714 		}
    715 
    716 		$r = wp_untrash_comment( $comment );
    717 
    718 		// Undo trash, not in Trash.
    719 		if ( ! isset( $_POST['comment_status'] ) || 'trash' !== $_POST['comment_status'] ) {
    720 			$delta = 1;
    721 		}
    722 	} elseif ( isset( $_POST['spam'] ) && 1 == $_POST['spam'] ) {
    723 		if ( 'spam' === $status ) {
    724 			wp_die( time() );
    725 		}
    726 
    727 		$r = wp_spam_comment( $comment );
    728 	} elseif ( isset( $_POST['unspam'] ) && 1 == $_POST['unspam'] ) {
    729 		if ( 'spam' !== $status ) {
    730 			wp_die( time() );
    731 		}
    732 
    733 		$r = wp_unspam_comment( $comment );
    734 
    735 		// Undo spam, not in spam.
    736 		if ( ! isset( $_POST['comment_status'] ) || 'spam' !== $_POST['comment_status'] ) {
    737 			$delta = 1;
    738 		}
    739 	} elseif ( isset( $_POST['delete'] ) && 1 == $_POST['delete'] ) {
    740 		$r = wp_delete_comment( $comment );
    741 	} else {
    742 		wp_die( -1 );
    743 	}
    744 
    745 	if ( $r ) {
    746 		// Decide if we need to send back '1' or a more complicated response including page links and comment counts.
    747 		_wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
    748 	}
    749 
    750 	wp_die( 0 );
    751 }
    752 
    753 /**
    754  * Ajax handler for deleting a tag.
    755  *
    756  * @since 3.1.0
    757  */
    758 function wp_ajax_delete_tag() {
    759 	$tag_id = (int) $_POST['tag_ID'];
    760 	check_ajax_referer( "delete-tag_$tag_id" );
    761 
    762 	if ( ! current_user_can( 'delete_term', $tag_id ) ) {
    763 		wp_die( -1 );
    764 	}
    765 
    766 	$taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
    767 	$tag      = get_term( $tag_id, $taxonomy );
    768 
    769 	if ( ! $tag || is_wp_error( $tag ) ) {
    770 		wp_die( 1 );
    771 	}
    772 
    773 	if ( wp_delete_term( $tag_id, $taxonomy ) ) {
    774 		wp_die( 1 );
    775 	} else {
    776 		wp_die( 0 );
    777 	}
    778 }
    779 
    780 /**
    781  * Ajax handler for deleting a link.
    782  *
    783  * @since 3.1.0
    784  */
    785 function wp_ajax_delete_link() {
    786 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    787 
    788 	check_ajax_referer( "delete-bookmark_$id" );
    789 
    790 	if ( ! current_user_can( 'manage_links' ) ) {
    791 		wp_die( -1 );
    792 	}
    793 
    794 	$link = get_bookmark( $id );
    795 	if ( ! $link || is_wp_error( $link ) ) {
    796 		wp_die( 1 );
    797 	}
    798 
    799 	if ( wp_delete_link( $id ) ) {
    800 		wp_die( 1 );
    801 	} else {
    802 		wp_die( 0 );
    803 	}
    804 }
    805 
    806 /**
    807  * Ajax handler for deleting meta.
    808  *
    809  * @since 3.1.0
    810  */
    811 function wp_ajax_delete_meta() {
    812 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    813 
    814 	check_ajax_referer( "delete-meta_$id" );
    815 	$meta = get_metadata_by_mid( 'post', $id );
    816 
    817 	if ( ! $meta ) {
    818 		wp_die( 1 );
    819 	}
    820 
    821 	if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) ) {
    822 		wp_die( -1 );
    823 	}
    824 
    825 	if ( delete_meta( $meta->meta_id ) ) {
    826 		wp_die( 1 );
    827 	}
    828 
    829 	wp_die( 0 );
    830 }
    831 
    832 /**
    833  * Ajax handler for deleting a post.
    834  *
    835  * @since 3.1.0
    836  *
    837  * @param string $action Action to perform.
    838  */
    839 function wp_ajax_delete_post( $action ) {
    840 	if ( empty( $action ) ) {
    841 		$action = 'delete-post';
    842 	}
    843 
    844 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    845 	check_ajax_referer( "{$action}_$id" );
    846 
    847 	if ( ! current_user_can( 'delete_post', $id ) ) {
    848 		wp_die( -1 );
    849 	}
    850 
    851 	if ( ! get_post( $id ) ) {
    852 		wp_die( 1 );
    853 	}
    854 
    855 	if ( wp_delete_post( $id ) ) {
    856 		wp_die( 1 );
    857 	} else {
    858 		wp_die( 0 );
    859 	}
    860 }
    861 
    862 /**
    863  * Ajax handler for sending a post to the Trash.
    864  *
    865  * @since 3.1.0
    866  *
    867  * @param string $action Action to perform.
    868  */
    869 function wp_ajax_trash_post( $action ) {
    870 	if ( empty( $action ) ) {
    871 		$action = 'trash-post';
    872 	}
    873 
    874 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    875 	check_ajax_referer( "{$action}_$id" );
    876 
    877 	if ( ! current_user_can( 'delete_post', $id ) ) {
    878 		wp_die( -1 );
    879 	}
    880 
    881 	if ( ! get_post( $id ) ) {
    882 		wp_die( 1 );
    883 	}
    884 
    885 	if ( 'trash-post' === $action ) {
    886 		$done = wp_trash_post( $id );
    887 	} else {
    888 		$done = wp_untrash_post( $id );
    889 	}
    890 
    891 	if ( $done ) {
    892 		wp_die( 1 );
    893 	}
    894 
    895 	wp_die( 0 );
    896 }
    897 
    898 /**
    899  * Ajax handler to restore a post from the Trash.
    900  *
    901  * @since 3.1.0
    902  *
    903  * @param string $action Action to perform.
    904  */
    905 function wp_ajax_untrash_post( $action ) {
    906 	if ( empty( $action ) ) {
    907 		$action = 'untrash-post';
    908 	}
    909 
    910 	wp_ajax_trash_post( $action );
    911 }
    912 
    913 /**
    914  * Ajax handler to delete a page.
    915  *
    916  * @since 3.1.0
    917  *
    918  * @param string $action Action to perform.
    919  */
    920 function wp_ajax_delete_page( $action ) {
    921 	if ( empty( $action ) ) {
    922 		$action = 'delete-page';
    923 	}
    924 
    925 	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    926 	check_ajax_referer( "{$action}_$id" );
    927 
    928 	if ( ! current_user_can( 'delete_page', $id ) ) {
    929 		wp_die( -1 );
    930 	}
    931 
    932 	if ( ! get_post( $id ) ) {
    933 		wp_die( 1 );
    934 	}
    935 
    936 	if ( wp_delete_post( $id ) ) {
    937 		wp_die( 1 );
    938 	} else {
    939 		wp_die( 0 );
    940 	}
    941 }
    942 
    943 /**
    944  * Ajax handler to dim a comment.
    945  *
    946  * @since 3.1.0
    947  */
    948 function wp_ajax_dim_comment() {
    949 	$id      = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
    950 	$comment = get_comment( $id );
    951 
    952 	if ( ! $comment ) {
    953 		$x = new WP_Ajax_Response(
    954 			array(
    955 				'what' => 'comment',
    956 				'id'   => new WP_Error(
    957 					'invalid_comment',
    958 					/* translators: %d: Comment ID. */
    959 					sprintf( __( 'Comment %d does not exist' ), $id )
    960 				),
    961 			)
    962 		);
    963 		$x->send();
    964 	}
    965 
    966 	if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) ) {
    967 		wp_die( -1 );
    968 	}
    969 
    970 	$current = wp_get_comment_status( $comment );
    971 
    972 	if ( isset( $_POST['new'] ) && $_POST['new'] == $current ) {
    973 		wp_die( time() );
    974 	}
    975 
    976 	check_ajax_referer( "approve-comment_$id" );
    977 
    978 	if ( in_array( $current, array( 'unapproved', 'spam' ), true ) ) {
    979 		$result = wp_set_comment_status( $comment, 'approve', true );
    980 	} else {
    981 		$result = wp_set_comment_status( $comment, 'hold', true );
    982 	}
    983 
    984 	if ( is_wp_error( $result ) ) {
    985 		$x = new WP_Ajax_Response(
    986 			array(
    987 				'what' => 'comment',
    988 				'id'   => $result,
    989 			)
    990 		);
    991 		$x->send();
    992 	}
    993 
    994 	// Decide if we need to send back '1' or a more complicated response including page links and comment counts.
    995 	_wp_ajax_delete_comment_response( $comment->comment_ID );
    996 	wp_die( 0 );
    997 }
    998 
    999 /**
   1000  * Ajax handler for adding a link category.
   1001  *
   1002  * @since 3.1.0
   1003  *
   1004  * @param string $action Action to perform.
   1005  */
   1006 function wp_ajax_add_link_category( $action ) {
   1007 	if ( empty( $action ) ) {
   1008 		$action = 'add-link-category';
   1009 	}
   1010 
   1011 	check_ajax_referer( $action );
   1012 	$tax = get_taxonomy( 'link_category' );
   1013 
   1014 	if ( ! current_user_can( $tax->cap->manage_terms ) ) {
   1015 		wp_die( -1 );
   1016 	}
   1017 
   1018 	$names = explode( ',', wp_unslash( $_POST['newcat'] ) );
   1019 	$x     = new WP_Ajax_Response();
   1020 
   1021 	foreach ( $names as $cat_name ) {
   1022 		$cat_name = trim( $cat_name );
   1023 		$slug     = sanitize_title( $cat_name );
   1024 
   1025 		if ( '' === $slug ) {
   1026 			continue;
   1027 		}
   1028 
   1029 		$cat_id = wp_insert_term( $cat_name, 'link_category' );
   1030 
   1031 		if ( ! $cat_id || is_wp_error( $cat_id ) ) {
   1032 			continue;
   1033 		} else {
   1034 			$cat_id = $cat_id['term_id'];
   1035 		}
   1036 
   1037 		$cat_name = esc_html( $cat_name );
   1038 
   1039 		$x->add(
   1040 			array(
   1041 				'what'     => 'link-category',
   1042 				'id'       => $cat_id,
   1043 				'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>",
   1044 				'position' => -1,
   1045 			)
   1046 		);
   1047 	}
   1048 	$x->send();
   1049 }
   1050 
   1051 /**
   1052  * Ajax handler to add a tag.
   1053  *
   1054  * @since 3.1.0
   1055  */
   1056 function wp_ajax_add_tag() {
   1057 	check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
   1058 	$taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
   1059 	$tax      = get_taxonomy( $taxonomy );
   1060 
   1061 	if ( ! current_user_can( $tax->cap->edit_terms ) ) {
   1062 		wp_die( -1 );
   1063 	}
   1064 
   1065 	$x = new WP_Ajax_Response();
   1066 
   1067 	$tag = wp_insert_term( $_POST['tag-name'], $taxonomy, $_POST );
   1068 
   1069 	if ( $tag && ! is_wp_error( $tag ) ) {
   1070 		$tag = get_term( $tag['term_id'], $taxonomy );
   1071 	}
   1072 
   1073 	if ( ! $tag || is_wp_error( $tag ) ) {
   1074 		$message = __( 'An error has occurred. Please reload the page and try again.' );
   1075 
   1076 		if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
   1077 			$message = $tag->get_error_message();
   1078 		}
   1079 
   1080 		$x->add(
   1081 			array(
   1082 				'what' => 'taxonomy',
   1083 				'data' => new WP_Error( 'error', $message ),
   1084 			)
   1085 		);
   1086 		$x->send();
   1087 	}
   1088 
   1089 	$wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
   1090 
   1091 	$level     = 0;
   1092 	$noparents = '';
   1093 
   1094 	if ( is_taxonomy_hierarchical( $taxonomy ) ) {
   1095 		$level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
   1096 		ob_start();
   1097 		$wp_list_table->single_row( $tag, $level );
   1098 		$noparents = ob_get_clean();
   1099 	}
   1100 
   1101 	ob_start();
   1102 	$wp_list_table->single_row( $tag );
   1103 	$parents = ob_get_clean();
   1104 
   1105 	$x->add(
   1106 		array(
   1107 			'what'         => 'taxonomy',
   1108 			'supplemental' => compact( 'parents', 'noparents' ),
   1109 		)
   1110 	);
   1111 
   1112 	$x->add(
   1113 		array(
   1114 			'what'         => 'term',
   1115 			'position'     => $level,
   1116 			'supplemental' => (array) $tag,
   1117 		)
   1118 	);
   1119 
   1120 	$x->send();
   1121 }
   1122 
   1123 /**
   1124  * Ajax handler for getting a tagcloud.
   1125  *
   1126  * @since 3.1.0
   1127  */
   1128 function wp_ajax_get_tagcloud() {
   1129 	if ( ! isset( $_POST['tax'] ) ) {
   1130 		wp_die( 0 );
   1131 	}
   1132 
   1133 	$taxonomy = sanitize_key( $_POST['tax'] );
   1134 	$tax      = get_taxonomy( $taxonomy );
   1135 
   1136 	if ( ! $tax ) {
   1137 		wp_die( 0 );
   1138 	}
   1139 
   1140 	if ( ! current_user_can( $tax->cap->assign_terms ) ) {
   1141 		wp_die( -1 );
   1142 	}
   1143 
   1144 	$tags = get_terms(
   1145 		array(
   1146 			'taxonomy' => $taxonomy,
   1147 			'number'   => 45,
   1148 			'orderby'  => 'count',
   1149 			'order'    => 'DESC',
   1150 		)
   1151 	);
   1152 
   1153 	if ( empty( $tags ) ) {
   1154 		wp_die( $tax->labels->not_found );
   1155 	}
   1156 
   1157 	if ( is_wp_error( $tags ) ) {
   1158 		wp_die( $tags->get_error_message() );
   1159 	}
   1160 
   1161 	foreach ( $tags as $key => $tag ) {
   1162 		$tags[ $key ]->link = '#';
   1163 		$tags[ $key ]->id   = $tag->term_id;
   1164 	}
   1165 
   1166 	// We need raw tag names here, so don't filter the output.
   1167 	$return = wp_generate_tag_cloud(
   1168 		$tags,
   1169 		array(
   1170 			'filter' => 0,
   1171 			'format' => 'list',
   1172 		)
   1173 	);
   1174 
   1175 	if ( empty( $return ) ) {
   1176 		wp_die( 0 );
   1177 	}
   1178 
   1179 	echo $return;
   1180 	wp_die();
   1181 }
   1182 
   1183 /**
   1184  * Ajax handler for getting comments.
   1185  *
   1186  * @since 3.1.0
   1187  *
   1188  * @global int $post_id
   1189  *
   1190  * @param string $action Action to perform.
   1191  */
   1192 function wp_ajax_get_comments( $action ) {
   1193 	global $post_id;
   1194 
   1195 	if ( empty( $action ) ) {
   1196 		$action = 'get-comments';
   1197 	}
   1198 
   1199 	check_ajax_referer( $action );
   1200 
   1201 	if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
   1202 		$id = absint( $_REQUEST['p'] );
   1203 		if ( ! empty( $id ) ) {
   1204 			$post_id = $id;
   1205 		}
   1206 	}
   1207 
   1208 	if ( empty( $post_id ) ) {
   1209 		wp_die( -1 );
   1210 	}
   1211 
   1212 	$wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
   1213 
   1214 	if ( ! current_user_can( 'edit_post', $post_id ) ) {
   1215 		wp_die( -1 );
   1216 	}
   1217 
   1218 	$wp_list_table->prepare_items();
   1219 
   1220 	if ( ! $wp_list_table->has_items() ) {
   1221 		wp_die( 1 );
   1222 	}
   1223 
   1224 	$x = new WP_Ajax_Response();
   1225 
   1226 	ob_start();
   1227 	foreach ( $wp_list_table->items as $comment ) {
   1228 		if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved ) {
   1229 			continue;
   1230 		}
   1231 		get_comment( $comment );
   1232 		$wp_list_table->single_row( $comment );
   1233 	}
   1234 	$comment_list_item = ob_get_clean();
   1235 
   1236 	$x->add(
   1237 		array(
   1238 			'what' => 'comments',
   1239 			'data' => $comment_list_item,
   1240 		)
   1241 	);
   1242 
   1243 	$x->send();
   1244 }
   1245 
   1246 /**
   1247  * Ajax handler for replying to a comment.
   1248  *
   1249  * @since 3.1.0
   1250  *
   1251  * @param string $action Action to perform.
   1252  */
   1253 function wp_ajax_replyto_comment( $action ) {
   1254 	if ( empty( $action ) ) {
   1255 		$action = 'replyto-comment';
   1256 	}
   1257 
   1258 	check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
   1259 
   1260 	$comment_post_ID = (int) $_POST['comment_post_ID'];
   1261 	$post            = get_post( $comment_post_ID );
   1262 
   1263 	if ( ! $post ) {
   1264 		wp_die( -1 );
   1265 	}
   1266 
   1267 	if ( ! current_user_can( 'edit_post', $comment_post_ID ) ) {
   1268 		wp_die( -1 );
   1269 	}
   1270 
   1271 	if ( empty( $post->post_status ) ) {
   1272 		wp_die( 1 );
   1273 	} elseif ( in_array( $post->post_status, array( 'draft', 'pending', 'trash' ), true ) ) {
   1274 		wp_die( __( 'Error: You can&#8217;t reply to a comment on a draft post.' ) );
   1275 	}
   1276 
   1277 	$user = wp_get_current_user();
   1278 
   1279 	if ( $user->exists() ) {
   1280 		$user_ID              = $user->ID;
   1281 		$comment_author       = wp_slash( $user->display_name );
   1282 		$comment_author_email = wp_slash( $user->user_email );
   1283 		$comment_author_url   = wp_slash( $user->user_url );
   1284 		$comment_content      = trim( $_POST['content'] );
   1285 		$comment_type         = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : 'comment';
   1286 
   1287 		if ( current_user_can( 'unfiltered_html' ) ) {
   1288 			if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) ) {
   1289 				$_POST['_wp_unfiltered_html_comment'] = '';
   1290 			}
   1291 
   1292 			if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
   1293 				kses_remove_filters(); // Start with a clean slate.
   1294 				kses_init_filters();   // Set up the filters.
   1295 				remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
   1296 				add_filter( 'pre_comment_content', 'wp_filter_kses' );
   1297 			}
   1298 		}
   1299 	} else {
   1300 		wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
   1301 	}
   1302 
   1303 	if ( '' === $comment_content ) {
   1304 		wp_die( __( 'Error: Please type your comment text.' ) );
   1305 	}
   1306 
   1307 	$comment_parent = 0;
   1308 
   1309 	if ( isset( $_POST['comment_ID'] ) ) {
   1310 		$comment_parent = absint( $_POST['comment_ID'] );
   1311 	}
   1312 
   1313 	$comment_auto_approved = false;
   1314 	$commentdata           = compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID' );
   1315 
   1316 	// Automatically approve parent comment.
   1317 	if ( ! empty( $_POST['approve_parent'] ) ) {
   1318 		$parent = get_comment( $comment_parent );
   1319 
   1320 		if ( $parent && '0' === $parent->comment_approved && $parent->comment_post_ID == $comment_post_ID ) {
   1321 			if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
   1322 				wp_die( -1 );
   1323 			}
   1324 
   1325 			if ( wp_set_comment_status( $parent, 'approve' ) ) {
   1326 				$comment_auto_approved = true;
   1327 			}
   1328 		}
   1329 	}
   1330 
   1331 	$comment_id = wp_new_comment( $commentdata );
   1332 
   1333 	if ( is_wp_error( $comment_id ) ) {
   1334 		wp_die( $comment_id->get_error_message() );
   1335 	}
   1336 
   1337 	$comment = get_comment( $comment_id );
   1338 
   1339 	if ( ! $comment ) {
   1340 		wp_die( 1 );
   1341 	}
   1342 
   1343 	$position = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
   1344 
   1345 	ob_start();
   1346 	if ( isset( $_REQUEST['mode'] ) && 'dashboard' === $_REQUEST['mode'] ) {
   1347 		require_once ABSPATH . 'wp-admin/includes/dashboard.php';
   1348 		_wp_dashboard_recent_comments_row( $comment );
   1349 	} else {
   1350 		if ( isset( $_REQUEST['mode'] ) && 'single' === $_REQUEST['mode'] ) {
   1351 			$wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
   1352 		} else {
   1353 			$wp_list_table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
   1354 		}
   1355 		$wp_list_table->single_row( $comment );
   1356 	}
   1357 	$comment_list_item = ob_get_clean();
   1358 
   1359 	$response = array(
   1360 		'what'     => 'comment',
   1361 		'id'       => $comment->comment_ID,
   1362 		'data'     => $comment_list_item,
   1363 		'position' => $position,
   1364 	);
   1365 
   1366 	$counts                   = wp_count_comments();
   1367 	$response['supplemental'] = array(
   1368 		'in_moderation'        => $counts->moderated,
   1369 		'i18n_comments_text'   => sprintf(
   1370 			/* translators: %s: Number of comments. */
   1371 			_n( '%s Comment', '%s Comments', $counts->approved ),
   1372 			number_format_i18n( $counts->approved )
   1373 		),
   1374 		'i18n_moderation_text' => sprintf(
   1375 			/* translators: %s: Number of comments. */
   1376 			_n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
   1377 			number_format_i18n( $counts->moderated )
   1378 		),
   1379 	);
   1380 
   1381 	if ( $comment_auto_approved ) {
   1382 		$response['supplemental']['parent_approved'] = $parent->comment_ID;
   1383 		$response['supplemental']['parent_post_id']  = $parent->comment_post_ID;
   1384 	}
   1385 
   1386 	$x = new WP_Ajax_Response();
   1387 	$x->add( $response );
   1388 	$x->send();
   1389 }
   1390 
   1391 /**
   1392  * Ajax handler for editing a comment.
   1393  *
   1394  * @since 3.1.0
   1395  */
   1396 function wp_ajax_edit_comment() {
   1397 	check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
   1398 
   1399 	$comment_id = (int) $_POST['comment_ID'];
   1400 
   1401 	if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
   1402 		wp_die( -1 );
   1403 	}
   1404 
   1405 	if ( '' === $_POST['content'] ) {
   1406 		wp_die( __( 'Error: Please type your comment text.' ) );
   1407 	}
   1408 
   1409 	if ( isset( $_POST['status'] ) ) {
   1410 		$_POST['comment_status'] = $_POST['status'];
   1411 	}
   1412 
   1413 	$updated = edit_comment();
   1414 	if ( is_wp_error( $updated ) ) {
   1415 		wp_die( $updated->get_error_message() );
   1416 	}
   1417 
   1418 	$position      = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
   1419 	$checkbox      = ( isset( $_POST['checkbox'] ) && true == $_POST['checkbox'] ) ? 1 : 0;
   1420 	$wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
   1421 
   1422 	$comment = get_comment( $comment_id );
   1423 
   1424 	if ( empty( $comment->comment_ID ) ) {
   1425 		wp_die( -1 );
   1426 	}
   1427 
   1428 	ob_start();
   1429 	$wp_list_table->single_row( $comment );
   1430 	$comment_list_item = ob_get_clean();
   1431 
   1432 	$x = new WP_Ajax_Response();
   1433 
   1434 	$x->add(
   1435 		array(
   1436 			'what'     => 'edit_comment',
   1437 			'id'       => $comment->comment_ID,
   1438 			'data'     => $comment_list_item,
   1439 			'position' => $position,
   1440 		)
   1441 	);
   1442 
   1443 	$x->send();
   1444 }
   1445 
   1446 /**
   1447  * Ajax handler for adding a menu item.
   1448  *
   1449  * @since 3.1.0
   1450  */
   1451 function wp_ajax_add_menu_item() {
   1452 	check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
   1453 
   1454 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   1455 		wp_die( -1 );
   1456 	}
   1457 
   1458 	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
   1459 
   1460 	// For performance reasons, we omit some object properties from the checklist.
   1461 	// The following is a hacky way to restore them when adding non-custom items.
   1462 	$menu_items_data = array();
   1463 
   1464 	foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
   1465 		if (
   1466 			! empty( $menu_item_data['menu-item-type'] ) &&
   1467 			'custom' !== $menu_item_data['menu-item-type'] &&
   1468 			! empty( $menu_item_data['menu-item-object-id'] )
   1469 		) {
   1470 			switch ( $menu_item_data['menu-item-type'] ) {
   1471 				case 'post_type':
   1472 					$_object = get_post( $menu_item_data['menu-item-object-id'] );
   1473 					break;
   1474 
   1475 				case 'post_type_archive':
   1476 					$_object = get_post_type_object( $menu_item_data['menu-item-object'] );
   1477 					break;
   1478 
   1479 				case 'taxonomy':
   1480 					$_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
   1481 					break;
   1482 			}
   1483 
   1484 			$_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
   1485 			$_menu_item  = reset( $_menu_items );
   1486 
   1487 			// Restore the missing menu item properties.
   1488 			$menu_item_data['menu-item-description'] = $_menu_item->description;
   1489 		}
   1490 
   1491 		$menu_items_data[] = $menu_item_data;
   1492 	}
   1493 
   1494 	$item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
   1495 	if ( is_wp_error( $item_ids ) ) {
   1496 		wp_die( 0 );
   1497 	}
   1498 
   1499 	$menu_items = array();
   1500 
   1501 	foreach ( (array) $item_ids as $menu_item_id ) {
   1502 		$menu_obj = get_post( $menu_item_id );
   1503 
   1504 		if ( ! empty( $menu_obj->ID ) ) {
   1505 			$menu_obj        = wp_setup_nav_menu_item( $menu_obj );
   1506 			$menu_obj->title = empty( $menu_obj->title ) ? __( 'Menu Item' ) : $menu_obj->title;
   1507 			$menu_obj->label = $menu_obj->title; // Don't show "(pending)" in ajax-added items.
   1508 			$menu_items[]    = $menu_obj;
   1509 		}
   1510 	}
   1511 
   1512 	/** This filter is documented in wp-admin/includes/nav-menu.php */
   1513 	$walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
   1514 
   1515 	if ( ! class_exists( $walker_class_name ) ) {
   1516 		wp_die( 0 );
   1517 	}
   1518 
   1519 	if ( ! empty( $menu_items ) ) {
   1520 		$args = array(
   1521 			'after'       => '',
   1522 			'before'      => '',
   1523 			'link_after'  => '',
   1524 			'link_before' => '',
   1525 			'walker'      => new $walker_class_name,
   1526 		);
   1527 
   1528 		echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
   1529 	}
   1530 
   1531 	wp_die();
   1532 }
   1533 
   1534 /**
   1535  * Ajax handler for adding meta.
   1536  *
   1537  * @since 3.1.0
   1538  */
   1539 function wp_ajax_add_meta() {
   1540 	check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
   1541 	$c    = 0;
   1542 	$pid  = (int) $_POST['post_id'];
   1543 	$post = get_post( $pid );
   1544 
   1545 	if ( isset( $_POST['metakeyselect'] ) || isset( $_POST['metakeyinput'] ) ) {
   1546 		if ( ! current_user_can( 'edit_post', $pid ) ) {
   1547 			wp_die( -1 );
   1548 		}
   1549 
   1550 		if ( isset( $_POST['metakeyselect'] ) && '#NONE#' === $_POST['metakeyselect'] && empty( $_POST['metakeyinput'] ) ) {
   1551 			wp_die( 1 );
   1552 		}
   1553 
   1554 		// If the post is an autodraft, save the post as a draft and then attempt to save the meta.
   1555 		if ( 'auto-draft' === $post->post_status ) {
   1556 			$post_data                = array();
   1557 			$post_data['action']      = 'draft'; // Warning fix.
   1558 			$post_data['post_ID']     = $pid;
   1559 			$post_data['post_type']   = $post->post_type;
   1560 			$post_data['post_status'] = 'draft';
   1561 			$now                      = time();
   1562 			/* translators: 1: Post creation date, 2: Post creation time. */
   1563 			$post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), gmdate( __( 'F j, Y' ), $now ), gmdate( __( 'g:i a' ), $now ) );
   1564 
   1565 			$pid = edit_post( $post_data );
   1566 
   1567 			if ( $pid ) {
   1568 				if ( is_wp_error( $pid ) ) {
   1569 					$x = new WP_Ajax_Response(
   1570 						array(
   1571 							'what' => 'meta',
   1572 							'data' => $pid,
   1573 						)
   1574 					);
   1575 					$x->send();
   1576 				}
   1577 
   1578 				$mid = add_meta( $pid );
   1579 				if ( ! $mid ) {
   1580 					wp_die( __( 'Please provide a custom field value.' ) );
   1581 				}
   1582 			} else {
   1583 				wp_die( 0 );
   1584 			}
   1585 		} else {
   1586 			$mid = add_meta( $pid );
   1587 			if ( ! $mid ) {
   1588 				wp_die( __( 'Please provide a custom field value.' ) );
   1589 			}
   1590 		}
   1591 
   1592 		$meta = get_metadata_by_mid( 'post', $mid );
   1593 		$pid  = (int) $meta->post_id;
   1594 		$meta = get_object_vars( $meta );
   1595 
   1596 		$x = new WP_Ajax_Response(
   1597 			array(
   1598 				'what'         => 'meta',
   1599 				'id'           => $mid,
   1600 				'data'         => _list_meta_row( $meta, $c ),
   1601 				'position'     => 1,
   1602 				'supplemental' => array( 'postid' => $pid ),
   1603 			)
   1604 		);
   1605 	} else { // Update?
   1606 		$mid   = (int) key( $_POST['meta'] );
   1607 		$key   = wp_unslash( $_POST['meta'][ $mid ]['key'] );
   1608 		$value = wp_unslash( $_POST['meta'][ $mid ]['value'] );
   1609 
   1610 		if ( '' === trim( $key ) ) {
   1611 			wp_die( __( 'Please provide a custom field name.' ) );
   1612 		}
   1613 
   1614 		$meta = get_metadata_by_mid( 'post', $mid );
   1615 
   1616 		if ( ! $meta ) {
   1617 			wp_die( 0 ); // If meta doesn't exist.
   1618 		}
   1619 
   1620 		if (
   1621 			is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
   1622 			! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
   1623 			! current_user_can( 'edit_post_meta', $meta->post_id, $key )
   1624 		) {
   1625 			wp_die( -1 );
   1626 		}
   1627 
   1628 		if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
   1629 			$u = update_metadata_by_mid( 'post', $mid, $value, $key );
   1630 			if ( ! $u ) {
   1631 				wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
   1632 			}
   1633 		}
   1634 
   1635 		$x = new WP_Ajax_Response(
   1636 			array(
   1637 				'what'         => 'meta',
   1638 				'id'           => $mid,
   1639 				'old_id'       => $mid,
   1640 				'data'         => _list_meta_row(
   1641 					array(
   1642 						'meta_key'   => $key,
   1643 						'meta_value' => $value,
   1644 						'meta_id'    => $mid,
   1645 					),
   1646 					$c
   1647 				),
   1648 				'position'     => 0,
   1649 				'supplemental' => array( 'postid' => $meta->post_id ),
   1650 			)
   1651 		);
   1652 	}
   1653 	$x->send();
   1654 }
   1655 
   1656 /**
   1657  * Ajax handler for adding a user.
   1658  *
   1659  * @since 3.1.0
   1660  *
   1661  * @param string $action Action to perform.
   1662  */
   1663 function wp_ajax_add_user( $action ) {
   1664 	if ( empty( $action ) ) {
   1665 		$action = 'add-user';
   1666 	}
   1667 
   1668 	check_ajax_referer( $action );
   1669 
   1670 	if ( ! current_user_can( 'create_users' ) ) {
   1671 		wp_die( -1 );
   1672 	}
   1673 
   1674 	$user_id = edit_user();
   1675 
   1676 	if ( ! $user_id ) {
   1677 		wp_die( 0 );
   1678 	} elseif ( is_wp_error( $user_id ) ) {
   1679 		$x = new WP_Ajax_Response(
   1680 			array(
   1681 				'what' => 'user',
   1682 				'id'   => $user_id,
   1683 			)
   1684 		);
   1685 		$x->send();
   1686 	}
   1687 
   1688 	$user_object   = get_userdata( $user_id );
   1689 	$wp_list_table = _get_list_table( 'WP_Users_List_Table' );
   1690 
   1691 	$role = current( $user_object->roles );
   1692 
   1693 	$x = new WP_Ajax_Response(
   1694 		array(
   1695 			'what'         => 'user',
   1696 			'id'           => $user_id,
   1697 			'data'         => $wp_list_table->single_row( $user_object, '', $role ),
   1698 			'supplemental' => array(
   1699 				'show-link' => sprintf(
   1700 					/* translators: %s: The new user. */
   1701 					__( 'User %s added' ),
   1702 					'<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
   1703 				),
   1704 				'role'      => $role,
   1705 			),
   1706 		)
   1707 	);
   1708 	$x->send();
   1709 }
   1710 
   1711 /**
   1712  * Ajax handler for closed post boxes.
   1713  *
   1714  * @since 3.1.0
   1715  */
   1716 function wp_ajax_closed_postboxes() {
   1717 	check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
   1718 	$closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed'] ) : array();
   1719 	$closed = array_filter( $closed );
   1720 
   1721 	$hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
   1722 	$hidden = array_filter( $hidden );
   1723 
   1724 	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
   1725 
   1726 	if ( sanitize_key( $page ) != $page ) {
   1727 		wp_die( 0 );
   1728 	}
   1729 
   1730 	$user = wp_get_current_user();
   1731 	if ( ! $user ) {
   1732 		wp_die( -1 );
   1733 	}
   1734 
   1735 	if ( is_array( $closed ) ) {
   1736 		update_user_meta( $user->ID, "closedpostboxes_$page", $closed );
   1737 	}
   1738 
   1739 	if ( is_array( $hidden ) ) {
   1740 		// Postboxes that are always shown.
   1741 		$hidden = array_diff( $hidden, array( 'submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu' ) );
   1742 		update_user_meta( $user->ID, "metaboxhidden_$page", $hidden );
   1743 	}
   1744 
   1745 	wp_die( 1 );
   1746 }
   1747 
   1748 /**
   1749  * Ajax handler for hidden columns.
   1750  *
   1751  * @since 3.1.0
   1752  */
   1753 function wp_ajax_hidden_columns() {
   1754 	check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
   1755 	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
   1756 
   1757 	if ( sanitize_key( $page ) != $page ) {
   1758 		wp_die( 0 );
   1759 	}
   1760 
   1761 	$user = wp_get_current_user();
   1762 	if ( ! $user ) {
   1763 		wp_die( -1 );
   1764 	}
   1765 
   1766 	$hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
   1767 	update_user_meta( $user->ID, "manage{$page}columnshidden", $hidden );
   1768 
   1769 	wp_die( 1 );
   1770 }
   1771 
   1772 /**
   1773  * Ajax handler for updating whether to display the welcome panel.
   1774  *
   1775  * @since 3.1.0
   1776  */
   1777 function wp_ajax_update_welcome_panel() {
   1778 	check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
   1779 
   1780 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   1781 		wp_die( -1 );
   1782 	}
   1783 
   1784 	update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
   1785 
   1786 	wp_die( 1 );
   1787 }
   1788 
   1789 /**
   1790  * Ajax handler for retrieving menu meta boxes.
   1791  *
   1792  * @since 3.1.0
   1793  */
   1794 function wp_ajax_menu_get_metabox() {
   1795 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   1796 		wp_die( -1 );
   1797 	}
   1798 
   1799 	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
   1800 
   1801 	if ( isset( $_POST['item-type'] ) && 'post_type' === $_POST['item-type'] ) {
   1802 		$type     = 'posttype';
   1803 		$callback = 'wp_nav_menu_item_post_type_meta_box';
   1804 		$items    = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
   1805 	} elseif ( isset( $_POST['item-type'] ) && 'taxonomy' === $_POST['item-type'] ) {
   1806 		$type     = 'taxonomy';
   1807 		$callback = 'wp_nav_menu_item_taxonomy_meta_box';
   1808 		$items    = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
   1809 	}
   1810 
   1811 	if ( ! empty( $_POST['item-object'] ) && isset( $items[ $_POST['item-object'] ] ) ) {
   1812 		$menus_meta_box_object = $items[ $_POST['item-object'] ];
   1813 
   1814 		/** This filter is documented in wp-admin/includes/nav-menu.php */
   1815 		$item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
   1816 
   1817 		$box_args = array(
   1818 			'id'       => 'add-' . $item->name,
   1819 			'title'    => $item->labels->name,
   1820 			'callback' => $callback,
   1821 			'args'     => $item,
   1822 		);
   1823 
   1824 		ob_start();
   1825 		$callback( null, $box_args );
   1826 
   1827 		$markup = ob_get_clean();
   1828 
   1829 		echo wp_json_encode(
   1830 			array(
   1831 				'replace-id' => $type . '-' . $item->name,
   1832 				'markup'     => $markup,
   1833 			)
   1834 		);
   1835 	}
   1836 
   1837 	wp_die();
   1838 }
   1839 
   1840 /**
   1841  * Ajax handler for internal linking.
   1842  *
   1843  * @since 3.1.0
   1844  */
   1845 function wp_ajax_wp_link_ajax() {
   1846 	check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
   1847 
   1848 	$args = array();
   1849 
   1850 	if ( isset( $_POST['search'] ) ) {
   1851 		$args['s'] = wp_unslash( $_POST['search'] );
   1852 	}
   1853 
   1854 	if ( isset( $_POST['term'] ) ) {
   1855 		$args['s'] = wp_unslash( $_POST['term'] );
   1856 	}
   1857 
   1858 	$args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
   1859 
   1860 	if ( ! class_exists( '_WP_Editors', false ) ) {
   1861 		require ABSPATH . WPINC . '/class-wp-editor.php';
   1862 	}
   1863 
   1864 	$results = _WP_Editors::wp_link_query( $args );
   1865 
   1866 	if ( ! isset( $results ) ) {
   1867 		wp_die( 0 );
   1868 	}
   1869 
   1870 	echo wp_json_encode( $results );
   1871 	echo "\n";
   1872 
   1873 	wp_die();
   1874 }
   1875 
   1876 /**
   1877  * Ajax handler for menu locations save.
   1878  *
   1879  * @since 3.1.0
   1880  */
   1881 function wp_ajax_menu_locations_save() {
   1882 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   1883 		wp_die( -1 );
   1884 	}
   1885 
   1886 	check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
   1887 
   1888 	if ( ! isset( $_POST['menu-locations'] ) ) {
   1889 		wp_die( 0 );
   1890 	}
   1891 
   1892 	set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
   1893 	wp_die( 1 );
   1894 }
   1895 
   1896 /**
   1897  * Ajax handler for saving the meta box order.
   1898  *
   1899  * @since 3.1.0
   1900  */
   1901 function wp_ajax_meta_box_order() {
   1902 	check_ajax_referer( 'meta-box-order' );
   1903 	$order        = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
   1904 	$page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
   1905 
   1906 	if ( 'auto' !== $page_columns ) {
   1907 		$page_columns = (int) $page_columns;
   1908 	}
   1909 
   1910 	$page = isset( $_POST['page'] ) ? $_POST['page'] : '';
   1911 
   1912 	if ( sanitize_key( $page ) != $page ) {
   1913 		wp_die( 0 );
   1914 	}
   1915 
   1916 	$user = wp_get_current_user();
   1917 	if ( ! $user ) {
   1918 		wp_die( -1 );
   1919 	}
   1920 
   1921 	if ( $order ) {
   1922 		update_user_meta( $user->ID, "meta-box-order_$page", $order );
   1923 	}
   1924 
   1925 	if ( $page_columns ) {
   1926 		update_user_meta( $user->ID, "screen_layout_$page", $page_columns );
   1927 	}
   1928 
   1929 	wp_send_json_success();
   1930 }
   1931 
   1932 /**
   1933  * Ajax handler for menu quick searching.
   1934  *
   1935  * @since 3.1.0
   1936  */
   1937 function wp_ajax_menu_quick_search() {
   1938 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   1939 		wp_die( -1 );
   1940 	}
   1941 
   1942 	require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
   1943 
   1944 	_wp_ajax_menu_quick_search( $_POST );
   1945 
   1946 	wp_die();
   1947 }
   1948 
   1949 /**
   1950  * Ajax handler to retrieve a permalink.
   1951  *
   1952  * @since 3.1.0
   1953  */
   1954 function wp_ajax_get_permalink() {
   1955 	check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
   1956 	$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
   1957 	wp_die( get_preview_post_link( $post_id ) );
   1958 }
   1959 
   1960 /**
   1961  * Ajax handler to retrieve a sample permalink.
   1962  *
   1963  * @since 3.1.0
   1964  */
   1965 function wp_ajax_sample_permalink() {
   1966 	check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
   1967 	$post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
   1968 	$title   = isset( $_POST['new_title'] ) ? $_POST['new_title'] : '';
   1969 	$slug    = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null;
   1970 	wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
   1971 }
   1972 
   1973 /**
   1974  * Ajax handler for Quick Edit saving a post from a list table.
   1975  *
   1976  * @since 3.1.0
   1977  *
   1978  * @global string $mode List table view mode.
   1979  */
   1980 function wp_ajax_inline_save() {
   1981 	global $mode;
   1982 
   1983 	check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
   1984 
   1985 	if ( ! isset( $_POST['post_ID'] ) || ! (int) $_POST['post_ID'] ) {
   1986 		wp_die();
   1987 	}
   1988 
   1989 	$post_ID = (int) $_POST['post_ID'];
   1990 
   1991 	if ( 'page' === $_POST['post_type'] ) {
   1992 		if ( ! current_user_can( 'edit_page', $post_ID ) ) {
   1993 			wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
   1994 		}
   1995 	} else {
   1996 		if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   1997 			wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
   1998 		}
   1999 	}
   2000 
   2001 	$last = wp_check_post_lock( $post_ID );
   2002 	if ( $last ) {
   2003 		$last_user      = get_userdata( $last );
   2004 		$last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
   2005 
   2006 		/* translators: %s: User's display name. */
   2007 		$msg_template = __( 'Saving is disabled: %s is currently editing this post.' );
   2008 
   2009 		if ( 'page' === $_POST['post_type'] ) {
   2010 			/* translators: %s: User's display name. */
   2011 			$msg_template = __( 'Saving is disabled: %s is currently editing this page.' );
   2012 		}
   2013 
   2014 		printf( $msg_template, esc_html( $last_user_name ) );
   2015 		wp_die();
   2016 	}
   2017 
   2018 	$data = &$_POST;
   2019 
   2020 	$post = get_post( $post_ID, ARRAY_A );
   2021 
   2022 	// Since it's coming from the database.
   2023 	$post = wp_slash( $post );
   2024 
   2025 	$data['content'] = $post['post_content'];
   2026 	$data['excerpt'] = $post['post_excerpt'];
   2027 
   2028 	// Rename.
   2029 	$data['user_ID'] = get_current_user_id();
   2030 
   2031 	if ( isset( $data['post_parent'] ) ) {
   2032 		$data['parent_id'] = $data['post_parent'];
   2033 	}
   2034 
   2035 	// Status.
   2036 	if ( isset( $data['keep_private'] ) && 'private' === $data['keep_private'] ) {
   2037 		$data['visibility']  = 'private';
   2038 		$data['post_status'] = 'private';
   2039 	} else {
   2040 		$data['post_status'] = $data['_status'];
   2041 	}
   2042 
   2043 	if ( empty( $data['comment_status'] ) ) {
   2044 		$data['comment_status'] = 'closed';
   2045 	}
   2046 
   2047 	if ( empty( $data['ping_status'] ) ) {
   2048 		$data['ping_status'] = 'closed';
   2049 	}
   2050 
   2051 	// Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
   2052 	if ( ! empty( $data['tax_input'] ) ) {
   2053 		foreach ( $data['tax_input'] as $taxonomy => $terms ) {
   2054 			$tax_object = get_taxonomy( $taxonomy );
   2055 			/** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
   2056 			if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
   2057 				unset( $data['tax_input'][ $taxonomy ] );
   2058 			}
   2059 		}
   2060 	}
   2061 
   2062 	// Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
   2063 	if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ), true ) ) {
   2064 		$post['post_status'] = 'publish';
   2065 		$data['post_name']   = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
   2066 	}
   2067 
   2068 	// Update the post.
   2069 	edit_post();
   2070 
   2071 	$wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
   2072 
   2073 	$mode = 'excerpt' === $_POST['post_view'] ? 'excerpt' : 'list';
   2074 
   2075 	$level = 0;
   2076 	if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
   2077 		$request_post = array( get_post( $_POST['post_ID'] ) );
   2078 		$parent       = $request_post[0]->post_parent;
   2079 
   2080 		while ( $parent > 0 ) {
   2081 			$parent_post = get_post( $parent );
   2082 			$parent      = $parent_post->post_parent;
   2083 			$level++;
   2084 		}
   2085 	}
   2086 
   2087 	$wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
   2088 
   2089 	wp_die();
   2090 }
   2091 
   2092 /**
   2093  * Ajax handler for quick edit saving for a term.
   2094  *
   2095  * @since 3.1.0
   2096  */
   2097 function wp_ajax_inline_save_tax() {
   2098 	check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
   2099 
   2100 	$taxonomy = sanitize_key( $_POST['taxonomy'] );
   2101 	$tax      = get_taxonomy( $taxonomy );
   2102 
   2103 	if ( ! $tax ) {
   2104 		wp_die( 0 );
   2105 	}
   2106 
   2107 	if ( ! isset( $_POST['tax_ID'] ) || ! (int) $_POST['tax_ID'] ) {
   2108 		wp_die( -1 );
   2109 	}
   2110 
   2111 	$id = (int) $_POST['tax_ID'];
   2112 
   2113 	if ( ! current_user_can( 'edit_term', $id ) ) {
   2114 		wp_die( -1 );
   2115 	}
   2116 
   2117 	$wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
   2118 
   2119 	$tag                  = get_term( $id, $taxonomy );
   2120 	$_POST['description'] = $tag->description;
   2121 
   2122 	$updated = wp_update_term( $id, $taxonomy, $_POST );
   2123 
   2124 	if ( $updated && ! is_wp_error( $updated ) ) {
   2125 		$tag = get_term( $updated['term_id'], $taxonomy );
   2126 		if ( ! $tag || is_wp_error( $tag ) ) {
   2127 			if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
   2128 				wp_die( $tag->get_error_message() );
   2129 			}
   2130 			wp_die( __( 'Item not updated.' ) );
   2131 		}
   2132 	} else {
   2133 		if ( is_wp_error( $updated ) && $updated->get_error_message() ) {
   2134 			wp_die( $updated->get_error_message() );
   2135 		}
   2136 		wp_die( __( 'Item not updated.' ) );
   2137 	}
   2138 
   2139 	$level  = 0;
   2140 	$parent = $tag->parent;
   2141 
   2142 	while ( $parent > 0 ) {
   2143 		$parent_tag = get_term( $parent, $taxonomy );
   2144 		$parent     = $parent_tag->parent;
   2145 		$level++;
   2146 	}
   2147 
   2148 	$wp_list_table->single_row( $tag, $level );
   2149 	wp_die();
   2150 }
   2151 
   2152 /**
   2153  * Ajax handler for querying posts for the Find Posts modal.
   2154  *
   2155  * @see window.findPosts
   2156  *
   2157  * @since 3.1.0
   2158  */
   2159 function wp_ajax_find_posts() {
   2160 	check_ajax_referer( 'find-posts' );
   2161 
   2162 	$post_types = get_post_types( array( 'public' => true ), 'objects' );
   2163 	unset( $post_types['attachment'] );
   2164 
   2165 	$s    = wp_unslash( $_POST['ps'] );
   2166 	$args = array(
   2167 		'post_type'      => array_keys( $post_types ),
   2168 		'post_status'    => 'any',
   2169 		'posts_per_page' => 50,
   2170 	);
   2171 
   2172 	if ( '' !== $s ) {
   2173 		$args['s'] = $s;
   2174 	}
   2175 
   2176 	$posts = get_posts( $args );
   2177 
   2178 	if ( ! $posts ) {
   2179 		wp_send_json_error( __( 'No items found.' ) );
   2180 	}
   2181 
   2182 	$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>';
   2183 	$alt  = '';
   2184 	foreach ( $posts as $post ) {
   2185 		$title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
   2186 		$alt   = ( 'alternate' === $alt ) ? '' : 'alternate';
   2187 
   2188 		switch ( $post->post_status ) {
   2189 			case 'publish':
   2190 			case 'private':
   2191 				$stat = __( 'Published' );
   2192 				break;
   2193 			case 'future':
   2194 				$stat = __( 'Scheduled' );
   2195 				break;
   2196 			case 'pending':
   2197 				$stat = __( 'Pending Review' );
   2198 				break;
   2199 			case 'draft':
   2200 				$stat = __( 'Draft' );
   2201 				break;
   2202 		}
   2203 
   2204 		if ( '0000-00-00 00:00:00' === $post->post_date ) {
   2205 			$time = '';
   2206 		} else {
   2207 			/* translators: Date format in table columns, see https://www.php.net/manual/datetime.format.php */
   2208 			$time = mysql2date( __( 'Y/m/d' ), $post->post_date );
   2209 		}
   2210 
   2211 		$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>';
   2212 		$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";
   2213 	}
   2214 
   2215 	$html .= '</tbody></table>';
   2216 
   2217 	wp_send_json_success( $html );
   2218 }
   2219 
   2220 /**
   2221  * Ajax handler for saving the widgets order.
   2222  *
   2223  * @since 3.1.0
   2224  */
   2225 function wp_ajax_widgets_order() {
   2226 	check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
   2227 
   2228 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   2229 		wp_die( -1 );
   2230 	}
   2231 
   2232 	unset( $_POST['savewidgets'], $_POST['action'] );
   2233 
   2234 	// Save widgets order for all sidebars.
   2235 	if ( is_array( $_POST['sidebars'] ) ) {
   2236 		$sidebars = array();
   2237 
   2238 		foreach ( wp_unslash( $_POST['sidebars'] ) as $key => $val ) {
   2239 			$sb = array();
   2240 
   2241 			if ( ! empty( $val ) ) {
   2242 				$val = explode( ',', $val );
   2243 
   2244 				foreach ( $val as $k => $v ) {
   2245 					if ( strpos( $v, 'widget-' ) === false ) {
   2246 						continue;
   2247 					}
   2248 
   2249 					$sb[ $k ] = substr( $v, strpos( $v, '_' ) + 1 );
   2250 				}
   2251 			}
   2252 			$sidebars[ $key ] = $sb;
   2253 		}
   2254 
   2255 		wp_set_sidebars_widgets( $sidebars );
   2256 		wp_die( 1 );
   2257 	}
   2258 
   2259 	wp_die( -1 );
   2260 }
   2261 
   2262 /**
   2263  * Ajax handler for saving a widget.
   2264  *
   2265  * @since 3.1.0
   2266  *
   2267  * @global array $wp_registered_widgets
   2268  * @global array $wp_registered_widget_controls
   2269  * @global array $wp_registered_widget_updates
   2270  */
   2271 function wp_ajax_save_widget() {
   2272 	global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
   2273 
   2274 	check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
   2275 
   2276 	if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['id_base'] ) ) {
   2277 		wp_die( -1 );
   2278 	}
   2279 
   2280 	unset( $_POST['savewidgets'], $_POST['action'] );
   2281 
   2282 	/**
   2283 	 * Fires early when editing the widgets displayed in sidebars.
   2284 	 *
   2285 	 * @since 2.8.0
   2286 	 */
   2287 	do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
   2288 
   2289 	/**
   2290 	 * Fires early when editing the widgets displayed in sidebars.
   2291 	 *
   2292 	 * @since 2.8.0
   2293 	 */
   2294 	do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
   2295 
   2296 	/** This action is documented in wp-admin/widgets.php */
   2297 	do_action( 'sidebar_admin_setup' );
   2298 
   2299 	$id_base      = wp_unslash( $_POST['id_base'] );
   2300 	$widget_id    = wp_unslash( $_POST['widget-id'] );
   2301 	$sidebar_id   = $_POST['sidebar'];
   2302 	$multi_number = ! empty( $_POST['multi_number'] ) ? (int) $_POST['multi_number'] : 0;
   2303 	$settings     = isset( $_POST[ 'widget-' . $id_base ] ) && is_array( $_POST[ 'widget-' . $id_base ] ) ? $_POST[ 'widget-' . $id_base ] : false;
   2304 	$error        = '<p>' . __( 'An error has occurred. Please reload the page and try again.' ) . '</p>';
   2305 
   2306 	$sidebars = wp_get_sidebars_widgets();
   2307 	$sidebar  = isset( $sidebars[ $sidebar_id ] ) ? $sidebars[ $sidebar_id ] : array();
   2308 
   2309 	// Delete.
   2310 	if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
   2311 
   2312 		if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
   2313 			wp_die( $error );
   2314 		}
   2315 
   2316 		$sidebar = array_diff( $sidebar, array( $widget_id ) );
   2317 		$_POST   = array(
   2318 			'sidebar'            => $sidebar_id,
   2319 			'widget-' . $id_base => array(),
   2320 			'the-widget-id'      => $widget_id,
   2321 			'delete_widget'      => '1',
   2322 		);
   2323 
   2324 		/** This action is documented in wp-admin/widgets.php */
   2325 		do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
   2326 
   2327 	} elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) {
   2328 		if ( ! $multi_number ) {
   2329 			wp_die( $error );
   2330 		}
   2331 
   2332 		$_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
   2333 		$widget_id                     = $id_base . '-' . $multi_number;
   2334 		$sidebar[]                     = $widget_id;
   2335 	}
   2336 	$_POST['widget-id'] = $sidebar;
   2337 
   2338 	foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
   2339 
   2340 		if ( $name == $id_base ) {
   2341 			if ( ! is_callable( $control['callback'] ) ) {
   2342 				continue;
   2343 			}
   2344 
   2345 			ob_start();
   2346 				call_user_func_array( $control['callback'], $control['params'] );
   2347 			ob_end_clean();
   2348 			break;
   2349 		}
   2350 	}
   2351 
   2352 	if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
   2353 		$sidebars[ $sidebar_id ] = $sidebar;
   2354 		wp_set_sidebars_widgets( $sidebars );
   2355 		echo "deleted:$widget_id";
   2356 		wp_die();
   2357 	}
   2358 
   2359 	if ( ! empty( $_POST['add_new'] ) ) {
   2360 		wp_die();
   2361 	}
   2362 
   2363 	$form = $wp_registered_widget_controls[ $widget_id ];
   2364 	if ( $form ) {
   2365 		call_user_func_array( $form['callback'], $form['params'] );
   2366 	}
   2367 
   2368 	wp_die();
   2369 }
   2370 
   2371 /**
   2372  * Ajax handler for updating a widget.
   2373  *
   2374  * @since 3.9.0
   2375  *
   2376  * @global WP_Customize_Manager $wp_customize
   2377  */
   2378 function wp_ajax_update_widget() {
   2379 	global $wp_customize;
   2380 	$wp_customize->widgets->wp_ajax_update_widget();
   2381 }
   2382 
   2383 /**
   2384  * Ajax handler for removing inactive widgets.
   2385  *
   2386  * @since 4.4.0
   2387  */
   2388 function wp_ajax_delete_inactive_widgets() {
   2389 	check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
   2390 
   2391 	if ( ! current_user_can( 'edit_theme_options' ) ) {
   2392 		wp_die( -1 );
   2393 	}
   2394 
   2395 	unset( $_POST['removeinactivewidgets'], $_POST['action'] );
   2396 	/** This action is documented in wp-admin/includes/ajax-actions.php */
   2397 	do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
   2398 	/** This action is documented in wp-admin/includes/ajax-actions.php */
   2399 	do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
   2400 	/** This action is documented in wp-admin/widgets.php */
   2401 	do_action( 'sidebar_admin_setup' );
   2402 
   2403 	$sidebars_widgets = wp_get_sidebars_widgets();
   2404 
   2405 	foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
   2406 		$pieces       = explode( '-', $widget_id );
   2407 		$multi_number = array_pop( $pieces );
   2408 		$id_base      = implode( '-', $pieces );
   2409 		$widget       = get_option( 'widget_' . $id_base );
   2410 		unset( $widget[ $multi_number ] );
   2411 		update_option( 'widget_' . $id_base, $widget );
   2412 		unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] );
   2413 	}
   2414 
   2415 	wp_set_sidebars_widgets( $sidebars_widgets );
   2416 
   2417 	wp_die();
   2418 }
   2419 
   2420 /**
   2421  * Ajax handler for creating missing image sub-sizes for just uploaded images.
   2422  *
   2423  * @since 5.3.0
   2424  */
   2425 function wp_ajax_media_create_image_subsizes() {
   2426 	check_ajax_referer( 'media-form' );
   2427 
   2428 	if ( ! current_user_can( 'upload_files' ) ) {
   2429 		wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) );
   2430 	}
   2431 
   2432 	if ( empty( $_POST['attachment_id'] ) ) {
   2433 		wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) );
   2434 	}
   2435 
   2436 	$attachment_id = (int) $_POST['attachment_id'];
   2437 
   2438 	if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) {
   2439 		// Upload failed. Cleanup.
   2440 		if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) {
   2441 			$attachment = get_post( $attachment_id );
   2442 
   2443 			// Created at most 10 min ago.
   2444 			if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) {
   2445 				wp_delete_attachment( $attachment_id, true );
   2446 				wp_send_json_success();
   2447 			}
   2448 		}
   2449 	}
   2450 
   2451 	// Set a custom header with the attachment_id.
   2452 	// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
   2453 	if ( ! headers_sent() ) {
   2454 		header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
   2455 	}
   2456 
   2457 	// This can still be pretty slow and cause timeout or out of memory errors.
   2458 	// The js that handles the response would need to also handle HTTP 500 errors.
   2459 	wp_update_image_subsizes( $attachment_id );
   2460 
   2461 	if ( ! empty( $_POST['_legacy_support'] ) ) {
   2462 		// The old (inline) uploader. Only needs the attachment_id.
   2463 		$response = array( 'id' => $attachment_id );
   2464 	} else {
   2465 		// Media modal and Media Library grid view.
   2466 		$response = wp_prepare_attachment_for_js( $attachment_id );
   2467 
   2468 		if ( ! $response ) {
   2469 			wp_send_json_error( array( 'message' => __( 'Upload failed.' ) ) );
   2470 		}
   2471 	}
   2472 
   2473 	// At this point the image has been uploaded successfully.
   2474 	wp_send_json_success( $response );
   2475 }
   2476 
   2477 /**
   2478  * Ajax handler for uploading attachments
   2479  *
   2480  * @since 3.3.0
   2481  */
   2482 function wp_ajax_upload_attachment() {
   2483 	check_ajax_referer( 'media-form' );
   2484 	/*
   2485 	 * This function does not use wp_send_json_success() / wp_send_json_error()
   2486 	 * as the html4 Plupload handler requires a text/html content-type for older IE.
   2487 	 * See https://core.trac.wordpress.org/ticket/31037
   2488 	 */
   2489 
   2490 	if ( ! current_user_can( 'upload_files' ) ) {
   2491 		echo wp_json_encode(
   2492 			array(
   2493 				'success' => false,
   2494 				'data'    => array(
   2495 					'message'  => __( 'Sorry, you are not allowed to upload files.' ),
   2496 					'filename' => esc_html( $_FILES['async-upload']['name'] ),
   2497 				),
   2498 			)
   2499 		);
   2500 
   2501 		wp_die();
   2502 	}
   2503 
   2504 	if ( isset( $_REQUEST['post_id'] ) ) {
   2505 		$post_id = $_REQUEST['post_id'];
   2506 
   2507 		if ( ! current_user_can( 'edit_post', $post_id ) ) {
   2508 			echo wp_json_encode(
   2509 				array(
   2510 					'success' => false,
   2511 					'data'    => array(
   2512 						'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
   2513 						'filename' => esc_html( $_FILES['async-upload']['name'] ),
   2514 					),
   2515 				)
   2516 			);
   2517 
   2518 			wp_die();
   2519 		}
   2520 	} else {
   2521 		$post_id = null;
   2522 	}
   2523 
   2524 	$post_data = ! empty( $_REQUEST['post_data'] ) ? _wp_get_allowed_postdata( _wp_translate_postdata( false, (array) $_REQUEST['post_data'] ) ) : array();
   2525 
   2526 	if ( is_wp_error( $post_data ) ) {
   2527 		wp_die( $post_data->get_error_message() );
   2528 	}
   2529 
   2530 	// If the context is custom header or background, make sure the uploaded file is an image.
   2531 	if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ), true ) ) {
   2532 		$wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
   2533 
   2534 		if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
   2535 			echo wp_json_encode(
   2536 				array(
   2537 					'success' => false,
   2538 					'data'    => array(
   2539 						'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
   2540 						'filename' => esc_html( $_FILES['async-upload']['name'] ),
   2541 					),
   2542 				)
   2543 			);
   2544 
   2545 			wp_die();
   2546 		}
   2547 	}
   2548 
   2549 	$attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
   2550 
   2551 	if ( is_wp_error( $attachment_id ) ) {
   2552 		echo wp_json_encode(
   2553 			array(
   2554 				'success' => false,
   2555 				'data'    => array(
   2556 					'message'  => $attachment_id->get_error_message(),
   2557 					'filename' => esc_html( $_FILES['async-upload']['name'] ),
   2558 				),
   2559 			)
   2560 		);
   2561 
   2562 		wp_die();
   2563 	}
   2564 
   2565 	if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
   2566 		if ( 'custom-background' === $post_data['context'] ) {
   2567 			update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
   2568 		}
   2569 
   2570 		if ( 'custom-header' === $post_data['context'] ) {
   2571 			update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
   2572 		}
   2573 	}
   2574 
   2575 	$attachment = wp_prepare_attachment_for_js( $attachment_id );
   2576 	if ( ! $attachment ) {
   2577 		wp_die();
   2578 	}
   2579 
   2580 	echo wp_json_encode(
   2581 		array(
   2582 			'success' => true,
   2583 			'data'    => $attachment,
   2584 		)
   2585 	);
   2586 
   2587 	wp_die();
   2588 }
   2589 
   2590 /**
   2591  * Ajax handler for image editing.
   2592  *
   2593  * @since 3.1.0
   2594  */
   2595 function wp_ajax_image_editor() {
   2596 	$attachment_id = (int) $_POST['postid'];
   2597 
   2598 	if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
   2599 		wp_die( -1 );
   2600 	}
   2601 
   2602 	check_ajax_referer( "image_editor-$attachment_id" );
   2603 	include_once ABSPATH . 'wp-admin/includes/image-edit.php';
   2604 
   2605 	$msg = false;
   2606 
   2607 	switch ( $_POST['do'] ) {
   2608 		case 'save':
   2609 			$msg = wp_save_image( $attachment_id );
   2610 			if ( ! empty( $msg->error ) ) {
   2611 				wp_send_json_error( $msg );
   2612 			}
   2613 
   2614 			wp_send_json_success( $msg );
   2615 			break;
   2616 		case 'scale':
   2617 			$msg = wp_save_image( $attachment_id );
   2618 			break;
   2619 		case 'restore':
   2620 			$msg = wp_restore_image( $attachment_id );
   2621 			break;
   2622 	}
   2623 
   2624 	ob_start();
   2625 	wp_image_editor( $attachment_id, $msg );
   2626 	$html = ob_get_clean();
   2627 
   2628 	if ( ! empty( $msg->error ) ) {
   2629 		wp_send_json_error(
   2630 			array(
   2631 				'message' => $msg,
   2632 				'html'    => $html,
   2633 			)
   2634 		);
   2635 	}
   2636 
   2637 	wp_send_json_success(
   2638 		array(
   2639 			'message' => $msg,
   2640 			'html'    => $html,
   2641 		)
   2642 	);
   2643 }
   2644 
   2645 /**
   2646  * Ajax handler for setting the featured image.
   2647  *
   2648  * @since 3.1.0
   2649  */
   2650 function wp_ajax_set_post_thumbnail() {
   2651 	$json = ! empty( $_REQUEST['json'] ); // New-style request.
   2652 
   2653 	$post_ID = (int) $_POST['post_id'];
   2654 	if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   2655 		wp_die( -1 );
   2656 	}
   2657 
   2658 	$thumbnail_id = (int) $_POST['thumbnail_id'];
   2659 
   2660 	if ( $json ) {
   2661 		check_ajax_referer( "update-post_$post_ID" );
   2662 	} else {
   2663 		check_ajax_referer( "set_post_thumbnail-$post_ID" );
   2664 	}
   2665 
   2666 	if ( '-1' == $thumbnail_id ) {
   2667 		if ( delete_post_thumbnail( $post_ID ) ) {
   2668 			$return = _wp_post_thumbnail_html( null, $post_ID );
   2669 			$json ? wp_send_json_success( $return ) : wp_die( $return );
   2670 		} else {
   2671 			wp_die( 0 );
   2672 		}
   2673 	}
   2674 
   2675 	if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
   2676 		$return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
   2677 		$json ? wp_send_json_success( $return ) : wp_die( $return );
   2678 	}
   2679 
   2680 	wp_die( 0 );
   2681 }
   2682 
   2683 /**
   2684  * Ajax handler for retrieving HTML for the featured image.
   2685  *
   2686  * @since 4.6.0
   2687  */
   2688 function wp_ajax_get_post_thumbnail_html() {
   2689 	$post_ID = (int) $_POST['post_id'];
   2690 
   2691 	check_ajax_referer( "update-post_$post_ID" );
   2692 
   2693 	if ( ! current_user_can( 'edit_post', $post_ID ) ) {
   2694 		wp_die( -1 );
   2695 	}
   2696 
   2697 	$thumbnail_id = (int) $_POST['thumbnail_id'];
   2698 
   2699 	// For backward compatibility, -1 refers to no featured image.
   2700 	if ( -1 === $thumbnail_id ) {
   2701 		$thumbnail_id = null;
   2702 	}
   2703 
   2704 	$return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
   2705 	wp_send_json_success( $return );
   2706 }
   2707 
   2708 /**
   2709  * Ajax handler for setting the featured image for an attachment.
   2710  *
   2711  * @since 4.0.0
   2712  *
   2713  * @see set_post_thumbnail()
   2714  */
   2715 function wp_ajax_set_attachment_thumbnail() {
   2716 	if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
   2717 		wp_send_json_error();
   2718 	}
   2719 
   2720 	$thumbnail_id = (int) $_POST['thumbnail_id'];
   2721 	if ( empty( $thumbnail_id ) ) {
   2722 		wp_send_json_error();
   2723 	}
   2724 
   2725 	$post_ids = array();
   2726 	// For each URL, try to find its corresponding post ID.
   2727 	foreach ( $_POST['urls'] as $url ) {
   2728 		$post_id = attachment_url_to_postid( $url );
   2729 		if ( ! empty( $post_id ) ) {
   2730 			$post_ids[] = $post_id;
   2731 		}
   2732 	}
   2733 
   2734 	if ( empty( $post_ids ) ) {
   2735 		wp_send_json_error();
   2736 	}
   2737 
   2738 	$success = 0;
   2739 	// For each found attachment, set its thumbnail.
   2740 	foreach ( $post_ids as $post_id ) {
   2741 		if ( ! current_user_can( 'edit_post', $post_id ) ) {
   2742 			continue;
   2743 		}
   2744 
   2745 		if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
   2746 			$success++;
   2747 		}
   2748 	}
   2749 
   2750 	if ( 0 === $success ) {
   2751 		wp_send_json_error();
   2752 	} else {
   2753 		wp_send_json_success();
   2754 	}
   2755 
   2756 	wp_send_json_error();
   2757 }
   2758 
   2759 /**
   2760  * Ajax handler for date formatting.
   2761  *
   2762  * @since 3.1.0
   2763  */
   2764 function wp_ajax_date_format() {
   2765 	wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
   2766 }
   2767 
   2768 /**
   2769  * Ajax handler for time formatting.
   2770  *
   2771  * @since 3.1.0
   2772  */
   2773 function wp_ajax_time_format() {
   2774 	wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
   2775 }
   2776 
   2777 /**
   2778  * Ajax handler for saving posts from the fullscreen editor.
   2779  *
   2780  * @since 3.1.0
   2781  * @deprecated 4.3.0
   2782  */
   2783 function wp_ajax_wp_fullscreen_save_post() {
   2784 	$post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
   2785 
   2786 	$post = null;
   2787 
   2788 	if ( $post_id ) {
   2789 		$post = get_post( $post_id );
   2790 	}
   2791 
   2792 	check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
   2793 
   2794 	$post_id = edit_post();
   2795 
   2796 	if ( is_wp_error( $post_id ) ) {
   2797 		wp_send_json_error();
   2798 	}
   2799 
   2800 	if ( $post ) {
   2801 		$last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
   2802 		$last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
   2803 	} else {
   2804 		$last_date = date_i18n( __( 'F j, Y' ) );
   2805 		$last_time = date_i18n( __( 'g:i a' ) );
   2806 	}
   2807 
   2808 	$last_id = get_post_meta( $post_id, '_edit_last', true );
   2809 	if ( $last_id ) {
   2810 		$last_user = get_userdata( $last_id );
   2811 		/* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */
   2812 		$last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
   2813 	} else {
   2814 		/* translators: 1: Date of last edit, 2: Time of last edit. */
   2815 		$last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
   2816 	}
   2817 
   2818 	wp_send_json_success( array( 'last_edited' => $last_edited ) );
   2819 }
   2820 
   2821 /**
   2822  * Ajax handler for removing a post lock.
   2823  *
   2824  * @since 3.1.0
   2825  */
   2826 function wp_ajax_wp_remove_post_lock() {
   2827 	if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
   2828 		wp_die( 0 );
   2829 	}
   2830 
   2831 	$post_id = (int) $_POST['post_ID'];
   2832 	$post    = get_post( $post_id );
   2833 
   2834 	if ( ! $post ) {
   2835 		wp_die( 0 );
   2836 	}
   2837 
   2838 	check_ajax_referer( 'update-post_' . $post_id );
   2839 
   2840 	if ( ! current_user_can( 'edit_post', $post_id ) ) {
   2841 		wp_die( -1 );
   2842 	}
   2843 
   2844 	$active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
   2845 
   2846 	if ( get_current_user_id() != $active_lock[1] ) {
   2847 		wp_die( 0 );
   2848 	}
   2849 
   2850 	/**
   2851 	 * Filters the post lock window duration.
   2852 	 *
   2853 	 * @since 3.3.0
   2854 	 *
   2855 	 * @param int $interval The interval in seconds the post lock duration
   2856 	 *                      should last, plus 5 seconds. Default 150.
   2857 	 */
   2858 	$new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
   2859 	update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
   2860 	wp_die( 1 );
   2861 }
   2862 
   2863 /**
   2864  * Ajax handler for dismissing a WordPress pointer.
   2865  *
   2866  * @since 3.1.0
   2867  */
   2868 function wp_ajax_dismiss_wp_pointer() {
   2869 	$pointer = $_POST['pointer'];
   2870 
   2871 	if ( sanitize_key( $pointer ) != $pointer ) {
   2872 		wp_die( 0 );
   2873 	}
   2874 
   2875 	//  check_ajax_referer( 'dismiss-pointer_' . $pointer );
   2876 
   2877 	$dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
   2878 
   2879 	if ( in_array( $pointer, $dismissed, true ) ) {
   2880 		wp_die( 0 );
   2881 	}
   2882 
   2883 	$dismissed[] = $pointer;
   2884 	$dismissed   = implode( ',', $dismissed );
   2885 
   2886 	update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
   2887 	wp_die( 1 );
   2888 }
   2889 
   2890 /**
   2891  * Ajax handler for getting an attachment.
   2892  *
   2893  * @since 3.5.0
   2894  */
   2895 function wp_ajax_get_attachment() {
   2896 	if ( ! isset( $_REQUEST['id'] ) ) {
   2897 		wp_send_json_error();
   2898 	}
   2899 
   2900 	$id = absint( $_REQUEST['id'] );
   2901 	if ( ! $id ) {
   2902 		wp_send_json_error();
   2903 	}
   2904 
   2905 	$post = get_post( $id );
   2906 	if ( ! $post ) {
   2907 		wp_send_json_error();
   2908 	}
   2909 
   2910 	if ( 'attachment' !== $post->post_type ) {
   2911 		wp_send_json_error();
   2912 	}
   2913 
   2914 	if ( ! current_user_can( 'upload_files' ) ) {
   2915 		wp_send_json_error();
   2916 	}
   2917 
   2918 	$attachment = wp_prepare_attachment_for_js( $id );
   2919 	if ( ! $attachment ) {
   2920 		wp_send_json_error();
   2921 	}
   2922 
   2923 	wp_send_json_success( $attachment );
   2924 }
   2925 
   2926 /**
   2927  * Ajax handler for querying attachments.
   2928  *
   2929  * @since 3.5.0
   2930  */
   2931 function wp_ajax_query_attachments() {
   2932 	if ( ! current_user_can( 'upload_files' ) ) {
   2933 		wp_send_json_error();
   2934 	}
   2935 
   2936 	$query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
   2937 	$keys  = array(
   2938 		's',
   2939 		'order',
   2940 		'orderby',
   2941 		'posts_per_page',
   2942 		'paged',
   2943 		'post_mime_type',
   2944 		'post_parent',
   2945 		'author',
   2946 		'post__in',
   2947 		'post__not_in',
   2948 		'year',
   2949 		'monthnum',
   2950 	);
   2951 
   2952 	foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
   2953 		if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
   2954 			$keys[] = $t->query_var;
   2955 		}
   2956 	}
   2957 
   2958 	$query              = array_intersect_key( $query, array_flip( $keys ) );
   2959 	$query['post_type'] = 'attachment';
   2960 
   2961 	if (
   2962 		MEDIA_TRASH &&
   2963 		! empty( $_REQUEST['query']['post_status'] ) &&
   2964 		'trash' === $_REQUEST['query']['post_status']
   2965 	) {
   2966 		$query['post_status'] = 'trash';
   2967 	} else {
   2968 		$query['post_status'] = 'inherit';
   2969 	}
   2970 
   2971 	if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
   2972 		$query['post_status'] .= ',private';
   2973 	}
   2974 
   2975 	// Filter query clauses to include filenames.
   2976 	if ( isset( $query['s'] ) ) {
   2977 		add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
   2978 	}
   2979 
   2980 	/**
   2981 	 * Filters the arguments passed to WP_Query during an Ajax
   2982 	 * call for querying attachments.
   2983 	 *
   2984 	 * @since 3.7.0
   2985 	 *
   2986 	 * @see WP_Query::parse_query()
   2987 	 *
   2988 	 * @param array $query An array of query variables.
   2989 	 */
   2990 	$query             = apply_filters( 'ajax_query_attachments_args', $query );
   2991 	$attachments_query = new WP_Query( $query );
   2992 
   2993 	$posts       = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
   2994 	$posts       = array_filter( $posts );
   2995 	$total_posts = $attachments_query->found_posts;
   2996 
   2997 	if ( $total_posts < 1 ) {
   2998 		// Out-of-bounds, run the query again without LIMIT for total count.
   2999 		unset( $query['paged'] );
   3000 
   3001 		$count_query = new WP_Query();
   3002 		$count_query->query( $query );
   3003 		$total_posts = $count_query->found_posts;
   3004 	}
   3005 
   3006 	$posts_per_page = (int) $attachments_query->query['posts_per_page'];
   3007 
   3008 	$max_pages = $posts_per_page ? ceil( $total_posts / $posts_per_page ) : 0;
   3009 
   3010 	header( 'X-WP-Total: ' . (int) $total_posts );
   3011 	header( 'X-WP-TotalPages: ' . (int) $max_pages );
   3012 
   3013 	wp_send_json_success( $posts );
   3014 }
   3015 
   3016 /**
   3017  * Ajax handler for updating attachment attributes.
   3018  *
   3019  * @since 3.5.0
   3020  */
   3021 function wp_ajax_save_attachment() {
   3022 	if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
   3023 		wp_send_json_error();
   3024 	}
   3025 
   3026 	$id = absint( $_REQUEST['id'] );
   3027 	if ( ! $id ) {
   3028 		wp_send_json_error();
   3029 	}
   3030 
   3031 	check_ajax_referer( 'update-post_' . $id, 'nonce' );
   3032 
   3033 	if ( ! current_user_can( 'edit_post', $id ) ) {
   3034 		wp_send_json_error();
   3035 	}
   3036 
   3037 	$changes = $_REQUEST['changes'];
   3038 	$post    = get_post( $id, ARRAY_A );
   3039 
   3040 	if ( 'attachment' !== $post['post_type'] ) {
   3041 		wp_send_json_error();
   3042 	}
   3043 
   3044 	if ( isset( $changes['parent'] ) ) {
   3045 		$post['post_parent'] = $changes['parent'];
   3046 	}
   3047 
   3048 	if ( isset( $changes['title'] ) ) {
   3049 		$post['post_title'] = $changes['title'];
   3050 	}
   3051 
   3052 	if ( isset( $changes['caption'] ) ) {
   3053 		$post['post_excerpt'] = $changes['caption'];
   3054 	}
   3055 
   3056 	if ( isset( $changes['description'] ) ) {
   3057 		$post['post_content'] = $changes['description'];
   3058 	}
   3059 
   3060 	if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
   3061 		$post['post_status'] = $changes['status'];
   3062 	}
   3063 
   3064 	if ( isset( $changes['alt'] ) ) {
   3065 		$alt = wp_unslash( $changes['alt'] );
   3066 		if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
   3067 			$alt = wp_strip_all_tags( $alt, true );
   3068 			update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
   3069 		}
   3070 	}
   3071 
   3072 	if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
   3073 		$changed = false;
   3074 		$id3data = wp_get_attachment_metadata( $post['ID'] );
   3075 
   3076 		if ( ! is_array( $id3data ) ) {
   3077 			$changed = true;
   3078 			$id3data = array();
   3079 		}
   3080 
   3081 		foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
   3082 			if ( isset( $changes[ $key ] ) ) {
   3083 				$changed         = true;
   3084 				$id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
   3085 			}
   3086 		}
   3087 
   3088 		if ( $changed ) {
   3089 			wp_update_attachment_metadata( $id, $id3data );
   3090 		}
   3091 	}
   3092 
   3093 	if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
   3094 		wp_delete_post( $id );
   3095 	} else {
   3096 		wp_update_post( $post );
   3097 	}
   3098 
   3099 	wp_send_json_success();
   3100 }
   3101 
   3102 /**
   3103  * Ajax handler for saving backward compatible attachment attributes.
   3104  *
   3105  * @since 3.5.0
   3106  */
   3107 function wp_ajax_save_attachment_compat() {
   3108 	if ( ! isset( $_REQUEST['id'] ) ) {
   3109 		wp_send_json_error();
   3110 	}
   3111 
   3112 	$id = absint( $_REQUEST['id'] );
   3113 	if ( ! $id ) {
   3114 		wp_send_json_error();
   3115 	}
   3116 
   3117 	if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
   3118 		wp_send_json_error();
   3119 	}
   3120 
   3121 	$attachment_data = $_REQUEST['attachments'][ $id ];
   3122 
   3123 	check_ajax_referer( 'update-post_' . $id, 'nonce' );
   3124 
   3125 	if ( ! current_user_can( 'edit_post', $id ) ) {
   3126 		wp_send_json_error();
   3127 	}
   3128 
   3129 	$post = get_post( $id, ARRAY_A );
   3130 
   3131 	if ( 'attachment' !== $post['post_type'] ) {
   3132 		wp_send_json_error();
   3133 	}
   3134 
   3135 	/** This filter is documented in wp-admin/includes/media.php */
   3136 	$post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
   3137 
   3138 	if ( isset( $post['errors'] ) ) {
   3139 		$errors = $post['errors']; // @todo return me and display me!
   3140 		unset( $post['errors'] );
   3141 	}
   3142 
   3143 	wp_update_post( $post );
   3144 
   3145 	foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
   3146 		if ( isset( $attachment_data[ $taxonomy ] ) ) {
   3147 			wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
   3148 		}
   3149 	}
   3150 
   3151 	$attachment = wp_prepare_attachment_for_js( $id );
   3152 
   3153 	if ( ! $attachment ) {
   3154 		wp_send_json_error();
   3155 	}
   3156 
   3157 	wp_send_json_success( $attachment );
   3158 }
   3159 
   3160 /**
   3161  * Ajax handler for saving the attachment order.
   3162  *
   3163  * @since 3.5.0
   3164  */
   3165 function wp_ajax_save_attachment_order() {
   3166 	if ( ! isset( $_REQUEST['post_id'] ) ) {
   3167 		wp_send_json_error();
   3168 	}
   3169 
   3170 	$post_id = absint( $_REQUEST['post_id'] );
   3171 	if ( ! $post_id ) {
   3172 		wp_send_json_error();
   3173 	}
   3174 
   3175 	if ( empty( $_REQUEST['attachments'] ) ) {
   3176 		wp_send_json_error();
   3177 	}
   3178 
   3179 	check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
   3180 
   3181 	$attachments = $_REQUEST['attachments'];
   3182 
   3183 	if ( ! current_user_can( 'edit_post', $post_id ) ) {
   3184 		wp_send_json_error();
   3185 	}
   3186 
   3187 	foreach ( $attachments as $attachment_id => $menu_order ) {
   3188 		if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
   3189 			continue;
   3190 		}
   3191 
   3192 		$attachment = get_post( $attachment_id );
   3193 
   3194 		if ( ! $attachment ) {
   3195 			continue;
   3196 		}
   3197 
   3198 		if ( 'attachment' !== $attachment->post_type ) {
   3199 			continue;
   3200 		}
   3201 
   3202 		wp_update_post(
   3203 			array(
   3204 				'ID'         => $attachment_id,
   3205 				'menu_order' => $menu_order,
   3206 			)
   3207 		);
   3208 	}
   3209 
   3210 	wp_send_json_success();
   3211 }
   3212 
   3213 /**
   3214  * Ajax handler for sending an attachment to the editor.
   3215  *
   3216  * Generates the HTML to send an attachment to the editor.
   3217  * Backward compatible with the {@see 'media_send_to_editor'} filter
   3218  * and the chain of filters that follow.
   3219  *
   3220  * @since 3.5.0
   3221  */
   3222 function wp_ajax_send_attachment_to_editor() {
   3223 	check_ajax_referer( 'media-send-to-editor', 'nonce' );
   3224 
   3225 	$attachment = wp_unslash( $_POST['attachment'] );
   3226 
   3227 	$id = (int) $attachment['id'];
   3228 
   3229 	$post = get_post( $id );
   3230 	if ( ! $post ) {
   3231 		wp_send_json_error();
   3232 	}
   3233 
   3234 	if ( 'attachment' !== $post->post_type ) {
   3235 		wp_send_json_error();
   3236 	}
   3237 
   3238 	if ( current_user_can( 'edit_post', $id ) ) {
   3239 		// If this attachment is unattached, attach it. Primarily a back compat thing.
   3240 		$insert_into_post_id = (int) $_POST['post_id'];
   3241 
   3242 		if ( 0 == $post->post_parent && $insert_into_post_id ) {
   3243 			wp_update_post(
   3244 				array(
   3245 					'ID'          => $id,
   3246 					'post_parent' => $insert_into_post_id,
   3247 				)
   3248 			);
   3249 		}
   3250 	}
   3251 
   3252 	$url = empty( $attachment['url'] ) ? '' : $attachment['url'];
   3253 	$rel = ( strpos( $url, 'attachment_id' ) || get_attachment_link( $id ) == $url );
   3254 
   3255 	remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
   3256 
   3257 	if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
   3258 		$align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
   3259 		$size  = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
   3260 		$alt   = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
   3261 
   3262 		// No whitespace-only captions.
   3263 		$caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
   3264 		if ( '' === trim( $caption ) ) {
   3265 			$caption = '';
   3266 		}
   3267 
   3268 		$title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
   3269 		$html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
   3270 	} elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
   3271 		$html = stripslashes_deep( $_POST['html'] );
   3272 	} else {
   3273 		$html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
   3274 		$rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
   3275 
   3276 		if ( ! empty( $url ) ) {
   3277 			$html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
   3278 		}
   3279 	}
   3280 
   3281 	/** This filter is documented in wp-admin/includes/media.php */
   3282 	$html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
   3283 
   3284 	wp_send_json_success( $html );
   3285 }
   3286 
   3287 /**
   3288  * Ajax handler for sending a link to the editor.
   3289  *
   3290  * Generates the HTML to send a non-image embed link to the editor.
   3291  *
   3292  * Backward compatible with the following filters:
   3293  * - file_send_to_editor_url
   3294  * - audio_send_to_editor_url
   3295  * - video_send_to_editor_url
   3296  *
   3297  * @since 3.5.0
   3298  *
   3299  * @global WP_Post  $post     Global post object.
   3300  * @global WP_Embed $wp_embed
   3301  */
   3302 function wp_ajax_send_link_to_editor() {
   3303 	global $post, $wp_embed;
   3304 
   3305 	check_ajax_referer( 'media-send-to-editor', 'nonce' );
   3306 
   3307 	$src = wp_unslash( $_POST['src'] );
   3308 	if ( ! $src ) {
   3309 		wp_send_json_error();
   3310 	}
   3311 
   3312 	if ( ! strpos( $src, '://' ) ) {
   3313 		$src = 'http://' . $src;
   3314 	}
   3315 
   3316 	$src = esc_url_raw( $src );
   3317 	if ( ! $src ) {
   3318 		wp_send_json_error();
   3319 	}
   3320 
   3321 	$link_text = trim( wp_unslash( $_POST['link_text'] ) );
   3322 	if ( ! $link_text ) {
   3323 		$link_text = wp_basename( $src );
   3324 	}
   3325 
   3326 	$post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
   3327 
   3328 	// Ping WordPress for an embed.
   3329 	$check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
   3330 
   3331 	// Fallback that WordPress creates when no oEmbed was found.
   3332 	$fallback = $wp_embed->maybe_make_link( $src );
   3333 
   3334 	if ( $check_embed !== $fallback ) {
   3335 		// TinyMCE view for [embed] will parse this.
   3336 		$html = '[embed]' . $src . '[/embed]';
   3337 	} elseif ( $link_text ) {
   3338 		$html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
   3339 	} else {
   3340 		$html = '';
   3341 	}
   3342 
   3343 	// Figure out what filter to run:
   3344 	$type = 'file';
   3345 	$ext  = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
   3346 	if ( $ext ) {
   3347 		$ext_type = wp_ext2type( $ext );
   3348 		if ( 'audio' === $ext_type || 'video' === $ext_type ) {
   3349 			$type = $ext_type;
   3350 		}
   3351 	}
   3352 
   3353 	/** This filter is documented in wp-admin/includes/media.php */
   3354 	$html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
   3355 
   3356 	wp_send_json_success( $html );
   3357 }
   3358 
   3359 /**
   3360  * Ajax handler for the Heartbeat API.
   3361  *
   3362  * Runs when the user is logged in.
   3363  *
   3364  * @since 3.6.0
   3365  */
   3366 function wp_ajax_heartbeat() {
   3367 	if ( empty( $_POST['_nonce'] ) ) {
   3368 		wp_send_json_error();
   3369 	}
   3370 
   3371 	$response    = array();
   3372 	$data        = array();
   3373 	$nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
   3374 
   3375 	// 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
   3376 	if ( ! empty( $_POST['screen_id'] ) ) {
   3377 		$screen_id = sanitize_key( $_POST['screen_id'] );
   3378 	} else {
   3379 		$screen_id = 'front';
   3380 	}
   3381 
   3382 	if ( ! empty( $_POST['data'] ) ) {
   3383 		$data = wp_unslash( (array) $_POST['data'] );
   3384 	}
   3385 
   3386 	if ( 1 !== $nonce_state ) {
   3387 		/**
   3388 		 * Filters the nonces to send to the New/Edit Post screen.
   3389 		 *
   3390 		 * @since 4.3.0
   3391 		 *
   3392 		 * @param array  $response  The Heartbeat response.
   3393 		 * @param array  $data      The $_POST data sent.
   3394 		 * @param string $screen_id The screen ID.
   3395 		 */
   3396 		$response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
   3397 
   3398 		if ( false === $nonce_state ) {
   3399 			// User is logged in but nonces have expired.
   3400 			$response['nonces_expired'] = true;
   3401 			wp_send_json( $response );
   3402 		}
   3403 	}
   3404 
   3405 	if ( ! empty( $data ) ) {
   3406 		/**
   3407 		 * Filters the Heartbeat response received.
   3408 		 *
   3409 		 * @since 3.6.0
   3410 		 *
   3411 		 * @param array  $response  The Heartbeat response.
   3412 		 * @param array  $data      The $_POST data sent.
   3413 		 * @param string $screen_id The screen ID.
   3414 		 */
   3415 		$response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
   3416 	}
   3417 
   3418 	/**
   3419 	 * Filters the Heartbeat response sent.
   3420 	 *
   3421 	 * @since 3.6.0
   3422 	 *
   3423 	 * @param array  $response  The Heartbeat response.
   3424 	 * @param string $screen_id The screen ID.
   3425 	 */
   3426 	$response = apply_filters( 'heartbeat_send', $response, $screen_id );
   3427 
   3428 	/**
   3429 	 * Fires when Heartbeat ticks in logged-in environments.
   3430 	 *
   3431 	 * Allows the transport to be easily replaced with long-polling.
   3432 	 *
   3433 	 * @since 3.6.0
   3434 	 *
   3435 	 * @param array  $response  The Heartbeat response.
   3436 	 * @param string $screen_id The screen ID.
   3437 	 */
   3438 	do_action( 'heartbeat_tick', $response, $screen_id );
   3439 
   3440 	// Send the current time according to the server.
   3441 	$response['server_time'] = time();
   3442 
   3443 	wp_send_json( $response );
   3444 }
   3445 
   3446 /**
   3447  * Ajax handler for getting revision diffs.
   3448  *
   3449  * @since 3.6.0
   3450  */
   3451 function wp_ajax_get_revision_diffs() {
   3452 	require ABSPATH . 'wp-admin/includes/revision.php';
   3453 
   3454 	$post = get_post( (int) $_REQUEST['post_id'] );
   3455 	if ( ! $post ) {
   3456 		wp_send_json_error();
   3457 	}
   3458 
   3459 	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
   3460 		wp_send_json_error();
   3461 	}
   3462 
   3463 	// Really just pre-loading the cache here.
   3464 	$revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) );
   3465 	if ( ! $revisions ) {
   3466 		wp_send_json_error();
   3467 	}
   3468 
   3469 	$return = array();
   3470 	set_time_limit( 0 );
   3471 
   3472 	foreach ( $_REQUEST['compare'] as $compare_key ) {
   3473 		list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
   3474 
   3475 		$return[] = array(
   3476 			'id'     => $compare_key,
   3477 			'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
   3478 		);
   3479 	}
   3480 	wp_send_json_success( $return );
   3481 }
   3482 
   3483 /**
   3484  * Ajax handler for auto-saving the selected color scheme for
   3485  * a user's own profile.
   3486  *
   3487  * @since 3.8.0
   3488  *
   3489  * @global array $_wp_admin_css_colors
   3490  */
   3491 function wp_ajax_save_user_color_scheme() {
   3492 	global $_wp_admin_css_colors;
   3493 
   3494 	check_ajax_referer( 'save-color-scheme', 'nonce' );
   3495 
   3496 	$color_scheme = sanitize_key( $_POST['color_scheme'] );
   3497 
   3498 	if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
   3499 		wp_send_json_error();
   3500 	}
   3501 
   3502 	$previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
   3503 	update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
   3504 
   3505 	wp_send_json_success(
   3506 		array(
   3507 			'previousScheme' => 'admin-color-' . $previous_color_scheme,
   3508 			'currentScheme'  => 'admin-color-' . $color_scheme,
   3509 		)
   3510 	);
   3511 }
   3512 
   3513 /**
   3514  * Ajax handler for getting themes from themes_api().
   3515  *
   3516  * @since 3.9.0
   3517  *
   3518  * @global array $themes_allowedtags
   3519  * @global array $theme_field_defaults
   3520  */
   3521 function wp_ajax_query_themes() {
   3522 	global $themes_allowedtags, $theme_field_defaults;
   3523 
   3524 	if ( ! current_user_can( 'install_themes' ) ) {
   3525 		wp_send_json_error();
   3526 	}
   3527 
   3528 	$args = wp_parse_args(
   3529 		wp_unslash( $_REQUEST['request'] ),
   3530 		array(
   3531 			'per_page' => 20,
   3532 			'fields'   => array_merge(
   3533 				(array) $theme_field_defaults,
   3534 				array(
   3535 					'reviews_url' => true, // Explicitly request the reviews URL to be linked from the Add Themes screen.
   3536 				)
   3537 			),
   3538 		)
   3539 	);
   3540 
   3541 	if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
   3542 		$user = get_user_option( 'wporg_favorites' );
   3543 		if ( $user ) {
   3544 			$args['user'] = $user;
   3545 		}
   3546 	}
   3547 
   3548 	$old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
   3549 
   3550 	/** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
   3551 	$args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
   3552 
   3553 	$api = themes_api( 'query_themes', $args );
   3554 
   3555 	if ( is_wp_error( $api ) ) {
   3556 		wp_send_json_error();
   3557 	}
   3558 
   3559 	$update_php = network_admin_url( 'update.php?action=install-theme' );
   3560 
   3561 	foreach ( $api->themes as &$theme ) {
   3562 		$theme->install_url = add_query_arg(
   3563 			array(
   3564 				'theme'    => $theme->slug,
   3565 				'_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
   3566 			),
   3567 			$update_php
   3568 		);
   3569 
   3570 		if ( current_user_can( 'switch_themes' ) ) {
   3571 			if ( is_multisite() ) {
   3572 				$theme->activate_url = add_query_arg(
   3573 					array(
   3574 						'action'   => 'enable',
   3575 						'_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
   3576 						'theme'    => $theme->slug,
   3577 					),
   3578 					network_admin_url( 'themes.php' )
   3579 				);
   3580 			} else {
   3581 				$theme->activate_url = add_query_arg(
   3582 					array(
   3583 						'action'     => 'activate',
   3584 						'_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
   3585 						'stylesheet' => $theme->slug,
   3586 					),
   3587 					admin_url( 'themes.php' )
   3588 				);
   3589 			}
   3590 		}
   3591 
   3592 		if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
   3593 			$theme->customize_url = add_query_arg(
   3594 				array(
   3595 					'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
   3596 				),
   3597 				wp_customize_url( $theme->slug )
   3598 			);
   3599 		}
   3600 
   3601 		$theme->name        = wp_kses( $theme->name, $themes_allowedtags );
   3602 		$theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
   3603 		$theme->version     = wp_kses( $theme->version, $themes_allowedtags );
   3604 		$theme->description = wp_kses( $theme->description, $themes_allowedtags );
   3605 
   3606 		$theme->stars = wp_star_rating(
   3607 			array(
   3608 				'rating' => $theme->rating,
   3609 				'type'   => 'percent',
   3610 				'number' => $theme->num_ratings,
   3611 				'echo'   => false,
   3612 			)
   3613 		);
   3614 
   3615 		$theme->num_ratings    = number_format_i18n( $theme->num_ratings );
   3616 		$theme->preview_url    = set_url_scheme( $theme->preview_url );
   3617 		$theme->compatible_wp  = is_wp_version_compatible( $theme->requires );
   3618 		$theme->compatible_php = is_php_version_compatible( $theme->requires_php );
   3619 	}
   3620 
   3621 	wp_send_json_success( $api );
   3622 }
   3623 
   3624 /**
   3625  * Apply [embed] Ajax handlers to a string.
   3626  *
   3627  * @since 4.0.0
   3628  *
   3629  * @global WP_Post    $post       Global post object.
   3630  * @global WP_Embed   $wp_embed   Embed API instance.
   3631  * @global WP_Scripts $wp_scripts
   3632  * @global int        $content_width
   3633  */
   3634 function wp_ajax_parse_embed() {
   3635 	global $post, $wp_embed, $content_width;
   3636 
   3637 	if ( empty( $_POST['shortcode'] ) ) {
   3638 		wp_send_json_error();
   3639 	}
   3640 
   3641 	$post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
   3642 
   3643 	if ( $post_id > 0 ) {
   3644 		$post = get_post( $post_id );
   3645 
   3646 		if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
   3647 			wp_send_json_error();
   3648 		}
   3649 		setup_postdata( $post );
   3650 	} elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
   3651 		wp_send_json_error();
   3652 	}
   3653 
   3654 	$shortcode = wp_unslash( $_POST['shortcode'] );
   3655 
   3656 	preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
   3657 	$atts = shortcode_parse_atts( $matches[3] );
   3658 
   3659 	if ( ! empty( $matches[5] ) ) {
   3660 		$url = $matches[5];
   3661 	} elseif ( ! empty( $atts['src'] ) ) {
   3662 		$url = $atts['src'];
   3663 	} else {
   3664 		$url = '';
   3665 	}
   3666 
   3667 	$parsed                         = false;
   3668 	$wp_embed->return_false_on_fail = true;
   3669 
   3670 	if ( 0 === $post_id ) {
   3671 		/*
   3672 		 * Refresh oEmbeds cached outside of posts that are past their TTL.
   3673 		 * Posts are excluded because they have separate logic for refreshing
   3674 		 * their post meta caches. See WP_Embed::cache_oembed().
   3675 		 */
   3676 		$wp_embed->usecache = false;
   3677 	}
   3678 
   3679 	if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
   3680 		// Admin is ssl and the user pasted non-ssl URL.
   3681 		// Check if the provider supports ssl embeds and use that for the preview.
   3682 		$ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
   3683 		$parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
   3684 
   3685 		if ( ! $parsed ) {
   3686 			$no_ssl_support = true;
   3687 		}
   3688 	}
   3689 
   3690 	// Set $content_width so any embeds fit in the destination iframe.
   3691 	if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
   3692 		if ( ! isset( $content_width ) ) {
   3693 			$content_width = (int) $_POST['maxwidth'];
   3694 		} else {
   3695 			$content_width = min( $content_width, (int) $_POST['maxwidth'] );
   3696 		}
   3697 	}
   3698 
   3699 	if ( $url && ! $parsed ) {
   3700 		$parsed = $wp_embed->run_shortcode( $shortcode );
   3701 	}
   3702 
   3703 	if ( ! $parsed ) {
   3704 		wp_send_json_error(
   3705 			array(
   3706 				'type'    => 'not-embeddable',
   3707 				/* translators: %s: URL that could not be embedded. */
   3708 				'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
   3709 			)
   3710 		);
   3711 	}
   3712 
   3713 	if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
   3714 		$styles     = '';
   3715 		$mce_styles = wpview_media_sandbox_styles();
   3716 
   3717 		foreach ( $mce_styles as $style ) {
   3718 			$styles .= sprintf( '<link rel="stylesheet" href="%s" />', $style );
   3719 		}
   3720 
   3721 		$html = do_shortcode( $parsed );
   3722 
   3723 		global $wp_scripts;
   3724 
   3725 		if ( ! empty( $wp_scripts ) ) {
   3726 			$wp_scripts->done = array();
   3727 		}
   3728 
   3729 		ob_start();
   3730 		wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
   3731 		$scripts = ob_get_clean();
   3732 
   3733 		$parsed = $styles . $html . $scripts;
   3734 	}
   3735 
   3736 	if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
   3737 		preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
   3738 		// Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
   3739 		wp_send_json_error(
   3740 			array(
   3741 				'type'    => 'not-ssl',
   3742 				'message' => __( 'This preview is unavailable in the editor.' ),
   3743 			)
   3744 		);
   3745 	}
   3746 
   3747 	$return = array(
   3748 		'body' => $parsed,
   3749 		'attr' => $wp_embed->last_attr,
   3750 	);
   3751 
   3752 	if ( strpos( $parsed, 'class="wp-embedded-content' ) ) {
   3753 		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
   3754 			$script_src = includes_url( 'js/wp-embed.js' );
   3755 		} else {
   3756 			$script_src = includes_url( 'js/wp-embed.min.js' );
   3757 		}
   3758 
   3759 		$return['head']    = '<script src="' . $script_src . '"></script>';
   3760 		$return['sandbox'] = true;
   3761 	}
   3762 
   3763 	wp_send_json_success( $return );
   3764 }
   3765 
   3766 /**
   3767  * @since 4.0.0
   3768  *
   3769  * @global WP_Post    $post       Global post object.
   3770  * @global WP_Scripts $wp_scripts
   3771  */
   3772 function wp_ajax_parse_media_shortcode() {
   3773 	global $post, $wp_scripts;
   3774 
   3775 	if ( empty( $_POST['shortcode'] ) ) {
   3776 		wp_send_json_error();
   3777 	}
   3778 
   3779 	$shortcode = wp_unslash( $_POST['shortcode'] );
   3780 
   3781 	if ( ! empty( $_POST['post_ID'] ) ) {
   3782 		$post = get_post( (int) $_POST['post_ID'] );
   3783 	}
   3784 
   3785 	// The embed shortcode requires a post.
   3786 	if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
   3787 		if ( 'embed' === $shortcode ) {
   3788 			wp_send_json_error();
   3789 		}
   3790 	} else {
   3791 		setup_postdata( $post );
   3792 	}
   3793 
   3794 	$parsed = do_shortcode( $shortcode );
   3795 
   3796 	if ( empty( $parsed ) ) {
   3797 		wp_send_json_error(
   3798 			array(
   3799 				'type'    => 'no-items',
   3800 				'message' => __( 'No items found.' ),
   3801 			)
   3802 		);
   3803 	}
   3804 
   3805 	$head   = '';
   3806 	$styles = wpview_media_sandbox_styles();
   3807 
   3808 	foreach ( $styles as $style ) {
   3809 		$head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
   3810 	}
   3811 
   3812 	if ( ! empty( $wp_scripts ) ) {
   3813 		$wp_scripts->done = array();
   3814 	}
   3815 
   3816 	ob_start();
   3817 
   3818 	echo $parsed;
   3819 
   3820 	if ( 'playlist' === $_REQUEST['type'] ) {
   3821 		wp_underscore_playlist_templates();
   3822 
   3823 		wp_print_scripts( 'wp-playlist' );
   3824 	} else {
   3825 		wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
   3826 	}
   3827 
   3828 	wp_send_json_success(
   3829 		array(
   3830 			'head' => $head,
   3831 			'body' => ob_get_clean(),
   3832 		)
   3833 	);
   3834 }
   3835 
   3836 /**
   3837  * Ajax handler for destroying multiple open sessions for a user.
   3838  *
   3839  * @since 4.1.0
   3840  */
   3841 function wp_ajax_destroy_sessions() {
   3842 	$user = get_userdata( (int) $_POST['user_id'] );
   3843 
   3844 	if ( $user ) {
   3845 		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
   3846 			$user = false;
   3847 		} elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
   3848 			$user = false;
   3849 		}
   3850 	}
   3851 
   3852 	if ( ! $user ) {
   3853 		wp_send_json_error(
   3854 			array(
   3855 				'message' => __( 'Could not log out user sessions. Please try again.' ),
   3856 			)
   3857 		);
   3858 	}
   3859 
   3860 	$sessions = WP_Session_Tokens::get_instance( $user->ID );
   3861 
   3862 	if ( get_current_user_id() === $user->ID ) {
   3863 		$sessions->destroy_others( wp_get_session_token() );
   3864 		$message = __( 'You are now logged out everywhere else.' );
   3865 	} else {
   3866 		$sessions->destroy_all();
   3867 		/* translators: %s: User's display name. */
   3868 		$message = sprintf( __( '%s has been logged out.' ), $user->display_name );
   3869 	}
   3870 
   3871 	wp_send_json_success( array( 'message' => $message ) );
   3872 }
   3873 
   3874 /**
   3875  * Ajax handler for cropping an image.
   3876  *
   3877  * @since 4.3.0
   3878  */
   3879 function wp_ajax_crop_image() {
   3880 	$attachment_id = absint( $_POST['id'] );
   3881 
   3882 	check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
   3883 
   3884 	if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
   3885 		wp_send_json_error();
   3886 	}
   3887 
   3888 	$context = str_replace( '_', '-', $_POST['context'] );
   3889 	$data    = array_map( 'absint', $_POST['cropDetails'] );
   3890 	$cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
   3891 
   3892 	if ( ! $cropped || is_wp_error( $cropped ) ) {
   3893 		wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
   3894 	}
   3895 
   3896 	switch ( $context ) {
   3897 		case 'site-icon':
   3898 			require_once ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
   3899 			$wp_site_icon = new WP_Site_Icon();
   3900 
   3901 			// Skip creating a new attachment if the attachment is a Site Icon.
   3902 			if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
   3903 
   3904 				// Delete the temporary cropped file, we don't need it.
   3905 				wp_delete_file( $cropped );
   3906 
   3907 				// Additional sizes in wp_prepare_attachment_for_js().
   3908 				add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
   3909 				break;
   3910 			}
   3911 
   3912 			/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
   3913 			$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
   3914 			$object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
   3915 			unset( $object['ID'] );
   3916 
   3917 			// Update the attachment.
   3918 			add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
   3919 			$attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
   3920 			remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
   3921 
   3922 			// Additional sizes in wp_prepare_attachment_for_js().
   3923 			add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
   3924 			break;
   3925 
   3926 		default:
   3927 			/**
   3928 			 * Fires before a cropped image is saved.
   3929 			 *
   3930 			 * Allows to add filters to modify the way a cropped image is saved.
   3931 			 *
   3932 			 * @since 4.3.0
   3933 			 *
   3934 			 * @param string $context       The Customizer control requesting the cropped image.
   3935 			 * @param int    $attachment_id The attachment ID of the original image.
   3936 			 * @param string $cropped       Path to the cropped image file.
   3937 			 */
   3938 			do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
   3939 
   3940 			/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
   3941 			$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
   3942 
   3943 			$parent_url = wp_get_attachment_url( $attachment_id );
   3944 			$url        = str_replace( wp_basename( $parent_url ), wp_basename( $cropped ), $parent_url );
   3945 
   3946 			$size       = wp_getimagesize( $cropped );
   3947 			$image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
   3948 
   3949 			$object = array(
   3950 				'post_title'     => wp_basename( $cropped ),
   3951 				'post_content'   => $url,
   3952 				'post_mime_type' => $image_type,
   3953 				'guid'           => $url,
   3954 				'context'        => $context,
   3955 			);
   3956 
   3957 			$attachment_id = wp_insert_attachment( $object, $cropped );
   3958 			$metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
   3959 
   3960 			/**
   3961 			 * Filters the cropped image attachment metadata.
   3962 			 *
   3963 			 * @since 4.3.0
   3964 			 *
   3965 			 * @see wp_generate_attachment_metadata()
   3966 			 *
   3967 			 * @param array $metadata Attachment metadata.
   3968 			 */
   3969 			$metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
   3970 			wp_update_attachment_metadata( $attachment_id, $metadata );
   3971 
   3972 			/**
   3973 			 * Filters the attachment ID for a cropped image.
   3974 			 *
   3975 			 * @since 4.3.0
   3976 			 *
   3977 			 * @param int    $attachment_id The attachment ID of the cropped image.
   3978 			 * @param string $context       The Customizer control requesting the cropped image.
   3979 			 */
   3980 			$attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
   3981 	}
   3982 
   3983 	wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
   3984 }
   3985 
   3986 /**
   3987  * Ajax handler for generating a password.
   3988  *
   3989  * @since 4.4.0
   3990  */
   3991 function wp_ajax_generate_password() {
   3992 	wp_send_json_success( wp_generate_password( 24 ) );
   3993 }
   3994 
   3995 /**
   3996  * Ajax handler for generating a password in the no-privilege context.
   3997  *
   3998  * @since 5.7.0
   3999  */
   4000 function wp_ajax_nopriv_generate_password() {
   4001 	wp_send_json_success( wp_generate_password( 24 ) );
   4002 }
   4003 
   4004 /**
   4005  * Ajax handler for saving the user's WordPress.org username.
   4006  *
   4007  * @since 4.4.0
   4008  */
   4009 function wp_ajax_save_wporg_username() {
   4010 	if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
   4011 		wp_send_json_error();
   4012 	}
   4013 
   4014 	check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
   4015 
   4016 	$username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
   4017 
   4018 	if ( ! $username ) {
   4019 		wp_send_json_error();
   4020 	}
   4021 
   4022 	wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
   4023 }
   4024 
   4025 /**
   4026  * Ajax handler for installing a theme.
   4027  *
   4028  * @since 4.6.0
   4029  *
   4030  * @see Theme_Upgrader
   4031  *
   4032  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4033  */
   4034 function wp_ajax_install_theme() {
   4035 	check_ajax_referer( 'updates' );
   4036 
   4037 	if ( empty( $_POST['slug'] ) ) {
   4038 		wp_send_json_error(
   4039 			array(
   4040 				'slug'         => '',
   4041 				'errorCode'    => 'no_theme_specified',
   4042 				'errorMessage' => __( 'No theme specified.' ),
   4043 			)
   4044 		);
   4045 	}
   4046 
   4047 	$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
   4048 
   4049 	$status = array(
   4050 		'install' => 'theme',
   4051 		'slug'    => $slug,
   4052 	);
   4053 
   4054 	if ( ! current_user_can( 'install_themes' ) ) {
   4055 		$status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
   4056 		wp_send_json_error( $status );
   4057 	}
   4058 
   4059 	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
   4060 	include_once ABSPATH . 'wp-admin/includes/theme.php';
   4061 
   4062 	$api = themes_api(
   4063 		'theme_information',
   4064 		array(
   4065 			'slug'   => $slug,
   4066 			'fields' => array( 'sections' => false ),
   4067 		)
   4068 	);
   4069 
   4070 	if ( is_wp_error( $api ) ) {
   4071 		$status['errorMessage'] = $api->get_error_message();
   4072 		wp_send_json_error( $status );
   4073 	}
   4074 
   4075 	$skin     = new WP_Ajax_Upgrader_Skin();
   4076 	$upgrader = new Theme_Upgrader( $skin );
   4077 	$result   = $upgrader->install( $api->download_link );
   4078 
   4079 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
   4080 		$status['debug'] = $skin->get_upgrade_messages();
   4081 	}
   4082 
   4083 	if ( is_wp_error( $result ) ) {
   4084 		$status['errorCode']    = $result->get_error_code();
   4085 		$status['errorMessage'] = $result->get_error_message();
   4086 		wp_send_json_error( $status );
   4087 	} elseif ( is_wp_error( $skin->result ) ) {
   4088 		$status['errorCode']    = $skin->result->get_error_code();
   4089 		$status['errorMessage'] = $skin->result->get_error_message();
   4090 		wp_send_json_error( $status );
   4091 	} elseif ( $skin->get_errors()->has_errors() ) {
   4092 		$status['errorMessage'] = $skin->get_error_messages();
   4093 		wp_send_json_error( $status );
   4094 	} elseif ( is_null( $result ) ) {
   4095 		global $wp_filesystem;
   4096 
   4097 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4098 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4099 
   4100 		// Pass through the error from WP_Filesystem if one was raised.
   4101 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4102 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4103 		}
   4104 
   4105 		wp_send_json_error( $status );
   4106 	}
   4107 
   4108 	$status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
   4109 
   4110 	if ( current_user_can( 'switch_themes' ) ) {
   4111 		if ( is_multisite() ) {
   4112 			$status['activateUrl'] = add_query_arg(
   4113 				array(
   4114 					'action'   => 'enable',
   4115 					'_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
   4116 					'theme'    => $slug,
   4117 				),
   4118 				network_admin_url( 'themes.php' )
   4119 			);
   4120 		} else {
   4121 			$status['activateUrl'] = add_query_arg(
   4122 				array(
   4123 					'action'     => 'activate',
   4124 					'_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
   4125 					'stylesheet' => $slug,
   4126 				),
   4127 				admin_url( 'themes.php' )
   4128 			);
   4129 		}
   4130 	}
   4131 
   4132 	if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
   4133 		$status['customizeUrl'] = add_query_arg(
   4134 			array(
   4135 				'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
   4136 			),
   4137 			wp_customize_url( $slug )
   4138 		);
   4139 	}
   4140 
   4141 	/*
   4142 	 * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
   4143 	 * on post-installation status.
   4144 	 */
   4145 	wp_send_json_success( $status );
   4146 }
   4147 
   4148 /**
   4149  * Ajax handler for updating a theme.
   4150  *
   4151  * @since 4.6.0
   4152  *
   4153  * @see Theme_Upgrader
   4154  *
   4155  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4156  */
   4157 function wp_ajax_update_theme() {
   4158 	check_ajax_referer( 'updates' );
   4159 
   4160 	if ( empty( $_POST['slug'] ) ) {
   4161 		wp_send_json_error(
   4162 			array(
   4163 				'slug'         => '',
   4164 				'errorCode'    => 'no_theme_specified',
   4165 				'errorMessage' => __( 'No theme specified.' ),
   4166 			)
   4167 		);
   4168 	}
   4169 
   4170 	$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
   4171 	$status     = array(
   4172 		'update'     => 'theme',
   4173 		'slug'       => $stylesheet,
   4174 		'oldVersion' => '',
   4175 		'newVersion' => '',
   4176 	);
   4177 
   4178 	if ( ! current_user_can( 'update_themes' ) ) {
   4179 		$status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
   4180 		wp_send_json_error( $status );
   4181 	}
   4182 
   4183 	$theme = wp_get_theme( $stylesheet );
   4184 	if ( $theme->exists() ) {
   4185 		$status['oldVersion'] = $theme->get( 'Version' );
   4186 	}
   4187 
   4188 	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
   4189 
   4190 	$current = get_site_transient( 'update_themes' );
   4191 	if ( empty( $current ) ) {
   4192 		wp_update_themes();
   4193 	}
   4194 
   4195 	$skin     = new WP_Ajax_Upgrader_Skin();
   4196 	$upgrader = new Theme_Upgrader( $skin );
   4197 	$result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
   4198 
   4199 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
   4200 		$status['debug'] = $skin->get_upgrade_messages();
   4201 	}
   4202 
   4203 	if ( is_wp_error( $skin->result ) ) {
   4204 		$status['errorCode']    = $skin->result->get_error_code();
   4205 		$status['errorMessage'] = $skin->result->get_error_message();
   4206 		wp_send_json_error( $status );
   4207 	} elseif ( $skin->get_errors()->has_errors() ) {
   4208 		$status['errorMessage'] = $skin->get_error_messages();
   4209 		wp_send_json_error( $status );
   4210 	} elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
   4211 
   4212 		// Theme is already at the latest version.
   4213 		if ( true === $result[ $stylesheet ] ) {
   4214 			$status['errorMessage'] = $upgrader->strings['up_to_date'];
   4215 			wp_send_json_error( $status );
   4216 		}
   4217 
   4218 		$theme = wp_get_theme( $stylesheet );
   4219 		if ( $theme->exists() ) {
   4220 			$status['newVersion'] = $theme->get( 'Version' );
   4221 		}
   4222 
   4223 		wp_send_json_success( $status );
   4224 	} elseif ( false === $result ) {
   4225 		global $wp_filesystem;
   4226 
   4227 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4228 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4229 
   4230 		// Pass through the error from WP_Filesystem if one was raised.
   4231 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4232 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4233 		}
   4234 
   4235 		wp_send_json_error( $status );
   4236 	}
   4237 
   4238 	// An unhandled error occurred.
   4239 	$status['errorMessage'] = __( 'Theme update failed.' );
   4240 	wp_send_json_error( $status );
   4241 }
   4242 
   4243 /**
   4244  * Ajax handler for deleting a theme.
   4245  *
   4246  * @since 4.6.0
   4247  *
   4248  * @see delete_theme()
   4249  *
   4250  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4251  */
   4252 function wp_ajax_delete_theme() {
   4253 	check_ajax_referer( 'updates' );
   4254 
   4255 	if ( empty( $_POST['slug'] ) ) {
   4256 		wp_send_json_error(
   4257 			array(
   4258 				'slug'         => '',
   4259 				'errorCode'    => 'no_theme_specified',
   4260 				'errorMessage' => __( 'No theme specified.' ),
   4261 			)
   4262 		);
   4263 	}
   4264 
   4265 	$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
   4266 	$status     = array(
   4267 		'delete' => 'theme',
   4268 		'slug'   => $stylesheet,
   4269 	);
   4270 
   4271 	if ( ! current_user_can( 'delete_themes' ) ) {
   4272 		$status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
   4273 		wp_send_json_error( $status );
   4274 	}
   4275 
   4276 	if ( ! wp_get_theme( $stylesheet )->exists() ) {
   4277 		$status['errorMessage'] = __( 'The requested theme does not exist.' );
   4278 		wp_send_json_error( $status );
   4279 	}
   4280 
   4281 	// Check filesystem credentials. `delete_theme()` will bail otherwise.
   4282 	$url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
   4283 
   4284 	ob_start();
   4285 	$credentials = request_filesystem_credentials( $url );
   4286 	ob_end_clean();
   4287 
   4288 	if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
   4289 		global $wp_filesystem;
   4290 
   4291 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4292 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4293 
   4294 		// Pass through the error from WP_Filesystem if one was raised.
   4295 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4296 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4297 		}
   4298 
   4299 		wp_send_json_error( $status );
   4300 	}
   4301 
   4302 	include_once ABSPATH . 'wp-admin/includes/theme.php';
   4303 
   4304 	$result = delete_theme( $stylesheet );
   4305 
   4306 	if ( is_wp_error( $result ) ) {
   4307 		$status['errorMessage'] = $result->get_error_message();
   4308 		wp_send_json_error( $status );
   4309 	} elseif ( false === $result ) {
   4310 		$status['errorMessage'] = __( 'Theme could not be deleted.' );
   4311 		wp_send_json_error( $status );
   4312 	}
   4313 
   4314 	wp_send_json_success( $status );
   4315 }
   4316 
   4317 /**
   4318  * Ajax handler for installing a plugin.
   4319  *
   4320  * @since 4.6.0
   4321  *
   4322  * @see Plugin_Upgrader
   4323  *
   4324  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4325  */
   4326 function wp_ajax_install_plugin() {
   4327 	check_ajax_referer( 'updates' );
   4328 
   4329 	if ( empty( $_POST['slug'] ) ) {
   4330 		wp_send_json_error(
   4331 			array(
   4332 				'slug'         => '',
   4333 				'errorCode'    => 'no_plugin_specified',
   4334 				'errorMessage' => __( 'No plugin specified.' ),
   4335 			)
   4336 		);
   4337 	}
   4338 
   4339 	$status = array(
   4340 		'install' => 'plugin',
   4341 		'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
   4342 	);
   4343 
   4344 	if ( ! current_user_can( 'install_plugins' ) ) {
   4345 		$status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
   4346 		wp_send_json_error( $status );
   4347 	}
   4348 
   4349 	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
   4350 	include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
   4351 
   4352 	$api = plugins_api(
   4353 		'plugin_information',
   4354 		array(
   4355 			'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
   4356 			'fields' => array(
   4357 				'sections' => false,
   4358 			),
   4359 		)
   4360 	);
   4361 
   4362 	if ( is_wp_error( $api ) ) {
   4363 		$status['errorMessage'] = $api->get_error_message();
   4364 		wp_send_json_error( $status );
   4365 	}
   4366 
   4367 	$status['pluginName'] = $api->name;
   4368 
   4369 	$skin     = new WP_Ajax_Upgrader_Skin();
   4370 	$upgrader = new Plugin_Upgrader( $skin );
   4371 	$result   = $upgrader->install( $api->download_link );
   4372 
   4373 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
   4374 		$status['debug'] = $skin->get_upgrade_messages();
   4375 	}
   4376 
   4377 	if ( is_wp_error( $result ) ) {
   4378 		$status['errorCode']    = $result->get_error_code();
   4379 		$status['errorMessage'] = $result->get_error_message();
   4380 		wp_send_json_error( $status );
   4381 	} elseif ( is_wp_error( $skin->result ) ) {
   4382 		$status['errorCode']    = $skin->result->get_error_code();
   4383 		$status['errorMessage'] = $skin->result->get_error_message();
   4384 		wp_send_json_error( $status );
   4385 	} elseif ( $skin->get_errors()->has_errors() ) {
   4386 		$status['errorMessage'] = $skin->get_error_messages();
   4387 		wp_send_json_error( $status );
   4388 	} elseif ( is_null( $result ) ) {
   4389 		global $wp_filesystem;
   4390 
   4391 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4392 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4393 
   4394 		// Pass through the error from WP_Filesystem if one was raised.
   4395 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4396 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4397 		}
   4398 
   4399 		wp_send_json_error( $status );
   4400 	}
   4401 
   4402 	$install_status = install_plugin_install_status( $api );
   4403 	$pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
   4404 
   4405 	// If installation request is coming from import page, do not return network activation link.
   4406 	$plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
   4407 
   4408 	if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
   4409 		$status['activateUrl'] = add_query_arg(
   4410 			array(
   4411 				'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
   4412 				'action'   => 'activate',
   4413 				'plugin'   => $install_status['file'],
   4414 			),
   4415 			$plugins_url
   4416 		);
   4417 	}
   4418 
   4419 	if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
   4420 		$status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
   4421 	}
   4422 
   4423 	wp_send_json_success( $status );
   4424 }
   4425 
   4426 /**
   4427  * Ajax handler for updating a plugin.
   4428  *
   4429  * @since 4.2.0
   4430  *
   4431  * @see Plugin_Upgrader
   4432  *
   4433  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4434  */
   4435 function wp_ajax_update_plugin() {
   4436 	check_ajax_referer( 'updates' );
   4437 
   4438 	if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
   4439 		wp_send_json_error(
   4440 			array(
   4441 				'slug'         => '',
   4442 				'errorCode'    => 'no_plugin_specified',
   4443 				'errorMessage' => __( 'No plugin specified.' ),
   4444 			)
   4445 		);
   4446 	}
   4447 
   4448 	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
   4449 
   4450 	$status = array(
   4451 		'update'     => 'plugin',
   4452 		'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
   4453 		'oldVersion' => '',
   4454 		'newVersion' => '',
   4455 	);
   4456 
   4457 	if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
   4458 		$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
   4459 		wp_send_json_error( $status );
   4460 	}
   4461 
   4462 	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
   4463 	$status['plugin']     = $plugin;
   4464 	$status['pluginName'] = $plugin_data['Name'];
   4465 
   4466 	if ( $plugin_data['Version'] ) {
   4467 		/* translators: %s: Plugin version. */
   4468 		$status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
   4469 	}
   4470 
   4471 	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
   4472 
   4473 	wp_update_plugins();
   4474 
   4475 	$skin     = new WP_Ajax_Upgrader_Skin();
   4476 	$upgrader = new Plugin_Upgrader( $skin );
   4477 	$result   = $upgrader->bulk_upgrade( array( $plugin ) );
   4478 
   4479 	if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
   4480 		$status['debug'] = $skin->get_upgrade_messages();
   4481 	}
   4482 
   4483 	if ( is_wp_error( $skin->result ) ) {
   4484 		$status['errorCode']    = $skin->result->get_error_code();
   4485 		$status['errorMessage'] = $skin->result->get_error_message();
   4486 		wp_send_json_error( $status );
   4487 	} elseif ( $skin->get_errors()->has_errors() ) {
   4488 		$status['errorMessage'] = $skin->get_error_messages();
   4489 		wp_send_json_error( $status );
   4490 	} elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
   4491 
   4492 		/*
   4493 		 * Plugin is already at the latest version.
   4494 		 *
   4495 		 * This may also be the return value if the `update_plugins` site transient is empty,
   4496 		 * e.g. when you update two plugins in quick succession before the transient repopulates.
   4497 		 *
   4498 		 * Preferably something can be done to ensure `update_plugins` isn't empty.
   4499 		 * For now, surface some sort of error here.
   4500 		 */
   4501 		if ( true === $result[ $plugin ] ) {
   4502 			$status['errorMessage'] = $upgrader->strings['up_to_date'];
   4503 			wp_send_json_error( $status );
   4504 		}
   4505 
   4506 		$plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
   4507 		$plugin_data = reset( $plugin_data );
   4508 
   4509 		if ( $plugin_data['Version'] ) {
   4510 			/* translators: %s: Plugin version. */
   4511 			$status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
   4512 		}
   4513 
   4514 		wp_send_json_success( $status );
   4515 	} elseif ( false === $result ) {
   4516 		global $wp_filesystem;
   4517 
   4518 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4519 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4520 
   4521 		// Pass through the error from WP_Filesystem if one was raised.
   4522 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4523 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4524 		}
   4525 
   4526 		wp_send_json_error( $status );
   4527 	}
   4528 
   4529 	// An unhandled error occurred.
   4530 	$status['errorMessage'] = __( 'Plugin update failed.' );
   4531 	wp_send_json_error( $status );
   4532 }
   4533 
   4534 /**
   4535  * Ajax handler for deleting a plugin.
   4536  *
   4537  * @since 4.6.0
   4538  *
   4539  * @see delete_plugins()
   4540  *
   4541  * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
   4542  */
   4543 function wp_ajax_delete_plugin() {
   4544 	check_ajax_referer( 'updates' );
   4545 
   4546 	if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
   4547 		wp_send_json_error(
   4548 			array(
   4549 				'slug'         => '',
   4550 				'errorCode'    => 'no_plugin_specified',
   4551 				'errorMessage' => __( 'No plugin specified.' ),
   4552 			)
   4553 		);
   4554 	}
   4555 
   4556 	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
   4557 
   4558 	$status = array(
   4559 		'delete' => 'plugin',
   4560 		'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
   4561 	);
   4562 
   4563 	if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
   4564 		$status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
   4565 		wp_send_json_error( $status );
   4566 	}
   4567 
   4568 	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
   4569 	$status['plugin']     = $plugin;
   4570 	$status['pluginName'] = $plugin_data['Name'];
   4571 
   4572 	if ( is_plugin_active( $plugin ) ) {
   4573 		$status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
   4574 		wp_send_json_error( $status );
   4575 	}
   4576 
   4577 	// Check filesystem credentials. `delete_plugins()` will bail otherwise.
   4578 	$url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
   4579 
   4580 	ob_start();
   4581 	$credentials = request_filesystem_credentials( $url );
   4582 	ob_end_clean();
   4583 
   4584 	if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
   4585 		global $wp_filesystem;
   4586 
   4587 		$status['errorCode']    = 'unable_to_connect_to_filesystem';
   4588 		$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
   4589 
   4590 		// Pass through the error from WP_Filesystem if one was raised.
   4591 		if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
   4592 			$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
   4593 		}
   4594 
   4595 		wp_send_json_error( $status );
   4596 	}
   4597 
   4598 	$result = delete_plugins( array( $plugin ) );
   4599 
   4600 	if ( is_wp_error( $result ) ) {
   4601 		$status['errorMessage'] = $result->get_error_message();
   4602 		wp_send_json_error( $status );
   4603 	} elseif ( false === $result ) {
   4604 		$status['errorMessage'] = __( 'Plugin could not be deleted.' );
   4605 		wp_send_json_error( $status );
   4606 	}
   4607 
   4608 	wp_send_json_success( $status );
   4609 }
   4610 
   4611 /**
   4612  * Ajax handler for searching plugins.
   4613  *
   4614  * @since 4.6.0
   4615  *
   4616  * @global string $s Search term.
   4617  */
   4618 function wp_ajax_search_plugins() {
   4619 	check_ajax_referer( 'updates' );
   4620 
   4621 	// Ensure after_plugin_row_{$plugin_file} gets hooked.
   4622 	wp_plugin_update_rows();
   4623 
   4624 	$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
   4625 	if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
   4626 		set_current_screen( $pagenow );
   4627 	}
   4628 
   4629 	/** @var WP_Plugins_List_Table $wp_list_table */
   4630 	$wp_list_table = _get_list_table(
   4631 		'WP_Plugins_List_Table',
   4632 		array(
   4633 			'screen' => get_current_screen(),
   4634 		)
   4635 	);
   4636 
   4637 	$status = array();
   4638 
   4639 	if ( ! $wp_list_table->ajax_user_can() ) {
   4640 		$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
   4641 		wp_send_json_error( $status );
   4642 	}
   4643 
   4644 	// Set the correct requester, so pagination works.
   4645 	$_SERVER['REQUEST_URI'] = add_query_arg(
   4646 		array_diff_key(
   4647 			$_POST,
   4648 			array(
   4649 				'_ajax_nonce' => null,
   4650 				'action'      => null,
   4651 			)
   4652 		),
   4653 		network_admin_url( 'plugins.php', 'relative' )
   4654 	);
   4655 
   4656 	$GLOBALS['s'] = wp_unslash( $_POST['s'] );
   4657 
   4658 	$wp_list_table->prepare_items();
   4659 
   4660 	ob_start();
   4661 	$wp_list_table->display();
   4662 	$status['count'] = count( $wp_list_table->items );
   4663 	$status['items'] = ob_get_clean();
   4664 
   4665 	wp_send_json_success( $status );
   4666 }
   4667 
   4668 /**
   4669  * Ajax handler for searching plugins to install.
   4670  *
   4671  * @since 4.6.0
   4672  */
   4673 function wp_ajax_search_install_plugins() {
   4674 	check_ajax_referer( 'updates' );
   4675 
   4676 	$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
   4677 	if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
   4678 		set_current_screen( $pagenow );
   4679 	}
   4680 
   4681 	/** @var WP_Plugin_Install_List_Table $wp_list_table */
   4682 	$wp_list_table = _get_list_table(
   4683 		'WP_Plugin_Install_List_Table',
   4684 		array(
   4685 			'screen' => get_current_screen(),
   4686 		)
   4687 	);
   4688 
   4689 	$status = array();
   4690 
   4691 	if ( ! $wp_list_table->ajax_user_can() ) {
   4692 		$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
   4693 		wp_send_json_error( $status );
   4694 	}
   4695 
   4696 	// Set the correct requester, so pagination works.
   4697 	$_SERVER['REQUEST_URI'] = add_query_arg(
   4698 		array_diff_key(
   4699 			$_POST,
   4700 			array(
   4701 				'_ajax_nonce' => null,
   4702 				'action'      => null,
   4703 			)
   4704 		),
   4705 		network_admin_url( 'plugin-install.php', 'relative' )
   4706 	);
   4707 
   4708 	$wp_list_table->prepare_items();
   4709 
   4710 	ob_start();
   4711 	$wp_list_table->display();
   4712 	$status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
   4713 	$status['items'] = ob_get_clean();
   4714 
   4715 	wp_send_json_success( $status );
   4716 }
   4717 
   4718 /**
   4719  * Ajax handler for editing a theme or plugin file.
   4720  *
   4721  * @since 4.9.0
   4722  *
   4723  * @see wp_edit_theme_plugin_file()
   4724  */
   4725 function wp_ajax_edit_theme_plugin_file() {
   4726 	$r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
   4727 
   4728 	if ( is_wp_error( $r ) ) {
   4729 		wp_send_json_error(
   4730 			array_merge(
   4731 				array(
   4732 					'code'    => $r->get_error_code(),
   4733 					'message' => $r->get_error_message(),
   4734 				),
   4735 				(array) $r->get_error_data()
   4736 			)
   4737 		);
   4738 	} else {
   4739 		wp_send_json_success(
   4740 			array(
   4741 				'message' => __( 'File edited successfully.' ),
   4742 			)
   4743 		);
   4744 	}
   4745 }
   4746 
   4747 /**
   4748  * Ajax handler for exporting a user's personal data.
   4749  *
   4750  * @since 4.9.6
   4751  */
   4752 function wp_ajax_wp_privacy_export_personal_data() {
   4753 
   4754 	if ( empty( $_POST['id'] ) ) {
   4755 		wp_send_json_error( __( 'Missing request ID.' ) );
   4756 	}
   4757 
   4758 	$request_id = (int) $_POST['id'];
   4759 
   4760 	if ( $request_id < 1 ) {
   4761 		wp_send_json_error( __( 'Invalid request ID.' ) );
   4762 	}
   4763 
   4764 	if ( ! current_user_can( 'export_others_personal_data' ) ) {
   4765 		wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
   4766 	}
   4767 
   4768 	check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
   4769 
   4770 	// Get the request.
   4771 	$request = wp_get_user_request( $request_id );
   4772 
   4773 	if ( ! $request || 'export_personal_data' !== $request->action_name ) {
   4774 		wp_send_json_error( __( 'Invalid request type.' ) );
   4775 	}
   4776 
   4777 	$email_address = $request->email;
   4778 	if ( ! is_email( $email_address ) ) {
   4779 		wp_send_json_error( __( 'A valid email address must be given.' ) );
   4780 	}
   4781 
   4782 	if ( ! isset( $_POST['exporter'] ) ) {
   4783 		wp_send_json_error( __( 'Missing exporter index.' ) );
   4784 	}
   4785 
   4786 	$exporter_index = (int) $_POST['exporter'];
   4787 
   4788 	if ( ! isset( $_POST['page'] ) ) {
   4789 		wp_send_json_error( __( 'Missing page index.' ) );
   4790 	}
   4791 
   4792 	$page = (int) $_POST['page'];
   4793 
   4794 	$send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
   4795 
   4796 	/**
   4797 	 * Filters the array of exporter callbacks.
   4798 	 *
   4799 	 * @since 4.9.6
   4800 	 *
   4801 	 * @param array $args {
   4802 	 *     An array of callable exporters of personal data. Default empty array.
   4803 	 *
   4804 	 *     @type array ...$0 {
   4805 	 *         Array of personal data exporters.
   4806 	 *
   4807 	 *         @type callable $callback               Callable exporter function that accepts an
   4808 	 *                                                email address and a page and returns an array
   4809 	 *                                                of name => value pairs of personal data.
   4810 	 *         @type string   $exporter_friendly_name Translated user facing friendly name for the
   4811 	 *                                                exporter.
   4812 	 *     }
   4813 	 * }
   4814 	 */
   4815 	$exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
   4816 
   4817 	if ( ! is_array( $exporters ) ) {
   4818 		wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
   4819 	}
   4820 
   4821 	// Do we have any registered exporters?
   4822 	if ( 0 < count( $exporters ) ) {
   4823 		if ( $exporter_index < 1 ) {
   4824 			wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
   4825 		}
   4826 
   4827 		if ( $exporter_index > count( $exporters ) ) {
   4828 			wp_send_json_error( __( 'Exporter index is out of range.' ) );
   4829 		}
   4830 
   4831 		if ( $page < 1 ) {
   4832 			wp_send_json_error( __( 'Page index cannot be less than one.' ) );
   4833 		}
   4834 
   4835 		$exporter_keys = array_keys( $exporters );
   4836 		$exporter_key  = $exporter_keys[ $exporter_index - 1 ];
   4837 		$exporter      = $exporters[ $exporter_key ];
   4838 
   4839 		if ( ! is_array( $exporter ) ) {
   4840 			wp_send_json_error(
   4841 				/* translators: %s: Exporter array index. */
   4842 				sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
   4843 			);
   4844 		}
   4845 
   4846 		if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
   4847 			wp_send_json_error(
   4848 				/* translators: %s: Exporter array index. */
   4849 				sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
   4850 			);
   4851 		}
   4852 
   4853 		$exporter_friendly_name = $exporter['exporter_friendly_name'];
   4854 
   4855 		if ( ! array_key_exists( 'callback', $exporter ) ) {
   4856 			wp_send_json_error(
   4857 				/* translators: %s: Exporter friendly name. */
   4858 				sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
   4859 			);
   4860 		}
   4861 
   4862 		if ( ! is_callable( $exporter['callback'] ) ) {
   4863 			wp_send_json_error(
   4864 				/* translators: %s: Exporter friendly name. */
   4865 				sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
   4866 			);
   4867 		}
   4868 
   4869 		$callback = $exporter['callback'];
   4870 		$response = call_user_func( $callback, $email_address, $page );
   4871 
   4872 		if ( is_wp_error( $response ) ) {
   4873 			wp_send_json_error( $response );
   4874 		}
   4875 
   4876 		if ( ! is_array( $response ) ) {
   4877 			wp_send_json_error(
   4878 				/* translators: %s: Exporter friendly name. */
   4879 				sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
   4880 			);
   4881 		}
   4882 
   4883 		if ( ! array_key_exists( 'data', $response ) ) {
   4884 			wp_send_json_error(
   4885 				/* translators: %s: Exporter friendly name. */
   4886 				sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
   4887 			);
   4888 		}
   4889 
   4890 		if ( ! is_array( $response['data'] ) ) {
   4891 			wp_send_json_error(
   4892 				/* translators: %s: Exporter friendly name. */
   4893 				sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
   4894 			);
   4895 		}
   4896 
   4897 		if ( ! array_key_exists( 'done', $response ) ) {
   4898 			wp_send_json_error(
   4899 				/* translators: %s: Exporter friendly name. */
   4900 				sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
   4901 			);
   4902 		}
   4903 	} else {
   4904 		// No exporters, so we're done.
   4905 		$exporter_key = '';
   4906 
   4907 		$response = array(
   4908 			'data' => array(),
   4909 			'done' => true,
   4910 		);
   4911 	}
   4912 
   4913 	/**
   4914 	 * Filters a page of personal data exporter data. Used to build the export report.
   4915 	 *
   4916 	 * Allows the export response to be consumed by destinations in addition to Ajax.
   4917 	 *
   4918 	 * @since 4.9.6
   4919 	 *
   4920 	 * @param array  $response        The personal data for the given exporter and page.
   4921 	 * @param int    $exporter_index  The index of the exporter that provided this data.
   4922 	 * @param string $email_address   The email address associated with this personal data.
   4923 	 * @param int    $page            The page for this response.
   4924 	 * @param int    $request_id      The privacy request post ID associated with this request.
   4925 	 * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
   4926 	 * @param string $exporter_key    The key (slug) of the exporter that provided this data.
   4927 	 */
   4928 	$response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
   4929 
   4930 	if ( is_wp_error( $response ) ) {
   4931 		wp_send_json_error( $response );
   4932 	}
   4933 
   4934 	wp_send_json_success( $response );
   4935 }
   4936 
   4937 /**
   4938  * Ajax handler for erasing personal data.
   4939  *
   4940  * @since 4.9.6
   4941  */
   4942 function wp_ajax_wp_privacy_erase_personal_data() {
   4943 
   4944 	if ( empty( $_POST['id'] ) ) {
   4945 		wp_send_json_error( __( 'Missing request ID.' ) );
   4946 	}
   4947 
   4948 	$request_id = (int) $_POST['id'];
   4949 
   4950 	if ( $request_id < 1 ) {
   4951 		wp_send_json_error( __( 'Invalid request ID.' ) );
   4952 	}
   4953 
   4954 	// Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
   4955 	if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
   4956 		wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
   4957 	}
   4958 
   4959 	check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
   4960 
   4961 	// Get the request.
   4962 	$request = wp_get_user_request( $request_id );
   4963 
   4964 	if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
   4965 		wp_send_json_error( __( 'Invalid request type.' ) );
   4966 	}
   4967 
   4968 	$email_address = $request->email;
   4969 
   4970 	if ( ! is_email( $email_address ) ) {
   4971 		wp_send_json_error( __( 'Invalid email address in request.' ) );
   4972 	}
   4973 
   4974 	if ( ! isset( $_POST['eraser'] ) ) {
   4975 		wp_send_json_error( __( 'Missing eraser index.' ) );
   4976 	}
   4977 
   4978 	$eraser_index = (int) $_POST['eraser'];
   4979 
   4980 	if ( ! isset( $_POST['page'] ) ) {
   4981 		wp_send_json_error( __( 'Missing page index.' ) );
   4982 	}
   4983 
   4984 	$page = (int) $_POST['page'];
   4985 
   4986 	/**
   4987 	 * Filters the array of personal data eraser callbacks.
   4988 	 *
   4989 	 * @since 4.9.6
   4990 	 *
   4991 	 * @param array $args {
   4992 	 *     An array of callable erasers of personal data. Default empty array.
   4993 	 *
   4994 	 *     @type array ...$0 {
   4995 	 *         Array of personal data exporters.
   4996 	 *
   4997 	 *         @type callable $callback               Callable eraser that accepts an email address and
   4998 	 *                                                a page and returns an array with boolean values for
   4999 	 *                                                whether items were removed or retained and any messages
   5000 	 *                                                from the eraser, as well as if additional pages are
   5001 	 *                                                available.
   5002 	 *         @type string   $exporter_friendly_name Translated user facing friendly name for the eraser.
   5003 	 *     }
   5004 	 * }
   5005 	 */
   5006 	$erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
   5007 
   5008 	// Do we have any registered erasers?
   5009 	if ( 0 < count( $erasers ) ) {
   5010 
   5011 		if ( $eraser_index < 1 ) {
   5012 			wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
   5013 		}
   5014 
   5015 		if ( $eraser_index > count( $erasers ) ) {
   5016 			wp_send_json_error( __( 'Eraser index is out of range.' ) );
   5017 		}
   5018 
   5019 		if ( $page < 1 ) {
   5020 			wp_send_json_error( __( 'Page index cannot be less than one.' ) );
   5021 		}
   5022 
   5023 		$eraser_keys = array_keys( $erasers );
   5024 		$eraser_key  = $eraser_keys[ $eraser_index - 1 ];
   5025 		$eraser      = $erasers[ $eraser_key ];
   5026 
   5027 		if ( ! is_array( $eraser ) ) {
   5028 			/* translators: %d: Eraser array index. */
   5029 			wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
   5030 		}
   5031 
   5032 		if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
   5033 			/* translators: %d: Eraser array index. */
   5034 			wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
   5035 		}
   5036 
   5037 		$eraser_friendly_name = $eraser['eraser_friendly_name'];
   5038 
   5039 		if ( ! array_key_exists( 'callback', $eraser ) ) {
   5040 			wp_send_json_error(
   5041 				sprintf(
   5042 					/* translators: %s: Eraser friendly name. */
   5043 					__( 'Eraser does not include a callback: %s.' ),
   5044 					esc_html( $eraser_friendly_name )
   5045 				)
   5046 			);
   5047 		}
   5048 
   5049 		if ( ! is_callable( $eraser['callback'] ) ) {
   5050 			wp_send_json_error(
   5051 				sprintf(
   5052 					/* translators: %s: Eraser friendly name. */
   5053 					__( 'Eraser callback is not valid: %s.' ),
   5054 					esc_html( $eraser_friendly_name )
   5055 				)
   5056 			);
   5057 		}
   5058 
   5059 		$callback = $eraser['callback'];
   5060 		$response = call_user_func( $callback, $email_address, $page );
   5061 
   5062 		if ( is_wp_error( $response ) ) {
   5063 			wp_send_json_error( $response );
   5064 		}
   5065 
   5066 		if ( ! is_array( $response ) ) {
   5067 			wp_send_json_error(
   5068 				sprintf(
   5069 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5070 					__( 'Did not receive array from %1$s eraser (index %2$d).' ),
   5071 					esc_html( $eraser_friendly_name ),
   5072 					$eraser_index
   5073 				)
   5074 			);
   5075 		}
   5076 
   5077 		if ( ! array_key_exists( 'items_removed', $response ) ) {
   5078 			wp_send_json_error(
   5079 				sprintf(
   5080 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5081 					__( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
   5082 					esc_html( $eraser_friendly_name ),
   5083 					$eraser_index
   5084 				)
   5085 			);
   5086 		}
   5087 
   5088 		if ( ! array_key_exists( 'items_retained', $response ) ) {
   5089 			wp_send_json_error(
   5090 				sprintf(
   5091 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5092 					__( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
   5093 					esc_html( $eraser_friendly_name ),
   5094 					$eraser_index
   5095 				)
   5096 			);
   5097 		}
   5098 
   5099 		if ( ! array_key_exists( 'messages', $response ) ) {
   5100 			wp_send_json_error(
   5101 				sprintf(
   5102 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5103 					__( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
   5104 					esc_html( $eraser_friendly_name ),
   5105 					$eraser_index
   5106 				)
   5107 			);
   5108 		}
   5109 
   5110 		if ( ! is_array( $response['messages'] ) ) {
   5111 			wp_send_json_error(
   5112 				sprintf(
   5113 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5114 					__( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
   5115 					esc_html( $eraser_friendly_name ),
   5116 					$eraser_index
   5117 				)
   5118 			);
   5119 		}
   5120 
   5121 		if ( ! array_key_exists( 'done', $response ) ) {
   5122 			wp_send_json_error(
   5123 				sprintf(
   5124 					/* translators: 1: Eraser friendly name, 2: Eraser array index. */
   5125 					__( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
   5126 					esc_html( $eraser_friendly_name ),
   5127 					$eraser_index
   5128 				)
   5129 			);
   5130 		}
   5131 	} else {
   5132 		// No erasers, so we're done.
   5133 		$eraser_key = '';
   5134 
   5135 		$response = array(
   5136 			'items_removed'  => false,
   5137 			'items_retained' => false,
   5138 			'messages'       => array(),
   5139 			'done'           => true,
   5140 		);
   5141 	}
   5142 
   5143 	/**
   5144 	 * Filters a page of personal data eraser data.
   5145 	 *
   5146 	 * Allows the erasure response to be consumed by destinations in addition to Ajax.
   5147 	 *
   5148 	 * @since 4.9.6
   5149 	 *
   5150 	 * @param array  $response        The personal data for the given exporter and page.
   5151 	 * @param int    $eraser_index    The index of the eraser that provided this data.
   5152 	 * @param string $email_address   The email address associated with this personal data.
   5153 	 * @param int    $page            The page for this response.
   5154 	 * @param int    $request_id      The privacy request post ID associated with this request.
   5155 	 * @param string $eraser_key      The key (slug) of the eraser that provided this data.
   5156 	 */
   5157 	$response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
   5158 
   5159 	if ( is_wp_error( $response ) ) {
   5160 		wp_send_json_error( $response );
   5161 	}
   5162 
   5163 	wp_send_json_success( $response );
   5164 }
   5165 
   5166 /**
   5167  * Ajax handler for site health checks on server communication.
   5168  *
   5169  * @since 5.2.0
   5170  * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
   5171  * @see WP_REST_Site_Health_Controller::test_dotorg_communication()
   5172  */
   5173 function wp_ajax_health_check_dotorg_communication() {
   5174 	_doing_it_wrong(
   5175 		'wp_ajax_health_check_dotorg_communication',
   5176 		sprintf(
   5177 		// translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
   5178 			__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
   5179 			'wp_ajax_health_check_dotorg_communication',
   5180 			'WP_REST_Site_Health_Controller::test_dotorg_communication'
   5181 		),
   5182 		'5.6.0'
   5183 	);
   5184 
   5185 	check_ajax_referer( 'health-check-site-status' );
   5186 
   5187 	if ( ! current_user_can( 'view_site_health_checks' ) ) {
   5188 		wp_send_json_error();
   5189 	}
   5190 
   5191 	if ( ! class_exists( 'WP_Site_Health' ) ) {
   5192 		require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
   5193 	}
   5194 
   5195 	$site_health = WP_Site_Health::get_instance();
   5196 	wp_send_json_success( $site_health->get_test_dotorg_communication() );
   5197 }
   5198 
   5199 /**
   5200  * Ajax handler for site health checks on background updates.
   5201  *
   5202  * @since 5.2.0
   5203  * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
   5204  * @see WP_REST_Site_Health_Controller::test_background_updates()
   5205  */
   5206 function wp_ajax_health_check_background_updates() {
   5207 	_doing_it_wrong(
   5208 		'wp_ajax_health_check_background_updates',
   5209 		sprintf(
   5210 		// translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
   5211 			__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
   5212 			'wp_ajax_health_check_background_updates',
   5213 			'WP_REST_Site_Health_Controller::test_background_updates'
   5214 		),
   5215 		'5.6.0'
   5216 	);
   5217 
   5218 	check_ajax_referer( 'health-check-site-status' );
   5219 
   5220 	if ( ! current_user_can( 'view_site_health_checks' ) ) {
   5221 		wp_send_json_error();
   5222 	}
   5223 
   5224 	if ( ! class_exists( 'WP_Site_Health' ) ) {
   5225 		require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
   5226 	}
   5227 
   5228 	$site_health = WP_Site_Health::get_instance();
   5229 	wp_send_json_success( $site_health->get_test_background_updates() );
   5230 }
   5231 
   5232 /**
   5233  * Ajax handler for site health checks on loopback requests.
   5234  *
   5235  * @since 5.2.0
   5236  * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
   5237  * @see WP_REST_Site_Health_Controller::test_loopback_requests()
   5238  */
   5239 function wp_ajax_health_check_loopback_requests() {
   5240 	_doing_it_wrong(
   5241 		'wp_ajax_health_check_loopback_requests',
   5242 		sprintf(
   5243 		// translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
   5244 			__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
   5245 			'wp_ajax_health_check_loopback_requests',
   5246 			'WP_REST_Site_Health_Controller::test_loopback_requests'
   5247 		),
   5248 		'5.6.0'
   5249 	);
   5250 
   5251 	check_ajax_referer( 'health-check-site-status' );
   5252 
   5253 	if ( ! current_user_can( 'view_site_health_checks' ) ) {
   5254 		wp_send_json_error();
   5255 	}
   5256 
   5257 	if ( ! class_exists( 'WP_Site_Health' ) ) {
   5258 		require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
   5259 	}
   5260 
   5261 	$site_health = WP_Site_Health::get_instance();
   5262 	wp_send_json_success( $site_health->get_test_loopback_requests() );
   5263 }
   5264 
   5265 /**
   5266  * Ajax handler for site health check to update the result status.
   5267  *
   5268  * @since 5.2.0
   5269  */
   5270 function wp_ajax_health_check_site_status_result() {
   5271 	check_ajax_referer( 'health-check-site-status-result' );
   5272 
   5273 	if ( ! current_user_can( 'view_site_health_checks' ) ) {
   5274 		wp_send_json_error();
   5275 	}
   5276 
   5277 	set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
   5278 
   5279 	wp_send_json_success();
   5280 }
   5281 
   5282 /**
   5283  * Ajax handler for site health check to get directories and database sizes.
   5284  *
   5285  * @since 5.2.0
   5286  * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
   5287  * @see WP_REST_Site_Health_Controller::get_directory_sizes()
   5288  */
   5289 function wp_ajax_health_check_get_sizes() {
   5290 	_doing_it_wrong(
   5291 		'wp_ajax_health_check_get_sizes',
   5292 		sprintf(
   5293 		// translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
   5294 			__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
   5295 			'wp_ajax_health_check_get_sizes',
   5296 			'WP_REST_Site_Health_Controller::get_directory_sizes'
   5297 		),
   5298 		'5.6.0'
   5299 	);
   5300 
   5301 	check_ajax_referer( 'health-check-site-status-result' );
   5302 
   5303 	if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
   5304 		wp_send_json_error();
   5305 	}
   5306 
   5307 	if ( ! class_exists( 'WP_Debug_Data' ) ) {
   5308 		require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
   5309 	}
   5310 
   5311 	$sizes_data = WP_Debug_Data::get_sizes();
   5312 	$all_sizes  = array( 'raw' => 0 );
   5313 
   5314 	foreach ( $sizes_data as $name => $value ) {
   5315 		$name = sanitize_text_field( $name );
   5316 		$data = array();
   5317 
   5318 		if ( isset( $value['size'] ) ) {
   5319 			if ( is_string( $value['size'] ) ) {
   5320 				$data['size'] = sanitize_text_field( $value['size'] );
   5321 			} else {
   5322 				$data['size'] = (int) $value['size'];
   5323 			}
   5324 		}
   5325 
   5326 		if ( isset( $value['debug'] ) ) {
   5327 			if ( is_string( $value['debug'] ) ) {
   5328 				$data['debug'] = sanitize_text_field( $value['debug'] );
   5329 			} else {
   5330 				$data['debug'] = (int) $value['debug'];
   5331 			}
   5332 		}
   5333 
   5334 		if ( ! empty( $value['raw'] ) ) {
   5335 			$data['raw'] = (int) $value['raw'];
   5336 		}
   5337 
   5338 		$all_sizes[ $name ] = $data;
   5339 	}
   5340 
   5341 	if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
   5342 		wp_send_json_error( $all_sizes );
   5343 	}
   5344 
   5345 	wp_send_json_success( $all_sizes );
   5346 }
   5347 
   5348 /**
   5349  * Ajax handler to renew the REST API nonce.
   5350  *
   5351  * @since 5.3.0
   5352  */
   5353 function wp_ajax_rest_nonce() {
   5354 	exit( wp_create_nonce( 'wp_rest' ) );
   5355 }
   5356 
   5357 /**
   5358  * Ajax handler to enable or disable plugin and theme auto-updates.
   5359  *
   5360  * @since 5.5.0
   5361  */
   5362 function wp_ajax_toggle_auto_updates() {
   5363 	check_ajax_referer( 'updates' );
   5364 
   5365 	if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
   5366 		wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
   5367 	}
   5368 
   5369 	$asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
   5370 
   5371 	if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
   5372 		wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
   5373 	}
   5374 	$state = $_POST['state'];
   5375 
   5376 	if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
   5377 		wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
   5378 	}
   5379 	$type = $_POST['type'];
   5380 
   5381 	switch ( $type ) {
   5382 		case 'plugin':
   5383 			if ( ! current_user_can( 'update_plugins' ) ) {
   5384 				$error_message = __( 'Sorry, you are not allowed to modify plugins.' );
   5385 				wp_send_json_error( array( 'error' => $error_message ) );
   5386 			}
   5387 
   5388 			$option = 'auto_update_plugins';
   5389 			/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
   5390 			$all_items = apply_filters( 'all_plugins', get_plugins() );
   5391 			break;
   5392 		case 'theme':
   5393 			if ( ! current_user_can( 'update_themes' ) ) {
   5394 				$error_message = __( 'Sorry, you are not allowed to modify themes.' );
   5395 				wp_send_json_error( array( 'error' => $error_message ) );
   5396 			}
   5397 
   5398 			$option    = 'auto_update_themes';
   5399 			$all_items = wp_get_themes();
   5400 			break;
   5401 		default:
   5402 			wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
   5403 	}
   5404 
   5405 	if ( ! array_key_exists( $asset, $all_items ) ) {
   5406 		$error_message = __( 'Invalid data. The item does not exist.' );
   5407 		wp_send_json_error( array( 'error' => $error_message ) );
   5408 	}
   5409 
   5410 	$auto_updates = (array) get_site_option( $option, array() );
   5411 
   5412 	if ( 'disable' === $state ) {
   5413 		$auto_updates = array_diff( $auto_updates, array( $asset ) );
   5414 	} else {
   5415 		$auto_updates[] = $asset;
   5416 		$auto_updates   = array_unique( $auto_updates );
   5417 	}
   5418 
   5419 	// Remove items that have been deleted since the site option was last updated.
   5420 	$auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
   5421 
   5422 	update_site_option( $option, $auto_updates );
   5423 
   5424 	wp_send_json_success();
   5425 }
   5426 
   5427 /**
   5428  * Ajax handler sends a password reset link.
   5429  *
   5430  * @since 5.7.0
   5431  */
   5432 function wp_ajax_send_password_reset() {
   5433 
   5434 	// Validate the nonce for this action.
   5435 	$user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
   5436 	check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' );
   5437 
   5438 	// Verify user capabilities.
   5439 	if ( ! current_user_can( 'edit_user', $user_id ) ) {
   5440 		wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) );
   5441 	}
   5442 
   5443 	// Send the password reset link.
   5444 	$user    = get_userdata( $user_id );
   5445 	$results = retrieve_password( $user->user_login );
   5446 
   5447 	if ( true === $results ) {
   5448 		wp_send_json_success(
   5449 			/* translators: %s: User's display name. */
   5450 			sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name )
   5451 		);
   5452 	} else {
   5453 		wp_send_json_error( $results->get_error_message() );
   5454 	}
   5455 }