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