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