]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/includes/ajax-actions.php
WordPress 4.7.2
[autoinstalls/wordpress.git] / wp-admin / includes / ajax-actions.php
1 <?php
2 /**
3  * Administration API: Core Ajax handlers
4  *
5  * @package WordPress
6  * @subpackage Administration
7  * @since 2.1.0
8  */
9
10 //
11 // No-privilege Ajax handlers.
12 //
13
14 /**
15  * Ajax handler for the Heartbeat API in
16  * the no-privilege context.
17  *
18  * Runs when the user is not logged in.
19  *
20  * @since 3.6.0
21  */
22 function wp_ajax_nopriv_heartbeat() {
23         $response = array();
24
25         // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
26         if ( ! empty($_POST['screen_id']) )
27                 $screen_id = sanitize_key($_POST['screen_id']);
28         else
29                 $screen_id = 'front';
30
31         if ( ! empty($_POST['data']) ) {
32                 $data = wp_unslash( (array) $_POST['data'] );
33
34                 /**
35                  * Filters Heartbeat Ajax response in no-privilege environments.
36                  *
37                  * @since 3.6.0
38                  *
39                  * @param array|object $response  The no-priv Heartbeat response object or array.
40                  * @param array        $data      An array of data passed via $_POST.
41                  * @param string       $screen_id The screen id.
42                  */
43                 $response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id );
44         }
45
46         /**
47          * Filters Heartbeat Ajax response when no data is passed.
48          *
49          * @since 3.6.0
50          *
51          * @param array|object $response  The Heartbeat response object or array.
52          * @param string       $screen_id The screen id.
53          */
54         $response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id );
55
56         /**
57          * Fires when Heartbeat ticks in no-privilege environments.
58          *
59          * Allows the transport to be easily replaced with long-polling.
60          *
61          * @since 3.6.0
62          *
63          * @param array|object $response  The no-priv Heartbeat response.
64          * @param string       $screen_id The screen id.
65          */
66         do_action( 'heartbeat_nopriv_tick', $response, $screen_id );
67
68         // Send the current time according to the server.
69         $response['server_time'] = time();
70
71         wp_send_json($response);
72 }
73
74 //
75 // GET-based Ajax handlers.
76 //
77
78 /**
79  * Ajax handler for fetching a list table.
80  *
81  * @since 3.1.0
82  */
83 function wp_ajax_fetch_list() {
84         $list_class = $_GET['list_args']['class'];
85         check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
86
87         $wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
88         if ( ! $wp_list_table ) {
89                 wp_die( 0 );
90         }
91
92         if ( ! $wp_list_table->ajax_user_can() ) {
93                 wp_die( -1 );
94         }
95
96         $wp_list_table->ajax_response();
97
98         wp_die( 0 );
99 }
100
101 /**
102  * Ajax handler for tag search.
103  *
104  * @since 3.1.0
105  */
106 function wp_ajax_ajax_tag_search() {
107         if ( ! isset( $_GET['tax'] ) ) {
108                 wp_die( 0 );
109         }
110
111         $taxonomy = sanitize_key( $_GET['tax'] );
112         $tax = get_taxonomy( $taxonomy );
113         if ( ! $tax ) {
114                 wp_die( 0 );
115         }
116
117         if ( ! current_user_can( $tax->cap->assign_terms ) ) {
118                 wp_die( -1 );
119         }
120
121         $s = wp_unslash( $_GET['q'] );
122
123         $comma = _x( ',', 'tag delimiter' );
124         if ( ',' !== $comma )
125                 $s = str_replace( $comma, ',', $s );
126         if ( false !== strpos( $s, ',' ) ) {
127                 $s = explode( ',', $s );
128                 $s = $s[count( $s ) - 1];
129         }
130         $s = trim( $s );
131
132         /**
133          * Filters the minimum number of characters required to fire a tag search via Ajax.
134          *
135          * @since 4.0.0
136          *
137          * @param int         $characters The minimum number of characters required. Default 2.
138          * @param WP_Taxonomy $tax        The taxonomy object.
139          * @param string      $s          The search term.
140          */
141         $term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
142
143         /*
144          * Require $term_search_min_chars chars for matching (default: 2)
145          * ensure it's a non-negative, non-zero integer.
146          */
147         if ( ( $term_search_min_chars == 0 ) || ( strlen( $s ) < $term_search_min_chars ) ){
148                 wp_die();
149         }
150
151         $results = get_terms( $taxonomy, array( 'name__like' => $s, 'fields' => 'names', 'hide_empty' => false ) );
152
153         echo join( $results, "\n" );
154         wp_die();
155 }
156
157 /**
158  * Ajax handler for compression testing.
159  *
160  * @since 3.1.0
161  */
162 function wp_ajax_wp_compression_test() {
163         if ( !current_user_can( 'manage_options' ) )
164                 wp_die( -1 );
165
166         if ( ini_get('zlib.output_compression') || 'ob_gzhandler' == ini_get('output_handler') ) {
167                 update_site_option('can_compress_scripts', 0);
168                 wp_die( 0 );
169         }
170
171         if ( isset($_GET['test']) ) {
172                 header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
173                 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
174                 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
175                 header('Content-Type: application/javascript; charset=UTF-8');
176                 $force_gzip = ( defined('ENFORCE_GZIP') && ENFORCE_GZIP );
177                 $test_str = '"wpCompressionTest Lorem ipsum dolor sit amet consectetuer mollis sapien urna ut a. Eu nonummy condimentum fringilla tempor pretium platea vel nibh netus Maecenas. Hac molestie amet justo quis pellentesque est ultrices interdum nibh Morbi. Cras mattis pretium Phasellus ante ipsum ipsum ut sociis Suspendisse Lorem. Ante et non molestie. Porta urna Vestibulum egestas id congue nibh eu risus gravida sit. Ac augue auctor Ut et non a elit massa id sodales. Elit eu Nulla at nibh adipiscing mattis lacus mauris at tempus. Netus nibh quis suscipit nec feugiat eget sed lorem et urna. Pellentesque lacus at ut massa consectetuer ligula ut auctor semper Pellentesque. Ut metus massa nibh quam Curabitur molestie nec mauris congue. Volutpat molestie elit justo facilisis neque ac risus Ut nascetur tristique. Vitae sit lorem tellus et quis Phasellus lacus tincidunt nunc Fusce. Pharetra wisi Suspendisse mus sagittis libero lacinia Integer consequat ac Phasellus. Et urna ac cursus tortor aliquam Aliquam amet tellus volutpat Vestibulum. Justo interdum condimentum In augue congue tellus sollicitudin Quisque quis nibh."';
178
179                  if ( 1 == $_GET['test'] ) {
180                         echo $test_str;
181                         wp_die();
182                  } elseif ( 2 == $_GET['test'] ) {
183                         if ( !isset($_SERVER['HTTP_ACCEPT_ENCODING']) )
184                                 wp_die( -1 );
185                         if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') && function_exists('gzdeflate') && ! $force_gzip ) {
186                                 header('Content-Encoding: deflate');
187                                 $out = gzdeflate( $test_str, 1 );
188                         } elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzencode') ) {
189                                 header('Content-Encoding: gzip');
190                                 $out = gzencode( $test_str, 1 );
191                         } else {
192                                 wp_die( -1 );
193                         }
194                         echo $out;
195                         wp_die();
196                 } elseif ( 'no' == $_GET['test'] ) {
197                         check_ajax_referer( 'update_can_compress_scripts' );
198                         update_site_option('can_compress_scripts', 0);
199                 } elseif ( 'yes' == $_GET['test'] ) {
200                         check_ajax_referer( 'update_can_compress_scripts' );
201                         update_site_option('can_compress_scripts', 1);
202                 }
203         }
204
205         wp_die( 0 );
206 }
207
208 /**
209  * Ajax handler for image editor previews.
210  *
211  * @since 3.1.0
212  */
213 function wp_ajax_imgedit_preview() {
214         $post_id = intval($_GET['postid']);
215         if ( empty($post_id) || !current_user_can('edit_post', $post_id) )
216                 wp_die( -1 );
217
218         check_ajax_referer( "image_editor-$post_id" );
219
220         include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
221         if ( ! stream_preview_image($post_id) )
222                 wp_die( -1 );
223
224         wp_die();
225 }
226
227 /**
228  * Ajax handler for oEmbed caching.
229  *
230  * @since 3.1.0
231  *
232  * @global WP_Embed $wp_embed
233  */
234 function wp_ajax_oembed_cache() {
235         $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
236         wp_die( 0 );
237 }
238
239 /**
240  * Ajax handler for user autocomplete.
241  *
242  * @since 3.4.0
243  */
244 function wp_ajax_autocomplete_user() {
245         if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) )
246                 wp_die( -1 );
247
248         /** This filter is documented in wp-admin/user-new.php */
249         if ( ! is_super_admin() && ! apply_filters( 'autocomplete_users_for_site_admins', false ) )
250                 wp_die( -1 );
251
252         $return = array();
253
254         // Check the type of request
255         // Current allowed values are `add` and `search`
256         if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
257                 $type = $_REQUEST['autocomplete_type'];
258         } else {
259                 $type = 'add';
260         }
261
262         // Check the desired field for value
263         // Current allowed values are `user_email` and `user_login`
264         if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
265                 $field = $_REQUEST['autocomplete_field'];
266         } else {
267                 $field = 'user_login';
268         }
269
270         // Exclude current users of this blog
271         if ( isset( $_REQUEST['site_id'] ) ) {
272                 $id = absint( $_REQUEST['site_id'] );
273         } else {
274                 $id = get_current_blog_id();
275         }
276
277         $include_blog_users = ( $type == 'search' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
278         $exclude_blog_users = ( $type == 'add' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
279
280         $users = get_users( array(
281                 'blog_id' => false,
282                 'search'  => '*' . $_REQUEST['term'] . '*',
283                 'include' => $include_blog_users,
284                 'exclude' => $exclude_blog_users,
285                 'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
286         ) );
287
288         foreach ( $users as $user ) {
289                 $return[] = array(
290                         /* translators: 1: user_login, 2: user_email */
291                         'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
292                         'value' => $user->$field,
293                 );
294         }
295
296         wp_die( wp_json_encode( $return ) );
297 }
298
299 /**
300  * Ajax handler for dashboard widgets.
301  *
302  * @since 3.4.0
303  */
304 function wp_ajax_dashboard_widgets() {
305         require_once ABSPATH . 'wp-admin/includes/dashboard.php';
306
307         $pagenow = $_GET['pagenow'];
308         if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
309                 set_current_screen( $pagenow );
310         }
311
312         switch ( $_GET['widget'] ) {
313                 case 'dashboard_primary' :
314                         wp_dashboard_primary();
315                         break;
316         }
317         wp_die();
318 }
319
320 /**
321  * Ajax handler for Customizer preview logged-in status.
322  *
323  * @since 3.4.0
324  */
325 function wp_ajax_logged_in() {
326         wp_die( 1 );
327 }
328
329 //
330 // Ajax helpers.
331 //
332
333 /**
334  * Sends back current comment total and new page links if they need to be updated.
335  *
336  * Contrary to normal success Ajax response ("1"), die with time() on success.
337  *
338  * @access private
339  * @since 2.7.0
340  *
341  * @param int $comment_id
342  * @param int $delta
343  */
344 function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
345         $total    = isset( $_POST['_total'] )    ? (int) $_POST['_total']    : 0;
346         $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
347         $page     = isset( $_POST['_page'] )     ? (int) $_POST['_page']     : 0;
348         $url      = isset( $_POST['_url'] )      ? esc_url_raw( $_POST['_url'] ) : '';
349
350         // JS didn't send us everything we need to know. Just die with success message
351         if ( ! $total || ! $per_page || ! $page || ! $url ) {
352                 $time           = time();
353                 $comment        = get_comment( $comment_id );
354                 $comment_status = '';
355                 $comment_link   = '';
356
357                 if ( $comment ) {
358                         $comment_status = $comment->comment_approved;
359                 }
360
361                 if ( 1 === (int) $comment_status ) {
362                         $comment_link = get_comment_link( $comment );
363                 }
364
365                 $counts = wp_count_comments();
366
367                 $x = new WP_Ajax_Response( array(
368                         'what' => 'comment',
369                         // Here for completeness - not used.
370                         'id' => $comment_id,
371                         'supplemental' => array(
372                                 'status' => $comment_status,
373                                 'postId' => $comment ? $comment->comment_post_ID : '',
374                                 'time' => $time,
375                                 'in_moderation' => $counts->moderated,
376                                 'i18n_comments_text' => sprintf(
377                                         _n( '%s Comment', '%s Comments', $counts->approved ),
378                                         number_format_i18n( $counts->approved )
379                                 ),
380                                 'i18n_moderation_text' => sprintf(
381                                         _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
382                                         number_format_i18n( $counts->moderated )
383                                 ),
384                                 'comment_link' => $comment_link,
385                         )
386                 ) );
387                 $x->send();
388         }
389
390         $total += $delta;
391         if ( $total < 0 )
392                 $total = 0;
393
394         // Only do the expensive stuff on a page-break, and about 1 other time per page
395         if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
396                 $post_id = 0;
397                 // What type of comment count are we looking for?
398                 $status = 'all';
399                 $parsed = parse_url( $url );
400                 if ( isset( $parsed['query'] ) ) {
401                         parse_str( $parsed['query'], $query_vars );
402                         if ( !empty( $query_vars['comment_status'] ) )
403                                 $status = $query_vars['comment_status'];
404                         if ( !empty( $query_vars['p'] ) )
405                                 $post_id = (int) $query_vars['p'];
406                         if ( ! empty( $query_vars['comment_type'] ) )
407                                 $type = $query_vars['comment_type'];
408                 }
409
410                 if ( empty( $type ) ) {
411                         // Only use the comment count if not filtering by a comment_type.
412                         $comment_count = wp_count_comments($post_id);
413
414                         // We're looking for a known type of comment count.
415                         if ( isset( $comment_count->$status ) ) {
416                                 $total = $comment_count->$status;
417                         }
418                 }
419                 // Else use the decremented value from above.
420         }
421
422         // The time since the last comment count.
423         $time = time();
424         $comment = get_comment( $comment_id );
425
426         $x = new WP_Ajax_Response( array(
427                 'what' => 'comment',
428                 // Here for completeness - not used.
429                 'id' => $comment_id,
430                 'supplemental' => array(
431                         'status' => $comment ? $comment->comment_approved : '',
432                         'postId' => $comment ? $comment->comment_post_ID : '',
433                         'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
434                         'total_pages' => ceil( $total / $per_page ),
435                         'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
436                         'total' => $total,
437                         'time' => $time
438                 )
439         ) );
440         $x->send();
441 }
442
443 //
444 // POST-based Ajax handlers.
445 //
446
447 /**
448  * Ajax handler for adding a hierarchical term.
449  *
450  * @access private
451  * @since 3.1.0
452  */
453 function _wp_ajax_add_hierarchical_term() {
454         $action = $_POST['action'];
455         $taxonomy = get_taxonomy(substr($action, 4));
456         check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
457         if ( !current_user_can( $taxonomy->cap->edit_terms ) )
458                 wp_die( -1 );
459         $names = explode(',', $_POST['new'.$taxonomy->name]);
460         $parent = isset($_POST['new'.$taxonomy->name.'_parent']) ? (int) $_POST['new'.$taxonomy->name.'_parent'] : 0;
461         if ( 0 > $parent )
462                 $parent = 0;
463         if ( $taxonomy->name == 'category' )
464                 $post_category = isset($_POST['post_category']) ? (array) $_POST['post_category'] : array();
465         else
466                 $post_category = ( isset($_POST['tax_input']) && isset($_POST['tax_input'][$taxonomy->name]) ) ? (array) $_POST['tax_input'][$taxonomy->name] : array();
467         $checked_categories = array_map( 'absint', (array) $post_category );
468         $popular_ids = wp_popular_terms_checklist($taxonomy->name, 0, 10, false);
469
470         foreach ( $names as $cat_name ) {
471                 $cat_name = trim($cat_name);
472                 $category_nicename = sanitize_title($cat_name);
473                 if ( '' === $category_nicename )
474                         continue;
475                 if ( !$cat_id = term_exists( $cat_name, $taxonomy->name, $parent ) )
476                         $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
477                 if ( is_wp_error( $cat_id ) ) {
478                         continue;
479                 } elseif ( is_array( $cat_id ) ) {
480                         $cat_id = $cat_id['term_id'];
481                 }
482                 $checked_categories[] = $cat_id;
483                 if ( $parent ) // Do these all at once in a second
484                         continue;
485
486                 ob_start();
487
488                 wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids ));
489
490                 $data = ob_get_clean();
491
492                 $add = array(
493                         'what' => $taxonomy->name,
494                         'id' => $cat_id,
495                         'data' => str_replace( array("\n", "\t"), '', $data),
496                         'position' => -1
497                 );
498         }
499
500         if ( $parent ) { // Foncy - replace the parent and all its children
501                 $parent = get_term( $parent, $taxonomy->name );
502                 $term_id = $parent->term_id;
503
504                 while ( $parent->parent ) { // get the top parent
505                         $parent = get_term( $parent->parent, $taxonomy->name );
506                         if ( is_wp_error( $parent ) )
507                                 break;
508                         $term_id = $parent->term_id;
509                 }
510
511                 ob_start();
512
513                 wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids));
514
515                 $data = ob_get_clean();
516
517                 $add = array(
518                         'what' => $taxonomy->name,
519                         'id' => $term_id,
520                         'data' => str_replace( array("\n", "\t"), '', $data),
521                         'position' => -1
522                 );
523         }
524
525         ob_start();
526
527         wp_dropdown_categories( array(
528                 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name',
529                 'hierarchical' => 1, 'show_option_none' => '&mdash; '.$taxonomy->labels->parent_item.' &mdash;'
530         ) );
531
532         $sup = ob_get_clean();
533
534         $add['supplemental'] = array( 'newcat_parent' => $sup );
535
536         $x = new WP_Ajax_Response( $add );
537         $x->send();
538 }
539
540 /**
541  * Ajax handler for deleting a comment.
542  *
543  * @since 3.1.0
544  */
545 function wp_ajax_delete_comment() {
546         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
547
548         if ( !$comment = get_comment( $id ) )
549                 wp_die( time() );
550         if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
551                 wp_die( -1 );
552
553         check_ajax_referer( "delete-comment_$id" );
554         $status = wp_get_comment_status( $comment );
555
556         $delta = -1;
557         if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) {
558                 if ( 'trash' == $status )
559                         wp_die( time() );
560                 $r = wp_trash_comment( $comment );
561         } elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) {
562                 if ( 'trash' != $status )
563                         wp_die( time() );
564                 $r = wp_untrash_comment( $comment );
565                 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash
566                         $delta = 1;
567         } elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) {
568                 if ( 'spam' == $status )
569                         wp_die( time() );
570                 $r = wp_spam_comment( $comment );
571         } elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) {
572                 if ( 'spam' != $status )
573                         wp_die( time() );
574                 $r = wp_unspam_comment( $comment );
575                 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam
576                         $delta = 1;
577         } elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) {
578                 $r = wp_delete_comment( $comment );
579         } else {
580                 wp_die( -1 );
581         }
582
583         if ( $r ) // Decide if we need to send back '1' or a more complicated response including page links and comment counts
584                 _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
585         wp_die( 0 );
586 }
587
588 /**
589  * Ajax handler for deleting a tag.
590  *
591  * @since 3.1.0
592  */
593 function wp_ajax_delete_tag() {
594         $tag_id = (int) $_POST['tag_ID'];
595         check_ajax_referer( "delete-tag_$tag_id" );
596
597         if ( ! current_user_can( 'delete_term', $tag_id ) ) {
598                 wp_die( -1 );
599         }
600
601         $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
602         $tag = get_term( $tag_id, $taxonomy );
603         if ( !$tag || is_wp_error( $tag ) )
604                 wp_die( 1 );
605
606         if ( wp_delete_term($tag_id, $taxonomy))
607                 wp_die( 1 );
608         else
609                 wp_die( 0 );
610 }
611
612 /**
613  * Ajax handler for deleting a link.
614  *
615  * @since 3.1.0
616  */
617 function wp_ajax_delete_link() {
618         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
619
620         check_ajax_referer( "delete-bookmark_$id" );
621         if ( !current_user_can( 'manage_links' ) )
622                 wp_die( -1 );
623
624         $link = get_bookmark( $id );
625         if ( !$link || is_wp_error( $link ) )
626                 wp_die( 1 );
627
628         if ( wp_delete_link( $id ) )
629                 wp_die( 1 );
630         else
631                 wp_die( 0 );
632 }
633
634 /**
635  * Ajax handler for deleting meta.
636  *
637  * @since 3.1.0
638  */
639 function wp_ajax_delete_meta() {
640         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
641
642         check_ajax_referer( "delete-meta_$id" );
643         if ( !$meta = get_metadata_by_mid( 'post', $id ) )
644                 wp_die( 1 );
645
646         if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta',  $meta->post_id, $meta->meta_key ) )
647                 wp_die( -1 );
648         if ( delete_meta( $meta->meta_id ) )
649                 wp_die( 1 );
650         wp_die( 0 );
651 }
652
653 /**
654  * Ajax handler for deleting a post.
655  *
656  * @since 3.1.0
657  *
658  * @param string $action Action to perform.
659  */
660 function wp_ajax_delete_post( $action ) {
661         if ( empty( $action ) )
662                 $action = 'delete-post';
663         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
664
665         check_ajax_referer( "{$action}_$id" );
666         if ( !current_user_can( 'delete_post', $id ) )
667                 wp_die( -1 );
668
669         if ( !get_post( $id ) )
670                 wp_die( 1 );
671
672         if ( wp_delete_post( $id ) )
673                 wp_die( 1 );
674         else
675                 wp_die( 0 );
676 }
677
678 /**
679  * Ajax handler for sending a post to the trash.
680  *
681  * @since 3.1.0
682  *
683  * @param string $action Action to perform.
684  */
685 function wp_ajax_trash_post( $action ) {
686         if ( empty( $action ) )
687                 $action = 'trash-post';
688         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
689
690         check_ajax_referer( "{$action}_$id" );
691         if ( !current_user_can( 'delete_post', $id ) )
692                 wp_die( -1 );
693
694         if ( !get_post( $id ) )
695                 wp_die( 1 );
696
697         if ( 'trash-post' == $action )
698                 $done = wp_trash_post( $id );
699         else
700                 $done = wp_untrash_post( $id );
701
702         if ( $done )
703                 wp_die( 1 );
704
705         wp_die( 0 );
706 }
707
708 /**
709  * Ajax handler to restore a post from the trash.
710  *
711  * @since 3.1.0
712  *
713  * @param string $action Action to perform.
714  */
715 function wp_ajax_untrash_post( $action ) {
716         if ( empty( $action ) )
717                 $action = 'untrash-post';
718         wp_ajax_trash_post( $action );
719 }
720
721 /**
722  * @since 3.1.0
723  *
724  * @param string $action
725  */
726 function wp_ajax_delete_page( $action ) {
727         if ( empty( $action ) )
728                 $action = 'delete-page';
729         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
730
731         check_ajax_referer( "{$action}_$id" );
732         if ( !current_user_can( 'delete_page', $id ) )
733                 wp_die( -1 );
734
735         if ( ! get_post( $id ) )
736                 wp_die( 1 );
737
738         if ( wp_delete_post( $id ) )
739                 wp_die( 1 );
740         else
741                 wp_die( 0 );
742 }
743
744 /**
745  * Ajax handler to dim a comment.
746  *
747  * @since 3.1.0
748  */
749 function wp_ajax_dim_comment() {
750         $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
751
752         if ( !$comment = get_comment( $id ) ) {
753                 $x = new WP_Ajax_Response( array(
754                         'what' => 'comment',
755                         'id' => new WP_Error('invalid_comment', sprintf(__('Comment %d does not exist'), $id))
756                 ) );
757                 $x->send();
758         }
759
760         if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) )
761                 wp_die( -1 );
762
763         $current = wp_get_comment_status( $comment );
764         if ( isset( $_POST['new'] ) && $_POST['new'] == $current )
765                 wp_die( time() );
766
767         check_ajax_referer( "approve-comment_$id" );
768         if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) {
769                 $result = wp_set_comment_status( $comment, 'approve', true );
770         } else {
771                 $result = wp_set_comment_status( $comment, 'hold', true );
772         }
773
774         if ( is_wp_error($result) ) {
775                 $x = new WP_Ajax_Response( array(
776                         'what' => 'comment',
777                         'id' => $result
778                 ) );
779                 $x->send();
780         }
781
782         // Decide if we need to send back '1' or a more complicated response including page links and comment counts
783         _wp_ajax_delete_comment_response( $comment->comment_ID );
784         wp_die( 0 );
785 }
786
787 /**
788  * Ajax handler for adding a link category.
789  *
790  * @since 3.1.0
791  *
792  * @param string $action Action to perform.
793  */
794 function wp_ajax_add_link_category( $action ) {
795         if ( empty( $action ) )
796                 $action = 'add-link-category';
797         check_ajax_referer( $action );
798         $tax = get_taxonomy( 'link_category' );
799         if ( ! current_user_can( $tax->cap->manage_terms ) ) {
800                 wp_die( -1 );
801         }
802         $names = explode(',', wp_unslash( $_POST['newcat'] ) );
803         $x = new WP_Ajax_Response();
804         foreach ( $names as $cat_name ) {
805                 $cat_name = trim($cat_name);
806                 $slug = sanitize_title($cat_name);
807                 if ( '' === $slug )
808                         continue;
809                 if ( !$cat_id = term_exists( $cat_name, 'link_category' ) )
810                         $cat_id = wp_insert_term( $cat_name, 'link_category' );
811                 if ( is_wp_error( $cat_id ) ) {
812                         continue;
813                 } elseif ( is_array( $cat_id ) ) {
814                         $cat_id = $cat_id['term_id'];
815                 }
816                 $cat_name = esc_html( $cat_name );
817                 $x->add( array(
818                         'what' => 'link-category',
819                         'id' => $cat_id,
820                         '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>",
821                         'position' => -1
822                 ) );
823         }
824         $x->send();
825 }
826
827 /**
828  * Ajax handler to add a tag.
829  *
830  * @since 3.1.0
831  */
832 function wp_ajax_add_tag() {
833         check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
834         $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
835         $tax = get_taxonomy($taxonomy);
836
837         if ( !current_user_can( $tax->cap->edit_terms ) )
838                 wp_die( -1 );
839
840         $x = new WP_Ajax_Response();
841
842         $tag = wp_insert_term($_POST['tag-name'], $taxonomy, $_POST );
843
844         if ( !$tag || is_wp_error($tag) || (!$tag = get_term( $tag['term_id'], $taxonomy )) ) {
845                 $message = __('An error has occurred. Please reload the page and try again.');
846                 if ( is_wp_error($tag) && $tag->get_error_message() )
847                         $message = $tag->get_error_message();
848
849                 $x->add( array(
850                         'what' => 'taxonomy',
851                         'data' => new WP_Error('error', $message )
852                 ) );
853                 $x->send();
854         }
855
856         $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
857
858         $level = 0;
859         if ( is_taxonomy_hierarchical($taxonomy) ) {
860                 $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
861                 ob_start();
862                 $wp_list_table->single_row( $tag, $level );
863                 $noparents = ob_get_clean();
864         }
865
866         ob_start();
867         $wp_list_table->single_row( $tag );
868         $parents = ob_get_clean();
869
870         $x->add( array(
871                 'what' => 'taxonomy',
872                 'supplemental' => compact('parents', 'noparents')
873         ) );
874         $x->add( array(
875                 'what' => 'term',
876                 'position' => $level,
877                 'supplemental' => (array) $tag
878         ) );
879         $x->send();
880 }
881
882 /**
883  * Ajax handler for getting a tagcloud.
884  *
885  * @since 3.1.0
886  */
887 function wp_ajax_get_tagcloud() {
888         if ( ! isset( $_POST['tax'] ) ) {
889                 wp_die( 0 );
890         }
891
892         $taxonomy = sanitize_key( $_POST['tax'] );
893         $tax = get_taxonomy( $taxonomy );
894         if ( ! $tax ) {
895                 wp_die( 0 );
896         }
897
898         if ( ! current_user_can( $tax->cap->assign_terms ) ) {
899                 wp_die( -1 );
900         }
901
902         $tags = get_terms( $taxonomy, array( 'number' => 45, 'orderby' => 'count', 'order' => 'DESC' ) );
903
904         if ( empty( $tags ) )
905                 wp_die( $tax->labels->not_found );
906
907         if ( is_wp_error( $tags ) )
908                 wp_die( $tags->get_error_message() );
909
910         foreach ( $tags as $key => $tag ) {
911                 $tags[ $key ]->link = '#';
912                 $tags[ $key ]->id = $tag->term_id;
913         }
914
915         // We need raw tag names here, so don't filter the output
916         $return = wp_generate_tag_cloud( $tags, array('filter' => 0) );
917
918         if ( empty($return) )
919                 wp_die( 0 );
920
921         echo $return;
922
923         wp_die();
924 }
925
926 /**
927  * Ajax handler for getting comments.
928  *
929  * @since 3.1.0
930  *
931  * @global int           $post_id
932  *
933  * @param string $action Action to perform.
934  */
935 function wp_ajax_get_comments( $action ) {
936         global $post_id;
937         if ( empty( $action ) ) {
938                 $action = 'get-comments';
939         }
940         check_ajax_referer( $action );
941
942         if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
943                 $id = absint( $_REQUEST['p'] );
944                 if ( ! empty( $id ) ) {
945                         $post_id = $id;
946                 }
947         }
948
949         if ( empty( $post_id ) ) {
950                 wp_die( -1 );
951         }
952
953         $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
954
955         if ( ! current_user_can( 'edit_post', $post_id ) ) {
956                 wp_die( -1 );
957         }
958
959         $wp_list_table->prepare_items();
960
961         if ( ! $wp_list_table->has_items() ) {
962                 wp_die( 1 );
963         }
964
965         $x = new WP_Ajax_Response();
966         ob_start();
967         foreach ( $wp_list_table->items as $comment ) {
968                 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved )
969                         continue;
970                 get_comment( $comment );
971                 $wp_list_table->single_row( $comment );
972         }
973         $comment_list_item = ob_get_clean();
974
975         $x->add( array(
976                 'what' => 'comments',
977                 'data' => $comment_list_item
978         ) );
979         $x->send();
980 }
981
982 /**
983  * Ajax handler for replying to a comment.
984  *
985  * @since 3.1.0
986  *
987  * @param string $action Action to perform.
988  */
989 function wp_ajax_replyto_comment( $action ) {
990         if ( empty( $action ) )
991                 $action = 'replyto-comment';
992
993         check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
994
995         $comment_post_ID = (int) $_POST['comment_post_ID'];
996         $post = get_post( $comment_post_ID );
997         if ( ! $post )
998                 wp_die( -1 );
999
1000         if ( !current_user_can( 'edit_post', $comment_post_ID ) )
1001                 wp_die( -1 );
1002
1003         if ( empty( $post->post_status ) )
1004                 wp_die( 1 );
1005         elseif ( in_array($post->post_status, array('draft', 'pending', 'trash') ) )
1006                 wp_die( __('ERROR: you are replying to a comment on a draft post.') );
1007
1008         $user = wp_get_current_user();
1009         if ( $user->exists() ) {
1010                 $user_ID = $user->ID;
1011                 $comment_author       = wp_slash( $user->display_name );
1012                 $comment_author_email = wp_slash( $user->user_email );
1013                 $comment_author_url   = wp_slash( $user->user_url );
1014                 $comment_content      = trim( $_POST['content'] );
1015                 $comment_type         = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
1016                 if ( current_user_can( 'unfiltered_html' ) ) {
1017                         if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) )
1018                                 $_POST['_wp_unfiltered_html_comment'] = '';
1019
1020                         if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
1021                                 kses_remove_filters(); // start with a clean slate
1022                                 kses_init_filters(); // set up the filters
1023                         }
1024                 }
1025         } else {
1026                 wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
1027         }
1028
1029         if ( '' == $comment_content )
1030                 wp_die( __( 'ERROR: please type a comment.' ) );
1031
1032         $comment_parent = 0;
1033         if ( isset( $_POST['comment_ID'] ) )
1034                 $comment_parent = absint( $_POST['comment_ID'] );
1035         $comment_auto_approved = false;
1036         $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
1037
1038         // Automatically approve parent comment.
1039         if ( !empty($_POST['approve_parent']) ) {
1040                 $parent = get_comment( $comment_parent );
1041
1042                 if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
1043                         if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
1044                                 wp_die( -1 );
1045                         }
1046
1047                         if ( wp_set_comment_status( $parent, 'approve' ) )
1048                                 $comment_auto_approved = true;
1049                 }
1050         }
1051
1052         $comment_id = wp_new_comment( $commentdata );
1053         $comment = get_comment($comment_id);
1054         if ( ! $comment ) wp_die( 1 );
1055
1056         $position = ( isset($_POST['position']) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1057
1058         ob_start();
1059         if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
1060                 require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
1061                 _wp_dashboard_recent_comments_row( $comment );
1062         } else {
1063                 if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
1064                         $wp_list_table = _get_list_table('WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1065                 } else {
1066                         $wp_list_table = _get_list_table('WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1067                 }
1068                 $wp_list_table->single_row( $comment );
1069         }
1070         $comment_list_item = ob_get_clean();
1071
1072         $response =  array(
1073                 'what' => 'comment',
1074                 'id' => $comment->comment_ID,
1075                 'data' => $comment_list_item,
1076                 'position' => $position
1077         );
1078
1079         $counts = wp_count_comments();
1080         $response['supplemental'] = array(
1081                 'in_moderation' => $counts->moderated,
1082                 'i18n_comments_text' => sprintf(
1083                         _n( '%s Comment', '%s Comments', $counts->approved ),
1084                         number_format_i18n( $counts->approved )
1085                 ),
1086                 'i18n_moderation_text' => sprintf(
1087                         _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
1088                         number_format_i18n( $counts->moderated )
1089                 )
1090         );
1091
1092         if ( $comment_auto_approved ) {
1093                 $response['supplemental']['parent_approved'] = $parent->comment_ID;
1094                 $response['supplemental']['parent_post_id'] = $parent->comment_post_ID;
1095         }
1096
1097         $x = new WP_Ajax_Response();
1098         $x->add( $response );
1099         $x->send();
1100 }
1101
1102 /**
1103  * Ajax handler for editing a comment.
1104  *
1105  * @since 3.1.0
1106  */
1107 function wp_ajax_edit_comment() {
1108         check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1109
1110         $comment_id = (int) $_POST['comment_ID'];
1111         if ( ! current_user_can( 'edit_comment', $comment_id ) )
1112                 wp_die( -1 );
1113
1114         if ( '' == $_POST['content'] )
1115                 wp_die( __( 'ERROR: please type a comment.' ) );
1116
1117         if ( isset( $_POST['status'] ) )
1118                 $_POST['comment_status'] = $_POST['status'];
1119         edit_comment();
1120
1121         $position = ( isset($_POST['position']) && (int) $_POST['position']) ? (int) $_POST['position'] : '-1';
1122         $checkbox = ( isset($_POST['checkbox']) && true == $_POST['checkbox'] ) ? 1 : 0;
1123         $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1124
1125         $comment = get_comment( $comment_id );
1126         if ( empty( $comment->comment_ID ) )
1127                 wp_die( -1 );
1128
1129         ob_start();
1130         $wp_list_table->single_row( $comment );
1131         $comment_list_item = ob_get_clean();
1132
1133         $x = new WP_Ajax_Response();
1134
1135         $x->add( array(
1136                 'what' => 'edit_comment',
1137                 'id' => $comment->comment_ID,
1138                 'data' => $comment_list_item,
1139                 'position' => $position
1140         ));
1141
1142         $x->send();
1143 }
1144
1145 /**
1146  * Ajax handler for adding a menu item.
1147  *
1148  * @since 3.1.0
1149  */
1150 function wp_ajax_add_menu_item() {
1151         check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1152
1153         if ( ! current_user_can( 'edit_theme_options' ) )
1154                 wp_die( -1 );
1155
1156         require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1157
1158         // For performance reasons, we omit some object properties from the checklist.
1159         // The following is a hacky way to restore them when adding non-custom items.
1160
1161         $menu_items_data = array();
1162         foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1163                 if (
1164                         ! empty( $menu_item_data['menu-item-type'] ) &&
1165                         'custom' != $menu_item_data['menu-item-type'] &&
1166                         ! empty( $menu_item_data['menu-item-object-id'] )
1167                 ) {
1168                         switch( $menu_item_data['menu-item-type'] ) {
1169                                 case 'post_type' :
1170                                         $_object = get_post( $menu_item_data['menu-item-object-id'] );
1171                                 break;
1172
1173                                 case 'post_type_archive' :
1174                                         $_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1175                                 break;
1176
1177                                 case 'taxonomy' :
1178                                         $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1179                                 break;
1180                         }
1181
1182                         $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1183                         $_menu_item = reset( $_menu_items );
1184
1185                         // Restore the missing menu item properties
1186                         $menu_item_data['menu-item-description'] = $_menu_item->description;
1187                 }
1188
1189                 $menu_items_data[] = $menu_item_data;
1190         }
1191
1192         $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1193         if ( is_wp_error( $item_ids ) )
1194                 wp_die( 0 );
1195
1196         $menu_items = array();
1197
1198         foreach ( (array) $item_ids as $menu_item_id ) {
1199                 $menu_obj = get_post( $menu_item_id );
1200                 if ( ! empty( $menu_obj->ID ) ) {
1201                         $menu_obj = wp_setup_nav_menu_item( $menu_obj );
1202                         $menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1203                         $menu_items[] = $menu_obj;
1204                 }
1205         }
1206
1207         /** This filter is documented in wp-admin/includes/nav-menu.php */
1208         $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1209
1210         if ( ! class_exists( $walker_class_name ) )
1211                 wp_die( 0 );
1212
1213         if ( ! empty( $menu_items ) ) {
1214                 $args = array(
1215                         'after' => '',
1216                         'before' => '',
1217                         'link_after' => '',
1218                         'link_before' => '',
1219                         'walker' => new $walker_class_name,
1220                 );
1221                 echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1222         }
1223         wp_die();
1224 }
1225
1226 /**
1227  * Ajax handler for adding meta.
1228  *
1229  * @since 3.1.0
1230  */
1231 function wp_ajax_add_meta() {
1232         check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1233         $c = 0;
1234         $pid = (int) $_POST['post_id'];
1235         $post = get_post( $pid );
1236
1237         if ( isset($_POST['metakeyselect']) || isset($_POST['metakeyinput']) ) {
1238                 if ( !current_user_can( 'edit_post', $pid ) )
1239                         wp_die( -1 );
1240                 if ( isset($_POST['metakeyselect']) && '#NONE#' == $_POST['metakeyselect'] && empty($_POST['metakeyinput']) )
1241                         wp_die( 1 );
1242
1243                 // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1244                 if ( $post->post_status == 'auto-draft' ) {
1245                         $post_data = array();
1246                         $post_data['action'] = 'draft'; // Warning fix
1247                         $post_data['post_ID'] = $pid;
1248                         $post_data['post_type'] = $post->post_type;
1249                         $post_data['post_status'] = 'draft';
1250                         $now = current_time('timestamp', 1);
1251                         /* translators: 1: Post creation date, 2: Post creation time */
1252                         $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) );
1253
1254                         $pid = edit_post( $post_data );
1255                         if ( $pid ) {
1256                                 if ( is_wp_error( $pid ) ) {
1257                                         $x = new WP_Ajax_Response( array(
1258                                                 'what' => 'meta',
1259                                                 'data' => $pid
1260                                         ) );
1261                                         $x->send();
1262                                 }
1263
1264                                 if ( !$mid = add_meta( $pid ) )
1265                                         wp_die( __( 'Please provide a custom field value.' ) );
1266                         } else {
1267                                 wp_die( 0 );
1268                         }
1269                 } elseif ( ! $mid = add_meta( $pid ) ) {
1270                         wp_die( __( 'Please provide a custom field value.' ) );
1271                 }
1272
1273                 $meta = get_metadata_by_mid( 'post', $mid );
1274                 $pid = (int) $meta->post_id;
1275                 $meta = get_object_vars( $meta );
1276                 $x = new WP_Ajax_Response( array(
1277                         'what' => 'meta',
1278                         'id' => $mid,
1279                         'data' => _list_meta_row( $meta, $c ),
1280                         'position' => 1,
1281                         'supplemental' => array('postid' => $pid)
1282                 ) );
1283         } else { // Update?
1284                 $mid = (int) key( $_POST['meta'] );
1285                 $key = wp_unslash( $_POST['meta'][$mid]['key'] );
1286                 $value = wp_unslash( $_POST['meta'][$mid]['value'] );
1287                 if ( '' == trim($key) )
1288                         wp_die( __( 'Please provide a custom field name.' ) );
1289                 if ( '' == trim($value) )
1290                         wp_die( __( 'Please provide a custom field value.' ) );
1291                 if ( ! $meta = get_metadata_by_mid( 'post', $mid ) )
1292                         wp_die( 0 ); // if meta doesn't exist
1293                 if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1294                         ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1295                         ! current_user_can( 'edit_post_meta', $meta->post_id, $key ) )
1296                         wp_die( -1 );
1297                 if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1298                         if ( !$u = update_metadata_by_mid( 'post', $mid, $value, $key ) )
1299                                 wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1300                 }
1301
1302                 $x = new WP_Ajax_Response( array(
1303                         'what' => 'meta',
1304                         'id' => $mid, 'old_id' => $mid,
1305                         'data' => _list_meta_row( array(
1306                                 'meta_key' => $key,
1307                                 'meta_value' => $value,
1308                                 'meta_id' => $mid
1309                         ), $c ),
1310                         'position' => 0,
1311                         'supplemental' => array('postid' => $meta->post_id)
1312                 ) );
1313         }
1314         $x->send();
1315 }
1316
1317 /**
1318  * Ajax handler for adding a user.
1319  *
1320  * @since 3.1.0
1321  *
1322  * @param string $action Action to perform.
1323  */
1324 function wp_ajax_add_user( $action ) {
1325         if ( empty( $action ) ) {
1326                 $action = 'add-user';
1327         }
1328
1329         check_ajax_referer( $action );
1330         if ( ! current_user_can('create_users') )
1331                 wp_die( -1 );
1332         if ( ! $user_id = edit_user() ) {
1333                 wp_die( 0 );
1334         } elseif ( is_wp_error( $user_id ) ) {
1335                 $x = new WP_Ajax_Response( array(
1336                         'what' => 'user',
1337                         'id' => $user_id
1338                 ) );
1339                 $x->send();
1340         }
1341         $user_object = get_userdata( $user_id );
1342
1343         $wp_list_table = _get_list_table('WP_Users_List_Table');
1344
1345         $role = current( $user_object->roles );
1346
1347         $x = new WP_Ajax_Response( array(
1348                 'what' => 'user',
1349                 'id' => $user_id,
1350                 'data' => $wp_list_table->single_row( $user_object, '', $role ),
1351                 'supplemental' => array(
1352                         'show-link' => sprintf(
1353                                 /* translators: %s: the new user */
1354                                 __( 'User %s added' ),
1355                                 '<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1356                         ),
1357                         'role' => $role,
1358                 )
1359         ) );
1360         $x->send();
1361 }
1362
1363 /**
1364  * Ajax handler for closed post boxes.
1365  *
1366  * @since 3.1.0
1367  */
1368 function wp_ajax_closed_postboxes() {
1369         check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1370         $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
1371         $closed = array_filter($closed);
1372
1373         $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden']) : array();
1374         $hidden = array_filter($hidden);
1375
1376         $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1377
1378         if ( $page != sanitize_key( $page ) )
1379                 wp_die( 0 );
1380
1381         if ( ! $user = wp_get_current_user() )
1382                 wp_die( -1 );
1383
1384         if ( is_array($closed) )
1385                 update_user_option($user->ID, "closedpostboxes_$page", $closed, true);
1386
1387         if ( is_array($hidden) ) {
1388                 $hidden = array_diff( $hidden, array('submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu') ); // postboxes that are always shown
1389                 update_user_option($user->ID, "metaboxhidden_$page", $hidden, true);
1390         }
1391
1392         wp_die( 1 );
1393 }
1394
1395 /**
1396  * Ajax handler for hidden columns.
1397  *
1398  * @since 3.1.0
1399  */
1400 function wp_ajax_hidden_columns() {
1401         check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1402         $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1403
1404         if ( $page != sanitize_key( $page ) )
1405                 wp_die( 0 );
1406
1407         if ( ! $user = wp_get_current_user() )
1408                 wp_die( -1 );
1409
1410         $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1411         update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true );
1412
1413         wp_die( 1 );
1414 }
1415
1416 /**
1417  * Ajax handler for updating whether to display the welcome panel.
1418  *
1419  * @since 3.1.0
1420  */
1421 function wp_ajax_update_welcome_panel() {
1422         check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1423
1424         if ( ! current_user_can( 'edit_theme_options' ) )
1425                 wp_die( -1 );
1426
1427         update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1428
1429         wp_die( 1 );
1430 }
1431
1432 /**
1433  * Ajax handler for retrieving menu meta boxes.
1434  *
1435  * @since 3.1.0
1436  */
1437 function wp_ajax_menu_get_metabox() {
1438         if ( ! current_user_can( 'edit_theme_options' ) )
1439                 wp_die( -1 );
1440
1441         require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1442
1443         if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1444                 $type = 'posttype';
1445                 $callback = 'wp_nav_menu_item_post_type_meta_box';
1446                 $items = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1447         } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1448                 $type = 'taxonomy';
1449                 $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1450                 $items = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1451         }
1452
1453         if ( ! empty( $_POST['item-object'] ) && isset( $items[$_POST['item-object']] ) ) {
1454                 $menus_meta_box_object = $items[ $_POST['item-object'] ];
1455
1456                 /** This filter is documented in wp-admin/includes/nav-menu.php */
1457                 $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1458                 ob_start();
1459                 call_user_func_array($callback, array(
1460                         null,
1461                         array(
1462                                 'id' => 'add-' . $item->name,
1463                                 'title' => $item->labels->name,
1464                                 'callback' => $callback,
1465                                 'args' => $item,
1466                         )
1467                 ));
1468
1469                 $markup = ob_get_clean();
1470
1471                 echo wp_json_encode(array(
1472                         'replace-id' => $type . '-' . $item->name,
1473                         'markup' => $markup,
1474                 ));
1475         }
1476
1477         wp_die();
1478 }
1479
1480 /**
1481  * Ajax handler for internal linking.
1482  *
1483  * @since 3.1.0
1484  */
1485 function wp_ajax_wp_link_ajax() {
1486         check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1487
1488         $args = array();
1489
1490         if ( isset( $_POST['search'] ) ) {
1491                 $args['s'] = wp_unslash( $_POST['search'] );
1492         }
1493
1494         if ( isset( $_POST['term'] ) ) {
1495                 $args['s'] = wp_unslash( $_POST['term'] );
1496         }
1497
1498         $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1499
1500         require(ABSPATH . WPINC . '/class-wp-editor.php');
1501         $results = _WP_Editors::wp_link_query( $args );
1502
1503         if ( ! isset( $results ) )
1504                 wp_die( 0 );
1505
1506         echo wp_json_encode( $results );
1507         echo "\n";
1508
1509         wp_die();
1510 }
1511
1512 /**
1513  * Ajax handler for menu locations save.
1514  *
1515  * @since 3.1.0
1516  */
1517 function wp_ajax_menu_locations_save() {
1518         if ( ! current_user_can( 'edit_theme_options' ) )
1519                 wp_die( -1 );
1520         check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1521         if ( ! isset( $_POST['menu-locations'] ) )
1522                 wp_die( 0 );
1523         set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1524         wp_die( 1 );
1525 }
1526
1527 /**
1528  * Ajax handler for saving the meta box order.
1529  *
1530  * @since 3.1.0
1531  */
1532 function wp_ajax_meta_box_order() {
1533         check_ajax_referer( 'meta-box-order' );
1534         $order = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1535         $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1536
1537         if ( $page_columns != 'auto' )
1538                 $page_columns = (int) $page_columns;
1539
1540         $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1541
1542         if ( $page != sanitize_key( $page ) )
1543                 wp_die( 0 );
1544
1545         if ( ! $user = wp_get_current_user() )
1546                 wp_die( -1 );
1547
1548         if ( $order )
1549                 update_user_option($user->ID, "meta-box-order_$page", $order, true);
1550
1551         if ( $page_columns )
1552                 update_user_option($user->ID, "screen_layout_$page", $page_columns, true);
1553
1554         wp_die( 1 );
1555 }
1556
1557 /**
1558  * Ajax handler for menu quick searching.
1559  *
1560  * @since 3.1.0
1561  */
1562 function wp_ajax_menu_quick_search() {
1563         if ( ! current_user_can( 'edit_theme_options' ) )
1564                 wp_die( -1 );
1565
1566         require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1567
1568         _wp_ajax_menu_quick_search( $_POST );
1569
1570         wp_die();
1571 }
1572
1573 /**
1574  * Ajax handler to retrieve a permalink.
1575  *
1576  * @since 3.1.0
1577  */
1578 function wp_ajax_get_permalink() {
1579         check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1580         $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1581         wp_die( get_preview_post_link( $post_id ) );
1582 }
1583
1584 /**
1585  * Ajax handler to retrieve a sample permalink.
1586  *
1587  * @since 3.1.0
1588  */
1589 function wp_ajax_sample_permalink() {
1590         check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1591         $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1592         $title = isset($_POST['new_title'])? $_POST['new_title'] : '';
1593         $slug = isset($_POST['new_slug'])? $_POST['new_slug'] : null;
1594         wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1595 }
1596
1597 /**
1598  * Ajax handler for Quick Edit saving a post from a list table.
1599  *
1600  * @since 3.1.0
1601  */
1602 function wp_ajax_inline_save() {
1603         global $mode;
1604
1605         check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1606
1607         if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
1608                 wp_die();
1609
1610         if ( 'page' == $_POST['post_type'] ) {
1611                 if ( ! current_user_can( 'edit_page', $post_ID ) )
1612                         wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
1613         } else {
1614                 if ( ! current_user_can( 'edit_post', $post_ID ) )
1615                         wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1616         }
1617
1618         if ( $last = wp_check_post_lock( $post_ID ) ) {
1619                 $last_user = get_userdata( $last );
1620                 $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1621                 printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ),        esc_html( $last_user_name ) );
1622                 wp_die();
1623         }
1624
1625         $data = &$_POST;
1626
1627         $post = get_post( $post_ID, ARRAY_A );
1628
1629         // Since it's coming from the database.
1630         $post = wp_slash($post);
1631
1632         $data['content'] = $post['post_content'];
1633         $data['excerpt'] = $post['post_excerpt'];
1634
1635         // Rename.
1636         $data['user_ID'] = get_current_user_id();
1637
1638         if ( isset($data['post_parent']) )
1639                 $data['parent_id'] = $data['post_parent'];
1640
1641         // Status.
1642         if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1643                 $data['visibility']  = 'private';
1644                 $data['post_status'] = 'private';
1645         } else {
1646                 $data['post_status'] = $data['_status'];
1647         }
1648
1649         if ( empty($data['comment_status']) )
1650                 $data['comment_status'] = 'closed';
1651         if ( empty($data['ping_status']) )
1652                 $data['ping_status'] = 'closed';
1653
1654         // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1655         if ( ! empty( $data['tax_input'] ) ) {
1656                 foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1657                         $tax_object = get_taxonomy( $taxonomy );
1658                         /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1659                         if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1660                                 unset( $data['tax_input'][ $taxonomy ] );
1661                         }
1662                 }
1663         }
1664
1665         // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1666         if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1667                 $post['post_status'] = 'publish';
1668                 $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1669         }
1670
1671         // Update the post.
1672         edit_post();
1673
1674         $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1675
1676         $mode = $_POST['post_view'] === 'excerpt' ? 'excerpt' : 'list';
1677
1678         $level = 0;
1679         if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
1680                 $request_post = array( get_post( $_POST['post_ID'] ) );
1681                 $parent       = $request_post[0]->post_parent;
1682
1683                 while ( $parent > 0 ) {
1684                         $parent_post = get_post( $parent );
1685                         $parent      = $parent_post->post_parent;
1686                         $level++;
1687                 }
1688         }
1689
1690         $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1691
1692         wp_die();
1693 }
1694
1695 /**
1696  * Ajax handler for quick edit saving for a term.
1697  *
1698  * @since 3.1.0
1699  */
1700 function wp_ajax_inline_save_tax() {
1701         check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1702
1703         $taxonomy = sanitize_key( $_POST['taxonomy'] );
1704         $tax = get_taxonomy( $taxonomy );
1705         if ( ! $tax )
1706                 wp_die( 0 );
1707
1708         if ( ! isset( $_POST['tax_ID'] ) || ! ( $id = (int) $_POST['tax_ID'] ) ) {
1709                 wp_die( -1 );
1710         }
1711
1712         if ( ! current_user_can( 'edit_term', $id ) ) {
1713                 wp_die( -1 );
1714         }
1715
1716         $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1717
1718         $tag = get_term( $id, $taxonomy );
1719         $_POST['description'] = $tag->description;
1720
1721         $updated = wp_update_term($id, $taxonomy, $_POST);
1722         if ( $updated && !is_wp_error($updated) ) {
1723                 $tag = get_term( $updated['term_id'], $taxonomy );
1724                 if ( !$tag || is_wp_error( $tag ) ) {
1725                         if ( is_wp_error($tag) && $tag->get_error_message() )
1726                                 wp_die( $tag->get_error_message() );
1727                         wp_die( __( 'Item not updated.' ) );
1728                 }
1729         } else {
1730                 if ( is_wp_error($updated) && $updated->get_error_message() )
1731                         wp_die( $updated->get_error_message() );
1732                 wp_die( __( 'Item not updated.' ) );
1733         }
1734         $level = 0;
1735         $parent = $tag->parent;
1736         while ( $parent > 0 ) {
1737                 $parent_tag = get_term( $parent, $taxonomy );
1738                 $parent = $parent_tag->parent;
1739                 $level++;
1740         }
1741         $wp_list_table->single_row( $tag, $level );
1742         wp_die();
1743 }
1744
1745 /**
1746  * Ajax handler for querying posts for the Find Posts modal.
1747  *
1748  * @see window.findPosts
1749  *
1750  * @since 3.1.0
1751  */
1752 function wp_ajax_find_posts() {
1753         check_ajax_referer( 'find-posts' );
1754
1755         $post_types = get_post_types( array( 'public' => true ), 'objects' );
1756         unset( $post_types['attachment'] );
1757
1758         $s = wp_unslash( $_POST['ps'] );
1759         $args = array(
1760                 'post_type' => array_keys( $post_types ),
1761                 'post_status' => 'any',
1762                 'posts_per_page' => 50,
1763         );
1764         if ( '' !== $s )
1765                 $args['s'] = $s;
1766
1767         $posts = get_posts( $args );
1768
1769         if ( ! $posts ) {
1770                 wp_send_json_error( __( 'No items found.' ) );
1771         }
1772
1773         $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>';
1774         $alt = '';
1775         foreach ( $posts as $post ) {
1776                 $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1777                 $alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1778
1779                 switch ( $post->post_status ) {
1780                         case 'publish' :
1781                         case 'private' :
1782                                 $stat = __('Published');
1783                                 break;
1784                         case 'future' :
1785                                 $stat = __('Scheduled');
1786                                 break;
1787                         case 'pending' :
1788                                 $stat = __('Pending Review');
1789                                 break;
1790                         case 'draft' :
1791                                 $stat = __('Draft');
1792                                 break;
1793                 }
1794
1795                 if ( '0000-00-00 00:00:00' == $post->post_date ) {
1796                         $time = '';
1797                 } else {
1798                         /* translators: date format in table columns, see https://secure.php.net/date */
1799                         $time = mysql2date(__('Y/m/d'), $post->post_date);
1800                 }
1801
1802                 $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>';
1803                 $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";
1804         }
1805
1806         $html .= '</tbody></table>';
1807
1808         wp_send_json_success( $html );
1809 }
1810
1811 /**
1812  * Ajax handler for saving the widgets order.
1813  *
1814  * @since 3.1.0
1815  */
1816 function wp_ajax_widgets_order() {
1817         check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1818
1819         if ( !current_user_can('edit_theme_options') )
1820                 wp_die( -1 );
1821
1822         unset( $_POST['savewidgets'], $_POST['action'] );
1823
1824         // Save widgets order for all sidebars.
1825         if ( is_array($_POST['sidebars']) ) {
1826                 $sidebars = array();
1827                 foreach ( $_POST['sidebars'] as $key => $val ) {
1828                         $sb = array();
1829                         if ( !empty($val) ) {
1830                                 $val = explode(',', $val);
1831                                 foreach ( $val as $k => $v ) {
1832                                         if ( strpos($v, 'widget-') === false )
1833                                                 continue;
1834
1835                                         $sb[$k] = substr($v, strpos($v, '_') + 1);
1836                                 }
1837                         }
1838                         $sidebars[$key] = $sb;
1839                 }
1840                 wp_set_sidebars_widgets($sidebars);
1841                 wp_die( 1 );
1842         }
1843
1844         wp_die( -1 );
1845 }
1846
1847 /**
1848  * Ajax handler for saving a widget.
1849  *
1850  * @since 3.1.0
1851  *
1852  * @global array $wp_registered_widgets
1853  * @global array $wp_registered_widget_controls
1854  * @global array $wp_registered_widget_updates
1855  */
1856 function wp_ajax_save_widget() {
1857         global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1858
1859         check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1860
1861         if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1862                 wp_die( -1 );
1863
1864         unset( $_POST['savewidgets'], $_POST['action'] );
1865
1866         /**
1867          * Fires early when editing the widgets displayed in sidebars.
1868          *
1869          * @since 2.8.0
1870          */
1871         do_action( 'load-widgets.php' );
1872
1873         /**
1874          * Fires early when editing the widgets displayed in sidebars.
1875          *
1876          * @since 2.8.0
1877          */
1878         do_action( 'widgets.php' );
1879
1880         /** This action is documented in wp-admin/widgets.php */
1881         do_action( 'sidebar_admin_setup' );
1882
1883         $id_base = $_POST['id_base'];
1884         $widget_id = $_POST['widget-id'];
1885         $sidebar_id = $_POST['sidebar'];
1886         $multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1887         $settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1888         $error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1889
1890         $sidebars = wp_get_sidebars_widgets();
1891         $sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1892
1893         // Delete.
1894         if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1895
1896                 if ( !isset($wp_registered_widgets[$widget_id]) )
1897                         wp_die( $error );
1898
1899                 $sidebar = array_diff( $sidebar, array($widget_id) );
1900                 $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1901
1902                 /** This action is documented in wp-admin/widgets.php */
1903                 do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
1904
1905         } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1906                 if ( !$multi_number )
1907                         wp_die( $error );
1908
1909                 $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1910                 $widget_id = $id_base . '-' . $multi_number;
1911                 $sidebar[] = $widget_id;
1912         }
1913         $_POST['widget-id'] = $sidebar;
1914
1915         foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1916
1917                 if ( $name == $id_base ) {
1918                         if ( !is_callable( $control['callback'] ) )
1919                                 continue;
1920
1921                         ob_start();
1922                                 call_user_func_array( $control['callback'], $control['params'] );
1923                         ob_end_clean();
1924                         break;
1925                 }
1926         }
1927
1928         if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1929                 $sidebars[$sidebar_id] = $sidebar;
1930                 wp_set_sidebars_widgets($sidebars);
1931                 echo "deleted:$widget_id";
1932                 wp_die();
1933         }
1934
1935         if ( !empty($_POST['add_new']) )
1936                 wp_die();
1937
1938         if ( $form = $wp_registered_widget_controls[$widget_id] )
1939                 call_user_func_array( $form['callback'], $form['params'] );
1940
1941         wp_die();
1942 }
1943
1944 /**
1945  * Ajax handler for saving a widget.
1946  *
1947  * @since 3.9.0
1948  *
1949  * @global WP_Customize_Manager $wp_customize
1950  */
1951 function wp_ajax_update_widget() {
1952         global $wp_customize;
1953         $wp_customize->widgets->wp_ajax_update_widget();
1954 }
1955
1956 /**
1957  * Ajax handler for removing inactive widgets.
1958  *
1959  * @since 4.4.0
1960  */
1961 function wp_ajax_delete_inactive_widgets() {
1962         check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
1963
1964         if ( ! current_user_can( 'edit_theme_options' ) ) {
1965                 wp_die( -1 );
1966         }
1967
1968         unset( $_POST['removeinactivewidgets'], $_POST['action'] );
1969
1970         do_action( 'load-widgets.php' );
1971         do_action( 'widgets.php' );
1972         do_action( 'sidebar_admin_setup' );
1973
1974         $sidebars_widgets = wp_get_sidebars_widgets();
1975
1976         foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
1977                 $pieces = explode( '-', $widget_id );
1978                 $multi_number = array_pop( $pieces );
1979                 $id_base = implode( '-', $pieces );
1980                 $widget = get_option( 'widget_' . $id_base );
1981                 unset( $widget[$multi_number] );
1982                 update_option( 'widget_' . $id_base, $widget );
1983                 unset( $sidebars_widgets['wp_inactive_widgets'][$key] );
1984         }
1985
1986         wp_set_sidebars_widgets( $sidebars_widgets );
1987
1988         wp_die();
1989 }
1990
1991 /**
1992  * Ajax handler for uploading attachments
1993  *
1994  * @since 3.3.0
1995  */
1996 function wp_ajax_upload_attachment() {
1997         check_ajax_referer( 'media-form' );
1998         /*
1999          * This function does not use wp_send_json_success() / wp_send_json_error()
2000          * as the html4 Plupload handler requires a text/html content-type for older IE.
2001          * See https://core.trac.wordpress.org/ticket/31037
2002          */
2003
2004         if ( ! current_user_can( 'upload_files' ) ) {
2005                 echo wp_json_encode( array(
2006                         'success' => false,
2007                         'data'    => array(
2008                                 'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2009                                 'filename' => $_FILES['async-upload']['name'],
2010                         )
2011                 ) );
2012
2013                 wp_die();
2014         }
2015
2016         if ( isset( $_REQUEST['post_id'] ) ) {
2017                 $post_id = $_REQUEST['post_id'];
2018                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2019                         echo wp_json_encode( array(
2020                                 'success' => false,
2021                                 'data'    => array(
2022                                         'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2023                                         'filename' => $_FILES['async-upload']['name'],
2024                                 )
2025                         ) );
2026
2027                         wp_die();
2028                 }
2029         } else {
2030                 $post_id = null;
2031         }
2032
2033         $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
2034
2035         // If the context is custom header or background, make sure the uploaded file is an image.
2036         if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
2037                 $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2038                 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2039                         echo wp_json_encode( array(
2040                                 'success' => false,
2041                                 'data'    => array(
2042                                         'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2043                                         'filename' => $_FILES['async-upload']['name'],
2044                                 )
2045                         ) );
2046
2047                         wp_die();
2048                 }
2049         }
2050
2051         $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2052
2053         if ( is_wp_error( $attachment_id ) ) {
2054                 echo wp_json_encode( array(
2055                         'success' => false,
2056                         'data'    => array(
2057                                 'message'  => $attachment_id->get_error_message(),
2058                                 'filename' => $_FILES['async-upload']['name'],
2059                         )
2060                 ) );
2061
2062                 wp_die();
2063         }
2064
2065         if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2066                 if ( 'custom-background' === $post_data['context'] )
2067                         update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2068
2069                 if ( 'custom-header' === $post_data['context'] )
2070                         update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2071         }
2072
2073         if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
2074                 wp_die();
2075
2076         echo wp_json_encode( array(
2077                 'success' => true,
2078                 'data'    => $attachment,
2079         ) );
2080
2081         wp_die();
2082 }
2083
2084 /**
2085  * Ajax handler for image editing.
2086  *
2087  * @since 3.1.0
2088  */
2089 function wp_ajax_image_editor() {
2090         $attachment_id = intval($_POST['postid']);
2091         if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
2092                 wp_die( -1 );
2093
2094         check_ajax_referer( "image_editor-$attachment_id" );
2095         include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
2096
2097         $msg = false;
2098         switch ( $_POST['do'] ) {
2099                 case 'save' :
2100                         $msg = wp_save_image($attachment_id);
2101                         $msg = wp_json_encode($msg);
2102                         wp_die( $msg );
2103                         break;
2104                 case 'scale' :
2105                         $msg = wp_save_image($attachment_id);
2106                         break;
2107                 case 'restore' :
2108                         $msg = wp_restore_image($attachment_id);
2109                         break;
2110         }
2111
2112         wp_image_editor($attachment_id, $msg);
2113         wp_die();
2114 }
2115
2116 /**
2117  * Ajax handler for setting the featured image.
2118  *
2119  * @since 3.1.0
2120  */
2121 function wp_ajax_set_post_thumbnail() {
2122         $json = ! empty( $_REQUEST['json'] ); // New-style request
2123
2124         $post_ID = intval( $_POST['post_id'] );
2125         if ( ! current_user_can( 'edit_post', $post_ID ) )
2126                 wp_die( -1 );
2127
2128         $thumbnail_id = intval( $_POST['thumbnail_id'] );
2129
2130         if ( $json )
2131                 check_ajax_referer( "update-post_$post_ID" );
2132         else
2133                 check_ajax_referer( "set_post_thumbnail-$post_ID" );
2134
2135         if ( $thumbnail_id == '-1' ) {
2136                 if ( delete_post_thumbnail( $post_ID ) ) {
2137                         $return = _wp_post_thumbnail_html( null, $post_ID );
2138                         $json ? wp_send_json_success( $return ) : wp_die( $return );
2139                 } else {
2140                         wp_die( 0 );
2141                 }
2142         }
2143
2144         if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2145                 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2146                 $json ? wp_send_json_success( $return ) : wp_die( $return );
2147         }
2148
2149         wp_die( 0 );
2150 }
2151
2152 /**
2153  * Ajax handler for retrieving HTML for the featured image.
2154  *
2155  * @since 4.6.0
2156  */
2157 function wp_ajax_get_post_thumbnail_html() {
2158         $post_ID = intval( $_POST['post_id'] );
2159
2160         check_ajax_referer( "update-post_$post_ID" );
2161
2162         if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2163                 wp_die( -1 );
2164         }
2165
2166         $thumbnail_id = intval( $_POST['thumbnail_id'] );
2167
2168         // For backward compatibility, -1 refers to no featured image.
2169         if ( -1 === $thumbnail_id ) {
2170                 $thumbnail_id = null;
2171         }
2172
2173         $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2174         wp_send_json_success( $return );
2175 }
2176
2177 /**
2178  * Ajax handler for setting the featured image for an attachment.
2179  *
2180  * @since 4.0.0
2181  *
2182  * @see set_post_thumbnail()
2183  */
2184 function wp_ajax_set_attachment_thumbnail() {
2185         if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2186                 wp_send_json_error();
2187         }
2188
2189         $thumbnail_id = (int) $_POST['thumbnail_id'];
2190         if ( empty( $thumbnail_id ) ) {
2191                 wp_send_json_error();
2192         }
2193
2194         $post_ids = array();
2195         // For each URL, try to find its corresponding post ID.
2196         foreach ( $_POST['urls'] as $url ) {
2197                 $post_id = attachment_url_to_postid( $url );
2198                 if ( ! empty( $post_id ) ) {
2199                         $post_ids[] = $post_id;
2200                 }
2201         }
2202
2203         if ( empty( $post_ids ) ) {
2204                 wp_send_json_error();
2205         }
2206
2207         $success = 0;
2208         // For each found attachment, set its thumbnail.
2209         foreach ( $post_ids as $post_id ) {
2210                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2211                         continue;
2212                 }
2213
2214                 if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2215                         $success++;
2216                 }
2217         }
2218
2219         if ( 0 === $success ) {
2220                 wp_send_json_error();
2221         } else {
2222                 wp_send_json_success();
2223         }
2224
2225         wp_send_json_error();
2226 }
2227
2228 /**
2229  * Ajax handler for date formatting.
2230  *
2231  * @since 3.1.0
2232  */
2233 function wp_ajax_date_format() {
2234         wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2235 }
2236
2237 /**
2238  * Ajax handler for time formatting.
2239  *
2240  * @since 3.1.0
2241  */
2242 function wp_ajax_time_format() {
2243         wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2244 }
2245
2246 /**
2247  * Ajax handler for saving posts from the fullscreen editor.
2248  *
2249  * @since 3.1.0
2250  * @deprecated 4.3.0
2251  */
2252 function wp_ajax_wp_fullscreen_save_post() {
2253         $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2254
2255         $post = null;
2256
2257         if ( $post_id )
2258                 $post = get_post( $post_id );
2259
2260         check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2261
2262         $post_id = edit_post();
2263
2264         if ( is_wp_error( $post_id ) ) {
2265                 wp_send_json_error();
2266         }
2267
2268         if ( $post ) {
2269                 $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2270                 $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2271         } else {
2272                 $last_date = date_i18n( __( 'F j, Y' ) );
2273                 $last_time = date_i18n( __( 'g:i a' ) );
2274         }
2275
2276         if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2277                 $last_user = get_userdata( $last_id );
2278                 $last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2279         } else {
2280                 $last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2281         }
2282
2283         wp_send_json_success( array( 'last_edited' => $last_edited ) );
2284 }
2285
2286 /**
2287  * Ajax handler for removing a post lock.
2288  *
2289  * @since 3.1.0
2290  */
2291 function wp_ajax_wp_remove_post_lock() {
2292         if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2293                 wp_die( 0 );
2294         $post_id = (int) $_POST['post_ID'];
2295         if ( ! $post = get_post( $post_id ) )
2296                 wp_die( 0 );
2297
2298         check_ajax_referer( 'update-post_' . $post_id );
2299
2300         if ( ! current_user_can( 'edit_post', $post_id ) )
2301                 wp_die( -1 );
2302
2303         $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2304         if ( $active_lock[1] != get_current_user_id() )
2305                 wp_die( 0 );
2306
2307         /**
2308          * Filters the post lock window duration.
2309          *
2310          * @since 3.3.0
2311          *
2312          * @param int $interval The interval in seconds the post lock duration
2313          *                      should last, plus 5 seconds. Default 150.
2314          */
2315         $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2316         update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2317         wp_die( 1 );
2318 }
2319
2320 /**
2321  * Ajax handler for dismissing a WordPress pointer.
2322  *
2323  * @since 3.1.0
2324  */
2325 function wp_ajax_dismiss_wp_pointer() {
2326         $pointer = $_POST['pointer'];
2327         if ( $pointer != sanitize_key( $pointer ) )
2328                 wp_die( 0 );
2329
2330 //      check_ajax_referer( 'dismiss-pointer_' . $pointer );
2331
2332         $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2333
2334         if ( in_array( $pointer, $dismissed ) )
2335                 wp_die( 0 );
2336
2337         $dismissed[] = $pointer;
2338         $dismissed = implode( ',', $dismissed );
2339
2340         update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2341         wp_die( 1 );
2342 }
2343
2344 /**
2345  * Ajax handler for getting an attachment.
2346  *
2347  * @since 3.5.0
2348  */
2349 function wp_ajax_get_attachment() {
2350         if ( ! isset( $_REQUEST['id'] ) )
2351                 wp_send_json_error();
2352
2353         if ( ! $id = absint( $_REQUEST['id'] ) )
2354                 wp_send_json_error();
2355
2356         if ( ! $post = get_post( $id ) )
2357                 wp_send_json_error();
2358
2359         if ( 'attachment' != $post->post_type )
2360                 wp_send_json_error();
2361
2362         if ( ! current_user_can( 'upload_files' ) )
2363                 wp_send_json_error();
2364
2365         if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2366                 wp_send_json_error();
2367
2368         wp_send_json_success( $attachment );
2369 }
2370
2371 /**
2372  * Ajax handler for querying attachments.
2373  *
2374  * @since 3.5.0
2375  */
2376 function wp_ajax_query_attachments() {
2377         if ( ! current_user_can( 'upload_files' ) )
2378                 wp_send_json_error();
2379
2380         $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2381         $keys = array(
2382                 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2383                 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2384         );
2385         foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2386                 if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2387                         $keys[] = $t->query_var;
2388                 }
2389         }
2390
2391         $query = array_intersect_key( $query, array_flip( $keys ) );
2392         $query['post_type'] = 'attachment';
2393         if ( MEDIA_TRASH
2394                 && ! empty( $_REQUEST['query']['post_status'] )
2395                 && 'trash' === $_REQUEST['query']['post_status'] ) {
2396                 $query['post_status'] = 'trash';
2397         } else {
2398                 $query['post_status'] = 'inherit';
2399         }
2400
2401         if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2402                 $query['post_status'] .= ',private';
2403
2404         // Filter query clauses to include filenames.
2405         if ( isset( $query['s'] ) ) {
2406                 add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
2407         }
2408
2409         /**
2410          * Filters the arguments passed to WP_Query during an Ajax
2411          * call for querying attachments.
2412          *
2413          * @since 3.7.0
2414          *
2415          * @see WP_Query::parse_query()
2416          *
2417          * @param array $query An array of query variables.
2418          */
2419         $query = apply_filters( 'ajax_query_attachments_args', $query );
2420         $query = new WP_Query( $query );
2421
2422         $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2423         $posts = array_filter( $posts );
2424
2425         wp_send_json_success( $posts );
2426 }
2427
2428 /**
2429  * Ajax handler for updating attachment attributes.
2430  *
2431  * @since 3.5.0
2432  */
2433 function wp_ajax_save_attachment() {
2434         if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2435                 wp_send_json_error();
2436
2437         if ( ! $id = absint( $_REQUEST['id'] ) )
2438                 wp_send_json_error();
2439
2440         check_ajax_referer( 'update-post_' . $id, 'nonce' );
2441
2442         if ( ! current_user_can( 'edit_post', $id ) )
2443                 wp_send_json_error();
2444
2445         $changes = $_REQUEST['changes'];
2446         $post    = get_post( $id, ARRAY_A );
2447
2448         if ( 'attachment' != $post['post_type'] )
2449                 wp_send_json_error();
2450
2451         if ( isset( $changes['parent'] ) )
2452                 $post['post_parent'] = $changes['parent'];
2453
2454         if ( isset( $changes['title'] ) )
2455                 $post['post_title'] = $changes['title'];
2456
2457         if ( isset( $changes['caption'] ) )
2458                 $post['post_excerpt'] = $changes['caption'];
2459
2460         if ( isset( $changes['description'] ) )
2461                 $post['post_content'] = $changes['description'];
2462
2463         if ( MEDIA_TRASH && isset( $changes['status'] ) )
2464                 $post['post_status'] = $changes['status'];
2465
2466         if ( isset( $changes['alt'] ) ) {
2467                 $alt = wp_unslash( $changes['alt'] );
2468                 if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2469                         $alt = wp_strip_all_tags( $alt, true );
2470                         update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2471                 }
2472         }
2473
2474         if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2475                 $changed = false;
2476                 $id3data = wp_get_attachment_metadata( $post['ID'] );
2477                 if ( ! is_array( $id3data ) ) {
2478                         $changed = true;
2479                         $id3data = array();
2480                 }
2481                 foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2482                         if ( isset( $changes[ $key ] ) ) {
2483                                 $changed = true;
2484                                 $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2485                         }
2486                 }
2487
2488                 if ( $changed ) {
2489                         wp_update_attachment_metadata( $id, $id3data );
2490                 }
2491         }
2492
2493         if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2494                 wp_delete_post( $id );
2495         } else {
2496                 wp_update_post( $post );
2497         }
2498
2499         wp_send_json_success();
2500 }
2501
2502 /**
2503  * Ajax handler for saving backward compatible attachment attributes.
2504  *
2505  * @since 3.5.0
2506  */
2507 function wp_ajax_save_attachment_compat() {
2508         if ( ! isset( $_REQUEST['id'] ) )
2509                 wp_send_json_error();
2510
2511         if ( ! $id = absint( $_REQUEST['id'] ) )
2512                 wp_send_json_error();
2513
2514         if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2515                 wp_send_json_error();
2516         $attachment_data = $_REQUEST['attachments'][ $id ];
2517
2518         check_ajax_referer( 'update-post_' . $id, 'nonce' );
2519
2520         if ( ! current_user_can( 'edit_post', $id ) )
2521                 wp_send_json_error();
2522
2523         $post = get_post( $id, ARRAY_A );
2524
2525         if ( 'attachment' != $post['post_type'] )
2526                 wp_send_json_error();
2527
2528         /** This filter is documented in wp-admin/includes/media.php */
2529         $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2530
2531         if ( isset( $post['errors'] ) ) {
2532                 $errors = $post['errors']; // @todo return me and display me!
2533                 unset( $post['errors'] );
2534         }
2535
2536         wp_update_post( $post );
2537
2538         foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2539                 if ( isset( $attachment_data[ $taxonomy ] ) )
2540                         wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2541         }
2542
2543         if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2544                 wp_send_json_error();
2545
2546         wp_send_json_success( $attachment );
2547 }
2548
2549 /**
2550  * Ajax handler for saving the attachment order.
2551  *
2552  * @since 3.5.0
2553  */
2554 function wp_ajax_save_attachment_order() {
2555         if ( ! isset( $_REQUEST['post_id'] ) )
2556                 wp_send_json_error();
2557
2558         if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2559                 wp_send_json_error();
2560
2561         if ( empty( $_REQUEST['attachments'] ) )
2562                 wp_send_json_error();
2563
2564         check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2565
2566         $attachments = $_REQUEST['attachments'];
2567
2568         if ( ! current_user_can( 'edit_post', $post_id ) )
2569                 wp_send_json_error();
2570
2571         foreach ( $attachments as $attachment_id => $menu_order ) {
2572                 if ( ! current_user_can( 'edit_post', $attachment_id ) )
2573                         continue;
2574                 if ( ! $attachment = get_post( $attachment_id ) )
2575                         continue;
2576                 if ( 'attachment' != $attachment->post_type )
2577                         continue;
2578
2579                 wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2580         }
2581
2582         wp_send_json_success();
2583 }
2584
2585 /**
2586  * Ajax handler for sending an attachment to the editor.
2587  *
2588  * Generates the HTML to send an attachment to the editor.
2589  * Backward compatible with the {@see 'media_send_to_editor'} filter
2590  * and the chain of filters that follow.
2591  *
2592  * @since 3.5.0
2593  */
2594 function wp_ajax_send_attachment_to_editor() {
2595         check_ajax_referer( 'media-send-to-editor', 'nonce' );
2596
2597         $attachment = wp_unslash( $_POST['attachment'] );
2598
2599         $id = intval( $attachment['id'] );
2600
2601         if ( ! $post = get_post( $id ) )
2602                 wp_send_json_error();
2603
2604         if ( 'attachment' != $post->post_type )
2605                 wp_send_json_error();
2606
2607         if ( current_user_can( 'edit_post', $id ) ) {
2608                 // If this attachment is unattached, attach it. Primarily a back compat thing.
2609                 if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2610                         wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2611                 }
2612         }
2613
2614         $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
2615         $rel = ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url );
2616
2617         remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2618
2619         if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2620                 $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2621                 $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2622                 $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2623
2624                 // No whitespace-only captions.
2625                 $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2626                 if ( '' === trim( $caption ) ) {
2627                         $caption = '';
2628                 }
2629
2630                 $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2631                 $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
2632         } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post )  ) {
2633                 $html = stripslashes_deep( $_POST['html'] );
2634         } else {
2635                 $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2636                 $rel = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized
2637
2638                 if ( ! empty( $url ) ) {
2639                         $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2640                 }
2641         }
2642
2643         /** This filter is documented in wp-admin/includes/media.php */
2644         $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2645
2646         wp_send_json_success( $html );
2647 }
2648
2649 /**
2650  * Ajax handler for sending a link to the editor.
2651  *
2652  * Generates the HTML to send a non-image embed link to the editor.
2653  *
2654  * Backward compatible with the following filters:
2655  * - file_send_to_editor_url
2656  * - audio_send_to_editor_url
2657  * - video_send_to_editor_url
2658  *
2659  * @since 3.5.0
2660  *
2661  * @global WP_Post  $post
2662  * @global WP_Embed $wp_embed
2663  */
2664 function wp_ajax_send_link_to_editor() {
2665         global $post, $wp_embed;
2666
2667         check_ajax_referer( 'media-send-to-editor', 'nonce' );
2668
2669         if ( ! $src = wp_unslash( $_POST['src'] ) )
2670                 wp_send_json_error();
2671
2672         if ( ! strpos( $src, '://' ) )
2673                 $src = 'http://' . $src;
2674
2675         if ( ! $src = esc_url_raw( $src ) )
2676                 wp_send_json_error();
2677
2678         if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2679                 $link_text = wp_basename( $src );
2680
2681         $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2682
2683         // Ping WordPress for an embed.
2684         $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2685
2686         // Fallback that WordPress creates when no oEmbed was found.
2687         $fallback = $wp_embed->maybe_make_link( $src );
2688
2689         if ( $check_embed !== $fallback ) {
2690                 // TinyMCE view for [embed] will parse this
2691                 $html = '[embed]' . $src . '[/embed]';
2692         } elseif ( $link_text ) {
2693                 $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2694         } else {
2695                 $html = '';
2696         }
2697
2698         // Figure out what filter to run:
2699         $type = 'file';
2700         if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2701                 && ( 'audio' == $ext_type || 'video' == $ext_type ) )
2702                         $type = $ext_type;
2703
2704         /** This filter is documented in wp-admin/includes/media.php */
2705         $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
2706
2707         wp_send_json_success( $html );
2708 }
2709
2710 /**
2711  * Ajax handler for the Heartbeat API.
2712  *
2713  * Runs when the user is logged in.
2714  *
2715  * @since 3.6.0
2716  */
2717 function wp_ajax_heartbeat() {
2718         if ( empty( $_POST['_nonce'] ) ) {
2719                 wp_send_json_error();
2720         }
2721
2722         $response = $data = array();
2723         $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
2724
2725         // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2726         if ( ! empty( $_POST['screen_id'] ) ) {
2727                 $screen_id = sanitize_key($_POST['screen_id']);
2728         } else {
2729                 $screen_id = 'front';
2730         }
2731
2732         if ( ! empty( $_POST['data'] ) ) {
2733                 $data = wp_unslash( (array) $_POST['data'] );
2734         }
2735
2736         if ( 1 !== $nonce_state ) {
2737                 $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
2738
2739                 if ( false === $nonce_state ) {
2740                         // User is logged in but nonces have expired.
2741                         $response['nonces_expired'] = true;
2742                         wp_send_json( $response );
2743                 }
2744         }
2745
2746         if ( ! empty( $data ) ) {
2747                 /**
2748                  * Filters the Heartbeat response received.
2749                  *
2750                  * @since 3.6.0
2751                  *
2752                  * @param array  $response  The Heartbeat response.
2753                  * @param array  $data      The $_POST data sent.
2754                  * @param string $screen_id The screen id.
2755                  */
2756                 $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2757         }
2758
2759         /**
2760          * Filters the Heartbeat response sent.
2761          *
2762          * @since 3.6.0
2763          *
2764          * @param array  $response  The Heartbeat response.
2765          * @param string $screen_id The screen id.
2766          */
2767         $response = apply_filters( 'heartbeat_send', $response, $screen_id );
2768
2769         /**
2770          * Fires when Heartbeat ticks in logged-in environments.
2771          *
2772          * Allows the transport to be easily replaced with long-polling.
2773          *
2774          * @since 3.6.0
2775          *
2776          * @param array  $response  The Heartbeat response.
2777          * @param string $screen_id The screen id.
2778          */
2779         do_action( 'heartbeat_tick', $response, $screen_id );
2780
2781         // Send the current time according to the server
2782         $response['server_time'] = time();
2783
2784         wp_send_json( $response );
2785 }
2786
2787 /**
2788  * Ajax handler for getting revision diffs.
2789  *
2790  * @since 3.6.0
2791  */
2792 function wp_ajax_get_revision_diffs() {
2793         require ABSPATH . 'wp-admin/includes/revision.php';
2794
2795         if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2796                 wp_send_json_error();
2797
2798         if ( ! current_user_can( 'edit_post', $post->ID ) )
2799                 wp_send_json_error();
2800
2801         // Really just pre-loading the cache here.
2802         if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2803                 wp_send_json_error();
2804
2805         $return = array();
2806         @set_time_limit( 0 );
2807
2808         foreach ( $_REQUEST['compare'] as $compare_key ) {
2809                 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2810
2811                 $return[] = array(
2812                         'id' => $compare_key,
2813                         'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2814                 );
2815         }
2816         wp_send_json_success( $return );
2817 }
2818
2819 /**
2820  * Ajax handler for auto-saving the selected color scheme for
2821  * a user's own profile.
2822  *
2823  * @since 3.8.0
2824  *
2825  * @global array $_wp_admin_css_colors
2826  */
2827 function wp_ajax_save_user_color_scheme() {
2828         global $_wp_admin_css_colors;
2829
2830         check_ajax_referer( 'save-color-scheme', 'nonce' );
2831
2832         $color_scheme = sanitize_key( $_POST['color_scheme'] );
2833
2834         if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2835                 wp_send_json_error();
2836         }
2837
2838         $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2839         update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2840
2841         wp_send_json_success( array(
2842                 'previousScheme' => 'admin-color-' . $previous_color_scheme,
2843                 'currentScheme'  => 'admin-color-' . $color_scheme
2844         ) );
2845 }
2846
2847 /**
2848  * Ajax handler for getting themes from themes_api().
2849  *
2850  * @since 3.9.0
2851  *
2852  * @global array $themes_allowedtags
2853  * @global array $theme_field_defaults
2854  */
2855 function wp_ajax_query_themes() {
2856         global $themes_allowedtags, $theme_field_defaults;
2857
2858         if ( ! current_user_can( 'install_themes' ) ) {
2859                 wp_send_json_error();
2860         }
2861
2862         $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2863                 'per_page' => 20,
2864                 'fields'   => $theme_field_defaults
2865         ) );
2866
2867         if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
2868                 $user = get_user_option( 'wporg_favorites' );
2869                 if ( $user ) {
2870                         $args['user'] = $user;
2871                 }
2872         }
2873
2874         $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2875
2876         /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2877         $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2878
2879         $api = themes_api( 'query_themes', $args );
2880
2881         if ( is_wp_error( $api ) ) {
2882                 wp_send_json_error();
2883         }
2884
2885         $update_php = network_admin_url( 'update.php?action=install-theme' );
2886         foreach ( $api->themes as &$theme ) {
2887                 $theme->install_url = add_query_arg( array(
2888                         'theme'    => $theme->slug,
2889                         '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2890                 ), $update_php );
2891
2892                 if ( current_user_can( 'switch_themes' ) ) {
2893                         if ( is_multisite() ) {
2894                                 $theme->activate_url = add_query_arg( array(
2895                                         'action'   => 'enable',
2896                                         '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
2897                                         'theme'    => $theme->slug,
2898                                 ), network_admin_url( 'themes.php' ) );
2899                         } else {
2900                                 $theme->activate_url = add_query_arg( array(
2901                                         'action'     => 'activate',
2902                                         '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
2903                                         'stylesheet' => $theme->slug,
2904                                 ), admin_url( 'themes.php' ) );
2905                         }
2906                 }
2907
2908                 if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
2909                         $theme->customize_url = add_query_arg( array(
2910                                 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
2911                         ), wp_customize_url( $theme->slug ) );
2912                 }
2913
2914                 $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
2915                 $theme->author      = wp_kses( $theme->author, $themes_allowedtags );
2916                 $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
2917                 $theme->description = wp_kses( $theme->description, $themes_allowedtags );
2918                 $theme->stars       = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) );
2919                 $theme->num_ratings = number_format_i18n( $theme->num_ratings );
2920                 $theme->preview_url = set_url_scheme( $theme->preview_url );
2921         }
2922
2923         wp_send_json_success( $api );
2924 }
2925
2926 /**
2927  * Apply [embed] Ajax handlers to a string.
2928  *
2929  * @since 4.0.0
2930  *
2931  * @global WP_Post    $post       Global $post.
2932  * @global WP_Embed   $wp_embed   Embed API instance.
2933  * @global WP_Scripts $wp_scripts
2934  */
2935 function wp_ajax_parse_embed() {
2936         global $post, $wp_embed;
2937
2938         if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2939                 wp_send_json_error();
2940         }
2941
2942         if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
2943                 wp_send_json_error();
2944         }
2945
2946         $shortcode = wp_unslash( $_POST['shortcode'] );
2947
2948         preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
2949         $atts = shortcode_parse_atts( $matches[3] );
2950         if ( ! empty( $matches[5] ) ) {
2951                 $url = $matches[5];
2952         } elseif ( ! empty( $atts['src'] ) ) {
2953                 $url = $atts['src'];
2954         } else {
2955                 $url = '';
2956         }
2957
2958         $parsed = false;
2959         setup_postdata( $post );
2960
2961         $wp_embed->return_false_on_fail = true;
2962
2963         if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
2964                 // Admin is ssl and the user pasted non-ssl URL.
2965                 // Check if the provider supports ssl embeds and use that for the preview.
2966                 $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
2967                 $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
2968
2969                 if ( ! $parsed ) {
2970                         $no_ssl_support = true;
2971                 }
2972         }
2973
2974         if ( $url && ! $parsed ) {
2975                 $parsed = $wp_embed->run_shortcode( $shortcode );
2976         }
2977
2978         if ( ! $parsed ) {
2979                 wp_send_json_error( array(
2980                         'type' => 'not-embeddable',
2981                         'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
2982                 ) );
2983         }
2984
2985         if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
2986                 $styles = '';
2987                 $mce_styles = wpview_media_sandbox_styles();
2988                 foreach ( $mce_styles as $style ) {
2989                         $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
2990                 }
2991
2992                 $html = do_shortcode( $parsed );
2993
2994                 global $wp_scripts;
2995                 if ( ! empty( $wp_scripts ) ) {
2996                         $wp_scripts->done = array();
2997                 }
2998                 ob_start();
2999                 wp_print_scripts( 'wp-mediaelement' );
3000                 $scripts = ob_get_clean();
3001
3002                 $parsed = $styles . $html . $scripts;
3003         }
3004
3005
3006         if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3007                 preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3008                 // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3009                 wp_send_json_error( array(
3010                         'type' => 'not-ssl',
3011                         'message' => __( 'This preview is unavailable in the editor.' ),
3012                 ) );
3013         }
3014
3015         wp_send_json_success( array(
3016                 'body' => $parsed,
3017                 'attr' => $wp_embed->last_attr
3018         ) );
3019 }
3020
3021 /**
3022  * @since 4.0.0
3023  *
3024  * @global WP_Post    $post
3025  * @global WP_Scripts $wp_scripts
3026  */
3027 function wp_ajax_parse_media_shortcode() {
3028         global $post, $wp_scripts;
3029
3030         if ( empty( $_POST['shortcode'] ) ) {
3031                 wp_send_json_error();
3032         }
3033
3034         $shortcode = wp_unslash( $_POST['shortcode'] );
3035
3036         if ( ! empty( $_POST['post_ID'] ) ) {
3037                 $post = get_post( (int) $_POST['post_ID'] );
3038         }
3039
3040         // the embed shortcode requires a post
3041         if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3042                 if ( 'embed' === $shortcode ) {
3043                         wp_send_json_error();
3044                 }
3045         } else {
3046                 setup_postdata( $post );
3047         }
3048
3049         $parsed = do_shortcode( $shortcode  );
3050
3051         if ( empty( $parsed ) ) {
3052                 wp_send_json_error( array(
3053                         'type' => 'no-items',
3054                         'message' => __( 'No items found.' ),
3055                 ) );
3056         }
3057
3058         $head = '';
3059         $styles = wpview_media_sandbox_styles();
3060
3061         foreach ( $styles as $style ) {
3062                 $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3063         }
3064
3065         if ( ! empty( $wp_scripts ) ) {
3066                 $wp_scripts->done = array();
3067         }
3068
3069         ob_start();
3070
3071         echo $parsed;
3072
3073         if ( 'playlist' === $_REQUEST['type'] ) {
3074                 wp_underscore_playlist_templates();
3075
3076                 wp_print_scripts( 'wp-playlist' );
3077         } else {
3078                 wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
3079         }
3080
3081         wp_send_json_success( array(
3082                 'head' => $head,
3083                 'body' => ob_get_clean()
3084         ) );
3085 }
3086
3087 /**
3088  * Ajax handler for destroying multiple open sessions for a user.
3089  *
3090  * @since 4.1.0
3091  */
3092 function wp_ajax_destroy_sessions() {
3093         $user = get_userdata( (int) $_POST['user_id'] );
3094         if ( $user ) {
3095                 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3096                         $user = false;
3097                 } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3098                         $user = false;
3099                 }
3100         }
3101
3102         if ( ! $user ) {
3103                 wp_send_json_error( array(
3104                         'message' => __( 'Could not log out user sessions. Please try again.' ),
3105                 ) );
3106         }
3107
3108         $sessions = WP_Session_Tokens::get_instance( $user->ID );
3109
3110         if ( $user->ID === get_current_user_id() ) {
3111                 $sessions->destroy_others( wp_get_session_token() );
3112                 $message = __( 'You are now logged out everywhere else.' );
3113         } else {
3114                 $sessions->destroy_all();
3115                 /* translators: 1: User's display name. */
3116                 $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3117         }
3118
3119         wp_send_json_success( array( 'message' => $message ) );
3120 }
3121
3122 /**
3123  * Ajax handler for saving a post from Press This.
3124  *
3125  * @since 4.2.0
3126  */
3127 function wp_ajax_press_this_save_post() {
3128         include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3129         $wp_press_this = new WP_Press_This();
3130         $wp_press_this->save_post();
3131 }
3132
3133 /**
3134  * Ajax handler for creating new category from Press This.
3135  *
3136  * @since 4.2.0
3137  */
3138 function wp_ajax_press_this_add_category() {
3139         include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3140         $wp_press_this = new WP_Press_This();
3141         $wp_press_this->add_category();
3142 }
3143
3144 /**
3145  * Ajax handler for cropping an image.
3146  *
3147  * @since 4.3.0
3148  */
3149 function wp_ajax_crop_image() {
3150         $attachment_id = absint( $_POST['id'] );
3151
3152         check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3153         if ( ! current_user_can( 'customize' ) ) {
3154                 wp_send_json_error();
3155         }
3156
3157         $context = str_replace( '_', '-', $_POST['context'] );
3158         $data    = array_map( 'absint', $_POST['cropDetails'] );
3159         $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3160
3161         if ( ! $cropped || is_wp_error( $cropped ) ) {
3162                 wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3163         }
3164
3165         switch ( $context ) {
3166                 case 'site-icon':
3167                         require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
3168                         $wp_site_icon = new WP_Site_Icon();
3169
3170                         // Skip creating a new attachment if the attachment is a Site Icon.
3171                         if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3172
3173                                 // Delete the temporary cropped file, we don't need it.
3174                                 wp_delete_file( $cropped );
3175
3176                                 // Additional sizes in wp_prepare_attachment_for_js().
3177                                 add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3178                                 break;
3179                         }
3180
3181                         /** This filter is documented in wp-admin/custom-header.php */
3182                         $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3183                         $object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3184                         unset( $object['ID'] );
3185
3186                         // Update the attachment.
3187                         add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3188                         $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3189                         remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3190
3191                         // Additional sizes in wp_prepare_attachment_for_js().
3192                         add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3193                         break;
3194
3195                 default:
3196
3197                         /**
3198                          * Fires before a cropped image is saved.
3199                          *
3200                          * Allows to add filters to modify the way a cropped image is saved.
3201                          *
3202                          * @since 4.3.0
3203                          *
3204                          * @param string $context       The Customizer control requesting the cropped image.
3205                          * @param int    $attachment_id The attachment ID of the original image.
3206                          * @param string $cropped       Path to the cropped image file.
3207                          */
3208                         do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3209
3210                         /** This filter is documented in wp-admin/custom-header.php */
3211                         $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3212
3213                         $parent_url = wp_get_attachment_url( $attachment_id );
3214                         $url        = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
3215
3216                         $size       = @getimagesize( $cropped );
3217                         $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3218
3219                         $object = array(
3220                                 'post_title'     => basename( $cropped ),
3221                                 'post_content'   => $url,
3222                                 'post_mime_type' => $image_type,
3223                                 'guid'           => $url,
3224                                 'context'        => $context,
3225                         );
3226
3227                         $attachment_id = wp_insert_attachment( $object, $cropped );
3228                         $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
3229
3230                         /**
3231                          * Filters the cropped image attachment metadata.
3232                          *
3233                          * @since 4.3.0
3234                          *
3235                          * @see wp_generate_attachment_metadata()
3236                          *
3237                          * @param array $metadata Attachment metadata.
3238                          */
3239                         $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3240                         wp_update_attachment_metadata( $attachment_id, $metadata );
3241
3242                         /**
3243                          * Filters the attachment ID for a cropped image.
3244                          *
3245                          * @since 4.3.0
3246                          *
3247                          * @param int    $attachment_id The attachment ID of the cropped image.
3248                          * @param string $context       The Customizer control requesting the cropped image.
3249                          */
3250                         $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3251         }
3252
3253         wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
3254 }
3255
3256 /**
3257  * Ajax handler for generating a password.
3258  *
3259  * @since 4.4.0
3260  */
3261 function wp_ajax_generate_password() {
3262         wp_send_json_success( wp_generate_password( 24 ) );
3263 }
3264
3265 /**
3266  * Ajax handler for saving the user's WordPress.org username.
3267  *
3268  * @since 4.4.0
3269  */
3270 function wp_ajax_save_wporg_username() {
3271         if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
3272                 wp_send_json_error();
3273         }
3274
3275         check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
3276
3277         $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
3278
3279         if ( ! $username ) {
3280                 wp_send_json_error();
3281         }
3282
3283         wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
3284 }
3285
3286 /**
3287  * Ajax handler for installing a theme.
3288  *
3289  * @since 4.6.0
3290  *
3291  * @see Theme_Upgrader
3292  */
3293 function wp_ajax_install_theme() {
3294         check_ajax_referer( 'updates' );
3295
3296         if ( empty( $_POST['slug'] ) ) {
3297                 wp_send_json_error( array(
3298                         'slug'         => '',
3299                         'errorCode'    => 'no_theme_specified',
3300                         'errorMessage' => __( 'No theme specified.' ),
3301                 ) );
3302         }
3303
3304         $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
3305
3306         $status = array(
3307                 'install' => 'theme',
3308                 'slug'    => $slug,
3309         );
3310
3311         if ( ! current_user_can( 'install_themes' ) ) {
3312                 $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
3313                 wp_send_json_error( $status );
3314         }
3315
3316         include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3317         include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3318
3319         $api = themes_api( 'theme_information', array(
3320                 'slug'   => $slug,
3321                 'fields' => array( 'sections' => false ),
3322         ) );
3323
3324         if ( is_wp_error( $api ) ) {
3325                 $status['errorMessage'] = $api->get_error_message();
3326                 wp_send_json_error( $status );
3327         }
3328
3329         $skin     = new WP_Ajax_Upgrader_Skin();
3330         $upgrader = new Theme_Upgrader( $skin );
3331         $result   = $upgrader->install( $api->download_link );
3332
3333         if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3334                 $status['debug'] = $skin->get_upgrade_messages();
3335         }
3336
3337         if ( is_wp_error( $result ) ) {
3338                 $status['errorCode']    = $result->get_error_code();
3339                 $status['errorMessage'] = $result->get_error_message();
3340                 wp_send_json_error( $status );
3341         } elseif ( is_wp_error( $skin->result ) ) {
3342                 $status['errorCode']    = $skin->result->get_error_code();
3343                 $status['errorMessage'] = $skin->result->get_error_message();
3344                 wp_send_json_error( $status );
3345         } elseif ( $skin->get_errors()->get_error_code() ) {
3346                 $status['errorMessage'] = $skin->get_error_messages();
3347                 wp_send_json_error( $status );
3348         } elseif ( is_null( $result ) ) {
3349                 global $wp_filesystem;
3350
3351                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3352                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3353
3354                 // Pass through the error from WP_Filesystem if one was raised.
3355                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3356                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3357                 }
3358
3359                 wp_send_json_error( $status );
3360         }
3361
3362         $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
3363
3364         if ( current_user_can( 'switch_themes' ) ) {
3365                 if ( is_multisite() ) {
3366                         $status['activateUrl'] = add_query_arg( array(
3367                                 'action'   => 'enable',
3368                                 '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
3369                                 'theme'    => $slug,
3370                         ), network_admin_url( 'themes.php' ) );
3371                 } else {
3372                         $status['activateUrl'] = add_query_arg( array(
3373                                 'action'     => 'activate',
3374                                 '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
3375                                 'stylesheet' => $slug,
3376                         ), admin_url( 'themes.php' ) );
3377                 }
3378         }
3379
3380         if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3381                 $status['customizeUrl'] = add_query_arg( array(
3382                         'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3383                 ), wp_customize_url( $slug ) );
3384         }
3385
3386         /*
3387          * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
3388          * on post-install status.
3389          */
3390         wp_send_json_success( $status );
3391 }
3392
3393 /**
3394  * Ajax handler for updating a theme.
3395  *
3396  * @since 4.6.0
3397  *
3398  * @see Theme_Upgrader
3399  */
3400 function wp_ajax_update_theme() {
3401         check_ajax_referer( 'updates' );
3402
3403         if ( empty( $_POST['slug'] ) ) {
3404                 wp_send_json_error( array(
3405                         'slug'         => '',
3406                         'errorCode'    => 'no_theme_specified',
3407                         'errorMessage' => __( 'No theme specified.' ),
3408                 ) );
3409         }
3410
3411         $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3412         $status     = array(
3413                 'update'     => 'theme',
3414                 'slug'       => $stylesheet,
3415                 'newVersion' => '',
3416         );
3417
3418         if ( ! current_user_can( 'update_themes' ) ) {
3419                 $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
3420                 wp_send_json_error( $status );
3421         }
3422
3423         include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3424
3425         $current = get_site_transient( 'update_themes' );
3426         if ( empty( $current ) ) {
3427                 wp_update_themes();
3428         }
3429
3430         $skin     = new WP_Ajax_Upgrader_Skin();
3431         $upgrader = new Theme_Upgrader( $skin );
3432         $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
3433
3434         if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3435                 $status['debug'] = $skin->get_upgrade_messages();
3436         }
3437
3438         if ( is_wp_error( $skin->result ) ) {
3439                 $status['errorCode']    = $skin->result->get_error_code();
3440                 $status['errorMessage'] = $skin->result->get_error_message();
3441                 wp_send_json_error( $status );
3442         } elseif ( $skin->get_errors()->get_error_code() ) {
3443                 $status['errorMessage'] = $skin->get_error_messages();
3444                 wp_send_json_error( $status );
3445         } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
3446
3447                 // Theme is already at the latest version.
3448                 if ( true === $result[ $stylesheet ] ) {
3449                         $status['errorMessage'] = $upgrader->strings['up_to_date'];
3450                         wp_send_json_error( $status );
3451                 }
3452
3453                 $theme = wp_get_theme( $stylesheet );
3454                 if ( $theme->get( 'Version' ) ) {
3455                         $status['newVersion'] = $theme->get( 'Version' );
3456                 }
3457
3458                 wp_send_json_success( $status );
3459         } elseif ( false === $result ) {
3460                 global $wp_filesystem;
3461
3462                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3463                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3464
3465                 // Pass through the error from WP_Filesystem if one was raised.
3466                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3467                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3468                 }
3469
3470                 wp_send_json_error( $status );
3471         }
3472
3473         // An unhandled error occurred.
3474         $status['errorMessage'] = __( 'Update failed.' );
3475         wp_send_json_error( $status );
3476 }
3477
3478 /**
3479  * Ajax handler for deleting a theme.
3480  *
3481  * @since 4.6.0
3482  *
3483  * @see delete_theme()
3484  */
3485 function wp_ajax_delete_theme() {
3486         check_ajax_referer( 'updates' );
3487
3488         if ( empty( $_POST['slug'] ) ) {
3489                 wp_send_json_error( array(
3490                         'slug'         => '',
3491                         'errorCode'    => 'no_theme_specified',
3492                         'errorMessage' => __( 'No theme specified.' ),
3493                 ) );
3494         }
3495
3496         $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3497         $status     = array(
3498                 'delete' => 'theme',
3499                 'slug'   => $stylesheet,
3500         );
3501
3502         if ( ! current_user_can( 'delete_themes' ) ) {
3503                 $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
3504                 wp_send_json_error( $status );
3505         }
3506
3507         if ( ! wp_get_theme( $stylesheet )->exists() ) {
3508                 $status['errorMessage'] = __( 'The requested theme does not exist.' );
3509                 wp_send_json_error( $status );
3510         }
3511
3512         // Check filesystem credentials. `delete_theme()` will bail otherwise.
3513         $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
3514         ob_start();
3515         $credentials = request_filesystem_credentials( $url );
3516         ob_end_clean();
3517         if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3518                 global $wp_filesystem;
3519
3520                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3521                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3522
3523                 // Pass through the error from WP_Filesystem if one was raised.
3524                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3525                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3526                 }
3527
3528                 wp_send_json_error( $status );
3529         }
3530
3531         include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3532
3533         $result = delete_theme( $stylesheet );
3534
3535         if ( is_wp_error( $result ) ) {
3536                 $status['errorMessage'] = $result->get_error_message();
3537                 wp_send_json_error( $status );
3538         } elseif ( false === $result ) {
3539                 $status['errorMessage'] = __( 'Theme could not be deleted.' );
3540                 wp_send_json_error( $status );
3541         }
3542
3543         wp_send_json_success( $status );
3544 }
3545
3546 /**
3547  * Ajax handler for installing a plugin.
3548  *
3549  * @since 4.6.0
3550  *
3551  * @see Plugin_Upgrader
3552  */
3553 function wp_ajax_install_plugin() {
3554         check_ajax_referer( 'updates' );
3555
3556         if ( empty( $_POST['slug'] ) ) {
3557                 wp_send_json_error( array(
3558                         'slug'         => '',
3559                         'errorCode'    => 'no_plugin_specified',
3560                         'errorMessage' => __( 'No plugin specified.' ),
3561                 ) );
3562         }
3563
3564         $status = array(
3565                 'install' => 'plugin',
3566                 'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3567         );
3568
3569         if ( ! current_user_can( 'install_plugins' ) ) {
3570                 $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
3571                 wp_send_json_error( $status );
3572         }
3573
3574         include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3575         include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
3576
3577         $api = plugins_api( 'plugin_information', array(
3578                 'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3579                 'fields' => array(
3580                         'sections' => false,
3581                 ),
3582         ) );
3583
3584         if ( is_wp_error( $api ) ) {
3585                 $status['errorMessage'] = $api->get_error_message();
3586                 wp_send_json_error( $status );
3587         }
3588
3589         $status['pluginName'] = $api->name;
3590
3591         $skin     = new WP_Ajax_Upgrader_Skin();
3592         $upgrader = new Plugin_Upgrader( $skin );
3593         $result   = $upgrader->install( $api->download_link );
3594
3595         if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3596                 $status['debug'] = $skin->get_upgrade_messages();
3597         }
3598
3599         if ( is_wp_error( $result ) ) {
3600                 $status['errorCode']    = $result->get_error_code();
3601                 $status['errorMessage'] = $result->get_error_message();
3602                 wp_send_json_error( $status );
3603         } elseif ( is_wp_error( $skin->result ) ) {
3604                 $status['errorCode']    = $skin->result->get_error_code();
3605                 $status['errorMessage'] = $skin->result->get_error_message();
3606                 wp_send_json_error( $status );
3607         } elseif ( $skin->get_errors()->get_error_code() ) {
3608                 $status['errorMessage'] = $skin->get_error_messages();
3609                 wp_send_json_error( $status );
3610         } elseif ( is_null( $result ) ) {
3611                 global $wp_filesystem;
3612
3613                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3614                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3615
3616                 // Pass through the error from WP_Filesystem if one was raised.
3617                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3618                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3619                 }
3620
3621                 wp_send_json_error( $status );
3622         }
3623
3624         $install_status = install_plugin_install_status( $api );
3625         $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3626
3627         // If install request is coming from import page, do not return network activation link.
3628         $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
3629
3630         if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) {
3631                 $status['activateUrl'] = add_query_arg( array(
3632                         '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
3633                         'action'   => 'activate',
3634                         'plugin'   => $install_status['file'],
3635                 ), $plugins_url );
3636         }
3637
3638         if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
3639                 $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
3640         }
3641
3642         wp_send_json_success( $status );
3643 }
3644
3645 /**
3646  * Ajax handler for updating a plugin.
3647  *
3648  * @since 4.2.0
3649  *
3650  * @see Plugin_Upgrader
3651  */
3652 function wp_ajax_update_plugin() {
3653         check_ajax_referer( 'updates' );
3654
3655         if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
3656                 wp_send_json_error( array(
3657                         'slug'         => '',
3658                         'errorCode'    => 'no_plugin_specified',
3659                         'errorMessage' => __( 'No plugin specified.' ),
3660                 ) );
3661         }
3662
3663         $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
3664
3665         $status = array(
3666                 'update'     => 'plugin',
3667                 'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3668                 'oldVersion' => '',
3669                 'newVersion' => '',
3670         );
3671
3672         if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
3673                 $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
3674                 wp_send_json_error( $status );
3675         }
3676
3677         $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3678         $status['plugin']     = $plugin;
3679         $status['pluginName'] = $plugin_data['Name'];
3680
3681         if ( $plugin_data['Version'] ) {
3682                 /* translators: %s: Plugin version */
3683                 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3684         }
3685
3686         include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3687
3688         wp_update_plugins();
3689
3690         $skin     = new WP_Ajax_Upgrader_Skin();
3691         $upgrader = new Plugin_Upgrader( $skin );
3692         $result   = $upgrader->bulk_upgrade( array( $plugin ) );
3693
3694         if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3695                 $status['debug'] = $skin->get_upgrade_messages();
3696         }
3697
3698         if ( is_wp_error( $skin->result ) ) {
3699                 $status['errorCode']    = $skin->result->get_error_code();
3700                 $status['errorMessage'] = $skin->result->get_error_message();
3701                 wp_send_json_error( $status );
3702         } elseif ( $skin->get_errors()->get_error_code() ) {
3703                 $status['errorMessage'] = $skin->get_error_messages();
3704                 wp_send_json_error( $status );
3705         } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
3706                 $plugin_update_data = current( $result );
3707
3708                 /*
3709                  * If the `update_plugins` site transient is empty (e.g. when you update
3710                  * two plugins in quick succession before the transient repopulates),
3711                  * this may be the return.
3712                  *
3713                  * Preferably something can be done to ensure `update_plugins` isn't empty.
3714                  * For now, surface some sort of error here.
3715                  */
3716                 if ( true === $plugin_update_data ) {
3717                         $status['errorMessage'] = __( 'Plugin update failed.' );
3718                         wp_send_json_error( $status );
3719                 }
3720
3721                 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
3722                 $plugin_data = reset( $plugin_data );
3723
3724                 if ( $plugin_data['Version'] ) {
3725                         /* translators: %s: Plugin version */
3726                         $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3727                 }
3728                 wp_send_json_success( $status );
3729         } elseif ( false === $result ) {
3730                 global $wp_filesystem;
3731
3732                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3733                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3734
3735                 // Pass through the error from WP_Filesystem if one was raised.
3736                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3737                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3738                 }
3739
3740                 wp_send_json_error( $status );
3741         }
3742
3743         // An unhandled error occurred.
3744         $status['errorMessage'] = __( 'Plugin update failed.' );
3745         wp_send_json_error( $status );
3746 }
3747
3748 /**
3749  * Ajax handler for deleting a plugin.
3750  *
3751  * @since 4.6.0
3752  *
3753  * @see delete_plugins()
3754  */
3755 function wp_ajax_delete_plugin() {
3756         check_ajax_referer( 'updates' );
3757
3758         if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
3759                 wp_send_json_error( array(
3760                         'slug'         => '',
3761                         'errorCode'    => 'no_plugin_specified',
3762                         'errorMessage' => __( 'No plugin specified.' ),
3763                 ) );
3764         }
3765
3766         $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
3767
3768         $status = array(
3769                 'delete' => 'plugin',
3770                 'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3771         );
3772
3773         if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
3774                 $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
3775                 wp_send_json_error( $status );
3776         }
3777
3778         $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3779         $status['plugin']     = $plugin;
3780         $status['pluginName'] = $plugin_data['Name'];
3781
3782         if ( is_plugin_active( $plugin ) ) {
3783                 $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
3784                 wp_send_json_error( $status );
3785         }
3786
3787         // Check filesystem credentials. `delete_plugins()` will bail otherwise.
3788         $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
3789         ob_start();
3790         $credentials = request_filesystem_credentials( $url );
3791         ob_end_clean();
3792         if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3793                 global $wp_filesystem;
3794
3795                 $status['errorCode']    = 'unable_to_connect_to_filesystem';
3796                 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3797
3798                 // Pass through the error from WP_Filesystem if one was raised.
3799                 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3800                         $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3801                 }
3802
3803                 wp_send_json_error( $status );
3804         }
3805
3806         $result = delete_plugins( array( $plugin ) );
3807
3808         if ( is_wp_error( $result ) ) {
3809                 $status['errorMessage'] = $result->get_error_message();
3810                 wp_send_json_error( $status );
3811         } elseif ( false === $result ) {
3812                 $status['errorMessage'] = __( 'Plugin could not be deleted.' );
3813                 wp_send_json_error( $status );
3814         }
3815
3816         wp_send_json_success( $status );
3817 }
3818
3819 /**
3820  * Ajax handler for searching plugins.
3821  *
3822  * @since 4.6.0
3823  *
3824  * @global string $s Search term.
3825  */
3826 function wp_ajax_search_plugins() {
3827         check_ajax_referer( 'updates' );
3828
3829         $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3830         if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
3831                 set_current_screen( $pagenow );
3832         }
3833
3834         /** @var WP_Plugins_List_Table $wp_list_table */
3835         $wp_list_table = _get_list_table( 'WP_Plugins_List_Table', array(
3836                 'screen' => get_current_screen(),
3837         ) );
3838
3839         $status = array();
3840
3841         if ( ! $wp_list_table->ajax_user_can() ) {
3842                 $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3843                 wp_send_json_error( $status );
3844         }
3845
3846         // Set the correct requester, so pagination works.
3847         $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3848                 '_ajax_nonce' => null,
3849                 'action'      => null,
3850         ) ), network_admin_url( 'plugins.php', 'relative' ) );
3851
3852         $GLOBALS['s'] = wp_unslash( $_POST['s'] );
3853
3854         $wp_list_table->prepare_items();
3855
3856         ob_start();
3857         $wp_list_table->display();
3858         $status['count'] = count( $wp_list_table->items );
3859         $status['items'] = ob_get_clean();
3860
3861         wp_send_json_success( $status );
3862 }
3863
3864 /**
3865  * Ajax handler for searching plugins to install.
3866  *
3867  * @since 4.6.0
3868  */
3869 function wp_ajax_search_install_plugins() {
3870         check_ajax_referer( 'updates' );
3871
3872         $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3873         if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
3874                 set_current_screen( $pagenow );
3875         }
3876
3877         /** @var WP_Plugin_Install_List_Table $wp_list_table */
3878         $wp_list_table = _get_list_table( 'WP_Plugin_Install_List_Table', array(
3879                 'screen' => get_current_screen(),
3880         ) );
3881
3882         $status = array();
3883
3884         if ( ! $wp_list_table->ajax_user_can() ) {
3885                 $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3886                 wp_send_json_error( $status );
3887         }
3888
3889         // Set the correct requester, so pagination works.
3890         $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3891                 '_ajax_nonce' => null,
3892                 'action'      => null,
3893         ) ), network_admin_url( 'plugin-install.php', 'relative' ) );
3894
3895         $wp_list_table->prepare_items();
3896
3897         ob_start();
3898         $wp_list_table->display();
3899         $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
3900         $status['items'] = ob_get_clean();
3901
3902         wp_send_json_success( $status );
3903 }