3 * WordPress Core Ajax Handlers.
6 * @subpackage Administration
10 // No-privilege Ajax handlers.
14 * Ajax handler for the Heartbeat API in
15 * the no-privilege context.
17 * Runs when the user is not logged in.
21 function wp_ajax_nopriv_heartbeat() {
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']);
30 if ( ! empty($_POST['data']) ) {
31 $data = wp_unslash( (array) $_POST['data'] );
34 * Filter Heartbeat AJAX response in no-privilege environments.
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.
42 $response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id );
46 * Filter Heartbeat AJAX response when no data is passed.
50 * @param array|object $response The Heartbeat response object or array.
51 * @param string $screen_id The screen id.
53 $response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id );
56 * Fires when Heartbeat ticks in no-privilege environments.
58 * Allows the transport to be easily replaced with long-polling.
62 * @param array|object $response The no-priv Heartbeat response.
63 * @param string $screen_id The screen id.
65 do_action( 'heartbeat_nopriv_tick', $response, $screen_id );
67 // Send the current time according to the server.
68 $response['server_time'] = time();
70 wp_send_json($response);
74 // GET-based Ajax handlers.
78 * Ajax handler for fetching a list table.
82 function wp_ajax_fetch_list() {
83 global $wp_list_table;
85 $list_class = $_GET['list_args']['class'];
86 check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
88 $wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
89 if ( ! $wp_list_table )
92 if ( ! $wp_list_table->ajax_user_can() )
95 $wp_list_table->ajax_response();
101 * Ajax handler for tag search.
105 function wp_ajax_ajax_tag_search() {
106 if ( ! isset( $_GET['tax'] ) ) {
110 $taxonomy = sanitize_key( $_GET['tax'] );
111 $tax = get_taxonomy( $taxonomy );
116 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
120 $s = wp_unslash( $_GET['q'] );
122 $comma = _x( ',', 'tag delimiter' );
123 if ( ',' !== $comma )
124 $s = str_replace( $comma, ',', $s );
125 if ( false !== strpos( $s, ',' ) ) {
126 $s = explode( ',', $s );
127 $s = $s[count( $s ) - 1];
132 * Filter the minimum number of characters required to fire a tag search via AJAX.
136 * @param int $characters The minimum number of characters required. Default 2.
137 * @param object $tax The taxonomy object.
138 * @param string $s The search term.
140 $term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
143 * Require $term_search_min_chars chars for matching (default: 2)
144 * ensure it's a non-negative, non-zero integer.
146 if ( ( $term_search_min_chars == 0 ) || ( strlen( $s ) < $term_search_min_chars ) ){
150 $results = get_terms( $taxonomy, array( 'name__like' => $s, 'fields' => 'names', 'hide_empty' => false ) );
152 echo join( $results, "\n" );
157 * Ajax handler for compression testing.
161 function wp_ajax_wp_compression_test() {
162 if ( !current_user_can( 'manage_options' ) )
165 if ( ini_get('zlib.output_compression') || 'ob_gzhandler' == ini_get('output_handler') ) {
166 update_site_option('can_compress_scripts', 0);
170 if ( isset($_GET['test']) ) {
171 header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
172 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
173 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
174 header( 'Pragma: no-cache' );
175 header('Content-Type: application/javascript; charset=UTF-8');
176 $force_gzip = ( defined('ENFORCE_GZIP') && ENFORCE_GZIP );
177 $test_str = '"wpCompressionTest Lorem ipsum dolor sit amet consectetuer mollis sapien urna ut a. Eu nonummy condimentum fringilla tempor pretium platea vel nibh netus Maecenas. Hac molestie amet justo quis pellentesque est ultrices interdum nibh Morbi. Cras mattis pretium Phasellus ante ipsum ipsum ut sociis Suspendisse Lorem. Ante et non molestie. Porta urna Vestibulum egestas id congue nibh eu risus gravida sit. Ac augue auctor Ut et non a elit massa id sodales. Elit eu Nulla at nibh adipiscing mattis lacus mauris at tempus. Netus nibh quis suscipit nec feugiat eget sed lorem et urna. Pellentesque lacus at ut massa consectetuer ligula ut auctor semper Pellentesque. Ut metus massa nibh quam Curabitur molestie nec mauris congue. Volutpat molestie elit justo facilisis neque ac risus Ut nascetur tristique. Vitae sit lorem tellus et quis Phasellus lacus tincidunt nunc Fusce. Pharetra wisi Suspendisse mus sagittis libero lacinia Integer consequat ac Phasellus. Et urna ac cursus tortor aliquam Aliquam amet tellus volutpat Vestibulum. Justo interdum condimentum In augue congue tellus sollicitudin Quisque quis nibh."';
179 if ( 1 == $_GET['test'] ) {
182 } elseif ( 2 == $_GET['test'] ) {
183 if ( !isset($_SERVER['HTTP_ACCEPT_ENCODING']) )
185 if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') && function_exists('gzdeflate') && ! $force_gzip ) {
186 header('Content-Encoding: deflate');
187 $out = gzdeflate( $test_str, 1 );
188 } elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzencode') ) {
189 header('Content-Encoding: gzip');
190 $out = gzencode( $test_str, 1 );
196 } elseif ( 'no' == $_GET['test'] ) {
197 update_site_option('can_compress_scripts', 0);
198 } elseif ( 'yes' == $_GET['test'] ) {
199 update_site_option('can_compress_scripts', 1);
207 * Ajax handler for image editor previews.
211 function wp_ajax_imgedit_preview() {
212 $post_id = intval($_GET['postid']);
213 if ( empty($post_id) || !current_user_can('edit_post', $post_id) )
216 check_ajax_referer( "image_editor-$post_id" );
218 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
219 if ( ! stream_preview_image($post_id) )
226 * Ajax handler for oEmbed caching.
230 function wp_ajax_oembed_cache() {
231 $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
236 * Ajax handler for user autocomplete.
240 function wp_ajax_autocomplete_user() {
241 if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) )
244 /** This filter is documented in wp-admin/user-new.php */
245 if ( ! is_super_admin() && ! apply_filters( 'autocomplete_users_for_site_admins', false ) )
250 // Check the type of request
251 // Current allowed values are `add` and `search`
252 if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
253 $type = $_REQUEST['autocomplete_type'];
258 // Check the desired field for value
259 // Current allowed values are `user_email` and `user_login`
260 if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
261 $field = $_REQUEST['autocomplete_field'];
263 $field = 'user_login';
266 // Exclude current users of this blog
267 if ( isset( $_REQUEST['site_id'] ) ) {
268 $id = absint( $_REQUEST['site_id'] );
270 $id = get_current_blog_id();
273 $include_blog_users = ( $type == 'search' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
274 $exclude_blog_users = ( $type == 'add' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
276 $users = get_users( array(
278 'search' => '*' . $_REQUEST['term'] . '*',
279 'include' => $include_blog_users,
280 'exclude' => $exclude_blog_users,
281 'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
284 foreach ( $users as $user ) {
286 /* translators: 1: user_login, 2: user_email */
287 'label' => sprintf( __( '%1$s (%2$s)' ), $user->user_login, $user->user_email ),
288 'value' => $user->$field,
292 wp_die( wp_json_encode( $return ) );
296 * Ajax handler for dashboard widgets.
300 function wp_ajax_dashboard_widgets() {
301 require_once ABSPATH . 'wp-admin/includes/dashboard.php';
303 $pagenow = $_GET['pagenow'];
304 if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
305 set_current_screen( $pagenow );
308 switch ( $_GET['widget'] ) {
309 case 'dashboard_primary' :
310 wp_dashboard_primary();
317 * Ajax handler for Customizer preview logged-in status.
321 function wp_ajax_logged_in() {
330 * Sends back current comment total and new page links if they need to be updated.
332 * Contrary to normal success AJAX response ("1"), die with time() on success.
336 * @param int $comment_id
339 function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
340 $total = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
341 $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
342 $page = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
343 $url = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : '';
345 // JS didn't send us everything we need to know. Just die with success message
346 if ( !$total || !$per_page || !$page || !$url )
353 // Only do the expensive stuff on a page-break, and about 1 other time per page
354 if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
356 $status = 'total_comments'; // What type of comment count are we looking for?
357 $parsed = parse_url( $url );
358 if ( isset( $parsed['query'] ) ) {
359 parse_str( $parsed['query'], $query_vars );
360 if ( !empty( $query_vars['comment_status'] ) )
361 $status = $query_vars['comment_status'];
362 if ( !empty( $query_vars['p'] ) )
363 $post_id = (int) $query_vars['p'];
366 $comment_count = wp_count_comments($post_id);
368 // We're looking for a known type of comment count.
369 if ( isset( $comment_count->$status ) )
370 $total = $comment_count->$status;
371 // Else use the decremented value from above.
374 // The time since the last comment count.
377 $x = new WP_Ajax_Response( array(
379 // Here for completeness - not used.
381 'supplemental' => array(
382 'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
383 'total_pages' => ceil( $total / $per_page ),
384 'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
393 // POST-based Ajax handlers.
397 * Ajax handler for adding a hierarchical term.
401 function _wp_ajax_add_hierarchical_term() {
402 $action = $_POST['action'];
403 $taxonomy = get_taxonomy(substr($action, 4));
404 check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
405 if ( !current_user_can( $taxonomy->cap->edit_terms ) )
407 $names = explode(',', $_POST['new'.$taxonomy->name]);
408 $parent = isset($_POST['new'.$taxonomy->name.'_parent']) ? (int) $_POST['new'.$taxonomy->name.'_parent'] : 0;
411 if ( $taxonomy->name == 'category' )
412 $post_category = isset($_POST['post_category']) ? (array) $_POST['post_category'] : array();
414 $post_category = ( isset($_POST['tax_input']) && isset($_POST['tax_input'][$taxonomy->name]) ) ? (array) $_POST['tax_input'][$taxonomy->name] : array();
415 $checked_categories = array_map( 'absint', (array) $post_category );
416 $popular_ids = wp_popular_terms_checklist($taxonomy->name, 0, 10, false);
418 foreach ( $names as $cat_name ) {
419 $cat_name = trim($cat_name);
420 $category_nicename = sanitize_title($cat_name);
421 if ( '' === $category_nicename )
423 if ( !$cat_id = term_exists( $cat_name, $taxonomy->name, $parent ) )
424 $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
425 if ( is_wp_error( $cat_id ) ) {
427 } elseif ( is_array( $cat_id ) ) {
428 $cat_id = $cat_id['term_id'];
430 $checked_categories[] = $cat_id;
431 if ( $parent ) // Do these all at once in a second
434 wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids ));
435 $data = ob_get_contents();
438 'what' => $taxonomy->name,
440 'data' => str_replace( array("\n", "\t"), '', $data),
445 if ( $parent ) { // Foncy - replace the parent and all its children
446 $parent = get_term( $parent, $taxonomy->name );
447 $term_id = $parent->term_id;
449 while ( $parent->parent ) { // get the top parent
450 $parent = get_term( $parent->parent, $taxonomy->name );
451 if ( is_wp_error( $parent ) )
453 $term_id = $parent->term_id;
457 wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids));
458 $data = ob_get_contents();
461 'what' => $taxonomy->name,
463 'data' => str_replace( array("\n", "\t"), '', $data),
469 wp_dropdown_categories( array(
470 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name',
471 'hierarchical' => 1, 'show_option_none' => '— '.$taxonomy->labels->parent_item.' —'
473 $sup = ob_get_contents();
475 $add['supplemental'] = array( 'newcat_parent' => $sup );
477 $x = new WP_Ajax_Response( $add );
482 * Ajax handler for deleting a comment.
486 function wp_ajax_delete_comment() {
487 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
489 if ( !$comment = get_comment( $id ) )
491 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
494 check_ajax_referer( "delete-comment_$id" );
495 $status = wp_get_comment_status( $comment->comment_ID );
498 if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) {
499 if ( 'trash' == $status )
501 $r = wp_trash_comment( $comment->comment_ID );
502 } elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) {
503 if ( 'trash' != $status )
505 $r = wp_untrash_comment( $comment->comment_ID );
506 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash
508 } elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) {
509 if ( 'spam' == $status )
511 $r = wp_spam_comment( $comment->comment_ID );
512 } elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) {
513 if ( 'spam' != $status )
515 $r = wp_unspam_comment( $comment->comment_ID );
516 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam
518 } elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) {
519 $r = wp_delete_comment( $comment->comment_ID );
524 if ( $r ) // Decide if we need to send back '1' or a more complicated response including page links and comment counts
525 _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
530 * Ajax handler for deleting a tag.
534 function wp_ajax_delete_tag() {
535 $tag_id = (int) $_POST['tag_ID'];
536 check_ajax_referer( "delete-tag_$tag_id" );
538 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
539 $tax = get_taxonomy($taxonomy);
541 if ( !current_user_can( $tax->cap->delete_terms ) )
544 $tag = get_term( $tag_id, $taxonomy );
545 if ( !$tag || is_wp_error( $tag ) )
548 if ( wp_delete_term($tag_id, $taxonomy))
555 * Ajax handler for deleting a link.
559 function wp_ajax_delete_link() {
560 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
562 check_ajax_referer( "delete-bookmark_$id" );
563 if ( !current_user_can( 'manage_links' ) )
566 $link = get_bookmark( $id );
567 if ( !$link || is_wp_error( $link ) )
570 if ( wp_delete_link( $id ) )
577 * Ajax handler for deleting meta.
581 function wp_ajax_delete_meta() {
582 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
584 check_ajax_referer( "delete-meta_$id" );
585 if ( !$meta = get_metadata_by_mid( 'post', $id ) )
588 if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) )
590 if ( delete_meta( $meta->meta_id ) )
596 * Ajax handler for deleting a post.
600 * @param string $action Action to perform.
602 function wp_ajax_delete_post( $action ) {
603 if ( empty( $action ) )
604 $action = 'delete-post';
605 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
607 check_ajax_referer( "{$action}_$id" );
608 if ( !current_user_can( 'delete_post', $id ) )
611 if ( !get_post( $id ) )
614 if ( wp_delete_post( $id ) )
621 * Ajax handler for sending a post to the trash.
625 * @param string $action Action to perform.
627 function wp_ajax_trash_post( $action ) {
628 if ( empty( $action ) )
629 $action = 'trash-post';
630 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
632 check_ajax_referer( "{$action}_$id" );
633 if ( !current_user_can( 'delete_post', $id ) )
636 if ( !get_post( $id ) )
639 if ( 'trash-post' == $action )
640 $done = wp_trash_post( $id );
642 $done = wp_untrash_post( $id );
651 * Ajax handler to restore a post from the trash.
655 * @param string $action Action to perform.
657 function wp_ajax_untrash_post( $action ) {
658 if ( empty( $action ) )
659 $action = 'untrash-post';
660 wp_ajax_trash_post( $action );
663 function wp_ajax_delete_page( $action ) {
664 if ( empty( $action ) )
665 $action = 'delete-page';
666 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
668 check_ajax_referer( "{$action}_$id" );
669 if ( !current_user_can( 'delete_page', $id ) )
672 if ( ! get_post( $id ) )
675 if ( wp_delete_post( $id ) )
682 * Ajax handler to dim a comment.
686 function wp_ajax_dim_comment() {
687 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
689 if ( !$comment = get_comment( $id ) ) {
690 $x = new WP_Ajax_Response( array(
692 'id' => new WP_Error('invalid_comment', sprintf(__('Comment %d does not exist'), $id))
697 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) )
700 $current = wp_get_comment_status( $comment->comment_ID );
701 if ( isset( $_POST['new'] ) && $_POST['new'] == $current )
704 check_ajax_referer( "approve-comment_$id" );
705 if ( in_array( $current, array( 'unapproved', 'spam' ) ) )
706 $result = wp_set_comment_status( $comment->comment_ID, 'approve', true );
708 $result = wp_set_comment_status( $comment->comment_ID, 'hold', true );
710 if ( is_wp_error($result) ) {
711 $x = new WP_Ajax_Response( array(
718 // Decide if we need to send back '1' or a more complicated response including page links and comment counts
719 _wp_ajax_delete_comment_response( $comment->comment_ID );
724 * Ajax handler for deleting a link category.
728 * @param string $action Action to perform.
730 function wp_ajax_add_link_category( $action ) {
731 if ( empty( $action ) )
732 $action = 'add-link-category';
733 check_ajax_referer( $action );
734 if ( !current_user_can( 'manage_categories' ) )
736 $names = explode(',', wp_unslash( $_POST['newcat'] ) );
737 $x = new WP_Ajax_Response();
738 foreach ( $names as $cat_name ) {
739 $cat_name = trim($cat_name);
740 $slug = sanitize_title($cat_name);
743 if ( !$cat_id = term_exists( $cat_name, 'link_category' ) )
744 $cat_id = wp_insert_term( $cat_name, 'link_category' );
745 if ( is_wp_error( $cat_id ) ) {
747 } elseif ( is_array( $cat_id ) ) {
748 $cat_id = $cat_id['term_id'];
750 $cat_name = esc_html( $cat_name );
752 'what' => 'link-category',
754 'data' => "<li id='link-category-$cat_id'><label for='in-link-category-$cat_id' class='selectit'><input value='" . esc_attr($cat_id) . "' type='checkbox' checked='checked' name='link_category[]' id='in-link-category-$cat_id'/> $cat_name</label></li>",
762 * Ajax handler to add a tag.
766 function wp_ajax_add_tag() {
767 global $wp_list_table;
769 check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
770 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
771 $tax = get_taxonomy($taxonomy);
773 if ( !current_user_can( $tax->cap->edit_terms ) )
776 $x = new WP_Ajax_Response();
778 $tag = wp_insert_term($_POST['tag-name'], $taxonomy, $_POST );
780 if ( !$tag || is_wp_error($tag) || (!$tag = get_term( $tag['term_id'], $taxonomy )) ) {
781 $message = __('An error has occurred. Please reload the page and try again.');
782 if ( is_wp_error($tag) && $tag->get_error_message() )
783 $message = $tag->get_error_message();
786 'what' => 'taxonomy',
787 'data' => new WP_Error('error', $message )
792 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
795 if ( is_taxonomy_hierarchical($taxonomy) ) {
796 $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
798 $wp_list_table->single_row( $tag, $level );
799 $noparents = ob_get_clean();
803 $wp_list_table->single_row( $tag );
804 $parents = ob_get_clean();
807 'what' => 'taxonomy',
808 'supplemental' => compact('parents', 'noparents')
812 'position' => $level,
813 'supplemental' => (array) $tag
819 * Ajax handler for getting a tagcloud.
823 function wp_ajax_get_tagcloud() {
824 if ( ! isset( $_POST['tax'] ) ) {
828 $taxonomy = sanitize_key( $_POST['tax'] );
829 $tax = get_taxonomy( $taxonomy );
834 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
838 $tags = get_terms( $taxonomy, array( 'number' => 45, 'orderby' => 'count', 'order' => 'DESC' ) );
840 if ( empty( $tags ) )
841 wp_die( $tax->labels->not_found );
843 if ( is_wp_error( $tags ) )
844 wp_die( $tags->get_error_message() );
846 foreach ( $tags as $key => $tag ) {
847 $tags[ $key ]->link = '#';
848 $tags[ $key ]->id = $tag->term_id;
851 // We need raw tag names here, so don't filter the output
852 $return = wp_generate_tag_cloud( $tags, array('filter' => 0) );
854 if ( empty($return) )
863 * Ajax handler for getting comments.
867 * @param string $action Action to perform.
869 function wp_ajax_get_comments( $action ) {
870 global $wp_list_table, $post_id;
871 if ( empty( $action ) )
872 $action = 'get-comments';
874 check_ajax_referer( $action );
876 if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
877 $id = absint( $_REQUEST['p'] );
878 if ( ! empty( $id ) )
882 if ( empty( $post_id ) )
885 $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
887 if ( ! current_user_can( 'edit_post', $post_id ) )
890 $wp_list_table->prepare_items();
892 if ( !$wp_list_table->has_items() )
895 $x = new WP_Ajax_Response();
897 foreach ( $wp_list_table->items as $comment ) {
898 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
900 get_comment( $comment );
901 $wp_list_table->single_row( $comment );
903 $comment_list_item = ob_get_contents();
907 'what' => 'comments',
908 'data' => $comment_list_item
914 * Ajax handler for replying to a comment.
918 * @param string $action Action to perform.
920 function wp_ajax_replyto_comment( $action ) {
921 global $wp_list_table;
922 if ( empty( $action ) )
923 $action = 'replyto-comment';
925 check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
927 $comment_post_ID = (int) $_POST['comment_post_ID'];
928 $post = get_post( $comment_post_ID );
932 if ( !current_user_can( 'edit_post', $comment_post_ID ) )
935 if ( empty( $post->post_status ) )
937 elseif ( in_array($post->post_status, array('draft', 'pending', 'trash') ) )
938 wp_die( __('ERROR: you are replying to a comment on a draft post.') );
940 $user = wp_get_current_user();
941 if ( $user->exists() ) {
942 $user_ID = $user->ID;
943 $comment_author = wp_slash( $user->display_name );
944 $comment_author_email = wp_slash( $user->user_email );
945 $comment_author_url = wp_slash( $user->user_url );
946 $comment_content = trim( $_POST['content'] );
947 $comment_type = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
948 if ( current_user_can( 'unfiltered_html' ) ) {
949 if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) )
950 $_POST['_wp_unfiltered_html_comment'] = '';
952 if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
953 kses_remove_filters(); // start with a clean slate
954 kses_init_filters(); // set up the filters
958 wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
961 if ( '' == $comment_content )
962 wp_die( __( 'ERROR: please type a comment.' ) );
965 if ( isset( $_POST['comment_ID'] ) )
966 $comment_parent = absint( $_POST['comment_ID'] );
967 $comment_auto_approved = false;
968 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
970 // Automatically approve parent comment.
971 if ( !empty($_POST['approve_parent']) ) {
972 $parent = get_comment( $comment_parent );
974 if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
975 if ( wp_set_comment_status( $parent->comment_ID, 'approve' ) )
976 $comment_auto_approved = true;
980 $comment_id = wp_new_comment( $commentdata );
981 $comment = get_comment($comment_id);
982 if ( ! $comment ) wp_die( 1 );
984 $position = ( isset($_POST['position']) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
987 if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
988 require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
989 _wp_dashboard_recent_comments_row( $comment );
991 if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
992 $wp_list_table = _get_list_table('WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
994 $wp_list_table = _get_list_table('WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
996 $wp_list_table->single_row( $comment );
998 $comment_list_item = ob_get_clean();
1001 'what' => 'comment',
1002 'id' => $comment->comment_ID,
1003 'data' => $comment_list_item,
1004 'position' => $position
1007 if ( $comment_auto_approved )
1008 $response['supplemental'] = array( 'parent_approved' => $parent->comment_ID );
1010 $x = new WP_Ajax_Response();
1011 $x->add( $response );
1016 * Ajax handler for editing a comment.
1020 function wp_ajax_edit_comment() {
1021 global $wp_list_table;
1023 check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1025 $comment_id = (int) $_POST['comment_ID'];
1026 if ( ! current_user_can( 'edit_comment', $comment_id ) )
1029 if ( '' == $_POST['content'] )
1030 wp_die( __( 'ERROR: please type a comment.' ) );
1032 if ( isset( $_POST['status'] ) )
1033 $_POST['comment_status'] = $_POST['status'];
1036 $position = ( isset($_POST['position']) && (int) $_POST['position']) ? (int) $_POST['position'] : '-1';
1037 $checkbox = ( isset($_POST['checkbox']) && true == $_POST['checkbox'] ) ? 1 : 0;
1038 $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1040 $comment = get_comment( $comment_id );
1041 if ( empty( $comment->comment_ID ) )
1045 $wp_list_table->single_row( $comment );
1046 $comment_list_item = ob_get_clean();
1048 $x = new WP_Ajax_Response();
1051 'what' => 'edit_comment',
1052 'id' => $comment->comment_ID,
1053 'data' => $comment_list_item,
1054 'position' => $position
1061 * Ajax handler for adding a menu item.
1065 function wp_ajax_add_menu_item() {
1066 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1068 if ( ! current_user_can( 'edit_theme_options' ) )
1071 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1073 // For performance reasons, we omit some object properties from the checklist.
1074 // The following is a hacky way to restore them when adding non-custom items.
1076 $menu_items_data = array();
1077 foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1079 ! empty( $menu_item_data['menu-item-type'] ) &&
1080 'custom' != $menu_item_data['menu-item-type'] &&
1081 ! empty( $menu_item_data['menu-item-object-id'] )
1083 switch( $menu_item_data['menu-item-type'] ) {
1085 $_object = get_post( $menu_item_data['menu-item-object-id'] );
1089 $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1093 $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1094 $_menu_item = reset( $_menu_items );
1096 // Restore the missing menu item properties
1097 $menu_item_data['menu-item-description'] = $_menu_item->description;
1100 $menu_items_data[] = $menu_item_data;
1103 $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1104 if ( is_wp_error( $item_ids ) )
1107 $menu_items = array();
1109 foreach ( (array) $item_ids as $menu_item_id ) {
1110 $menu_obj = get_post( $menu_item_id );
1111 if ( ! empty( $menu_obj->ID ) ) {
1112 $menu_obj = wp_setup_nav_menu_item( $menu_obj );
1113 $menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1114 $menu_items[] = $menu_obj;
1118 /** This filter is documented in wp-admin/includes/nav-menu.php */
1119 $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1121 if ( ! class_exists( $walker_class_name ) )
1124 if ( ! empty( $menu_items ) ) {
1129 'link_before' => '',
1130 'walker' => new $walker_class_name,
1132 echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1138 * Ajax handler for adding meta.
1142 function wp_ajax_add_meta() {
1143 check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1145 $pid = (int) $_POST['post_id'];
1146 $post = get_post( $pid );
1148 if ( isset($_POST['metakeyselect']) || isset($_POST['metakeyinput']) ) {
1149 if ( !current_user_can( 'edit_post', $pid ) )
1151 if ( isset($_POST['metakeyselect']) && '#NONE#' == $_POST['metakeyselect'] && empty($_POST['metakeyinput']) )
1154 // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1155 if ( $post->post_status == 'auto-draft' ) {
1156 $save_POST = $_POST; // Backup $_POST
1157 $_POST = array(); // Make it empty for edit_post()
1158 $_POST['action'] = 'draft'; // Warning fix
1159 $_POST['post_ID'] = $pid;
1160 $_POST['post_type'] = $post->post_type;
1161 $_POST['post_status'] = 'draft';
1162 $now = current_time('timestamp', 1);
1163 $_POST['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( get_option( 'date_format' ), $now ), date( get_option( 'time_format' ), $now ) );
1165 if ( $pid = edit_post() ) {
1166 if ( is_wp_error( $pid ) ) {
1167 $x = new WP_Ajax_Response( array(
1173 $_POST = $save_POST; // Now we can restore original $_POST again
1174 if ( !$mid = add_meta( $pid ) )
1175 wp_die( __( 'Please provide a custom field value.' ) );
1179 } elseif ( ! $mid = add_meta( $pid ) ) {
1180 wp_die( __( 'Please provide a custom field value.' ) );
1183 $meta = get_metadata_by_mid( 'post', $mid );
1184 $pid = (int) $meta->post_id;
1185 $meta = get_object_vars( $meta );
1186 $x = new WP_Ajax_Response( array(
1189 'data' => _list_meta_row( $meta, $c ),
1191 'supplemental' => array('postid' => $pid)
1194 $mid = (int) key( $_POST['meta'] );
1195 $key = wp_unslash( $_POST['meta'][$mid]['key'] );
1196 $value = wp_unslash( $_POST['meta'][$mid]['value'] );
1197 if ( '' == trim($key) )
1198 wp_die( __( 'Please provide a custom field name.' ) );
1199 if ( '' == trim($value) )
1200 wp_die( __( 'Please provide a custom field value.' ) );
1201 if ( ! $meta = get_metadata_by_mid( 'post', $mid ) )
1202 wp_die( 0 ); // if meta doesn't exist
1203 if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1204 ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1205 ! current_user_can( 'edit_post_meta', $meta->post_id, $key ) )
1207 if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1208 if ( !$u = update_metadata_by_mid( 'post', $mid, $value, $key ) )
1209 wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1212 $x = new WP_Ajax_Response( array(
1214 'id' => $mid, 'old_id' => $mid,
1215 'data' => _list_meta_row( array(
1217 'meta_value' => $value,
1221 'supplemental' => array('postid' => $meta->post_id)
1228 * Ajax handler for adding a user.
1232 * @param string $action Action to perform.
1234 function wp_ajax_add_user( $action ) {
1235 global $wp_list_table;
1236 if ( empty( $action ) )
1237 $action = 'add-user';
1239 check_ajax_referer( $action );
1240 if ( ! current_user_can('create_users') )
1242 if ( ! $user_id = edit_user() ) {
1244 } elseif ( is_wp_error( $user_id ) ) {
1245 $x = new WP_Ajax_Response( array(
1251 $user_object = get_userdata( $user_id );
1253 $wp_list_table = _get_list_table('WP_Users_List_Table');
1255 $role = current( $user_object->roles );
1257 $x = new WP_Ajax_Response( array(
1260 'data' => $wp_list_table->single_row( $user_object, '', $role ),
1261 'supplemental' => array(
1262 'show-link' => sprintf(__( 'User <a href="#%s">%s</a> added' ), "user-$user_id", $user_object->user_login),
1270 * Ajax handler for closed post boxes.
1274 function wp_ajax_closed_postboxes() {
1275 check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1276 $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
1277 $closed = array_filter($closed);
1279 $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden']) : array();
1280 $hidden = array_filter($hidden);
1282 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1284 if ( $page != sanitize_key( $page ) )
1287 if ( ! $user = wp_get_current_user() )
1290 if ( is_array($closed) )
1291 update_user_option($user->ID, "closedpostboxes_$page", $closed, true);
1293 if ( is_array($hidden) ) {
1294 $hidden = array_diff( $hidden, array('submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu') ); // postboxes that are always shown
1295 update_user_option($user->ID, "metaboxhidden_$page", $hidden, true);
1302 * Ajax handler for hidden columns.
1306 function wp_ajax_hidden_columns() {
1307 check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1308 $hidden = explode( ',', isset( $_POST['hidden'] ) ? $_POST['hidden'] : '' );
1309 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1311 if ( $page != sanitize_key( $page ) )
1314 if ( ! $user = wp_get_current_user() )
1317 if ( is_array($hidden) )
1318 update_user_option($user->ID, "manage{$page}columnshidden", $hidden, true);
1324 * Ajax handler for updating whether to display the welcome panel.
1328 function wp_ajax_update_welcome_panel() {
1329 check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1331 if ( ! current_user_can( 'edit_theme_options' ) )
1334 update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1340 * Ajax handler for retrieving menu meta boxes.
1344 function wp_ajax_menu_get_metabox() {
1345 if ( ! current_user_can( 'edit_theme_options' ) )
1348 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1350 if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1352 $callback = 'wp_nav_menu_item_post_type_meta_box';
1353 $items = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1354 } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1356 $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1357 $items = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1360 if ( ! empty( $_POST['item-object'] ) && isset( $items[$_POST['item-object']] ) ) {
1361 $menus_meta_box_object = $items[ $_POST['item-object'] ];
1363 /** This filter is documented in wp-admin/includes/nav-menu.php */
1364 $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1366 call_user_func_array($callback, array(
1369 'id' => 'add-' . $item->name,
1370 'title' => $item->labels->name,
1371 'callback' => $callback,
1376 $markup = ob_get_clean();
1378 echo wp_json_encode(array(
1379 'replace-id' => $type . '-' . $item->name,
1380 'markup' => $markup,
1388 * Ajax handler for internal linking.
1392 function wp_ajax_wp_link_ajax() {
1393 check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1397 if ( isset( $_POST['search'] ) )
1398 $args['s'] = wp_unslash( $_POST['search'] );
1399 $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1401 require(ABSPATH . WPINC . '/class-wp-editor.php');
1402 $results = _WP_Editors::wp_link_query( $args );
1404 if ( ! isset( $results ) )
1407 echo wp_json_encode( $results );
1414 * Ajax handler for menu locations save.
1418 function wp_ajax_menu_locations_save() {
1419 if ( ! current_user_can( 'edit_theme_options' ) )
1421 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1422 if ( ! isset( $_POST['menu-locations'] ) )
1424 set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1429 * Ajax handler for saving the meta box order.
1433 function wp_ajax_meta_box_order() {
1434 check_ajax_referer( 'meta-box-order' );
1435 $order = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1436 $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1438 if ( $page_columns != 'auto' )
1439 $page_columns = (int) $page_columns;
1441 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1443 if ( $page != sanitize_key( $page ) )
1446 if ( ! $user = wp_get_current_user() )
1450 update_user_option($user->ID, "meta-box-order_$page", $order, true);
1452 if ( $page_columns )
1453 update_user_option($user->ID, "screen_layout_$page", $page_columns, true);
1459 * Ajax handler for menu quick searching.
1463 function wp_ajax_menu_quick_search() {
1464 if ( ! current_user_can( 'edit_theme_options' ) )
1467 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1469 _wp_ajax_menu_quick_search( $_POST );
1475 * Ajax handler to retrieve a permalink.
1479 function wp_ajax_get_permalink() {
1480 check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1481 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1482 wp_die( add_query_arg( array( 'preview' => 'true' ), get_permalink( $post_id ) ) );
1486 * Ajax handler to retrieve a sample permalink.
1490 function wp_ajax_sample_permalink() {
1491 check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1492 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1493 $title = isset($_POST['new_title'])? $_POST['new_title'] : '';
1494 $slug = isset($_POST['new_slug'])? $_POST['new_slug'] : null;
1495 wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1499 * Ajax handler for Quick Edit saving a post from a list table.
1503 function wp_ajax_inline_save() {
1504 global $wp_list_table;
1506 check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1508 if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
1511 if ( 'page' == $_POST['post_type'] ) {
1512 if ( ! current_user_can( 'edit_page', $post_ID ) )
1513 wp_die( __( 'You are not allowed to edit this page.' ) );
1515 if ( ! current_user_can( 'edit_post', $post_ID ) )
1516 wp_die( __( 'You are not allowed to edit this post.' ) );
1519 if ( $last = wp_check_post_lock( $post_ID ) ) {
1520 $last_user = get_userdata( $last );
1521 $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1522 printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ), esc_html( $last_user_name ) );
1528 $post = get_post( $post_ID, ARRAY_A );
1530 // Since it's coming from the database.
1531 $post = wp_slash($post);
1533 $data['content'] = $post['post_content'];
1534 $data['excerpt'] = $post['post_excerpt'];
1537 $data['user_ID'] = get_current_user_id();
1539 if ( isset($data['post_parent']) )
1540 $data['parent_id'] = $data['post_parent'];
1543 if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1544 $data['visibility'] = 'private';
1545 $data['post_status'] = 'private';
1547 $data['post_status'] = $data['_status'];
1550 if ( empty($data['comment_status']) )
1551 $data['comment_status'] = 'closed';
1552 if ( empty($data['ping_status']) )
1553 $data['ping_status'] = 'closed';
1555 // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1556 if ( ! empty( $data['tax_input'] ) ) {
1557 foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1558 $tax_object = get_taxonomy( $taxonomy );
1559 /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1560 if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1561 unset( $data['tax_input'][ $taxonomy ] );
1566 // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1567 if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1568 $post['post_status'] = 'publish';
1569 $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1575 $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1578 $request_post = array( get_post( $_POST['post_ID'] ) );
1579 $parent = $request_post[0]->post_parent;
1581 while ( $parent > 0 ) {
1582 $parent_post = get_post( $parent );
1583 $parent = $parent_post->post_parent;
1587 $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1593 * Ajax handler for quick edit saving for a term.
1597 function wp_ajax_inline_save_tax() {
1598 global $wp_list_table;
1600 check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1602 $taxonomy = sanitize_key( $_POST['taxonomy'] );
1603 $tax = get_taxonomy( $taxonomy );
1607 if ( ! current_user_can( $tax->cap->edit_terms ) )
1610 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1612 if ( ! isset($_POST['tax_ID']) || ! ( $id = (int) $_POST['tax_ID'] ) )
1615 $tag = get_term( $id, $taxonomy );
1616 $_POST['description'] = $tag->description;
1618 $updated = wp_update_term($id, $taxonomy, $_POST);
1619 if ( $updated && !is_wp_error($updated) ) {
1620 $tag = get_term( $updated['term_id'], $taxonomy );
1621 if ( !$tag || is_wp_error( $tag ) ) {
1622 if ( is_wp_error($tag) && $tag->get_error_message() )
1623 wp_die( $tag->get_error_message() );
1624 wp_die( __( 'Item not updated.' ) );
1627 if ( is_wp_error($updated) && $updated->get_error_message() )
1628 wp_die( $updated->get_error_message() );
1629 wp_die( __( 'Item not updated.' ) );
1632 $parent = $tag->parent;
1633 while ( $parent > 0 ) {
1634 $parent_tag = get_term( $parent, $taxonomy );
1635 $parent = $parent_tag->parent;
1638 $wp_list_table->single_row( $tag, $level );
1643 * Ajax handler for querying posts for the Find Posts modal.
1645 * @see window.findPosts
1649 function wp_ajax_find_posts() {
1650 check_ajax_referer( 'find-posts' );
1652 $post_types = get_post_types( array( 'public' => true ), 'objects' );
1653 unset( $post_types['attachment'] );
1655 $s = wp_unslash( $_POST['ps'] );
1657 'post_type' => array_keys( $post_types ),
1658 'post_status' => 'any',
1659 'posts_per_page' => 50,
1664 $posts = get_posts( $args );
1667 wp_send_json_error( __( 'No items found.' ) );
1670 $html = '<table class="widefat"><thead><tr><th class="found-radio"><br /></th><th>'.__('Title').'</th><th class="no-break">'.__('Type').'</th><th class="no-break">'.__('Date').'</th><th class="no-break">'.__('Status').'</th></tr></thead><tbody>';
1672 foreach ( $posts as $post ) {
1673 $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1674 $alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1676 switch ( $post->post_status ) {
1679 $stat = __('Published');
1682 $stat = __('Scheduled');
1685 $stat = __('Pending Review');
1688 $stat = __('Draft');
1692 if ( '0000-00-00 00:00:00' == $post->post_date ) {
1695 /* translators: date format in table columns, see http://php.net/date */
1696 $time = mysql2date(__('Y/m/d'), $post->post_date);
1699 $html .= '<tr class="' . trim( 'found-posts ' . $alt ) . '"><td class="found-radio"><input type="radio" id="found-'.$post->ID.'" name="found_post_id" value="' . esc_attr($post->ID) . '"></td>';
1700 $html .= '<td><label for="found-'.$post->ID.'">' . esc_html( $title ) . '</label></td><td class="no-break">' . esc_html( $post_types[$post->post_type]->labels->singular_name ) . '</td><td class="no-break">'.esc_html( $time ) . '</td><td class="no-break">' . esc_html( $stat ). ' </td></tr>' . "\n\n";
1703 $html .= '</tbody></table>';
1705 wp_send_json_success( $html );
1709 * Ajax handler for saving the widgets order.
1713 function wp_ajax_widgets_order() {
1714 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1716 if ( !current_user_can('edit_theme_options') )
1719 unset( $_POST['savewidgets'], $_POST['action'] );
1721 // Save widgets order for all sidebars.
1722 if ( is_array($_POST['sidebars']) ) {
1723 $sidebars = array();
1724 foreach ( $_POST['sidebars'] as $key => $val ) {
1726 if ( !empty($val) ) {
1727 $val = explode(',', $val);
1728 foreach ( $val as $k => $v ) {
1729 if ( strpos($v, 'widget-') === false )
1732 $sb[$k] = substr($v, strpos($v, '_') + 1);
1735 $sidebars[$key] = $sb;
1737 wp_set_sidebars_widgets($sidebars);
1745 * Ajax handler for saving a widget.
1749 function wp_ajax_save_widget() {
1750 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1752 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1754 if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1757 unset( $_POST['savewidgets'], $_POST['action'] );
1760 * Fires early when editing the widgets displayed in sidebars.
1764 do_action( 'load-widgets.php' );
1767 * Fires early when editing the widgets displayed in sidebars.
1771 do_action( 'widgets.php' );
1773 /** This action is documented in wp-admin/widgets.php */
1774 do_action( 'sidebar_admin_setup' );
1776 $id_base = $_POST['id_base'];
1777 $widget_id = $_POST['widget-id'];
1778 $sidebar_id = $_POST['sidebar'];
1779 $multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1780 $settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1781 $error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1783 $sidebars = wp_get_sidebars_widgets();
1784 $sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1787 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1789 if ( !isset($wp_registered_widgets[$widget_id]) )
1792 $sidebar = array_diff( $sidebar, array($widget_id) );
1793 $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1794 } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1795 if ( !$multi_number )
1798 $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1799 $widget_id = $id_base . '-' . $multi_number;
1800 $sidebar[] = $widget_id;
1802 $_POST['widget-id'] = $sidebar;
1804 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1806 if ( $name == $id_base ) {
1807 if ( !is_callable( $control['callback'] ) )
1811 call_user_func_array( $control['callback'], $control['params'] );
1817 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1818 $sidebars[$sidebar_id] = $sidebar;
1819 wp_set_sidebars_widgets($sidebars);
1820 echo "deleted:$widget_id";
1824 if ( !empty($_POST['add_new']) )
1827 if ( $form = $wp_registered_widget_controls[$widget_id] )
1828 call_user_func_array( $form['callback'], $form['params'] );
1834 * Ajax handler for saving a widget.
1838 function wp_ajax_update_widget() {
1839 global $wp_customize;
1840 $wp_customize->widgets->wp_ajax_update_widget();
1844 * Ajax handler for uploading attachments
1848 function wp_ajax_upload_attachment() {
1849 check_ajax_referer( 'media-form' );
1851 * This function does not use wp_send_json_success() / wp_send_json_error()
1852 * as the html4 Plupload handler requires a text/html content-type for older IE.
1853 * See https://core.trac.wordpress.org/ticket/31037
1856 if ( ! current_user_can( 'upload_files' ) ) {
1857 echo wp_json_encode( array(
1860 'message' => __( "You don't have permission to upload files." ),
1861 'filename' => $_FILES['async-upload']['name'],
1868 if ( isset( $_REQUEST['post_id'] ) ) {
1869 $post_id = $_REQUEST['post_id'];
1870 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1871 echo wp_json_encode( array(
1874 'message' => __( "You don't have permission to attach files to this post." ),
1875 'filename' => $_FILES['async-upload']['name'],
1885 $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
1887 // If the context is custom header or background, make sure the uploaded file is an image.
1888 if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
1889 $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
1890 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
1891 echo wp_json_encode( array(
1894 'message' => __( 'The uploaded file is not a valid image. Please try again.' ),
1895 'filename' => $_FILES['async-upload']['name'],
1903 $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
1905 if ( is_wp_error( $attachment_id ) ) {
1906 echo wp_json_encode( array(
1909 'message' => $attachment_id->get_error_message(),
1910 'filename' => $_FILES['async-upload']['name'],
1917 if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
1918 if ( 'custom-background' === $post_data['context'] )
1919 update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
1921 if ( 'custom-header' === $post_data['context'] )
1922 update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
1925 if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
1928 echo wp_json_encode( array(
1930 'data' => $attachment,
1937 * Ajax handler for image editing.
1941 function wp_ajax_image_editor() {
1942 $attachment_id = intval($_POST['postid']);
1943 if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
1946 check_ajax_referer( "image_editor-$attachment_id" );
1947 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
1950 switch ( $_POST['do'] ) {
1952 $msg = wp_save_image($attachment_id);
1953 $msg = wp_json_encode($msg);
1957 $msg = wp_save_image($attachment_id);
1960 $msg = wp_restore_image($attachment_id);
1964 wp_image_editor($attachment_id, $msg);
1969 * Ajax handler for setting the featured image.
1973 function wp_ajax_set_post_thumbnail() {
1974 $json = ! empty( $_REQUEST['json'] ); // New-style request
1976 $post_ID = intval( $_POST['post_id'] );
1977 if ( ! current_user_can( 'edit_post', $post_ID ) )
1980 $thumbnail_id = intval( $_POST['thumbnail_id'] );
1983 check_ajax_referer( "update-post_$post_ID" );
1985 check_ajax_referer( "set_post_thumbnail-$post_ID" );
1987 if ( $thumbnail_id == '-1' ) {
1988 if ( delete_post_thumbnail( $post_ID ) ) {
1989 $return = _wp_post_thumbnail_html( null, $post_ID );
1990 $json ? wp_send_json_success( $return ) : wp_die( $return );
1996 if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
1997 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
1998 $json ? wp_send_json_success( $return ) : wp_die( $return );
2005 * AJAX handler for setting the featured image for an attachment.
2009 * @see set_post_thumbnail()
2011 function wp_ajax_set_attachment_thumbnail() {
2012 if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2013 wp_send_json_error();
2016 $thumbnail_id = (int) $_POST['thumbnail_id'];
2017 if ( empty( $thumbnail_id ) ) {
2018 wp_send_json_error();
2021 $post_ids = array();
2022 // For each URL, try to find its corresponding post ID.
2023 foreach ( $_POST['urls'] as $url ) {
2024 $post_id = attachment_url_to_postid( $url );
2025 if ( ! empty( $post_id ) ) {
2026 $post_ids[] = $post_id;
2030 if ( empty( $post_ids ) ) {
2031 wp_send_json_error();
2035 // For each found attachment, set its thumbnail.
2036 foreach ( $post_ids as $post_id ) {
2037 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2041 if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2046 if ( 0 === $success ) {
2047 wp_send_json_error();
2049 wp_send_json_success();
2052 wp_send_json_error();
2056 * Ajax handler for date formatting.
2060 function wp_ajax_date_format() {
2061 wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2065 * Ajax handler for time formatting.
2069 function wp_ajax_time_format() {
2070 wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2074 * Ajax handler for saving posts from the fullscreen editor.
2078 function wp_ajax_wp_fullscreen_save_post() {
2079 $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2084 $post = get_post( $post_id );
2086 check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2088 $post_id = edit_post();
2090 if ( is_wp_error( $post_id ) ) {
2091 wp_send_json_error();
2095 $last_date = mysql2date( get_option('date_format'), $post->post_modified );
2096 $last_time = mysql2date( get_option('time_format'), $post->post_modified );
2098 $last_date = date_i18n( get_option('date_format') );
2099 $last_time = date_i18n( get_option('time_format') );
2102 if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2103 $last_user = get_userdata( $last_id );
2104 $last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2106 $last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2109 wp_send_json_success( array( 'last_edited' => $last_edited ) );
2113 * Ajax handler for removing a post lock.
2117 function wp_ajax_wp_remove_post_lock() {
2118 if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2120 $post_id = (int) $_POST['post_ID'];
2121 if ( ! $post = get_post( $post_id ) )
2124 check_ajax_referer( 'update-post_' . $post_id );
2126 if ( ! current_user_can( 'edit_post', $post_id ) )
2129 $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2130 if ( $active_lock[1] != get_current_user_id() )
2134 * Filter the post lock window duration.
2138 * @param int $interval The interval in seconds the post lock duration
2139 * should last, plus 5 seconds. Default 150.
2141 $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2142 update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2147 * Ajax handler for dismissing a WordPress pointer.
2151 function wp_ajax_dismiss_wp_pointer() {
2152 $pointer = $_POST['pointer'];
2153 if ( $pointer != sanitize_key( $pointer ) )
2156 // check_ajax_referer( 'dismiss-pointer_' . $pointer );
2158 $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2160 if ( in_array( $pointer, $dismissed ) )
2163 $dismissed[] = $pointer;
2164 $dismissed = implode( ',', $dismissed );
2166 update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2171 * Ajax handler for getting an attachment.
2175 function wp_ajax_get_attachment() {
2176 if ( ! isset( $_REQUEST['id'] ) )
2177 wp_send_json_error();
2179 if ( ! $id = absint( $_REQUEST['id'] ) )
2180 wp_send_json_error();
2182 if ( ! $post = get_post( $id ) )
2183 wp_send_json_error();
2185 if ( 'attachment' != $post->post_type )
2186 wp_send_json_error();
2188 if ( ! current_user_can( 'upload_files' ) )
2189 wp_send_json_error();
2191 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2192 wp_send_json_error();
2194 wp_send_json_success( $attachment );
2198 * Ajax handler for querying attachments.
2202 function wp_ajax_query_attachments() {
2203 if ( ! current_user_can( 'upload_files' ) )
2204 wp_send_json_error();
2206 $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2208 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2209 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2211 foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2212 if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2213 $keys[] = $t->query_var;
2217 $query = array_intersect_key( $query, array_flip( $keys ) );
2218 $query['post_type'] = 'attachment';
2220 && ! empty( $_REQUEST['query']['post_status'] )
2221 && 'trash' === $_REQUEST['query']['post_status'] ) {
2222 $query['post_status'] = 'trash';
2224 $query['post_status'] = 'inherit';
2227 if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2228 $query['post_status'] .= ',private';
2231 * Filter the arguments passed to WP_Query during an AJAX
2232 * call for querying attachments.
2236 * @see WP_Query::parse_query()
2238 * @param array $query An array of query variables.
2240 $query = apply_filters( 'ajax_query_attachments_args', $query );
2241 $query = new WP_Query( $query );
2243 $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2244 $posts = array_filter( $posts );
2246 wp_send_json_success( $posts );
2250 * Ajax handler for updating attachment attributes.
2254 function wp_ajax_save_attachment() {
2255 if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2256 wp_send_json_error();
2258 if ( ! $id = absint( $_REQUEST['id'] ) )
2259 wp_send_json_error();
2261 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2263 if ( ! current_user_can( 'edit_post', $id ) )
2264 wp_send_json_error();
2266 $changes = $_REQUEST['changes'];
2267 $post = get_post( $id, ARRAY_A );
2269 if ( 'attachment' != $post['post_type'] )
2270 wp_send_json_error();
2272 if ( isset( $changes['parent'] ) )
2273 $post['post_parent'] = $changes['parent'];
2275 if ( isset( $changes['title'] ) )
2276 $post['post_title'] = $changes['title'];
2278 if ( isset( $changes['caption'] ) )
2279 $post['post_excerpt'] = $changes['caption'];
2281 if ( isset( $changes['description'] ) )
2282 $post['post_content'] = $changes['description'];
2284 if ( MEDIA_TRASH && isset( $changes['status'] ) )
2285 $post['post_status'] = $changes['status'];
2287 if ( isset( $changes['alt'] ) ) {
2288 $alt = wp_unslash( $changes['alt'] );
2289 if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2290 $alt = wp_strip_all_tags( $alt, true );
2291 update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2295 if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2297 $id3data = wp_get_attachment_metadata( $post['ID'] );
2298 if ( ! is_array( $id3data ) ) {
2302 foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2303 if ( isset( $changes[ $key ] ) ) {
2305 $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2310 wp_update_attachment_metadata( $id, $id3data );
2314 if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2315 wp_delete_post( $id );
2317 wp_update_post( $post );
2320 wp_send_json_success();
2324 * Ajax handler for saving backwards compatible attachment attributes.
2328 function wp_ajax_save_attachment_compat() {
2329 if ( ! isset( $_REQUEST['id'] ) )
2330 wp_send_json_error();
2332 if ( ! $id = absint( $_REQUEST['id'] ) )
2333 wp_send_json_error();
2335 if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2336 wp_send_json_error();
2337 $attachment_data = $_REQUEST['attachments'][ $id ];
2339 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2341 if ( ! current_user_can( 'edit_post', $id ) )
2342 wp_send_json_error();
2344 $post = get_post( $id, ARRAY_A );
2346 if ( 'attachment' != $post['post_type'] )
2347 wp_send_json_error();
2349 /** This filter is documented in wp-admin/includes/media.php */
2350 $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2352 if ( isset( $post['errors'] ) ) {
2353 $errors = $post['errors']; // @todo return me and display me!
2354 unset( $post['errors'] );
2357 wp_update_post( $post );
2359 foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2360 if ( isset( $attachment_data[ $taxonomy ] ) )
2361 wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2364 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2365 wp_send_json_error();
2367 wp_send_json_success( $attachment );
2371 * Ajax handler for saving the attachment order.
2375 function wp_ajax_save_attachment_order() {
2376 if ( ! isset( $_REQUEST['post_id'] ) )
2377 wp_send_json_error();
2379 if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2380 wp_send_json_error();
2382 if ( empty( $_REQUEST['attachments'] ) )
2383 wp_send_json_error();
2385 check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2387 $attachments = $_REQUEST['attachments'];
2389 if ( ! current_user_can( 'edit_post', $post_id ) )
2390 wp_send_json_error();
2392 foreach ( $attachments as $attachment_id => $menu_order ) {
2393 if ( ! current_user_can( 'edit_post', $attachment_id ) )
2395 if ( ! $attachment = get_post( $attachment_id ) )
2397 if ( 'attachment' != $attachment->post_type )
2400 wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2403 wp_send_json_success();
2407 * Ajax handler for sending an attachment to the editor.
2409 * Generates the HTML to send an attachment to the editor.
2410 * Backwards compatible with the media_send_to_editor filter
2411 * and the chain of filters that follow.
2415 function wp_ajax_send_attachment_to_editor() {
2416 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2418 $attachment = wp_unslash( $_POST['attachment'] );
2420 $id = intval( $attachment['id'] );
2422 if ( ! $post = get_post( $id ) )
2423 wp_send_json_error();
2425 if ( 'attachment' != $post->post_type )
2426 wp_send_json_error();
2428 if ( current_user_can( 'edit_post', $id ) ) {
2429 // If this attachment is unattached, attach it. Primarily a back compat thing.
2430 if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2431 wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2436 $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2437 if ( ! empty( $attachment['url'] ) ) {
2438 $url = $attachment['url'];
2439 if ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url )
2440 $rel = ' rel="attachment wp-att-' . $id . '"';
2441 $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2444 remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2446 if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2447 $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2448 $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2449 $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2451 // No whitespace-only captions.
2452 $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2453 if ( '' === trim( $caption ) ) {
2457 $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2458 $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt );
2459 } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
2460 $html = stripslashes_deep( $_POST['html'] );
2463 /** This filter is documented in wp-admin/includes/media.php */
2464 $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2466 wp_send_json_success( $html );
2470 * Ajax handler for sending a link to the editor.
2472 * Generates the HTML to send a non-image embed link to the editor.
2474 * Backwards compatible with the following filters:
2475 * - file_send_to_editor_url
2476 * - audio_send_to_editor_url
2477 * - video_send_to_editor_url
2481 function wp_ajax_send_link_to_editor() {
2482 global $post, $wp_embed;
2484 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2486 if ( ! $src = wp_unslash( $_POST['src'] ) )
2487 wp_send_json_error();
2489 if ( ! strpos( $src, '://' ) )
2490 $src = 'http://' . $src;
2492 if ( ! $src = esc_url_raw( $src ) )
2493 wp_send_json_error();
2495 if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2496 $link_text = wp_basename( $src );
2498 $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2500 // Ping WordPress for an embed.
2501 $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2503 // Fallback that WordPress creates when no oEmbed was found.
2504 $fallback = $wp_embed->maybe_make_link( $src );
2506 if ( $check_embed !== $fallback ) {
2507 // TinyMCE view for [embed] will parse this
2508 $html = '[embed]' . $src . '[/embed]';
2509 } elseif ( $link_text ) {
2510 $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2515 // Figure out what filter to run:
2517 if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2518 && ( 'audio' == $ext_type || 'video' == $ext_type ) )
2521 /** This filter is documented in wp-admin/includes/media.php */
2522 $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
2524 wp_send_json_success( $html );
2528 * Ajax handler for the Heartbeat API.
2530 * Runs when the user is logged in.
2534 function wp_ajax_heartbeat() {
2535 if ( empty( $_POST['_nonce'] ) )
2536 wp_send_json_error();
2538 $response = array();
2540 if ( false === wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' ) ) {
2541 // User is logged in but nonces have expired.
2542 $response['nonces_expired'] = true;
2543 wp_send_json($response);
2546 // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2547 if ( ! empty($_POST['screen_id']) )
2548 $screen_id = sanitize_key($_POST['screen_id']);
2550 $screen_id = 'front';
2552 if ( ! empty($_POST['data']) ) {
2553 $data = wp_unslash( (array) $_POST['data'] );
2556 * Filter the Heartbeat response received.
2560 * @param array|object $response The Heartbeat response object or array.
2561 * @param array $data The $_POST data sent.
2562 * @param string $screen_id The screen id.
2564 $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2568 * Filter the Heartbeat response sent.
2572 * @param array|object $response The Heartbeat response object or array.
2573 * @param string $screen_id The screen id.
2575 $response = apply_filters( 'heartbeat_send', $response, $screen_id );
2578 * Fires when Heartbeat ticks in logged-in environments.
2580 * Allows the transport to be easily replaced with long-polling.
2584 * @param array|object $response The Heartbeat response object or array.
2585 * @param string $screen_id The screen id.
2587 do_action( 'heartbeat_tick', $response, $screen_id );
2589 // Send the current time according to the server
2590 $response['server_time'] = time();
2592 wp_send_json($response);
2596 * Ajax handler for getting revision diffs.
2600 function wp_ajax_get_revision_diffs() {
2601 require ABSPATH . 'wp-admin/includes/revision.php';
2603 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2604 wp_send_json_error();
2606 if ( ! current_user_can( 'read_post', $post->ID ) )
2607 wp_send_json_error();
2609 // Really just pre-loading the cache here.
2610 if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2611 wp_send_json_error();
2614 @set_time_limit( 0 );
2616 foreach ( $_REQUEST['compare'] as $compare_key ) {
2617 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2620 'id' => $compare_key,
2621 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2624 wp_send_json_success( $return );
2628 * Ajax handler for auto-saving the selected color scheme for
2629 * a user's own profile.
2633 function wp_ajax_save_user_color_scheme() {
2634 global $_wp_admin_css_colors;
2636 check_ajax_referer( 'save-color-scheme', 'nonce' );
2638 $color_scheme = sanitize_key( $_POST['color_scheme'] );
2640 if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2641 wp_send_json_error();
2644 $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2645 update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2647 wp_send_json_success( array(
2648 'previousScheme' => 'admin-color-' . $previous_color_scheme,
2649 'currentScheme' => 'admin-color-' . $color_scheme
2654 * Ajax handler for getting themes from themes_api().
2658 function wp_ajax_query_themes() {
2659 global $themes_allowedtags, $theme_field_defaults;
2661 if ( ! current_user_can( 'install_themes' ) ) {
2662 wp_send_json_error();
2665 $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2667 'fields' => $theme_field_defaults
2670 $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2672 /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2673 $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2675 $api = themes_api( 'query_themes', $args );
2677 if ( is_wp_error( $api ) ) {
2678 wp_send_json_error();
2681 $update_php = network_admin_url( 'update.php?action=install-theme' );
2682 foreach ( $api->themes as &$theme ) {
2683 $theme->install_url = add_query_arg( array(
2684 'theme' => $theme->slug,
2685 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2688 $theme->name = wp_kses( $theme->name, $themes_allowedtags );
2689 $theme->author = wp_kses( $theme->author, $themes_allowedtags );
2690 $theme->version = wp_kses( $theme->version, $themes_allowedtags );
2691 $theme->description = wp_kses( $theme->description, $themes_allowedtags );
2692 $theme->num_ratings = sprintf( _n( '(based on %s rating)', '(based on %s ratings)', $theme->num_ratings ), number_format_i18n( $theme->num_ratings ) );
2693 $theme->preview_url = set_url_scheme( $theme->preview_url );
2696 wp_send_json_success( $api );
2700 * Apply [embed] AJAX handlers to a string.
2704 * @global WP_Post $post Global $post.
2705 * @global WP_Embed $wp_embed Embed API instance.
2707 function wp_ajax_parse_embed() {
2708 global $post, $wp_embed;
2710 if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2711 wp_send_json_error();
2714 if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
2715 wp_send_json_error();
2718 $shortcode = wp_unslash( $_POST['shortcode'] );
2719 $url = str_replace( '[embed]', '', str_replace( '[/embed]', '', $shortcode ) );
2722 setup_postdata( $post );
2724 $wp_embed->return_false_on_fail = true;
2726 if ( is_ssl() && preg_match( '%^\\[embed[^\\]]*\\]http://%i', $shortcode ) ) {
2727 // Admin is ssl and the user pasted non-ssl URL.
2728 // Check if the provider supports ssl embeds and use that for the preview.
2729 $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
2730 $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
2733 $no_ssl_support = true;
2738 $parsed = $wp_embed->run_shortcode( $shortcode );
2742 wp_send_json_error( array(
2743 'type' => 'not-embeddable',
2744 'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
2748 if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
2750 $mce_styles = wpview_media_sandbox_styles();
2751 foreach ( $mce_styles as $style ) {
2752 $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
2755 $html = do_shortcode( $parsed );
2758 if ( ! empty( $wp_scripts ) ) {
2759 $wp_scripts->done = array();
2762 wp_print_scripts( 'wp-mediaelement' );
2763 $scripts = ob_get_clean();
2765 $parsed = $styles . $html . $scripts;
2769 if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
2770 preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
2771 // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
2772 wp_send_json_error( array(
2773 'type' => 'not-ssl',
2774 'message' => __( 'This preview is unavailable in the editor.' ),
2778 wp_send_json_success( array(
2783 function wp_ajax_parse_media_shortcode() {
2784 global $post, $wp_scripts;
2786 if ( empty( $_POST['shortcode'] ) ) {
2787 wp_send_json_error();
2790 $shortcode = wp_unslash( $_POST['shortcode'] );
2792 if ( ! empty( $_POST['post_ID'] ) ) {
2793 $post = get_post( (int) $_POST['post_ID'] );
2796 // the embed shortcode requires a post
2797 if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
2798 if ( 'embed' === $shortcode ) {
2799 wp_send_json_error();
2802 setup_postdata( $post );
2805 $parsed = do_shortcode( $shortcode );
2807 if ( empty( $parsed ) ) {
2808 wp_send_json_error( array(
2809 'type' => 'no-items',
2810 'message' => __( 'No items found.' ),
2815 $styles = wpview_media_sandbox_styles();
2817 foreach ( $styles as $style ) {
2818 $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
2821 if ( ! empty( $wp_scripts ) ) {
2822 $wp_scripts->done = array();
2829 if ( 'playlist' === $_REQUEST['type'] ) {
2830 wp_underscore_playlist_templates();
2832 wp_print_scripts( 'wp-playlist' );
2834 wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
2837 wp_send_json_success( array(
2839 'body' => ob_get_clean()
2844 * AJAX handler for destroying multiple open sessions for a user.
2848 function wp_ajax_destroy_sessions() {
2850 $user = get_userdata( (int) $_POST['user_id'] );
2852 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2854 } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
2860 wp_send_json_error( array(
2861 'message' => __( 'Could not log out user sessions. Please try again.' ),
2865 $sessions = WP_Session_Tokens::get_instance( $user->ID );
2867 if ( $user->ID === get_current_user_id() ) {
2868 $sessions->destroy_others( wp_get_session_token() );
2869 $message = __( 'You are now logged out everywhere else.' );
2871 $sessions->destroy_all();
2872 /* translators: 1: User's display name. */
2873 $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
2876 wp_send_json_success( array( 'message' => $message ) );
2881 * AJAX handler for updating a plugin.
2885 * @see Plugin_Upgrader
2887 function wp_ajax_update_plugin() {
2888 global $wp_filesystem;
2890 $plugin = urldecode( $_POST['plugin'] );
2893 'update' => 'plugin',
2894 'plugin' => $plugin,
2895 'slug' => sanitize_key( $_POST['slug'] ),
2900 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
2901 if ( $plugin_data['Version'] ) {
2902 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
2905 if ( ! current_user_can( 'update_plugins' ) ) {
2906 $status['error'] = __( 'You do not have sufficient permissions to update plugins on this site.' );
2907 wp_send_json_error( $status );
2910 check_ajax_referer( 'updates' );
2912 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
2914 wp_update_plugins();
2916 $skin = new Automatic_Upgrader_Skin();
2917 $upgrader = new Plugin_Upgrader( $skin );
2918 $result = $upgrader->bulk_upgrade( array( $plugin ) );
2920 if ( is_array( $result ) && empty( $result[$plugin] ) && is_wp_error( $skin->result ) ) {
2921 $result = $skin->result;
2924 if ( is_array( $result ) && !empty( $result[ $plugin ] ) ) {
2925 $plugin_update_data = current( $result );
2928 * If the `update_plugins` site transient is empty (e.g. when you update
2929 * two plugins in quick succession before the transient repopulates),
2930 * this may be the return.
2932 * Preferably something can be done to ensure `update_plugins` isn't empty.
2933 * For now, surface some sort of error here.
2935 if ( $plugin_update_data === true ) {
2936 wp_send_json_error( $status );
2939 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
2940 $plugin_data = reset( $plugin_data );
2942 if ( $plugin_data['Version'] ) {
2943 $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
2946 wp_send_json_success( $status );
2947 } else if ( is_wp_error( $result ) ) {
2948 $status['error'] = $result->get_error_message();
2949 wp_send_json_error( $status );
2951 } else if ( is_bool( $result ) && ! $result ) {
2952 $status['errorCode'] = 'unable_to_connect_to_filesystem';
2953 $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
2955 // Pass through the error from WP_Filesystem if one was raised
2956 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
2957 $status['error'] = $wp_filesystem->errors->get_error_message();
2960 wp_send_json_error( $status );
2966 * AJAX handler for saving a post from Press This.
2970 function wp_ajax_press_this_save_post() {
2971 if ( empty( $GLOBALS['wp_press_this'] ) ) {
2972 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
2975 $GLOBALS['wp_press_this']->save_post();
2979 * AJAX handler for creating new category from Press This.
2983 function wp_ajax_press_this_add_category() {
2984 if ( empty( $GLOBALS['wp_press_this'] ) ) {
2985 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
2988 $GLOBALS['wp_press_this']->add_category();