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