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