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