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 * @global WP_List_Table $wp_list_table
84 function wp_ajax_fetch_list() {
85 global $wp_list_table;
87 $list_class = $_GET['list_args']['class'];
88 check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
90 $wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
91 if ( ! $wp_list_table )
94 if ( ! $wp_list_table->ajax_user_can() )
97 $wp_list_table->ajax_response();
103 * Ajax handler for tag search.
107 function wp_ajax_ajax_tag_search() {
108 if ( ! isset( $_GET['tax'] ) ) {
112 $taxonomy = sanitize_key( $_GET['tax'] );
113 $tax = get_taxonomy( $taxonomy );
118 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
122 $s = wp_unslash( $_GET['q'] );
124 $comma = _x( ',', 'tag delimiter' );
125 if ( ',' !== $comma )
126 $s = str_replace( $comma, ',', $s );
127 if ( false !== strpos( $s, ',' ) ) {
128 $s = explode( ',', $s );
129 $s = $s[count( $s ) - 1];
134 * Filter the minimum number of characters required to fire a tag search via AJAX.
138 * @param int $characters The minimum number of characters required. Default 2.
139 * @param object $tax The taxonomy object.
140 * @param string $s The search term.
142 $term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
145 * Require $term_search_min_chars chars for matching (default: 2)
146 * ensure it's a non-negative, non-zero integer.
148 if ( ( $term_search_min_chars == 0 ) || ( strlen( $s ) < $term_search_min_chars ) ){
152 $results = get_terms( $taxonomy, array( 'name__like' => $s, 'fields' => 'names', 'hide_empty' => false ) );
154 echo join( $results, "\n" );
159 * Ajax handler for compression testing.
163 function wp_ajax_wp_compression_test() {
164 if ( !current_user_can( 'manage_options' ) )
167 if ( ini_get('zlib.output_compression') || 'ob_gzhandler' == ini_get('output_handler') ) {
168 update_site_option('can_compress_scripts', 0);
172 if ( isset($_GET['test']) ) {
173 header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
174 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
175 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
176 header( 'Pragma: no-cache' );
177 header('Content-Type: application/javascript; charset=UTF-8');
178 $force_gzip = ( defined('ENFORCE_GZIP') && ENFORCE_GZIP );
179 $test_str = '"wpCompressionTest Lorem ipsum dolor sit amet consectetuer mollis sapien urna ut a. Eu nonummy condimentum fringilla tempor pretium platea vel nibh netus Maecenas. Hac molestie amet justo quis pellentesque est ultrices interdum nibh Morbi. Cras mattis pretium Phasellus ante ipsum ipsum ut sociis Suspendisse Lorem. Ante et non molestie. Porta urna Vestibulum egestas id congue nibh eu risus gravida sit. Ac augue auctor Ut et non a elit massa id sodales. Elit eu Nulla at nibh adipiscing mattis lacus mauris at tempus. Netus nibh quis suscipit nec feugiat eget sed lorem et urna. Pellentesque lacus at ut massa consectetuer ligula ut auctor semper Pellentesque. Ut metus massa nibh quam Curabitur molestie nec mauris congue. Volutpat molestie elit justo facilisis neque ac risus Ut nascetur tristique. Vitae sit lorem tellus et quis Phasellus lacus tincidunt nunc Fusce. Pharetra wisi Suspendisse mus sagittis libero lacinia Integer consequat ac Phasellus. Et urna ac cursus tortor aliquam Aliquam amet tellus volutpat Vestibulum. Justo interdum condimentum In augue congue tellus sollicitudin Quisque quis nibh."';
181 if ( 1 == $_GET['test'] ) {
184 } elseif ( 2 == $_GET['test'] ) {
185 if ( !isset($_SERVER['HTTP_ACCEPT_ENCODING']) )
187 if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') && function_exists('gzdeflate') && ! $force_gzip ) {
188 header('Content-Encoding: deflate');
189 $out = gzdeflate( $test_str, 1 );
190 } elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzencode') ) {
191 header('Content-Encoding: gzip');
192 $out = gzencode( $test_str, 1 );
198 } elseif ( 'no' == $_GET['test'] ) {
199 update_site_option('can_compress_scripts', 0);
200 } elseif ( 'yes' == $_GET['test'] ) {
201 update_site_option('can_compress_scripts', 1);
209 * Ajax handler for image editor previews.
213 function wp_ajax_imgedit_preview() {
214 $post_id = intval($_GET['postid']);
215 if ( empty($post_id) || !current_user_can('edit_post', $post_id) )
218 check_ajax_referer( "image_editor-$post_id" );
220 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
221 if ( ! stream_preview_image($post_id) )
228 * Ajax handler for oEmbed caching.
232 * @global WP_Embed $wp_embed
234 function wp_ajax_oembed_cache() {
235 $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
240 * Ajax handler for user autocomplete.
244 function wp_ajax_autocomplete_user() {
245 if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) )
248 /** This filter is documented in wp-admin/user-new.php */
249 if ( ! is_super_admin() && ! apply_filters( 'autocomplete_users_for_site_admins', false ) )
254 // Check the type of request
255 // Current allowed values are `add` and `search`
256 if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
257 $type = $_REQUEST['autocomplete_type'];
262 // Check the desired field for value
263 // Current allowed values are `user_email` and `user_login`
264 if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
265 $field = $_REQUEST['autocomplete_field'];
267 $field = 'user_login';
270 // Exclude current users of this blog
271 if ( isset( $_REQUEST['site_id'] ) ) {
272 $id = absint( $_REQUEST['site_id'] );
274 $id = get_current_blog_id();
277 $include_blog_users = ( $type == 'search' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
278 $exclude_blog_users = ( $type == 'add' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
280 $users = get_users( array(
282 'search' => '*' . $_REQUEST['term'] . '*',
283 'include' => $include_blog_users,
284 'exclude' => $exclude_blog_users,
285 'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
288 foreach ( $users as $user ) {
290 /* translators: 1: user_login, 2: user_email */
291 'label' => sprintf( __( '%1$s (%2$s)' ), $user->user_login, $user->user_email ),
292 'value' => $user->$field,
296 wp_die( wp_json_encode( $return ) );
300 * Ajax handler for dashboard widgets.
304 function wp_ajax_dashboard_widgets() {
305 require_once ABSPATH . 'wp-admin/includes/dashboard.php';
307 $pagenow = $_GET['pagenow'];
308 if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
309 set_current_screen( $pagenow );
312 switch ( $_GET['widget'] ) {
313 case 'dashboard_primary' :
314 wp_dashboard_primary();
321 * Ajax handler for Customizer preview logged-in status.
325 function wp_ajax_logged_in() {
334 * Sends back current comment total and new page links if they need to be updated.
336 * Contrary to normal success AJAX response ("1"), die with time() on success.
340 * @param int $comment_id
343 function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
344 $total = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
345 $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
346 $page = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
347 $url = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : '';
349 // JS didn't send us everything we need to know. Just die with success message
350 if ( !$total || !$per_page || !$page || !$url )
357 // Only do the expensive stuff on a page-break, and about 1 other time per page
358 if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
360 $status = 'total_comments'; // What type of comment count are we looking for?
361 $parsed = parse_url( $url );
362 if ( isset( $parsed['query'] ) ) {
363 parse_str( $parsed['query'], $query_vars );
364 if ( !empty( $query_vars['comment_status'] ) )
365 $status = $query_vars['comment_status'];
366 if ( !empty( $query_vars['p'] ) )
367 $post_id = (int) $query_vars['p'];
370 $comment_count = wp_count_comments($post_id);
372 // We're looking for a known type of comment count.
373 if ( isset( $comment_count->$status ) )
374 $total = $comment_count->$status;
375 // Else use the decremented value from above.
378 // The time since the last comment count.
381 $x = new WP_Ajax_Response( array(
383 // Here for completeness - not used.
385 'supplemental' => array(
386 'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
387 'total_pages' => ceil( $total / $per_page ),
388 'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
397 // POST-based Ajax handlers.
401 * Ajax handler for adding a hierarchical term.
405 function _wp_ajax_add_hierarchical_term() {
406 $action = $_POST['action'];
407 $taxonomy = get_taxonomy(substr($action, 4));
408 check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
409 if ( !current_user_can( $taxonomy->cap->edit_terms ) )
411 $names = explode(',', $_POST['new'.$taxonomy->name]);
412 $parent = isset($_POST['new'.$taxonomy->name.'_parent']) ? (int) $_POST['new'.$taxonomy->name.'_parent'] : 0;
415 if ( $taxonomy->name == 'category' )
416 $post_category = isset($_POST['post_category']) ? (array) $_POST['post_category'] : array();
418 $post_category = ( isset($_POST['tax_input']) && isset($_POST['tax_input'][$taxonomy->name]) ) ? (array) $_POST['tax_input'][$taxonomy->name] : array();
419 $checked_categories = array_map( 'absint', (array) $post_category );
420 $popular_ids = wp_popular_terms_checklist($taxonomy->name, 0, 10, false);
422 foreach ( $names as $cat_name ) {
423 $cat_name = trim($cat_name);
424 $category_nicename = sanitize_title($cat_name);
425 if ( '' === $category_nicename )
427 if ( !$cat_id = term_exists( $cat_name, $taxonomy->name, $parent ) )
428 $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
429 if ( is_wp_error( $cat_id ) ) {
431 } elseif ( is_array( $cat_id ) ) {
432 $cat_id = $cat_id['term_id'];
434 $checked_categories[] = $cat_id;
435 if ( $parent ) // Do these all at once in a second
440 wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids ));
442 $data = ob_get_clean();
445 'what' => $taxonomy->name,
447 'data' => str_replace( array("\n", "\t"), '', $data),
452 if ( $parent ) { // Foncy - replace the parent and all its children
453 $parent = get_term( $parent, $taxonomy->name );
454 $term_id = $parent->term_id;
456 while ( $parent->parent ) { // get the top parent
457 $parent = get_term( $parent->parent, $taxonomy->name );
458 if ( is_wp_error( $parent ) )
460 $term_id = $parent->term_id;
465 wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids));
467 $data = ob_get_clean();
470 'what' => $taxonomy->name,
472 'data' => str_replace( array("\n", "\t"), '', $data),
479 wp_dropdown_categories( array(
480 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name',
481 'hierarchical' => 1, 'show_option_none' => '— '.$taxonomy->labels->parent_item.' —'
484 $sup = ob_get_clean();
486 $add['supplemental'] = array( 'newcat_parent' => $sup );
488 $x = new WP_Ajax_Response( $add );
493 * Ajax handler for deleting a comment.
497 function wp_ajax_delete_comment() {
498 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
500 if ( !$comment = get_comment( $id ) )
502 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
505 check_ajax_referer( "delete-comment_$id" );
506 $status = wp_get_comment_status( $comment->comment_ID );
509 if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) {
510 if ( 'trash' == $status )
512 $r = wp_trash_comment( $comment->comment_ID );
513 } elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) {
514 if ( 'trash' != $status )
516 $r = wp_untrash_comment( $comment->comment_ID );
517 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash
519 } elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) {
520 if ( 'spam' == $status )
522 $r = wp_spam_comment( $comment->comment_ID );
523 } elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) {
524 if ( 'spam' != $status )
526 $r = wp_unspam_comment( $comment->comment_ID );
527 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam
529 } elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) {
530 $r = wp_delete_comment( $comment->comment_ID );
535 if ( $r ) // Decide if we need to send back '1' or a more complicated response including page links and comment counts
536 _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
541 * Ajax handler for deleting a tag.
545 function wp_ajax_delete_tag() {
546 $tag_id = (int) $_POST['tag_ID'];
547 check_ajax_referer( "delete-tag_$tag_id" );
549 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
550 $tax = get_taxonomy($taxonomy);
552 if ( !current_user_can( $tax->cap->delete_terms ) )
555 $tag = get_term( $tag_id, $taxonomy );
556 if ( !$tag || is_wp_error( $tag ) )
559 if ( wp_delete_term($tag_id, $taxonomy))
566 * Ajax handler for deleting a link.
570 function wp_ajax_delete_link() {
571 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
573 check_ajax_referer( "delete-bookmark_$id" );
574 if ( !current_user_can( 'manage_links' ) )
577 $link = get_bookmark( $id );
578 if ( !$link || is_wp_error( $link ) )
581 if ( wp_delete_link( $id ) )
588 * Ajax handler for deleting meta.
592 function wp_ajax_delete_meta() {
593 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
595 check_ajax_referer( "delete-meta_$id" );
596 if ( !$meta = get_metadata_by_mid( 'post', $id ) )
599 if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) )
601 if ( delete_meta( $meta->meta_id ) )
607 * Ajax handler for deleting a post.
611 * @param string $action Action to perform.
613 function wp_ajax_delete_post( $action ) {
614 if ( empty( $action ) )
615 $action = 'delete-post';
616 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
618 check_ajax_referer( "{$action}_$id" );
619 if ( !current_user_can( 'delete_post', $id ) )
622 if ( !get_post( $id ) )
625 if ( wp_delete_post( $id ) )
632 * Ajax handler for sending a post to the trash.
636 * @param string $action Action to perform.
638 function wp_ajax_trash_post( $action ) {
639 if ( empty( $action ) )
640 $action = 'trash-post';
641 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
643 check_ajax_referer( "{$action}_$id" );
644 if ( !current_user_can( 'delete_post', $id ) )
647 if ( !get_post( $id ) )
650 if ( 'trash-post' == $action )
651 $done = wp_trash_post( $id );
653 $done = wp_untrash_post( $id );
662 * Ajax handler to restore a post from the trash.
666 * @param string $action Action to perform.
668 function wp_ajax_untrash_post( $action ) {
669 if ( empty( $action ) )
670 $action = 'untrash-post';
671 wp_ajax_trash_post( $action );
677 * @param string $action
679 function wp_ajax_delete_page( $action ) {
680 if ( empty( $action ) )
681 $action = 'delete-page';
682 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
684 check_ajax_referer( "{$action}_$id" );
685 if ( !current_user_can( 'delete_page', $id ) )
688 if ( ! get_post( $id ) )
691 if ( wp_delete_post( $id ) )
698 * Ajax handler to dim a comment.
702 function wp_ajax_dim_comment() {
703 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
705 if ( !$comment = get_comment( $id ) ) {
706 $x = new WP_Ajax_Response( array(
708 'id' => new WP_Error('invalid_comment', sprintf(__('Comment %d does not exist'), $id))
713 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) )
716 $current = wp_get_comment_status( $comment->comment_ID );
717 if ( isset( $_POST['new'] ) && $_POST['new'] == $current )
720 check_ajax_referer( "approve-comment_$id" );
721 if ( in_array( $current, array( 'unapproved', 'spam' ) ) )
722 $result = wp_set_comment_status( $comment->comment_ID, 'approve', true );
724 $result = wp_set_comment_status( $comment->comment_ID, 'hold', true );
726 if ( is_wp_error($result) ) {
727 $x = new WP_Ajax_Response( array(
734 // Decide if we need to send back '1' or a more complicated response including page links and comment counts
735 _wp_ajax_delete_comment_response( $comment->comment_ID );
740 * Ajax handler for deleting a link category.
744 * @param string $action Action to perform.
746 function wp_ajax_add_link_category( $action ) {
747 if ( empty( $action ) )
748 $action = 'add-link-category';
749 check_ajax_referer( $action );
750 if ( !current_user_can( 'manage_categories' ) )
752 $names = explode(',', wp_unslash( $_POST['newcat'] ) );
753 $x = new WP_Ajax_Response();
754 foreach ( $names as $cat_name ) {
755 $cat_name = trim($cat_name);
756 $slug = sanitize_title($cat_name);
759 if ( !$cat_id = term_exists( $cat_name, 'link_category' ) )
760 $cat_id = wp_insert_term( $cat_name, 'link_category' );
761 if ( is_wp_error( $cat_id ) ) {
763 } elseif ( is_array( $cat_id ) ) {
764 $cat_id = $cat_id['term_id'];
766 $cat_name = esc_html( $cat_name );
768 'what' => 'link-category',
770 'data' => "<li id='link-category-$cat_id'><label for='in-link-category-$cat_id' class='selectit'><input value='" . esc_attr($cat_id) . "' type='checkbox' checked='checked' name='link_category[]' id='in-link-category-$cat_id'/> $cat_name</label></li>",
778 * Ajax handler to add a tag.
782 * @global WP_List_Table $wp_list_table
784 function wp_ajax_add_tag() {
785 global $wp_list_table;
787 check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
788 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
789 $tax = get_taxonomy($taxonomy);
791 if ( !current_user_can( $tax->cap->edit_terms ) )
794 $x = new WP_Ajax_Response();
796 $tag = wp_insert_term($_POST['tag-name'], $taxonomy, $_POST );
798 if ( !$tag || is_wp_error($tag) || (!$tag = get_term( $tag['term_id'], $taxonomy )) ) {
799 $message = __('An error has occurred. Please reload the page and try again.');
800 if ( is_wp_error($tag) && $tag->get_error_message() )
801 $message = $tag->get_error_message();
804 'what' => 'taxonomy',
805 'data' => new WP_Error('error', $message )
810 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
813 if ( is_taxonomy_hierarchical($taxonomy) ) {
814 $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
816 $wp_list_table->single_row( $tag, $level );
817 $noparents = ob_get_clean();
821 $wp_list_table->single_row( $tag );
822 $parents = ob_get_clean();
825 'what' => 'taxonomy',
826 'supplemental' => compact('parents', 'noparents')
830 'position' => $level,
831 'supplemental' => (array) $tag
837 * Ajax handler for getting a tagcloud.
841 function wp_ajax_get_tagcloud() {
842 if ( ! isset( $_POST['tax'] ) ) {
846 $taxonomy = sanitize_key( $_POST['tax'] );
847 $tax = get_taxonomy( $taxonomy );
852 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
856 $tags = get_terms( $taxonomy, array( 'number' => 45, 'orderby' => 'count', 'order' => 'DESC' ) );
858 if ( empty( $tags ) )
859 wp_die( $tax->labels->not_found );
861 if ( is_wp_error( $tags ) )
862 wp_die( $tags->get_error_message() );
864 foreach ( $tags as $key => $tag ) {
865 $tags[ $key ]->link = '#';
866 $tags[ $key ]->id = $tag->term_id;
869 // We need raw tag names here, so don't filter the output
870 $return = wp_generate_tag_cloud( $tags, array('filter' => 0) );
872 if ( empty($return) )
881 * Ajax handler for getting comments.
885 * @global WP_List_Table $wp_list_table
886 * @global int $post_id
888 * @param string $action Action to perform.
890 function wp_ajax_get_comments( $action ) {
891 global $wp_list_table, $post_id;
892 if ( empty( $action ) )
893 $action = 'get-comments';
895 check_ajax_referer( $action );
897 if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
898 $id = absint( $_REQUEST['p'] );
899 if ( ! empty( $id ) )
903 if ( empty( $post_id ) )
906 $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
908 if ( ! current_user_can( 'edit_post', $post_id ) )
911 $wp_list_table->prepare_items();
913 if ( !$wp_list_table->has_items() )
916 $x = new WP_Ajax_Response();
918 foreach ( $wp_list_table->items as $comment ) {
919 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
921 get_comment( $comment );
922 $wp_list_table->single_row( $comment );
924 $comment_list_item = ob_get_clean();
927 'what' => 'comments',
928 'data' => $comment_list_item
934 * Ajax handler for replying to a comment.
938 * @global WP_List_Table $wp_list_table
940 * @param string $action Action to perform.
942 function wp_ajax_replyto_comment( $action ) {
943 global $wp_list_table;
944 if ( empty( $action ) )
945 $action = 'replyto-comment';
947 check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
949 $comment_post_ID = (int) $_POST['comment_post_ID'];
950 $post = get_post( $comment_post_ID );
954 if ( !current_user_can( 'edit_post', $comment_post_ID ) )
957 if ( empty( $post->post_status ) )
959 elseif ( in_array($post->post_status, array('draft', 'pending', 'trash') ) )
960 wp_die( __('ERROR: you are replying to a comment on a draft post.') );
962 $user = wp_get_current_user();
963 if ( $user->exists() ) {
964 $user_ID = $user->ID;
965 $comment_author = wp_slash( $user->display_name );
966 $comment_author_email = wp_slash( $user->user_email );
967 $comment_author_url = wp_slash( $user->user_url );
968 $comment_content = trim( $_POST['content'] );
969 $comment_type = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
970 if ( current_user_can( 'unfiltered_html' ) ) {
971 if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) )
972 $_POST['_wp_unfiltered_html_comment'] = '';
974 if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
975 kses_remove_filters(); // start with a clean slate
976 kses_init_filters(); // set up the filters
980 wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
983 if ( '' == $comment_content )
984 wp_die( __( 'ERROR: please type a comment.' ) );
987 if ( isset( $_POST['comment_ID'] ) )
988 $comment_parent = absint( $_POST['comment_ID'] );
989 $comment_auto_approved = false;
990 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
992 // Automatically approve parent comment.
993 if ( !empty($_POST['approve_parent']) ) {
994 $parent = get_comment( $comment_parent );
996 if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
997 if ( wp_set_comment_status( $parent->comment_ID, 'approve' ) )
998 $comment_auto_approved = true;
1002 $comment_id = wp_new_comment( $commentdata );
1003 $comment = get_comment($comment_id);
1004 if ( ! $comment ) wp_die( 1 );
1006 $position = ( isset($_POST['position']) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1009 if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
1010 require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
1011 _wp_dashboard_recent_comments_row( $comment );
1013 if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
1014 $wp_list_table = _get_list_table('WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1016 $wp_list_table = _get_list_table('WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1018 $wp_list_table->single_row( $comment );
1020 $comment_list_item = ob_get_clean();
1023 'what' => 'comment',
1024 'id' => $comment->comment_ID,
1025 'data' => $comment_list_item,
1026 'position' => $position
1029 if ( $comment_auto_approved )
1030 $response['supplemental'] = array( 'parent_approved' => $parent->comment_ID );
1032 $x = new WP_Ajax_Response();
1033 $x->add( $response );
1038 * Ajax handler for editing a comment.
1042 * @global WP_List_Table $wp_list_table
1044 function wp_ajax_edit_comment() {
1045 global $wp_list_table;
1047 check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1049 $comment_id = (int) $_POST['comment_ID'];
1050 if ( ! current_user_can( 'edit_comment', $comment_id ) )
1053 if ( '' == $_POST['content'] )
1054 wp_die( __( 'ERROR: please type a comment.' ) );
1056 if ( isset( $_POST['status'] ) )
1057 $_POST['comment_status'] = $_POST['status'];
1060 $position = ( isset($_POST['position']) && (int) $_POST['position']) ? (int) $_POST['position'] : '-1';
1061 $checkbox = ( isset($_POST['checkbox']) && true == $_POST['checkbox'] ) ? 1 : 0;
1062 $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1064 $comment = get_comment( $comment_id );
1065 if ( empty( $comment->comment_ID ) )
1069 $wp_list_table->single_row( $comment );
1070 $comment_list_item = ob_get_clean();
1072 $x = new WP_Ajax_Response();
1075 'what' => 'edit_comment',
1076 'id' => $comment->comment_ID,
1077 'data' => $comment_list_item,
1078 'position' => $position
1085 * Ajax handler for adding a menu item.
1089 function wp_ajax_add_menu_item() {
1090 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1092 if ( ! current_user_can( 'edit_theme_options' ) )
1095 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1097 // For performance reasons, we omit some object properties from the checklist.
1098 // The following is a hacky way to restore them when adding non-custom items.
1100 $menu_items_data = array();
1101 foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1103 ! empty( $menu_item_data['menu-item-type'] ) &&
1104 'custom' != $menu_item_data['menu-item-type'] &&
1105 ! empty( $menu_item_data['menu-item-object-id'] )
1107 switch( $menu_item_data['menu-item-type'] ) {
1109 $_object = get_post( $menu_item_data['menu-item-object-id'] );
1113 $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1117 $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1118 $_menu_item = reset( $_menu_items );
1120 // Restore the missing menu item properties
1121 $menu_item_data['menu-item-description'] = $_menu_item->description;
1124 $menu_items_data[] = $menu_item_data;
1127 $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1128 if ( is_wp_error( $item_ids ) )
1131 $menu_items = array();
1133 foreach ( (array) $item_ids as $menu_item_id ) {
1134 $menu_obj = get_post( $menu_item_id );
1135 if ( ! empty( $menu_obj->ID ) ) {
1136 $menu_obj = wp_setup_nav_menu_item( $menu_obj );
1137 $menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1138 $menu_items[] = $menu_obj;
1142 /** This filter is documented in wp-admin/includes/nav-menu.php */
1143 $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1145 if ( ! class_exists( $walker_class_name ) )
1148 if ( ! empty( $menu_items ) ) {
1153 'link_before' => '',
1154 'walker' => new $walker_class_name,
1156 echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1162 * Ajax handler for adding meta.
1166 function wp_ajax_add_meta() {
1167 check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1169 $pid = (int) $_POST['post_id'];
1170 $post = get_post( $pid );
1172 if ( isset($_POST['metakeyselect']) || isset($_POST['metakeyinput']) ) {
1173 if ( !current_user_can( 'edit_post', $pid ) )
1175 if ( isset($_POST['metakeyselect']) && '#NONE#' == $_POST['metakeyselect'] && empty($_POST['metakeyinput']) )
1178 // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1179 if ( $post->post_status == 'auto-draft' ) {
1180 $save_POST = $_POST; // Backup $_POST
1181 $_POST = array(); // Make it empty for edit_post()
1182 $_POST['action'] = 'draft'; // Warning fix
1183 $_POST['post_ID'] = $pid;
1184 $_POST['post_type'] = $post->post_type;
1185 $_POST['post_status'] = 'draft';
1186 $now = current_time('timestamp', 1);
1187 $_POST['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( get_option( 'date_format' ), $now ), date( get_option( 'time_format' ), $now ) );
1189 if ( $pid = edit_post() ) {
1190 if ( is_wp_error( $pid ) ) {
1191 $x = new WP_Ajax_Response( array(
1197 $_POST = $save_POST; // Now we can restore original $_POST again
1198 if ( !$mid = add_meta( $pid ) )
1199 wp_die( __( 'Please provide a custom field value.' ) );
1203 } elseif ( ! $mid = add_meta( $pid ) ) {
1204 wp_die( __( 'Please provide a custom field value.' ) );
1207 $meta = get_metadata_by_mid( 'post', $mid );
1208 $pid = (int) $meta->post_id;
1209 $meta = get_object_vars( $meta );
1210 $x = new WP_Ajax_Response( array(
1213 'data' => _list_meta_row( $meta, $c ),
1215 'supplemental' => array('postid' => $pid)
1218 $mid = (int) key( $_POST['meta'] );
1219 $key = wp_unslash( $_POST['meta'][$mid]['key'] );
1220 $value = wp_unslash( $_POST['meta'][$mid]['value'] );
1221 if ( '' == trim($key) )
1222 wp_die( __( 'Please provide a custom field name.' ) );
1223 if ( '' == trim($value) )
1224 wp_die( __( 'Please provide a custom field value.' ) );
1225 if ( ! $meta = get_metadata_by_mid( 'post', $mid ) )
1226 wp_die( 0 ); // if meta doesn't exist
1227 if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1228 ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1229 ! current_user_can( 'edit_post_meta', $meta->post_id, $key ) )
1231 if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1232 if ( !$u = update_metadata_by_mid( 'post', $mid, $value, $key ) )
1233 wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1236 $x = new WP_Ajax_Response( array(
1238 'id' => $mid, 'old_id' => $mid,
1239 'data' => _list_meta_row( array(
1241 'meta_value' => $value,
1245 'supplemental' => array('postid' => $meta->post_id)
1252 * Ajax handler for adding a user.
1256 * @global WP_List_Table $wp_list_table
1258 * @param string $action Action to perform.
1260 function wp_ajax_add_user( $action ) {
1261 global $wp_list_table;
1262 if ( empty( $action ) )
1263 $action = 'add-user';
1265 check_ajax_referer( $action );
1266 if ( ! current_user_can('create_users') )
1268 if ( ! $user_id = edit_user() ) {
1270 } elseif ( is_wp_error( $user_id ) ) {
1271 $x = new WP_Ajax_Response( array(
1277 $user_object = get_userdata( $user_id );
1279 $wp_list_table = _get_list_table('WP_Users_List_Table');
1281 $role = current( $user_object->roles );
1283 $x = new WP_Ajax_Response( array(
1286 'data' => $wp_list_table->single_row( $user_object, '', $role ),
1287 'supplemental' => array(
1288 'show-link' => sprintf(__( 'User <a href="#%s">%s</a> added' ), "user-$user_id", $user_object->user_login),
1296 * Ajax handler for closed post boxes.
1300 function wp_ajax_closed_postboxes() {
1301 check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1302 $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
1303 $closed = array_filter($closed);
1305 $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden']) : array();
1306 $hidden = array_filter($hidden);
1308 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1310 if ( $page != sanitize_key( $page ) )
1313 if ( ! $user = wp_get_current_user() )
1316 if ( is_array($closed) )
1317 update_user_option($user->ID, "closedpostboxes_$page", $closed, true);
1319 if ( is_array($hidden) ) {
1320 $hidden = array_diff( $hidden, array('submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu') ); // postboxes that are always shown
1321 update_user_option($user->ID, "metaboxhidden_$page", $hidden, true);
1328 * Ajax handler for hidden columns.
1332 function wp_ajax_hidden_columns() {
1333 check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1334 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1336 if ( $page != sanitize_key( $page ) )
1339 if ( ! $user = wp_get_current_user() )
1342 $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1343 update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true );
1349 * Ajax handler for updating whether to display the welcome panel.
1353 function wp_ajax_update_welcome_panel() {
1354 check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1356 if ( ! current_user_can( 'edit_theme_options' ) )
1359 update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1365 * Ajax handler for retrieving menu meta boxes.
1369 function wp_ajax_menu_get_metabox() {
1370 if ( ! current_user_can( 'edit_theme_options' ) )
1373 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1375 if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1377 $callback = 'wp_nav_menu_item_post_type_meta_box';
1378 $items = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1379 } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1381 $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1382 $items = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1385 if ( ! empty( $_POST['item-object'] ) && isset( $items[$_POST['item-object']] ) ) {
1386 $menus_meta_box_object = $items[ $_POST['item-object'] ];
1388 /** This filter is documented in wp-admin/includes/nav-menu.php */
1389 $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1391 call_user_func_array($callback, array(
1394 'id' => 'add-' . $item->name,
1395 'title' => $item->labels->name,
1396 'callback' => $callback,
1401 $markup = ob_get_clean();
1403 echo wp_json_encode(array(
1404 'replace-id' => $type . '-' . $item->name,
1405 'markup' => $markup,
1413 * Ajax handler for internal linking.
1417 function wp_ajax_wp_link_ajax() {
1418 check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1422 if ( isset( $_POST['search'] ) )
1423 $args['s'] = wp_unslash( $_POST['search'] );
1424 $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1426 require(ABSPATH . WPINC . '/class-wp-editor.php');
1427 $results = _WP_Editors::wp_link_query( $args );
1429 if ( ! isset( $results ) )
1432 echo wp_json_encode( $results );
1439 * Ajax handler for menu locations save.
1443 function wp_ajax_menu_locations_save() {
1444 if ( ! current_user_can( 'edit_theme_options' ) )
1446 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1447 if ( ! isset( $_POST['menu-locations'] ) )
1449 set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1454 * Ajax handler for saving the meta box order.
1458 function wp_ajax_meta_box_order() {
1459 check_ajax_referer( 'meta-box-order' );
1460 $order = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1461 $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1463 if ( $page_columns != 'auto' )
1464 $page_columns = (int) $page_columns;
1466 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1468 if ( $page != sanitize_key( $page ) )
1471 if ( ! $user = wp_get_current_user() )
1475 update_user_option($user->ID, "meta-box-order_$page", $order, true);
1477 if ( $page_columns )
1478 update_user_option($user->ID, "screen_layout_$page", $page_columns, true);
1484 * Ajax handler for menu quick searching.
1488 function wp_ajax_menu_quick_search() {
1489 if ( ! current_user_can( 'edit_theme_options' ) )
1492 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1494 _wp_ajax_menu_quick_search( $_POST );
1500 * Ajax handler to retrieve a permalink.
1504 function wp_ajax_get_permalink() {
1505 check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1506 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1507 wp_die( add_query_arg( array( 'preview' => 'true' ), get_permalink( $post_id ) ) );
1511 * Ajax handler to retrieve a sample permalink.
1515 function wp_ajax_sample_permalink() {
1516 check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1517 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1518 $title = isset($_POST['new_title'])? $_POST['new_title'] : '';
1519 $slug = isset($_POST['new_slug'])? $_POST['new_slug'] : null;
1520 wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1524 * Ajax handler for Quick Edit saving a post from a list table.
1528 * @global WP_List_Table $wp_list_table
1530 function wp_ajax_inline_save() {
1531 global $wp_list_table;
1533 check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1535 if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
1538 if ( 'page' == $_POST['post_type'] ) {
1539 if ( ! current_user_can( 'edit_page', $post_ID ) )
1540 wp_die( __( 'You are not allowed to edit this page.' ) );
1542 if ( ! current_user_can( 'edit_post', $post_ID ) )
1543 wp_die( __( 'You are not allowed to edit this post.' ) );
1546 if ( $last = wp_check_post_lock( $post_ID ) ) {
1547 $last_user = get_userdata( $last );
1548 $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1549 printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ), esc_html( $last_user_name ) );
1555 $post = get_post( $post_ID, ARRAY_A );
1557 // Since it's coming from the database.
1558 $post = wp_slash($post);
1560 $data['content'] = $post['post_content'];
1561 $data['excerpt'] = $post['post_excerpt'];
1564 $data['user_ID'] = get_current_user_id();
1566 if ( isset($data['post_parent']) )
1567 $data['parent_id'] = $data['post_parent'];
1570 if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1571 $data['visibility'] = 'private';
1572 $data['post_status'] = 'private';
1574 $data['post_status'] = $data['_status'];
1577 if ( empty($data['comment_status']) )
1578 $data['comment_status'] = 'closed';
1579 if ( empty($data['ping_status']) )
1580 $data['ping_status'] = 'closed';
1582 // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1583 if ( ! empty( $data['tax_input'] ) ) {
1584 foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1585 $tax_object = get_taxonomy( $taxonomy );
1586 /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1587 if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1588 unset( $data['tax_input'][ $taxonomy ] );
1593 // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1594 if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1595 $post['post_status'] = 'publish';
1596 $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1602 $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1605 $request_post = array( get_post( $_POST['post_ID'] ) );
1606 $parent = $request_post[0]->post_parent;
1608 while ( $parent > 0 ) {
1609 $parent_post = get_post( $parent );
1610 $parent = $parent_post->post_parent;
1614 $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1620 * Ajax handler for quick edit saving for a term.
1624 * @global WP_List_Table $wp_list_table
1626 function wp_ajax_inline_save_tax() {
1627 global $wp_list_table;
1629 check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1631 $taxonomy = sanitize_key( $_POST['taxonomy'] );
1632 $tax = get_taxonomy( $taxonomy );
1636 if ( ! current_user_can( $tax->cap->edit_terms ) )
1639 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1641 if ( ! isset($_POST['tax_ID']) || ! ( $id = (int) $_POST['tax_ID'] ) )
1644 $tag = get_term( $id, $taxonomy );
1645 $_POST['description'] = $tag->description;
1647 $updated = wp_update_term($id, $taxonomy, $_POST);
1648 if ( $updated && !is_wp_error($updated) ) {
1649 $tag = get_term( $updated['term_id'], $taxonomy );
1650 if ( !$tag || is_wp_error( $tag ) ) {
1651 if ( is_wp_error($tag) && $tag->get_error_message() )
1652 wp_die( $tag->get_error_message() );
1653 wp_die( __( 'Item not updated.' ) );
1656 if ( is_wp_error($updated) && $updated->get_error_message() )
1657 wp_die( $updated->get_error_message() );
1658 wp_die( __( 'Item not updated.' ) );
1661 $parent = $tag->parent;
1662 while ( $parent > 0 ) {
1663 $parent_tag = get_term( $parent, $taxonomy );
1664 $parent = $parent_tag->parent;
1667 $wp_list_table->single_row( $tag, $level );
1672 * Ajax handler for querying posts for the Find Posts modal.
1674 * @see window.findPosts
1678 function wp_ajax_find_posts() {
1679 check_ajax_referer( 'find-posts' );
1681 $post_types = get_post_types( array( 'public' => true ), 'objects' );
1682 unset( $post_types['attachment'] );
1684 $s = wp_unslash( $_POST['ps'] );
1686 'post_type' => array_keys( $post_types ),
1687 'post_status' => 'any',
1688 'posts_per_page' => 50,
1693 $posts = get_posts( $args );
1696 wp_send_json_error( __( 'No items found.' ) );
1699 $html = '<table class="widefat"><thead><tr><th class="found-radio"><br /></th><th>'.__('Title').'</th><th class="no-break">'.__('Type').'</th><th class="no-break">'.__('Date').'</th><th class="no-break">'.__('Status').'</th></tr></thead><tbody>';
1701 foreach ( $posts as $post ) {
1702 $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1703 $alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1705 switch ( $post->post_status ) {
1708 $stat = __('Published');
1711 $stat = __('Scheduled');
1714 $stat = __('Pending Review');
1717 $stat = __('Draft');
1721 if ( '0000-00-00 00:00:00' == $post->post_date ) {
1724 /* translators: date format in table columns, see http://php.net/date */
1725 $time = mysql2date(__('Y/m/d'), $post->post_date);
1728 $html .= '<tr class="' . trim( 'found-posts ' . $alt ) . '"><td class="found-radio"><input type="radio" id="found-'.$post->ID.'" name="found_post_id" value="' . esc_attr($post->ID) . '"></td>';
1729 $html .= '<td><label for="found-'.$post->ID.'">' . esc_html( $title ) . '</label></td><td class="no-break">' . esc_html( $post_types[$post->post_type]->labels->singular_name ) . '</td><td class="no-break">'.esc_html( $time ) . '</td><td class="no-break">' . esc_html( $stat ). ' </td></tr>' . "\n\n";
1732 $html .= '</tbody></table>';
1734 wp_send_json_success( $html );
1738 * Ajax handler for saving the widgets order.
1742 function wp_ajax_widgets_order() {
1743 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1745 if ( !current_user_can('edit_theme_options') )
1748 unset( $_POST['savewidgets'], $_POST['action'] );
1750 // Save widgets order for all sidebars.
1751 if ( is_array($_POST['sidebars']) ) {
1752 $sidebars = array();
1753 foreach ( $_POST['sidebars'] as $key => $val ) {
1755 if ( !empty($val) ) {
1756 $val = explode(',', $val);
1757 foreach ( $val as $k => $v ) {
1758 if ( strpos($v, 'widget-') === false )
1761 $sb[$k] = substr($v, strpos($v, '_') + 1);
1764 $sidebars[$key] = $sb;
1766 wp_set_sidebars_widgets($sidebars);
1774 * Ajax handler for saving a widget.
1778 * @global array $wp_registered_widgets
1779 * @global array $wp_registered_widget_controls
1780 * @global array $wp_registered_widget_updates
1782 function wp_ajax_save_widget() {
1783 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1785 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1787 if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1790 unset( $_POST['savewidgets'], $_POST['action'] );
1793 * Fires early when editing the widgets displayed in sidebars.
1797 do_action( 'load-widgets.php' );
1800 * Fires early when editing the widgets displayed in sidebars.
1804 do_action( 'widgets.php' );
1806 /** This action is documented in wp-admin/widgets.php */
1807 do_action( 'sidebar_admin_setup' );
1809 $id_base = $_POST['id_base'];
1810 $widget_id = $_POST['widget-id'];
1811 $sidebar_id = $_POST['sidebar'];
1812 $multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1813 $settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1814 $error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1816 $sidebars = wp_get_sidebars_widgets();
1817 $sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1820 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1822 if ( !isset($wp_registered_widgets[$widget_id]) )
1825 $sidebar = array_diff( $sidebar, array($widget_id) );
1826 $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1827 } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1828 if ( !$multi_number )
1831 $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1832 $widget_id = $id_base . '-' . $multi_number;
1833 $sidebar[] = $widget_id;
1835 $_POST['widget-id'] = $sidebar;
1837 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1839 if ( $name == $id_base ) {
1840 if ( !is_callable( $control['callback'] ) )
1844 call_user_func_array( $control['callback'], $control['params'] );
1850 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1851 $sidebars[$sidebar_id] = $sidebar;
1852 wp_set_sidebars_widgets($sidebars);
1853 echo "deleted:$widget_id";
1857 if ( !empty($_POST['add_new']) )
1860 if ( $form = $wp_registered_widget_controls[$widget_id] )
1861 call_user_func_array( $form['callback'], $form['params'] );
1867 * Ajax handler for saving a widget.
1871 * @global WP_Customize_Manager $wp_customize
1873 function wp_ajax_update_widget() {
1874 global $wp_customize;
1875 $wp_customize->widgets->wp_ajax_update_widget();
1879 * Ajax handler for uploading attachments
1883 function wp_ajax_upload_attachment() {
1884 check_ajax_referer( 'media-form' );
1886 * This function does not use wp_send_json_success() / wp_send_json_error()
1887 * as the html4 Plupload handler requires a text/html content-type for older IE.
1888 * See https://core.trac.wordpress.org/ticket/31037
1891 if ( ! current_user_can( 'upload_files' ) ) {
1892 echo wp_json_encode( array(
1895 'message' => __( "You don't have permission to upload files." ),
1896 'filename' => $_FILES['async-upload']['name'],
1903 if ( isset( $_REQUEST['post_id'] ) ) {
1904 $post_id = $_REQUEST['post_id'];
1905 if ( ! current_user_can( 'edit_post', $post_id ) ) {
1906 echo wp_json_encode( array(
1909 'message' => __( "You don't have permission to attach files to this post." ),
1910 'filename' => $_FILES['async-upload']['name'],
1920 $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
1922 // If the context is custom header or background, make sure the uploaded file is an image.
1923 if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
1924 $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
1925 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
1926 echo wp_json_encode( array(
1929 'message' => __( 'The uploaded file is not a valid image. Please try again.' ),
1930 'filename' => $_FILES['async-upload']['name'],
1938 $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
1940 if ( is_wp_error( $attachment_id ) ) {
1941 echo wp_json_encode( array(
1944 'message' => $attachment_id->get_error_message(),
1945 'filename' => $_FILES['async-upload']['name'],
1952 if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
1953 if ( 'custom-background' === $post_data['context'] )
1954 update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
1956 if ( 'custom-header' === $post_data['context'] )
1957 update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
1960 if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
1963 echo wp_json_encode( array(
1965 'data' => $attachment,
1972 * Ajax handler for image editing.
1976 function wp_ajax_image_editor() {
1977 $attachment_id = intval($_POST['postid']);
1978 if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
1981 check_ajax_referer( "image_editor-$attachment_id" );
1982 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
1985 switch ( $_POST['do'] ) {
1987 $msg = wp_save_image($attachment_id);
1988 $msg = wp_json_encode($msg);
1992 $msg = wp_save_image($attachment_id);
1995 $msg = wp_restore_image($attachment_id);
1999 wp_image_editor($attachment_id, $msg);
2004 * Ajax handler for setting the featured image.
2008 function wp_ajax_set_post_thumbnail() {
2009 $json = ! empty( $_REQUEST['json'] ); // New-style request
2011 $post_ID = intval( $_POST['post_id'] );
2012 if ( ! current_user_can( 'edit_post', $post_ID ) )
2015 $thumbnail_id = intval( $_POST['thumbnail_id'] );
2018 check_ajax_referer( "update-post_$post_ID" );
2020 check_ajax_referer( "set_post_thumbnail-$post_ID" );
2022 if ( $thumbnail_id == '-1' ) {
2023 if ( delete_post_thumbnail( $post_ID ) ) {
2024 $return = _wp_post_thumbnail_html( null, $post_ID );
2025 $json ? wp_send_json_success( $return ) : wp_die( $return );
2031 if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2032 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2033 $json ? wp_send_json_success( $return ) : wp_die( $return );
2040 * AJAX handler for setting the featured image for an attachment.
2044 * @see set_post_thumbnail()
2046 function wp_ajax_set_attachment_thumbnail() {
2047 if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2048 wp_send_json_error();
2051 $thumbnail_id = (int) $_POST['thumbnail_id'];
2052 if ( empty( $thumbnail_id ) ) {
2053 wp_send_json_error();
2056 $post_ids = array();
2057 // For each URL, try to find its corresponding post ID.
2058 foreach ( $_POST['urls'] as $url ) {
2059 $post_id = attachment_url_to_postid( $url );
2060 if ( ! empty( $post_id ) ) {
2061 $post_ids[] = $post_id;
2065 if ( empty( $post_ids ) ) {
2066 wp_send_json_error();
2070 // For each found attachment, set its thumbnail.
2071 foreach ( $post_ids as $post_id ) {
2072 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2076 if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2081 if ( 0 === $success ) {
2082 wp_send_json_error();
2084 wp_send_json_success();
2087 wp_send_json_error();
2091 * Ajax handler for date formatting.
2095 function wp_ajax_date_format() {
2096 wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2100 * Ajax handler for time formatting.
2104 function wp_ajax_time_format() {
2105 wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2109 * Ajax handler for saving posts from the fullscreen editor.
2114 function wp_ajax_wp_fullscreen_save_post() {
2115 $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2120 $post = get_post( $post_id );
2122 check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2124 $post_id = edit_post();
2126 if ( is_wp_error( $post_id ) ) {
2127 wp_send_json_error();
2131 $last_date = mysql2date( get_option('date_format'), $post->post_modified );
2132 $last_time = mysql2date( get_option('time_format'), $post->post_modified );
2134 $last_date = date_i18n( get_option('date_format') );
2135 $last_time = date_i18n( get_option('time_format') );
2138 if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2139 $last_user = get_userdata( $last_id );
2140 $last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2142 $last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2145 wp_send_json_success( array( 'last_edited' => $last_edited ) );
2149 * Ajax handler for removing a post lock.
2153 function wp_ajax_wp_remove_post_lock() {
2154 if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2156 $post_id = (int) $_POST['post_ID'];
2157 if ( ! $post = get_post( $post_id ) )
2160 check_ajax_referer( 'update-post_' . $post_id );
2162 if ( ! current_user_can( 'edit_post', $post_id ) )
2165 $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2166 if ( $active_lock[1] != get_current_user_id() )
2170 * Filter the post lock window duration.
2174 * @param int $interval The interval in seconds the post lock duration
2175 * should last, plus 5 seconds. Default 150.
2177 $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2178 update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2183 * Ajax handler for dismissing a WordPress pointer.
2187 function wp_ajax_dismiss_wp_pointer() {
2188 $pointer = $_POST['pointer'];
2189 if ( $pointer != sanitize_key( $pointer ) )
2192 // check_ajax_referer( 'dismiss-pointer_' . $pointer );
2194 $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2196 if ( in_array( $pointer, $dismissed ) )
2199 $dismissed[] = $pointer;
2200 $dismissed = implode( ',', $dismissed );
2202 update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2207 * Ajax handler for getting an attachment.
2211 function wp_ajax_get_attachment() {
2212 if ( ! isset( $_REQUEST['id'] ) )
2213 wp_send_json_error();
2215 if ( ! $id = absint( $_REQUEST['id'] ) )
2216 wp_send_json_error();
2218 if ( ! $post = get_post( $id ) )
2219 wp_send_json_error();
2221 if ( 'attachment' != $post->post_type )
2222 wp_send_json_error();
2224 if ( ! current_user_can( 'upload_files' ) )
2225 wp_send_json_error();
2227 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2228 wp_send_json_error();
2230 wp_send_json_success( $attachment );
2234 * Ajax handler for querying attachments.
2238 function wp_ajax_query_attachments() {
2239 if ( ! current_user_can( 'upload_files' ) )
2240 wp_send_json_error();
2242 $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2244 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2245 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2247 foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2248 if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2249 $keys[] = $t->query_var;
2253 $query = array_intersect_key( $query, array_flip( $keys ) );
2254 $query['post_type'] = 'attachment';
2256 && ! empty( $_REQUEST['query']['post_status'] )
2257 && 'trash' === $_REQUEST['query']['post_status'] ) {
2258 $query['post_status'] = 'trash';
2260 $query['post_status'] = 'inherit';
2263 if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2264 $query['post_status'] .= ',private';
2267 * Filter the arguments passed to WP_Query during an AJAX
2268 * call for querying attachments.
2272 * @see WP_Query::parse_query()
2274 * @param array $query An array of query variables.
2276 $query = apply_filters( 'ajax_query_attachments_args', $query );
2277 $query = new WP_Query( $query );
2279 $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2280 $posts = array_filter( $posts );
2282 wp_send_json_success( $posts );
2286 * Ajax handler for updating attachment attributes.
2290 function wp_ajax_save_attachment() {
2291 if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2292 wp_send_json_error();
2294 if ( ! $id = absint( $_REQUEST['id'] ) )
2295 wp_send_json_error();
2297 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2299 if ( ! current_user_can( 'edit_post', $id ) )
2300 wp_send_json_error();
2302 $changes = $_REQUEST['changes'];
2303 $post = get_post( $id, ARRAY_A );
2305 if ( 'attachment' != $post['post_type'] )
2306 wp_send_json_error();
2308 if ( isset( $changes['parent'] ) )
2309 $post['post_parent'] = $changes['parent'];
2311 if ( isset( $changes['title'] ) )
2312 $post['post_title'] = $changes['title'];
2314 if ( isset( $changes['caption'] ) )
2315 $post['post_excerpt'] = $changes['caption'];
2317 if ( isset( $changes['description'] ) )
2318 $post['post_content'] = $changes['description'];
2320 if ( MEDIA_TRASH && isset( $changes['status'] ) )
2321 $post['post_status'] = $changes['status'];
2323 if ( isset( $changes['alt'] ) ) {
2324 $alt = wp_unslash( $changes['alt'] );
2325 if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2326 $alt = wp_strip_all_tags( $alt, true );
2327 update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2331 if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2333 $id3data = wp_get_attachment_metadata( $post['ID'] );
2334 if ( ! is_array( $id3data ) ) {
2338 foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2339 if ( isset( $changes[ $key ] ) ) {
2341 $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2346 wp_update_attachment_metadata( $id, $id3data );
2350 if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2351 wp_delete_post( $id );
2353 wp_update_post( $post );
2356 wp_send_json_success();
2360 * Ajax handler for saving backwards compatible attachment attributes.
2364 function wp_ajax_save_attachment_compat() {
2365 if ( ! isset( $_REQUEST['id'] ) )
2366 wp_send_json_error();
2368 if ( ! $id = absint( $_REQUEST['id'] ) )
2369 wp_send_json_error();
2371 if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2372 wp_send_json_error();
2373 $attachment_data = $_REQUEST['attachments'][ $id ];
2375 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2377 if ( ! current_user_can( 'edit_post', $id ) )
2378 wp_send_json_error();
2380 $post = get_post( $id, ARRAY_A );
2382 if ( 'attachment' != $post['post_type'] )
2383 wp_send_json_error();
2385 /** This filter is documented in wp-admin/includes/media.php */
2386 $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2388 if ( isset( $post['errors'] ) ) {
2389 $errors = $post['errors']; // @todo return me and display me!
2390 unset( $post['errors'] );
2393 wp_update_post( $post );
2395 foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2396 if ( isset( $attachment_data[ $taxonomy ] ) )
2397 wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2400 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2401 wp_send_json_error();
2403 wp_send_json_success( $attachment );
2407 * Ajax handler for saving the attachment order.
2411 function wp_ajax_save_attachment_order() {
2412 if ( ! isset( $_REQUEST['post_id'] ) )
2413 wp_send_json_error();
2415 if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2416 wp_send_json_error();
2418 if ( empty( $_REQUEST['attachments'] ) )
2419 wp_send_json_error();
2421 check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2423 $attachments = $_REQUEST['attachments'];
2425 if ( ! current_user_can( 'edit_post', $post_id ) )
2426 wp_send_json_error();
2428 foreach ( $attachments as $attachment_id => $menu_order ) {
2429 if ( ! current_user_can( 'edit_post', $attachment_id ) )
2431 if ( ! $attachment = get_post( $attachment_id ) )
2433 if ( 'attachment' != $attachment->post_type )
2436 wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2439 wp_send_json_success();
2443 * Ajax handler for sending an attachment to the editor.
2445 * Generates the HTML to send an attachment to the editor.
2446 * Backwards compatible with the media_send_to_editor filter
2447 * and the chain of filters that follow.
2451 function wp_ajax_send_attachment_to_editor() {
2452 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2454 $attachment = wp_unslash( $_POST['attachment'] );
2456 $id = intval( $attachment['id'] );
2458 if ( ! $post = get_post( $id ) )
2459 wp_send_json_error();
2461 if ( 'attachment' != $post->post_type )
2462 wp_send_json_error();
2464 if ( current_user_can( 'edit_post', $id ) ) {
2465 // If this attachment is unattached, attach it. Primarily a back compat thing.
2466 if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2467 wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2472 $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2473 if ( ! empty( $attachment['url'] ) ) {
2474 $url = $attachment['url'];
2475 if ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url )
2476 $rel = ' rel="attachment wp-att-' . $id . '"';
2477 $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2480 remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2482 if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2483 $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2484 $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2485 $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2487 // No whitespace-only captions.
2488 $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2489 if ( '' === trim( $caption ) ) {
2493 $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2494 $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt );
2495 } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
2496 $html = stripslashes_deep( $_POST['html'] );
2499 /** This filter is documented in wp-admin/includes/media.php */
2500 $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2502 wp_send_json_success( $html );
2506 * Ajax handler for sending a link to the editor.
2508 * Generates the HTML to send a non-image embed link to the editor.
2510 * Backwards compatible with the following filters:
2511 * - file_send_to_editor_url
2512 * - audio_send_to_editor_url
2513 * - video_send_to_editor_url
2517 * @global WP_Post $post
2518 * @global WP_Embed $wp_embed
2520 function wp_ajax_send_link_to_editor() {
2521 global $post, $wp_embed;
2523 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2525 if ( ! $src = wp_unslash( $_POST['src'] ) )
2526 wp_send_json_error();
2528 if ( ! strpos( $src, '://' ) )
2529 $src = 'http://' . $src;
2531 if ( ! $src = esc_url_raw( $src ) )
2532 wp_send_json_error();
2534 if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2535 $link_text = wp_basename( $src );
2537 $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2539 // Ping WordPress for an embed.
2540 $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2542 // Fallback that WordPress creates when no oEmbed was found.
2543 $fallback = $wp_embed->maybe_make_link( $src );
2545 if ( $check_embed !== $fallback ) {
2546 // TinyMCE view for [embed] will parse this
2547 $html = '[embed]' . $src . '[/embed]';
2548 } elseif ( $link_text ) {
2549 $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2554 // Figure out what filter to run:
2556 if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2557 && ( 'audio' == $ext_type || 'video' == $ext_type ) )
2560 /** This filter is documented in wp-admin/includes/media.php */
2561 $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
2563 wp_send_json_success( $html );
2567 * Ajax handler for the Heartbeat API.
2569 * Runs when the user is logged in.
2573 function wp_ajax_heartbeat() {
2574 if ( empty( $_POST['_nonce'] ) ) {
2575 wp_send_json_error();
2578 $response = $data = array();
2579 $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
2581 // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2582 if ( ! empty( $_POST['screen_id'] ) ) {
2583 $screen_id = sanitize_key($_POST['screen_id']);
2585 $screen_id = 'front';
2588 if ( ! empty( $_POST['data'] ) ) {
2589 $data = wp_unslash( (array) $_POST['data'] );
2592 if ( 1 !== $nonce_state ) {
2593 $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
2595 if ( false === $nonce_state ) {
2596 // User is logged in but nonces have expired.
2597 $response['nonces_expired'] = true;
2598 wp_send_json( $response );
2602 if ( ! empty( $data ) ) {
2604 * Filter the Heartbeat response received.
2608 * @param array|object $response The Heartbeat response object or array.
2609 * @param array $data The $_POST data sent.
2610 * @param string $screen_id The screen id.
2612 $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2616 * Filter the Heartbeat response sent.
2620 * @param array|object $response The Heartbeat response object or array.
2621 * @param string $screen_id The screen id.
2623 $response = apply_filters( 'heartbeat_send', $response, $screen_id );
2626 * Fires when Heartbeat ticks in logged-in environments.
2628 * Allows the transport to be easily replaced with long-polling.
2632 * @param array|object $response The Heartbeat response object or array.
2633 * @param string $screen_id The screen id.
2635 do_action( 'heartbeat_tick', $response, $screen_id );
2637 // Send the current time according to the server
2638 $response['server_time'] = time();
2640 wp_send_json( $response );
2644 * Ajax handler for getting revision diffs.
2648 function wp_ajax_get_revision_diffs() {
2649 require ABSPATH . 'wp-admin/includes/revision.php';
2651 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2652 wp_send_json_error();
2654 if ( ! current_user_can( 'read_post', $post->ID ) )
2655 wp_send_json_error();
2657 // Really just pre-loading the cache here.
2658 if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2659 wp_send_json_error();
2662 @set_time_limit( 0 );
2664 foreach ( $_REQUEST['compare'] as $compare_key ) {
2665 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2668 'id' => $compare_key,
2669 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2672 wp_send_json_success( $return );
2676 * Ajax handler for auto-saving the selected color scheme for
2677 * a user's own profile.
2681 * @global array $_wp_admin_css_colors
2683 function wp_ajax_save_user_color_scheme() {
2684 global $_wp_admin_css_colors;
2686 check_ajax_referer( 'save-color-scheme', 'nonce' );
2688 $color_scheme = sanitize_key( $_POST['color_scheme'] );
2690 if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2691 wp_send_json_error();
2694 $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2695 update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2697 wp_send_json_success( array(
2698 'previousScheme' => 'admin-color-' . $previous_color_scheme,
2699 'currentScheme' => 'admin-color-' . $color_scheme
2704 * Ajax handler for getting themes from themes_api().
2708 * @global array $themes_allowedtags
2709 * @global array $theme_field_defaults
2711 function wp_ajax_query_themes() {
2712 global $themes_allowedtags, $theme_field_defaults;
2714 if ( ! current_user_can( 'install_themes' ) ) {
2715 wp_send_json_error();
2718 $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2720 'fields' => $theme_field_defaults
2723 $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2725 /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2726 $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2728 $api = themes_api( 'query_themes', $args );
2730 if ( is_wp_error( $api ) ) {
2731 wp_send_json_error();
2734 $update_php = network_admin_url( 'update.php?action=install-theme' );
2735 foreach ( $api->themes as &$theme ) {
2736 $theme->install_url = add_query_arg( array(
2737 'theme' => $theme->slug,
2738 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2741 $theme->name = wp_kses( $theme->name, $themes_allowedtags );
2742 $theme->author = wp_kses( $theme->author, $themes_allowedtags );
2743 $theme->version = wp_kses( $theme->version, $themes_allowedtags );
2744 $theme->description = wp_kses( $theme->description, $themes_allowedtags );
2745 $theme->num_ratings = sprintf( _n( '(based on %s rating)', '(based on %s ratings)', $theme->num_ratings ), number_format_i18n( $theme->num_ratings ) );
2746 $theme->preview_url = set_url_scheme( $theme->preview_url );
2749 wp_send_json_success( $api );
2753 * Apply [embed] AJAX handlers to a string.
2757 * @global WP_Post $post Global $post.
2758 * @global WP_Embed $wp_embed Embed API instance.
2759 * @global WP_Scripts $wp_scripts
2761 function wp_ajax_parse_embed() {
2762 global $post, $wp_embed;
2764 if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2765 wp_send_json_error();
2768 if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
2769 wp_send_json_error();
2772 $shortcode = wp_unslash( $_POST['shortcode'] );
2774 preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
2775 $atts = shortcode_parse_atts( $matches[3] );
2776 if ( ! empty( $matches[5] ) ) {
2778 } elseif ( ! empty( $atts['src'] ) ) {
2779 $url = $atts['src'];
2785 setup_postdata( $post );
2787 $wp_embed->return_false_on_fail = true;
2789 if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
2790 // Admin is ssl and the user pasted non-ssl URL.
2791 // Check if the provider supports ssl embeds and use that for the preview.
2792 $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
2793 $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
2796 $no_ssl_support = true;
2800 if ( $url && ! $parsed ) {
2801 $parsed = $wp_embed->run_shortcode( $shortcode );
2805 wp_send_json_error( array(
2806 'type' => 'not-embeddable',
2807 'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
2811 if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
2813 $mce_styles = wpview_media_sandbox_styles();
2814 foreach ( $mce_styles as $style ) {
2815 $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
2818 $html = do_shortcode( $parsed );
2821 if ( ! empty( $wp_scripts ) ) {
2822 $wp_scripts->done = array();
2825 wp_print_scripts( 'wp-mediaelement' );
2826 $scripts = ob_get_clean();
2828 $parsed = $styles . $html . $scripts;
2832 if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
2833 preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
2834 // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
2835 wp_send_json_error( array(
2836 'type' => 'not-ssl',
2837 'message' => __( 'This preview is unavailable in the editor.' ),
2841 wp_send_json_success( array(
2843 'attr' => $wp_embed->last_attr
2850 * @global WP_Post $post
2851 * @global WP_Scripts $wp_scripts
2853 function wp_ajax_parse_media_shortcode() {
2854 global $post, $wp_scripts;
2856 if ( empty( $_POST['shortcode'] ) ) {
2857 wp_send_json_error();
2860 $shortcode = wp_unslash( $_POST['shortcode'] );
2862 if ( ! empty( $_POST['post_ID'] ) ) {
2863 $post = get_post( (int) $_POST['post_ID'] );
2866 // the embed shortcode requires a post
2867 if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
2868 if ( 'embed' === $shortcode ) {
2869 wp_send_json_error();
2872 setup_postdata( $post );
2875 $parsed = do_shortcode( $shortcode );
2877 if ( empty( $parsed ) ) {
2878 wp_send_json_error( array(
2879 'type' => 'no-items',
2880 'message' => __( 'No items found.' ),
2885 $styles = wpview_media_sandbox_styles();
2887 foreach ( $styles as $style ) {
2888 $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
2891 if ( ! empty( $wp_scripts ) ) {
2892 $wp_scripts->done = array();
2899 if ( 'playlist' === $_REQUEST['type'] ) {
2900 wp_underscore_playlist_templates();
2902 wp_print_scripts( 'wp-playlist' );
2904 wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
2907 wp_send_json_success( array(
2909 'body' => ob_get_clean()
2914 * AJAX handler for destroying multiple open sessions for a user.
2918 function wp_ajax_destroy_sessions() {
2919 $user = get_userdata( (int) $_POST['user_id'] );
2921 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
2923 } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
2929 wp_send_json_error( array(
2930 'message' => __( 'Could not log out user sessions. Please try again.' ),
2934 $sessions = WP_Session_Tokens::get_instance( $user->ID );
2936 if ( $user->ID === get_current_user_id() ) {
2937 $sessions->destroy_others( wp_get_session_token() );
2938 $message = __( 'You are now logged out everywhere else.' );
2940 $sessions->destroy_all();
2941 /* translators: 1: User's display name. */
2942 $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
2945 wp_send_json_success( array( 'message' => $message ) );
2950 * AJAX handler for updating a plugin.
2954 * @see Plugin_Upgrader
2956 function wp_ajax_update_plugin() {
2957 global $wp_filesystem;
2959 $plugin = urldecode( $_POST['plugin'] );
2962 'update' => 'plugin',
2963 'plugin' => $plugin,
2964 'slug' => sanitize_key( $_POST['slug'] ),
2969 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
2970 if ( $plugin_data['Version'] ) {
2971 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
2974 if ( ! current_user_can( 'update_plugins' ) ) {
2975 $status['error'] = __( 'You do not have sufficient permissions to update plugins for this site.' );
2976 wp_send_json_error( $status );
2979 check_ajax_referer( 'updates' );
2981 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
2983 wp_update_plugins();
2985 $skin = new Automatic_Upgrader_Skin();
2986 $upgrader = new Plugin_Upgrader( $skin );
2987 $result = $upgrader->bulk_upgrade( array( $plugin ) );
2989 if ( is_array( $result ) && empty( $result[$plugin] ) && is_wp_error( $skin->result ) ) {
2990 $result = $skin->result;
2993 if ( is_array( $result ) && !empty( $result[ $plugin ] ) ) {
2994 $plugin_update_data = current( $result );
2997 * If the `update_plugins` site transient is empty (e.g. when you update
2998 * two plugins in quick succession before the transient repopulates),
2999 * this may be the return.
3001 * Preferably something can be done to ensure `update_plugins` isn't empty.
3002 * For now, surface some sort of error here.
3004 if ( $plugin_update_data === true ) {
3005 wp_send_json_error( $status );
3008 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
3009 $plugin_data = reset( $plugin_data );
3011 if ( $plugin_data['Version'] ) {
3012 $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3015 wp_send_json_success( $status );
3016 } else if ( is_wp_error( $result ) ) {
3017 $status['error'] = $result->get_error_message();
3018 wp_send_json_error( $status );
3020 } else if ( is_bool( $result ) && ! $result ) {
3021 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3022 $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3024 // Pass through the error from WP_Filesystem if one was raised
3025 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3026 $status['error'] = $wp_filesystem->errors->get_error_message();
3029 wp_send_json_error( $status );
3035 * AJAX handler for saving a post from Press This.
3039 * @global WP_Press_This $wp_press_this
3041 function wp_ajax_press_this_save_post() {
3042 if ( empty( $GLOBALS['wp_press_this'] ) ) {
3043 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3046 $GLOBALS['wp_press_this']->save_post();
3050 * AJAX handler for creating new category from Press This.
3054 * @global WP_Press_This $wp_press_this
3056 function wp_ajax_press_this_add_category() {
3057 if ( empty( $GLOBALS['wp_press_this'] ) ) {
3058 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3061 $GLOBALS['wp_press_this']->add_category();
3065 * AJAX handler for cropping an image.
3069 * @global WP_Site_Icon $wp_site_icon
3071 function wp_ajax_crop_image() {
3072 $attachment_id = absint( $_POST['id'] );
3074 check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3075 if ( ! current_user_can( 'customize' ) ) {
3076 wp_send_json_error();
3079 $context = str_replace( '_', '-', $_POST['context'] );
3080 $data = array_map( 'absint', $_POST['cropDetails'] );
3081 $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3083 if ( ! $cropped || is_wp_error( $cropped ) ) {
3084 wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3087 switch ( $context ) {
3089 require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
3090 global $wp_site_icon;
3092 // Skip creating a new attachment if the attachment is a Site Icon.
3093 if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3095 // Delete the temporary cropped file, we don't need it.
3096 wp_delete_file( $cropped );
3098 // Additional sizes in wp_prepare_attachment_for_js().
3099 add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3103 /** This filter is documented in wp-admin/custom-header.php */
3104 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3105 $object = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3106 unset( $object['ID'] );
3108 // Update the attachment.
3109 add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3110 $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3111 remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3113 // Additional sizes in wp_prepare_attachment_for_js().
3114 add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3120 * Fires before a cropped image is saved.
3122 * Allows to add filters to modify the way a cropped image is saved.
3126 * @param string $context The Customizer control requesting the cropped image.
3127 * @param int $attachment_id The attachment ID of the original image.
3128 * @param string $cropped Path to the cropped image file.
3130 do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3132 /** This filter is documented in wp-admin/custom-header.php */
3133 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3135 $parent_url = get_post( $attachment_id )->guid;
3136 $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
3138 $size = @getimagesize( $cropped );
3139 $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3142 'post_title' => basename( $cropped ),
3143 'post_content' => $url,
3144 'post_mime_type' => $image_type,
3146 'context' => $context,
3149 $attachment_id = wp_insert_attachment( $object, $cropped );
3150 $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
3153 * Filter the cropped image attachment metadata.
3157 * @see wp_generate_attachment_metadata()
3159 * @param array $metadata Attachment metadata.
3161 $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3162 wp_update_attachment_metadata( $attachment_id, $metadata );
3165 * Filter the attachment ID for a cropped image.
3169 * @param int $attachment_id The attachment ID of the cropped image.
3170 * @param string $context The Customizer control requesting the cropped image.
3172 $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3175 wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );