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