WordPress 4.2.5
[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['visibility']  = 'private';
1545                 $data['post_status'] = 'private';
1546         } else {
1547                 $data['post_status'] = $data['_status'];
1548         }
1549
1550         if ( empty($data['comment_status']) )
1551                 $data['comment_status'] = 'closed';
1552         if ( empty($data['ping_status']) )
1553                 $data['ping_status'] = 'closed';
1554
1555         // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1556         if ( ! empty( $data['tax_input'] ) ) {
1557                 foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1558                         $tax_object = get_taxonomy( $taxonomy );
1559                         /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1560                         if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1561                                 unset( $data['tax_input'][ $taxonomy ] );
1562                         }
1563                 }
1564         }
1565
1566         // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1567         if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1568                 $post['post_status'] = 'publish';
1569                 $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1570         }
1571
1572         // Update the post.
1573         edit_post();
1574
1575         $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1576
1577         $level = 0;
1578         $request_post = array( get_post( $_POST['post_ID'] ) );
1579         $parent = $request_post[0]->post_parent;
1580
1581         while ( $parent > 0 ) {
1582                 $parent_post = get_post( $parent );
1583                 $parent = $parent_post->post_parent;
1584                 $level++;
1585         }
1586
1587         $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1588
1589         wp_die();
1590 }
1591
1592 /**
1593  * Ajax handler for quick edit saving for a term.
1594  *
1595  * @since 3.1.0
1596  */
1597 function wp_ajax_inline_save_tax() {
1598         global $wp_list_table;
1599
1600         check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1601
1602         $taxonomy = sanitize_key( $_POST['taxonomy'] );
1603         $tax = get_taxonomy( $taxonomy );
1604         if ( ! $tax )
1605                 wp_die( 0 );
1606
1607         if ( ! current_user_can( $tax->cap->edit_terms ) )
1608                 wp_die( -1 );
1609
1610         $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1611
1612         if ( ! isset($_POST['tax_ID']) || ! ( $id = (int) $_POST['tax_ID'] ) )
1613                 wp_die( -1 );
1614
1615         $tag = get_term( $id, $taxonomy );
1616         $_POST['description'] = $tag->description;
1617
1618         $updated = wp_update_term($id, $taxonomy, $_POST);
1619         if ( $updated && !is_wp_error($updated) ) {
1620                 $tag = get_term( $updated['term_id'], $taxonomy );
1621                 if ( !$tag || is_wp_error( $tag ) ) {
1622                         if ( is_wp_error($tag) && $tag->get_error_message() )
1623                                 wp_die( $tag->get_error_message() );
1624                         wp_die( __( 'Item not updated.' ) );
1625                 }
1626         } else {
1627                 if ( is_wp_error($updated) && $updated->get_error_message() )
1628                         wp_die( $updated->get_error_message() );
1629                 wp_die( __( 'Item not updated.' ) );
1630         }
1631         $level = 0;
1632         $parent = $tag->parent;
1633         while ( $parent > 0 ) {
1634                 $parent_tag = get_term( $parent, $taxonomy );
1635                 $parent = $parent_tag->parent;
1636                 $level++;
1637         }
1638         $wp_list_table->single_row( $tag, $level );
1639         wp_die();
1640 }
1641
1642 /**
1643  * Ajax handler for querying posts for the Find Posts modal.
1644  *
1645  * @see window.findPosts
1646  *
1647  * @since 3.1.0
1648  */
1649 function wp_ajax_find_posts() {
1650         check_ajax_referer( 'find-posts' );
1651
1652         $post_types = get_post_types( array( 'public' => true ), 'objects' );
1653         unset( $post_types['attachment'] );
1654
1655         $s = wp_unslash( $_POST['ps'] );
1656         $args = array(
1657                 'post_type' => array_keys( $post_types ),
1658                 'post_status' => 'any',
1659                 'posts_per_page' => 50,
1660         );
1661         if ( '' !== $s )
1662                 $args['s'] = $s;
1663
1664         $posts = get_posts( $args );
1665
1666         if ( ! $posts ) {
1667                 wp_send_json_error( __( 'No items found.' ) );
1668         }
1669
1670         $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>';
1671         $alt = '';
1672         foreach ( $posts as $post ) {
1673                 $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1674                 $alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1675
1676                 switch ( $post->post_status ) {
1677                         case 'publish' :
1678                         case 'private' :
1679                                 $stat = __('Published');
1680                                 break;
1681                         case 'future' :
1682                                 $stat = __('Scheduled');
1683                                 break;
1684                         case 'pending' :
1685                                 $stat = __('Pending Review');
1686                                 break;
1687                         case 'draft' :
1688                                 $stat = __('Draft');
1689                                 break;
1690                 }
1691
1692                 if ( '0000-00-00 00:00:00' == $post->post_date ) {
1693                         $time = '';
1694                 } else {
1695                         /* translators: date format in table columns, see http://php.net/date */
1696                         $time = mysql2date(__('Y/m/d'), $post->post_date);
1697                 }
1698
1699                 $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>';
1700                 $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";
1701         }
1702
1703         $html .= '</tbody></table>';
1704
1705         wp_send_json_success( $html );
1706 }
1707
1708 /**
1709  * Ajax handler for saving the widgets order.
1710  *
1711  * @since 3.1.0
1712  */
1713 function wp_ajax_widgets_order() {
1714         check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1715
1716         if ( !current_user_can('edit_theme_options') )
1717                 wp_die( -1 );
1718
1719         unset( $_POST['savewidgets'], $_POST['action'] );
1720
1721         // Save widgets order for all sidebars.
1722         if ( is_array($_POST['sidebars']) ) {
1723                 $sidebars = array();
1724                 foreach ( $_POST['sidebars'] as $key => $val ) {
1725                         $sb = array();
1726                         if ( !empty($val) ) {
1727                                 $val = explode(',', $val);
1728                                 foreach ( $val as $k => $v ) {
1729                                         if ( strpos($v, 'widget-') === false )
1730                                                 continue;
1731
1732                                         $sb[$k] = substr($v, strpos($v, '_') + 1);
1733                                 }
1734                         }
1735                         $sidebars[$key] = $sb;
1736                 }
1737                 wp_set_sidebars_widgets($sidebars);
1738                 wp_die( 1 );
1739         }
1740
1741         wp_die( -1 );
1742 }
1743
1744 /**
1745  * Ajax handler for saving a widget.
1746  *
1747  * @since 3.1.0
1748  */
1749 function wp_ajax_save_widget() {
1750         global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1751
1752         check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1753
1754         if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1755                 wp_die( -1 );
1756
1757         unset( $_POST['savewidgets'], $_POST['action'] );
1758
1759         /**
1760          * Fires early when editing the widgets displayed in sidebars.
1761          *
1762          * @since 2.8.0
1763          */
1764         do_action( 'load-widgets.php' );
1765
1766         /**
1767          * Fires early when editing the widgets displayed in sidebars.
1768          *
1769          * @since 2.8.0
1770          */
1771         do_action( 'widgets.php' );
1772
1773         /** This action is documented in wp-admin/widgets.php */
1774         do_action( 'sidebar_admin_setup' );
1775
1776         $id_base = $_POST['id_base'];
1777         $widget_id = $_POST['widget-id'];
1778         $sidebar_id = $_POST['sidebar'];
1779         $multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1780         $settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1781         $error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1782
1783         $sidebars = wp_get_sidebars_widgets();
1784         $sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1785
1786         // Delete.
1787         if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1788
1789                 if ( !isset($wp_registered_widgets[$widget_id]) )
1790                         wp_die( $error );
1791
1792                 $sidebar = array_diff( $sidebar, array($widget_id) );
1793                 $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1794         } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1795                 if ( !$multi_number )
1796                         wp_die( $error );
1797
1798                 $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1799                 $widget_id = $id_base . '-' . $multi_number;
1800                 $sidebar[] = $widget_id;
1801         }
1802         $_POST['widget-id'] = $sidebar;
1803
1804         foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1805
1806                 if ( $name == $id_base ) {
1807                         if ( !is_callable( $control['callback'] ) )
1808                                 continue;
1809
1810                         ob_start();
1811                                 call_user_func_array( $control['callback'], $control['params'] );
1812                         ob_end_clean();
1813                         break;
1814                 }
1815         }
1816
1817         if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1818                 $sidebars[$sidebar_id] = $sidebar;
1819                 wp_set_sidebars_widgets($sidebars);
1820                 echo "deleted:$widget_id";
1821                 wp_die();
1822         }
1823
1824         if ( !empty($_POST['add_new']) )
1825                 wp_die();
1826
1827         if ( $form = $wp_registered_widget_controls[$widget_id] )
1828                 call_user_func_array( $form['callback'], $form['params'] );
1829
1830         wp_die();
1831 }
1832
1833 /**
1834  * Ajax handler for saving a widget.
1835  *
1836  * @since 3.9.0
1837  */
1838 function wp_ajax_update_widget() {
1839         global $wp_customize;
1840         $wp_customize->widgets->wp_ajax_update_widget();
1841 }
1842
1843 /**
1844  * Ajax handler for uploading attachments
1845  *
1846  * @since 3.3.0
1847  */
1848 function wp_ajax_upload_attachment() {
1849         check_ajax_referer( 'media-form' );
1850         /*
1851          * This function does not use wp_send_json_success() / wp_send_json_error()
1852          * as the html4 Plupload handler requires a text/html content-type for older IE.
1853          * See https://core.trac.wordpress.org/ticket/31037
1854          */
1855
1856         if ( ! current_user_can( 'upload_files' ) ) {
1857                 echo wp_json_encode( array(
1858                         'success' => false,
1859                         'data'    => array(
1860                                 'message'  => __( "You don't have permission to upload files." ),
1861                                 'filename' => $_FILES['async-upload']['name'],
1862                         )
1863                 ) );
1864
1865                 wp_die();
1866         }
1867
1868         if ( isset( $_REQUEST['post_id'] ) ) {
1869                 $post_id = $_REQUEST['post_id'];
1870                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1871                         echo wp_json_encode( array(
1872                                 'success' => false,
1873                                 'data'    => array(
1874                                         'message'  => __( "You don't have permission to attach files to this post." ),
1875                                         'filename' => $_FILES['async-upload']['name'],
1876                                 )
1877                         ) );
1878
1879                         wp_die();
1880                 }
1881         } else {
1882                 $post_id = null;
1883         }
1884
1885         $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
1886
1887         // If the context is custom header or background, make sure the uploaded file is an image.
1888         if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
1889                 $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
1890                 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
1891                         echo wp_json_encode( array(
1892                                 'success' => false,
1893                                 'data'    => array(
1894                                         'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
1895                                         'filename' => $_FILES['async-upload']['name'],
1896                                 )
1897                         ) );
1898
1899                         wp_die();
1900                 }
1901         }
1902
1903         $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
1904
1905         if ( is_wp_error( $attachment_id ) ) {
1906                 echo wp_json_encode( array(
1907                         'success' => false,
1908                         'data'    => array(
1909                                 'message'  => $attachment_id->get_error_message(),
1910                                 'filename' => $_FILES['async-upload']['name'],
1911                         )
1912                 ) );
1913
1914                 wp_die();
1915         }
1916
1917         if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
1918                 if ( 'custom-background' === $post_data['context'] )
1919                         update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
1920
1921                 if ( 'custom-header' === $post_data['context'] )
1922                         update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
1923         }
1924
1925         if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
1926                 wp_die();
1927
1928         echo wp_json_encode( array(
1929                 'success' => true,
1930                 'data'    => $attachment,
1931         ) );
1932
1933         wp_die();
1934 }
1935
1936 /**
1937  * Ajax handler for image editing.
1938  *
1939  * @since 3.1.0
1940  */
1941 function wp_ajax_image_editor() {
1942         $attachment_id = intval($_POST['postid']);
1943         if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
1944                 wp_die( -1 );
1945
1946         check_ajax_referer( "image_editor-$attachment_id" );
1947         include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
1948
1949         $msg = false;
1950         switch ( $_POST['do'] ) {
1951                 case 'save' :
1952                         $msg = wp_save_image($attachment_id);
1953                         $msg = wp_json_encode($msg);
1954                         wp_die( $msg );
1955                         break;
1956                 case 'scale' :
1957                         $msg = wp_save_image($attachment_id);
1958                         break;
1959                 case 'restore' :
1960                         $msg = wp_restore_image($attachment_id);
1961                         break;
1962         }
1963
1964         wp_image_editor($attachment_id, $msg);
1965         wp_die();
1966 }
1967
1968 /**
1969  * Ajax handler for setting the featured image.
1970  *
1971  * @since 3.1.0
1972  */
1973 function wp_ajax_set_post_thumbnail() {
1974         $json = ! empty( $_REQUEST['json'] ); // New-style request
1975
1976         $post_ID = intval( $_POST['post_id'] );
1977         if ( ! current_user_can( 'edit_post', $post_ID ) )
1978                 wp_die( -1 );
1979
1980         $thumbnail_id = intval( $_POST['thumbnail_id'] );
1981
1982         if ( $json )
1983                 check_ajax_referer( "update-post_$post_ID" );
1984         else
1985                 check_ajax_referer( "set_post_thumbnail-$post_ID" );
1986
1987         if ( $thumbnail_id == '-1' ) {
1988                 if ( delete_post_thumbnail( $post_ID ) ) {
1989                         $return = _wp_post_thumbnail_html( null, $post_ID );
1990                         $json ? wp_send_json_success( $return ) : wp_die( $return );
1991                 } else {
1992                         wp_die( 0 );
1993                 }
1994         }
1995
1996         if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
1997                 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
1998                 $json ? wp_send_json_success( $return ) : wp_die( $return );
1999         }
2000
2001         wp_die( 0 );
2002 }
2003
2004 /**
2005  * AJAX handler for setting the featured image for an attachment.
2006  *
2007  * @since 4.0.0
2008  *
2009  * @see set_post_thumbnail()
2010  */
2011 function wp_ajax_set_attachment_thumbnail() {
2012         if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2013                 wp_send_json_error();
2014         }
2015
2016         $thumbnail_id = (int) $_POST['thumbnail_id'];
2017         if ( empty( $thumbnail_id ) ) {
2018                 wp_send_json_error();
2019         }
2020
2021         $post_ids = array();
2022         // For each URL, try to find its corresponding post ID.
2023         foreach ( $_POST['urls'] as $url ) {
2024                 $post_id = attachment_url_to_postid( $url );
2025                 if ( ! empty( $post_id ) ) {
2026                         $post_ids[] = $post_id;
2027                 }
2028         }
2029
2030         if ( empty( $post_ids ) ) {
2031                 wp_send_json_error();
2032         }
2033
2034         $success = 0;
2035         // For each found attachment, set its thumbnail.
2036         foreach ( $post_ids as $post_id ) {
2037                 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2038                         continue;
2039                 }
2040
2041                 if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2042                         $success++;
2043                 }
2044         }
2045
2046         if ( 0 === $success ) {
2047                 wp_send_json_error();
2048         } else {
2049                 wp_send_json_success();
2050         }
2051
2052         wp_send_json_error();
2053 }
2054
2055 /**
2056  * Ajax handler for date formatting.
2057  *
2058  * @since 3.1.0
2059  */
2060 function wp_ajax_date_format() {
2061         wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2062 }
2063
2064 /**
2065  * Ajax handler for time formatting.
2066  *
2067  * @since 3.1.0
2068  */
2069 function wp_ajax_time_format() {
2070         wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2071 }
2072
2073 /**
2074  * Ajax handler for saving posts from the fullscreen editor.
2075  *
2076  * @since 3.1.0
2077  */
2078 function wp_ajax_wp_fullscreen_save_post() {
2079         $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2080
2081         $post = null;
2082
2083         if ( $post_id )
2084                 $post = get_post( $post_id );
2085
2086         check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2087
2088         $post_id = edit_post();
2089
2090         if ( is_wp_error( $post_id ) ) {
2091                 wp_send_json_error();
2092         }
2093
2094         if ( $post ) {
2095                 $last_date = mysql2date( get_option('date_format'), $post->post_modified );
2096                 $last_time = mysql2date( get_option('time_format'), $post->post_modified );
2097         } else {
2098                 $last_date = date_i18n( get_option('date_format') );
2099                 $last_time = date_i18n( get_option('time_format') );
2100         }
2101
2102         if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2103                 $last_user = get_userdata( $last_id );
2104                 $last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2105         } else {
2106                 $last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2107         }
2108
2109         wp_send_json_success( array( 'last_edited' => $last_edited ) );
2110 }
2111
2112 /**
2113  * Ajax handler for removing a post lock.
2114  *
2115  * @since 3.1.0
2116  */
2117 function wp_ajax_wp_remove_post_lock() {
2118         if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2119                 wp_die( 0 );
2120         $post_id = (int) $_POST['post_ID'];
2121         if ( ! $post = get_post( $post_id ) )
2122                 wp_die( 0 );
2123
2124         check_ajax_referer( 'update-post_' . $post_id );
2125
2126         if ( ! current_user_can( 'edit_post', $post_id ) )
2127                 wp_die( -1 );
2128
2129         $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2130         if ( $active_lock[1] != get_current_user_id() )
2131                 wp_die( 0 );
2132
2133         /**
2134          * Filter the post lock window duration.
2135          *
2136          * @since 3.3.0
2137          *
2138          * @param int $interval The interval in seconds the post lock duration
2139          *                      should last, plus 5 seconds. Default 150.
2140          */
2141         $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2142         update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2143         wp_die( 1 );
2144 }
2145
2146 /**
2147  * Ajax handler for dismissing a WordPress pointer.
2148  *
2149  * @since 3.1.0
2150  */
2151 function wp_ajax_dismiss_wp_pointer() {
2152         $pointer = $_POST['pointer'];
2153         if ( $pointer != sanitize_key( $pointer ) )
2154                 wp_die( 0 );
2155
2156 //      check_ajax_referer( 'dismiss-pointer_' . $pointer );
2157
2158         $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2159
2160         if ( in_array( $pointer, $dismissed ) )
2161                 wp_die( 0 );
2162
2163         $dismissed[] = $pointer;
2164         $dismissed = implode( ',', $dismissed );
2165
2166         update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2167         wp_die( 1 );
2168 }
2169
2170 /**
2171  * Ajax handler for getting an attachment.
2172  *
2173  * @since 3.5.0
2174  */
2175 function wp_ajax_get_attachment() {
2176         if ( ! isset( $_REQUEST['id'] ) )
2177                 wp_send_json_error();
2178
2179         if ( ! $id = absint( $_REQUEST['id'] ) )
2180                 wp_send_json_error();
2181
2182         if ( ! $post = get_post( $id ) )
2183                 wp_send_json_error();
2184
2185         if ( 'attachment' != $post->post_type )
2186                 wp_send_json_error();
2187
2188         if ( ! current_user_can( 'upload_files' ) )
2189                 wp_send_json_error();
2190
2191         if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2192                 wp_send_json_error();
2193
2194         wp_send_json_success( $attachment );
2195 }
2196
2197 /**
2198  * Ajax handler for querying attachments.
2199  *
2200  * @since 3.5.0
2201  */
2202 function wp_ajax_query_attachments() {
2203         if ( ! current_user_can( 'upload_files' ) )
2204                 wp_send_json_error();
2205
2206         $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2207         $keys = array(
2208                 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2209                 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2210         );
2211         foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2212                 if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2213                         $keys[] = $t->query_var;
2214                 }
2215         }
2216
2217         $query = array_intersect_key( $query, array_flip( $keys ) );
2218         $query['post_type'] = 'attachment';
2219         if ( MEDIA_TRASH
2220                 && ! empty( $_REQUEST['query']['post_status'] )
2221                 && 'trash' === $_REQUEST['query']['post_status'] ) {
2222                 $query['post_status'] = 'trash';
2223         } else {
2224                 $query['post_status'] = 'inherit';
2225         }
2226
2227         if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2228                 $query['post_status'] .= ',private';
2229
2230         /**
2231          * Filter the arguments passed to WP_Query during an AJAX
2232          * call for querying attachments.
2233          *
2234          * @since 3.7.0
2235          *
2236          * @see WP_Query::parse_query()
2237          *
2238          * @param array $query An array of query variables.
2239          */
2240         $query = apply_filters( 'ajax_query_attachments_args', $query );
2241         $query = new WP_Query( $query );
2242
2243         $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2244         $posts = array_filter( $posts );
2245
2246         wp_send_json_success( $posts );
2247 }
2248
2249 /**
2250  * Ajax handler for updating attachment attributes.
2251  *
2252  * @since 3.5.0
2253  */
2254 function wp_ajax_save_attachment() {
2255         if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2256                 wp_send_json_error();
2257
2258         if ( ! $id = absint( $_REQUEST['id'] ) )
2259                 wp_send_json_error();
2260
2261         check_ajax_referer( 'update-post_' . $id, 'nonce' );
2262
2263         if ( ! current_user_can( 'edit_post', $id ) )
2264                 wp_send_json_error();
2265
2266         $changes = $_REQUEST['changes'];
2267         $post    = get_post( $id, ARRAY_A );
2268
2269         if ( 'attachment' != $post['post_type'] )
2270                 wp_send_json_error();
2271
2272         if ( isset( $changes['parent'] ) )
2273                 $post['post_parent'] = $changes['parent'];
2274
2275         if ( isset( $changes['title'] ) )
2276                 $post['post_title'] = $changes['title'];
2277
2278         if ( isset( $changes['caption'] ) )
2279                 $post['post_excerpt'] = $changes['caption'];
2280
2281         if ( isset( $changes['description'] ) )
2282                 $post['post_content'] = $changes['description'];
2283
2284         if ( MEDIA_TRASH && isset( $changes['status'] ) )
2285                 $post['post_status'] = $changes['status'];
2286
2287         if ( isset( $changes['alt'] ) ) {
2288                 $alt = wp_unslash( $changes['alt'] );
2289                 if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2290                         $alt = wp_strip_all_tags( $alt, true );
2291                         update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2292                 }
2293         }
2294
2295         if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2296                 $changed = false;
2297                 $id3data = wp_get_attachment_metadata( $post['ID'] );
2298                 if ( ! is_array( $id3data ) ) {
2299                         $changed = true;
2300                         $id3data = array();
2301                 }
2302                 foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2303                         if ( isset( $changes[ $key ] ) ) {
2304                                 $changed = true;
2305                                 $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2306                         }
2307                 }
2308
2309                 if ( $changed ) {
2310                         wp_update_attachment_metadata( $id, $id3data );
2311                 }
2312         }
2313
2314         if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2315                 wp_delete_post( $id );
2316         } else {
2317                 wp_update_post( $post );
2318         }
2319
2320         wp_send_json_success();
2321 }
2322
2323 /**
2324  * Ajax handler for saving backwards compatible attachment attributes.
2325  *
2326  * @since 3.5.0
2327  */
2328 function wp_ajax_save_attachment_compat() {
2329         if ( ! isset( $_REQUEST['id'] ) )
2330                 wp_send_json_error();
2331
2332         if ( ! $id = absint( $_REQUEST['id'] ) )
2333                 wp_send_json_error();
2334
2335         if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2336                 wp_send_json_error();
2337         $attachment_data = $_REQUEST['attachments'][ $id ];
2338
2339         check_ajax_referer( 'update-post_' . $id, 'nonce' );
2340
2341         if ( ! current_user_can( 'edit_post', $id ) )
2342                 wp_send_json_error();
2343
2344         $post = get_post( $id, ARRAY_A );
2345
2346         if ( 'attachment' != $post['post_type'] )
2347                 wp_send_json_error();
2348
2349         /** This filter is documented in wp-admin/includes/media.php */
2350         $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2351
2352         if ( isset( $post['errors'] ) ) {
2353                 $errors = $post['errors']; // @todo return me and display me!
2354                 unset( $post['errors'] );
2355         }
2356
2357         wp_update_post( $post );
2358
2359         foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2360                 if ( isset( $attachment_data[ $taxonomy ] ) )
2361                         wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2362         }
2363
2364         if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2365                 wp_send_json_error();
2366
2367         wp_send_json_success( $attachment );
2368 }
2369
2370 /**
2371  * Ajax handler for saving the attachment order.
2372  *
2373  * @since 3.5.0
2374  */
2375 function wp_ajax_save_attachment_order() {
2376         if ( ! isset( $_REQUEST['post_id'] ) )
2377                 wp_send_json_error();
2378
2379         if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2380                 wp_send_json_error();
2381
2382         if ( empty( $_REQUEST['attachments'] ) )
2383                 wp_send_json_error();
2384
2385         check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2386
2387         $attachments = $_REQUEST['attachments'];
2388
2389         if ( ! current_user_can( 'edit_post', $post_id ) )
2390                 wp_send_json_error();
2391
2392         foreach ( $attachments as $attachment_id => $menu_order ) {
2393                 if ( ! current_user_can( 'edit_post', $attachment_id ) )
2394                         continue;
2395                 if ( ! $attachment = get_post( $attachment_id ) )
2396                         continue;
2397                 if ( 'attachment' != $attachment->post_type )
2398                         continue;
2399
2400                 wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2401         }
2402
2403         wp_send_json_success();
2404 }
2405
2406 /**
2407  * Ajax handler for sending an attachment to the editor.
2408  *
2409  * Generates the HTML to send an attachment to the editor.
2410  * Backwards compatible with the media_send_to_editor filter
2411  * and the chain of filters that follow.
2412  *
2413  * @since 3.5.0
2414  */
2415 function wp_ajax_send_attachment_to_editor() {
2416         check_ajax_referer( 'media-send-to-editor', 'nonce' );
2417
2418         $attachment = wp_unslash( $_POST['attachment'] );
2419
2420         $id = intval( $attachment['id'] );
2421
2422         if ( ! $post = get_post( $id ) )
2423                 wp_send_json_error();
2424
2425         if ( 'attachment' != $post->post_type )
2426                 wp_send_json_error();
2427
2428         if ( current_user_can( 'edit_post', $id ) ) {
2429                 // If this attachment is unattached, attach it. Primarily a back compat thing.
2430                 if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2431                         wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2432                 }
2433         }
2434
2435         $rel = $url = '';
2436         $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2437         if ( ! empty( $attachment['url'] ) ) {
2438                 $url = $attachment['url'];
2439                 if ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url )
2440                         $rel = ' rel="attachment wp-att-' . $id . '"';
2441                 $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2442         }
2443
2444         remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2445
2446         if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2447                 $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2448                 $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2449                 $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2450
2451                 // No whitespace-only captions.
2452                 $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2453                 if ( '' === trim( $caption ) ) {
2454                         $caption = '';
2455                 }
2456
2457                 $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2458                 $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt );
2459         } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post )  ) {
2460                 $html = stripslashes_deep( $_POST['html'] );
2461         }
2462
2463         /** This filter is documented in wp-admin/includes/media.php */
2464         $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2465
2466         wp_send_json_success( $html );
2467 }
2468
2469 /**
2470  * Ajax handler for sending a link to the editor.
2471  *
2472  * Generates the HTML to send a non-image embed link to the editor.
2473  *
2474  * Backwards compatible with the following filters:
2475  * - file_send_to_editor_url
2476  * - audio_send_to_editor_url
2477  * - video_send_to_editor_url
2478  *
2479  * @since 3.5.0
2480  */
2481 function wp_ajax_send_link_to_editor() {
2482         global $post, $wp_embed;
2483
2484         check_ajax_referer( 'media-send-to-editor', 'nonce' );
2485
2486         if ( ! $src = wp_unslash( $_POST['src'] ) )
2487                 wp_send_json_error();
2488
2489         if ( ! strpos( $src, '://' ) )
2490                 $src = 'http://' . $src;
2491
2492         if ( ! $src = esc_url_raw( $src ) )
2493                 wp_send_json_error();
2494
2495         if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2496                 $link_text = wp_basename( $src );
2497
2498         $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2499
2500         // Ping WordPress for an embed.
2501         $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2502
2503         // Fallback that WordPress creates when no oEmbed was found.
2504         $fallback = $wp_embed->maybe_make_link( $src );
2505
2506         if ( $check_embed !== $fallback ) {
2507                 // TinyMCE view for [embed] will parse this
2508                 $html = '[embed]' . $src . '[/embed]';
2509         } elseif ( $link_text ) {
2510                 $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2511         } else {
2512                 $html = '';
2513         }
2514
2515         // Figure out what filter to run:
2516         $type = 'file';
2517         if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2518                 && ( 'audio' == $ext_type || 'video' == $ext_type ) )
2519                         $type = $ext_type;
2520
2521         /** This filter is documented in wp-admin/includes/media.php */
2522         $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
2523
2524         wp_send_json_success( $html );
2525 }
2526
2527 /**
2528  * Ajax handler for the Heartbeat API.
2529  *
2530  * Runs when the user is logged in.
2531  *
2532  * @since 3.6.0
2533  */
2534 function wp_ajax_heartbeat() {
2535         if ( empty( $_POST['_nonce'] ) )
2536                 wp_send_json_error();
2537
2538         $response = array();
2539
2540         if ( false === wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' ) ) {
2541                 // User is logged in but nonces have expired.
2542                 $response['nonces_expired'] = true;
2543                 wp_send_json($response);
2544         }
2545
2546         // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2547         if ( ! empty($_POST['screen_id']) )
2548                 $screen_id = sanitize_key($_POST['screen_id']);
2549         else
2550                 $screen_id = 'front';
2551
2552         if ( ! empty($_POST['data']) ) {
2553                 $data = wp_unslash( (array) $_POST['data'] );
2554
2555                 /**
2556                  * Filter the Heartbeat response received.
2557                  *
2558                  * @since 3.6.0
2559                  *
2560                  * @param array|object $response  The Heartbeat response object or array.
2561                  * @param array        $data      The $_POST data sent.
2562                  * @param string       $screen_id The screen id.
2563                  */
2564                 $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2565         }
2566
2567         /**
2568          * Filter the Heartbeat response sent.
2569          *
2570          * @since 3.6.0
2571          *
2572          * @param array|object $response  The Heartbeat response object or array.
2573          * @param string       $screen_id The screen id.
2574          */
2575         $response = apply_filters( 'heartbeat_send', $response, $screen_id );
2576
2577         /**
2578          * Fires when Heartbeat ticks in logged-in environments.
2579          *
2580          * Allows the transport to be easily replaced with long-polling.
2581          *
2582          * @since 3.6.0
2583          *
2584          * @param array|object $response  The Heartbeat response object or array.
2585          * @param string       $screen_id The screen id.
2586          */
2587         do_action( 'heartbeat_tick', $response, $screen_id );
2588
2589         // Send the current time according to the server
2590         $response['server_time'] = time();
2591
2592         wp_send_json($response);
2593 }
2594
2595 /**
2596  * Ajax handler for getting revision diffs.
2597  *
2598  * @since 3.6.0
2599  */
2600 function wp_ajax_get_revision_diffs() {
2601         require ABSPATH . 'wp-admin/includes/revision.php';
2602
2603         if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2604                 wp_send_json_error();
2605
2606         if ( ! current_user_can( 'read_post', $post->ID ) )
2607                 wp_send_json_error();
2608
2609         // Really just pre-loading the cache here.
2610         if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2611                 wp_send_json_error();
2612
2613         $return = array();
2614         @set_time_limit( 0 );
2615
2616         foreach ( $_REQUEST['compare'] as $compare_key ) {
2617                 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2618
2619                 $return[] = array(
2620                         'id' => $compare_key,
2621                         'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2622                 );
2623         }
2624         wp_send_json_success( $return );
2625 }
2626
2627 /**
2628  * Ajax handler for auto-saving the selected color scheme for
2629  * a user's own profile.
2630  *
2631  * @since 3.8.0
2632  */
2633 function wp_ajax_save_user_color_scheme() {
2634         global $_wp_admin_css_colors;
2635
2636         check_ajax_referer( 'save-color-scheme', 'nonce' );
2637
2638         $color_scheme = sanitize_key( $_POST['color_scheme'] );
2639
2640         if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2641                 wp_send_json_error();
2642         }
2643
2644         $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2645         update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2646
2647         wp_send_json_success( array(
2648                 'previousScheme' => 'admin-color-' . $previous_color_scheme,
2649                 'currentScheme'  => 'admin-color-' . $color_scheme
2650         ) );
2651 }
2652
2653 /**
2654  * Ajax handler for getting themes from themes_api().
2655  *
2656  * @since 3.9.0
2657  */
2658 function wp_ajax_query_themes() {
2659         global $themes_allowedtags, $theme_field_defaults;
2660
2661         if ( ! current_user_can( 'install_themes' ) ) {
2662                 wp_send_json_error();
2663         }
2664
2665         $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2666                 'per_page' => 20,
2667                 'fields'   => $theme_field_defaults
2668         ) );
2669
2670         $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2671
2672         /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2673         $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2674
2675         $api = themes_api( 'query_themes', $args );
2676
2677         if ( is_wp_error( $api ) ) {
2678                 wp_send_json_error();
2679         }
2680
2681         $update_php = network_admin_url( 'update.php?action=install-theme' );
2682         foreach ( $api->themes as &$theme ) {
2683                 $theme->install_url = add_query_arg( array(
2684                         'theme'    => $theme->slug,
2685                         '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2686                 ), $update_php );
2687
2688                 $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
2689                 $theme->author      = wp_kses( $theme->author, $themes_allowedtags );
2690                 $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
2691                 $theme->description = wp_kses( $theme->description, $themes_allowedtags );
2692                 $theme->num_ratings = sprintf( _n( '(based on %s rating)', '(based on %s ratings)', $theme->num_ratings ), number_format_i18n( $theme->num_ratings ) );
2693                 $theme->preview_url = set_url_scheme( $theme->preview_url );
2694         }
2695
2696         wp_send_json_success( $api );
2697 }
2698
2699 /**
2700  * Apply [embed] AJAX handlers to a string.
2701  *
2702  * @since 4.0.0
2703  *
2704  * @global WP_Post  $post     Global $post.
2705  * @global WP_Embed $wp_embed Embed API instance.
2706  */
2707 function wp_ajax_parse_embed() {
2708         global $post, $wp_embed;
2709
2710         if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2711                 wp_send_json_error();
2712         }
2713
2714         if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
2715                 wp_send_json_error();
2716         }
2717
2718         $shortcode = wp_unslash( $_POST['shortcode'] );
2719         $url = str_replace( '[embed]', '', str_replace( '[/embed]', '', $shortcode ) );
2720
2721         $parsed = false;
2722         setup_postdata( $post );
2723
2724         $wp_embed->return_false_on_fail = true;
2725
2726         if ( is_ssl() && preg_match( '%^\\[embed[^\\]]*\\]http://%i', $shortcode ) ) {
2727                 // Admin is ssl and the user pasted non-ssl URL.
2728                 // Check if the provider supports ssl embeds and use that for the preview.
2729                 $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
2730                 $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
2731
2732                 if ( ! $parsed ) {
2733                         $no_ssl_support = true;
2734                 }
2735         }
2736
2737         if ( ! $parsed ) {
2738                 $parsed = $wp_embed->run_shortcode( $shortcode );
2739         }
2740
2741         if ( ! $parsed ) {
2742                 wp_send_json_error( array(
2743                         'type' => 'not-embeddable',
2744                         'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
2745                 ) );
2746         }
2747
2748         if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
2749                 $styles = '';
2750                 $mce_styles = wpview_media_sandbox_styles();
2751                 foreach ( $mce_styles as $style ) {
2752                         $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
2753                 }
2754
2755                 $html = do_shortcode( $parsed );
2756
2757                 global $wp_scripts;
2758                 if ( ! empty( $wp_scripts ) ) {
2759                         $wp_scripts->done = array();
2760                 }
2761                 ob_start();
2762                 wp_print_scripts( 'wp-mediaelement' );
2763                 $scripts = ob_get_clean();
2764
2765                 $parsed = $styles . $html . $scripts;
2766         }
2767
2768
2769         if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
2770                 preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
2771                 // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
2772                 wp_send_json_error( array(
2773                         'type' => 'not-ssl',
2774                         'message' => __( 'This preview is unavailable in the editor.' ),
2775                 ) );
2776         }
2777
2778         wp_send_json_success( array(
2779                 'body' => $parsed
2780         ) );
2781 }
2782
2783 function wp_ajax_parse_media_shortcode() {
2784         global $post, $wp_scripts;
2785
2786         if ( empty( $_POST['shortcode'] ) ) {
2787                 wp_send_json_error();
2788         }
2789
2790         $shortcode = wp_unslash( $_POST['shortcode'] );
2791
2792         if ( ! empty( $_POST['post_ID'] ) ) {
2793                 $post = get_post( (int) $_POST['post_ID'] );
2794         }
2795
2796         // the embed shortcode requires a post
2797         if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
2798                 if ( 'embed' === $shortcode ) {
2799                         wp_send_json_error();
2800                 }
2801         } else {
2802                 setup_postdata( $post );
2803         }
2804
2805         $parsed = do_shortcode( $shortcode  );
2806
2807         if ( empty( $parsed ) ) {
2808                 wp_send_json_error( array(
2809                         'type' => 'no-items',
2810                         'message' => __( 'No items found.' ),
2811                 ) );
2812         }
2813
2814         $head = '';
2815         $styles = wpview_media_sandbox_styles();
2816
2817         foreach ( $styles as $style ) {
2818                 $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
2819         }
2820
2821         if ( ! empty( $wp_scripts ) ) {
2822                 $wp_scripts->done = array();
2823         }
2824
2825         ob_start();
2826
2827         echo $parsed;
2828
2829         if ( 'playlist' === $_REQUEST['type'] ) {
2830                 wp_underscore_playlist_templates();
2831
2832                 wp_print_scripts( 'wp-playlist' );
2833         } else {
2834                 wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
2835         }
2836
2837         wp_send_json_success( array(
2838                 'head' => $head,
2839                 'body' => ob_get_clean()
2840         ) );
2841 }
2842
2843 /**
2844  * AJAX handler for destroying multiple open sessions for a user.
2845  *
2846  * @since 4.1.0
2847  */
2848 function wp_ajax_destroy_sessions() {
2849
2850         $user = get_userdata( (int) $_POST['user_id'] );
2851         if ( $user ) {
2852                 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2853                         $user = false;
2854                 } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
2855                         $user = false;
2856                 }
2857         }
2858
2859         if ( ! $user ) {
2860                 wp_send_json_error( array(
2861                         'message' => __( 'Could not log out user sessions. Please try again.' ),
2862                 ) );
2863         }
2864
2865         $sessions = WP_Session_Tokens::get_instance( $user->ID );
2866
2867         if ( $user->ID === get_current_user_id() ) {
2868                 $sessions->destroy_others( wp_get_session_token() );
2869                 $message = __( 'You are now logged out everywhere else.' );
2870         } else {
2871                 $sessions->destroy_all();
2872                 /* translators: 1: User's display name. */
2873                 $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
2874         }
2875
2876         wp_send_json_success( array( 'message' => $message ) );
2877 }
2878
2879
2880 /**
2881  * AJAX handler for updating a plugin.
2882  *
2883  * @since 4.2.0
2884  *
2885  * @see Plugin_Upgrader
2886  */
2887 function wp_ajax_update_plugin() {
2888         global $wp_filesystem;
2889
2890         $plugin = urldecode( $_POST['plugin'] );
2891
2892         $status = array(
2893                 'update'     => 'plugin',
2894                 'plugin'     => $plugin,
2895                 'slug'       => sanitize_key( $_POST['slug'] ),
2896                 'oldVersion' => '',
2897                 'newVersion' => '',
2898         );
2899
2900         $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
2901         if ( $plugin_data['Version'] ) {
2902                 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
2903         }
2904
2905         if ( ! current_user_can( 'update_plugins' ) ) {
2906                 $status['error'] = __( 'You do not have sufficient permissions to update plugins on this site.' );
2907                 wp_send_json_error( $status );
2908         }
2909
2910         check_ajax_referer( 'updates' );
2911
2912         include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
2913
2914         wp_update_plugins();
2915
2916         $skin = new Automatic_Upgrader_Skin();
2917         $upgrader = new Plugin_Upgrader( $skin );
2918         $result = $upgrader->bulk_upgrade( array( $plugin ) );
2919
2920         if ( is_array( $result ) && empty( $result[$plugin] ) && is_wp_error( $skin->result ) ) {
2921                 $result = $skin->result;
2922         }
2923
2924         if ( is_array( $result ) && !empty( $result[ $plugin ] ) ) {
2925                 $plugin_update_data = current( $result );
2926
2927                 /*
2928                  * If the `update_plugins` site transient is empty (e.g. when you update
2929                  * two plugins in quick succession before the transient repopulates),
2930                  * this may be the return.
2931                  *
2932                  * Preferably something can be done to ensure `update_plugins` isn't empty.
2933                  * For now, surface some sort of error here.
2934                  */
2935                 if ( $plugin_update_data === true ) {
2936                         wp_send_json_error( $status );
2937                 }
2938
2939                 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
2940                 $plugin_data = reset( $plugin_data );
2941
2942                 if ( $plugin_data['Version'] ) {
2943                         $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
2944                 }
2945
2946                 wp_send_json_success( $status );
2947         } else if ( is_wp_error( $result ) ) {
2948                 $status['error'] = $result->get_error_message();
2949                 wp_send_json_error( $status );
2950
2951         } else if ( is_bool( $result ) && ! $result ) {
2952                 $status['errorCode'] = 'unable_to_connect_to_filesystem';
2953                 $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
2954
2955                 // Pass through the error from WP_Filesystem if one was raised
2956                 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
2957                         $status['error'] = $wp_filesystem->errors->get_error_message();
2958                 }
2959
2960                 wp_send_json_error( $status );
2961
2962         }
2963 }
2964
2965 /**
2966  * AJAX handler for saving a post from Press This.
2967  *
2968  * @since 4.2.0
2969  */
2970 function wp_ajax_press_this_save_post() {
2971         if ( empty( $GLOBALS['wp_press_this'] ) ) {
2972                 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
2973         }
2974
2975         $GLOBALS['wp_press_this']->save_post();
2976 }
2977
2978 /**
2979  * AJAX handler for creating new category from Press This.
2980  *
2981  * @since 4.2.0
2982  */
2983 function wp_ajax_press_this_add_category() {
2984         if ( empty( $GLOBALS['wp_press_this'] ) ) {
2985                 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
2986         }
2987
2988         $GLOBALS['wp_press_this']->add_category();
2989 }