3 * Administration API: Core Ajax handlers
6 * @subpackage Administration
11 // No-privilege Ajax handlers.
15 * Ajax handler for the Heartbeat API in
16 * the no-privilege context.
18 * Runs when the user is not logged in.
22 function wp_ajax_nopriv_heartbeat() {
25 // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
26 if ( ! empty($_POST['screen_id']) )
27 $screen_id = sanitize_key($_POST['screen_id']);
31 if ( ! empty($_POST['data']) ) {
32 $data = wp_unslash( (array) $_POST['data'] );
35 * Filters Heartbeat Ajax response in no-privilege environments.
39 * @param array|object $response The no-priv Heartbeat response object or array.
40 * @param array $data An array of data passed via $_POST.
41 * @param string $screen_id The screen id.
43 $response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id );
47 * Filters Heartbeat Ajax response when no data is passed.
51 * @param array|object $response The Heartbeat response object or array.
52 * @param string $screen_id The screen id.
54 $response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id );
57 * Fires when Heartbeat ticks in no-privilege environments.
59 * Allows the transport to be easily replaced with long-polling.
63 * @param array|object $response The no-priv Heartbeat response.
64 * @param string $screen_id The screen id.
66 do_action( 'heartbeat_nopriv_tick', $response, $screen_id );
68 // Send the current time according to the server.
69 $response['server_time'] = time();
71 wp_send_json($response);
75 // GET-based Ajax handlers.
79 * Ajax handler for fetching a list table.
83 * @global WP_List_Table $wp_list_table
85 function wp_ajax_fetch_list() {
86 global $wp_list_table;
88 $list_class = $_GET['list_args']['class'];
89 check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
91 $wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
92 if ( ! $wp_list_table )
95 if ( ! $wp_list_table->ajax_user_can() )
98 $wp_list_table->ajax_response();
104 * Ajax handler for tag search.
108 function wp_ajax_ajax_tag_search() {
109 if ( ! isset( $_GET['tax'] ) ) {
113 $taxonomy = sanitize_key( $_GET['tax'] );
114 $tax = get_taxonomy( $taxonomy );
119 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
123 $s = wp_unslash( $_GET['q'] );
125 $comma = _x( ',', 'tag delimiter' );
126 if ( ',' !== $comma )
127 $s = str_replace( $comma, ',', $s );
128 if ( false !== strpos( $s, ',' ) ) {
129 $s = explode( ',', $s );
130 $s = $s[count( $s ) - 1];
135 * Filters the minimum number of characters required to fire a tag search via Ajax.
139 * @param int $characters The minimum number of characters required. Default 2.
140 * @param object $tax The taxonomy object.
141 * @param string $s The search term.
143 $term_search_min_chars = (int) apply_filters( 'term_search_min_chars', 2, $tax, $s );
146 * Require $term_search_min_chars chars for matching (default: 2)
147 * ensure it's a non-negative, non-zero integer.
149 if ( ( $term_search_min_chars == 0 ) || ( strlen( $s ) < $term_search_min_chars ) ){
153 $results = get_terms( $taxonomy, array( 'name__like' => $s, 'fields' => 'names', 'hide_empty' => false ) );
155 echo join( $results, "\n" );
160 * Ajax handler for compression testing.
164 function wp_ajax_wp_compression_test() {
165 if ( !current_user_can( 'manage_options' ) )
168 if ( ini_get('zlib.output_compression') || 'ob_gzhandler' == ini_get('output_handler') ) {
169 update_site_option('can_compress_scripts', 0);
173 if ( isset($_GET['test']) ) {
174 header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
175 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
176 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
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 check_ajax_referer( 'update_can_compress_scripts' );
200 update_site_option('can_compress_scripts', 0);
201 } elseif ( 'yes' == $_GET['test'] ) {
202 check_ajax_referer( 'update_can_compress_scripts' );
203 update_site_option('can_compress_scripts', 1);
211 * Ajax handler for image editor previews.
215 function wp_ajax_imgedit_preview() {
216 $post_id = intval($_GET['postid']);
217 if ( empty($post_id) || !current_user_can('edit_post', $post_id) )
220 check_ajax_referer( "image_editor-$post_id" );
222 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
223 if ( ! stream_preview_image($post_id) )
230 * Ajax handler for oEmbed caching.
234 * @global WP_Embed $wp_embed
236 function wp_ajax_oembed_cache() {
237 $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
242 * Ajax handler for user autocomplete.
246 function wp_ajax_autocomplete_user() {
247 if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) )
250 /** This filter is documented in wp-admin/user-new.php */
251 if ( ! is_super_admin() && ! apply_filters( 'autocomplete_users_for_site_admins', false ) )
256 // Check the type of request
257 // Current allowed values are `add` and `search`
258 if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
259 $type = $_REQUEST['autocomplete_type'];
264 // Check the desired field for value
265 // Current allowed values are `user_email` and `user_login`
266 if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
267 $field = $_REQUEST['autocomplete_field'];
269 $field = 'user_login';
272 // Exclude current users of this blog
273 if ( isset( $_REQUEST['site_id'] ) ) {
274 $id = absint( $_REQUEST['site_id'] );
276 $id = get_current_blog_id();
279 $include_blog_users = ( $type == 'search' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
280 $exclude_blog_users = ( $type == 'add' ? get_users( array( 'blog_id' => $id, 'fields' => 'ID' ) ) : array() );
282 $users = get_users( array(
284 'search' => '*' . $_REQUEST['term'] . '*',
285 'include' => $include_blog_users,
286 'exclude' => $exclude_blog_users,
287 'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
290 foreach ( $users as $user ) {
292 /* translators: 1: user_login, 2: user_email */
293 'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
294 'value' => $user->$field,
298 wp_die( wp_json_encode( $return ) );
302 * Ajax handler for dashboard widgets.
306 function wp_ajax_dashboard_widgets() {
307 require_once ABSPATH . 'wp-admin/includes/dashboard.php';
309 $pagenow = $_GET['pagenow'];
310 if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
311 set_current_screen( $pagenow );
314 switch ( $_GET['widget'] ) {
315 case 'dashboard_primary' :
316 wp_dashboard_primary();
323 * Ajax handler for Customizer preview logged-in status.
327 function wp_ajax_logged_in() {
336 * Sends back current comment total and new page links if they need to be updated.
338 * Contrary to normal success Ajax response ("1"), die with time() on success.
343 * @param int $comment_id
346 function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
347 $total = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
348 $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
349 $page = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
350 $url = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : '';
352 // JS didn't send us everything we need to know. Just die with success message
353 if ( ! $total || ! $per_page || ! $page || ! $url ) {
355 $comment = get_comment( $comment_id );
356 $comment_status = '';
360 $comment_status = $comment->comment_approved;
363 if ( 1 === (int) $comment_status ) {
364 $comment_link = get_comment_link( $comment );
367 $counts = wp_count_comments();
369 $x = new WP_Ajax_Response( array(
371 // Here for completeness - not used.
373 'supplemental' => array(
374 'status' => $comment_status,
375 'postId' => $comment ? $comment->comment_post_ID : '',
377 'in_moderation' => $counts->moderated,
378 'i18n_comments_text' => sprintf(
379 _n( '%s Comment', '%s Comments', $counts->approved ),
380 number_format_i18n( $counts->approved )
382 'i18n_moderation_text' => sprintf(
383 _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
384 number_format_i18n( $counts->moderated )
386 'comment_link' => $comment_link,
396 // Only do the expensive stuff on a page-break, and about 1 other time per page
397 if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
399 // What type of comment count are we looking for?
401 $parsed = parse_url( $url );
402 if ( isset( $parsed['query'] ) ) {
403 parse_str( $parsed['query'], $query_vars );
404 if ( !empty( $query_vars['comment_status'] ) )
405 $status = $query_vars['comment_status'];
406 if ( !empty( $query_vars['p'] ) )
407 $post_id = (int) $query_vars['p'];
408 if ( ! empty( $query_vars['comment_type'] ) )
409 $type = $query_vars['comment_type'];
412 if ( empty( $type ) ) {
413 // Only use the comment count if not filtering by a comment_type.
414 $comment_count = wp_count_comments($post_id);
416 // We're looking for a known type of comment count.
417 if ( isset( $comment_count->$status ) ) {
418 $total = $comment_count->$status;
421 // Else use the decremented value from above.
424 // The time since the last comment count.
426 $comment = get_comment( $comment_id );
428 $x = new WP_Ajax_Response( array(
430 // Here for completeness - not used.
432 'supplemental' => array(
433 'status' => $comment ? $comment->comment_approved : '',
434 'postId' => $comment ? $comment->comment_post_ID : '',
435 'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
436 'total_pages' => ceil( $total / $per_page ),
437 'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
446 // POST-based Ajax handlers.
450 * Ajax handler for adding a hierarchical term.
455 function _wp_ajax_add_hierarchical_term() {
456 $action = $_POST['action'];
457 $taxonomy = get_taxonomy(substr($action, 4));
458 check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
459 if ( !current_user_can( $taxonomy->cap->edit_terms ) )
461 $names = explode(',', $_POST['new'.$taxonomy->name]);
462 $parent = isset($_POST['new'.$taxonomy->name.'_parent']) ? (int) $_POST['new'.$taxonomy->name.'_parent'] : 0;
465 if ( $taxonomy->name == 'category' )
466 $post_category = isset($_POST['post_category']) ? (array) $_POST['post_category'] : array();
468 $post_category = ( isset($_POST['tax_input']) && isset($_POST['tax_input'][$taxonomy->name]) ) ? (array) $_POST['tax_input'][$taxonomy->name] : array();
469 $checked_categories = array_map( 'absint', (array) $post_category );
470 $popular_ids = wp_popular_terms_checklist($taxonomy->name, 0, 10, false);
472 foreach ( $names as $cat_name ) {
473 $cat_name = trim($cat_name);
474 $category_nicename = sanitize_title($cat_name);
475 if ( '' === $category_nicename )
477 if ( !$cat_id = term_exists( $cat_name, $taxonomy->name, $parent ) )
478 $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
479 if ( is_wp_error( $cat_id ) ) {
481 } elseif ( is_array( $cat_id ) ) {
482 $cat_id = $cat_id['term_id'];
484 $checked_categories[] = $cat_id;
485 if ( $parent ) // Do these all at once in a second
490 wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids ));
492 $data = ob_get_clean();
495 'what' => $taxonomy->name,
497 'data' => str_replace( array("\n", "\t"), '', $data),
502 if ( $parent ) { // Foncy - replace the parent and all its children
503 $parent = get_term( $parent, $taxonomy->name );
504 $term_id = $parent->term_id;
506 while ( $parent->parent ) { // get the top parent
507 $parent = get_term( $parent->parent, $taxonomy->name );
508 if ( is_wp_error( $parent ) )
510 $term_id = $parent->term_id;
515 wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids));
517 $data = ob_get_clean();
520 'what' => $taxonomy->name,
522 'data' => str_replace( array("\n", "\t"), '', $data),
529 wp_dropdown_categories( array(
530 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name',
531 'hierarchical' => 1, 'show_option_none' => '— '.$taxonomy->labels->parent_item.' —'
534 $sup = ob_get_clean();
536 $add['supplemental'] = array( 'newcat_parent' => $sup );
538 $x = new WP_Ajax_Response( $add );
543 * Ajax handler for deleting a comment.
547 function wp_ajax_delete_comment() {
548 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
550 if ( !$comment = get_comment( $id ) )
552 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) )
555 check_ajax_referer( "delete-comment_$id" );
556 $status = wp_get_comment_status( $comment );
559 if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) {
560 if ( 'trash' == $status )
562 $r = wp_trash_comment( $comment );
563 } elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) {
564 if ( 'trash' != $status )
566 $r = wp_untrash_comment( $comment );
567 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash
569 } elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) {
570 if ( 'spam' == $status )
572 $r = wp_spam_comment( $comment );
573 } elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) {
574 if ( 'spam' != $status )
576 $r = wp_unspam_comment( $comment );
577 if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam
579 } elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) {
580 $r = wp_delete_comment( $comment );
585 if ( $r ) // Decide if we need to send back '1' or a more complicated response including page links and comment counts
586 _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
591 * Ajax handler for deleting a tag.
595 function wp_ajax_delete_tag() {
596 $tag_id = (int) $_POST['tag_ID'];
597 check_ajax_referer( "delete-tag_$tag_id" );
599 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
600 $tax = get_taxonomy($taxonomy);
602 if ( !current_user_can( $tax->cap->delete_terms ) )
605 $tag = get_term( $tag_id, $taxonomy );
606 if ( !$tag || is_wp_error( $tag ) )
609 if ( wp_delete_term($tag_id, $taxonomy))
616 * Ajax handler for deleting a link.
620 function wp_ajax_delete_link() {
621 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
623 check_ajax_referer( "delete-bookmark_$id" );
624 if ( !current_user_can( 'manage_links' ) )
627 $link = get_bookmark( $id );
628 if ( !$link || is_wp_error( $link ) )
631 if ( wp_delete_link( $id ) )
638 * Ajax handler for deleting meta.
642 function wp_ajax_delete_meta() {
643 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
645 check_ajax_referer( "delete-meta_$id" );
646 if ( !$meta = get_metadata_by_mid( 'post', $id ) )
649 if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) )
651 if ( delete_meta( $meta->meta_id ) )
657 * Ajax handler for deleting a post.
661 * @param string $action Action to perform.
663 function wp_ajax_delete_post( $action ) {
664 if ( empty( $action ) )
665 $action = 'delete-post';
666 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
668 check_ajax_referer( "{$action}_$id" );
669 if ( !current_user_can( 'delete_post', $id ) )
672 if ( !get_post( $id ) )
675 if ( wp_delete_post( $id ) )
682 * Ajax handler for sending a post to the trash.
686 * @param string $action Action to perform.
688 function wp_ajax_trash_post( $action ) {
689 if ( empty( $action ) )
690 $action = 'trash-post';
691 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
693 check_ajax_referer( "{$action}_$id" );
694 if ( !current_user_can( 'delete_post', $id ) )
697 if ( !get_post( $id ) )
700 if ( 'trash-post' == $action )
701 $done = wp_trash_post( $id );
703 $done = wp_untrash_post( $id );
712 * Ajax handler to restore a post from the trash.
716 * @param string $action Action to perform.
718 function wp_ajax_untrash_post( $action ) {
719 if ( empty( $action ) )
720 $action = 'untrash-post';
721 wp_ajax_trash_post( $action );
727 * @param string $action
729 function wp_ajax_delete_page( $action ) {
730 if ( empty( $action ) )
731 $action = 'delete-page';
732 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
734 check_ajax_referer( "{$action}_$id" );
735 if ( !current_user_can( 'delete_page', $id ) )
738 if ( ! get_post( $id ) )
741 if ( wp_delete_post( $id ) )
748 * Ajax handler to dim a comment.
752 function wp_ajax_dim_comment() {
753 $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
755 if ( !$comment = get_comment( $id ) ) {
756 $x = new WP_Ajax_Response( array(
758 'id' => new WP_Error('invalid_comment', sprintf(__('Comment %d does not exist'), $id))
763 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) )
766 $current = wp_get_comment_status( $comment );
767 if ( isset( $_POST['new'] ) && $_POST['new'] == $current )
770 check_ajax_referer( "approve-comment_$id" );
771 if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) {
772 $result = wp_set_comment_status( $comment, 'approve', true );
774 $result = wp_set_comment_status( $comment, 'hold', true );
777 if ( is_wp_error($result) ) {
778 $x = new WP_Ajax_Response( array(
785 // Decide if we need to send back '1' or a more complicated response including page links and comment counts
786 _wp_ajax_delete_comment_response( $comment->comment_ID );
791 * Ajax handler for deleting a link category.
795 * @param string $action Action to perform.
797 function wp_ajax_add_link_category( $action ) {
798 if ( empty( $action ) )
799 $action = 'add-link-category';
800 check_ajax_referer( $action );
801 if ( !current_user_can( 'manage_categories' ) )
803 $names = explode(',', wp_unslash( $_POST['newcat'] ) );
804 $x = new WP_Ajax_Response();
805 foreach ( $names as $cat_name ) {
806 $cat_name = trim($cat_name);
807 $slug = sanitize_title($cat_name);
810 if ( !$cat_id = term_exists( $cat_name, 'link_category' ) )
811 $cat_id = wp_insert_term( $cat_name, 'link_category' );
812 if ( is_wp_error( $cat_id ) ) {
814 } elseif ( is_array( $cat_id ) ) {
815 $cat_id = $cat_id['term_id'];
817 $cat_name = esc_html( $cat_name );
819 'what' => 'link-category',
821 '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>",
829 * Ajax handler to add a tag.
833 * @global WP_List_Table $wp_list_table
835 function wp_ajax_add_tag() {
836 global $wp_list_table;
838 check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
839 $taxonomy = !empty($_POST['taxonomy']) ? $_POST['taxonomy'] : 'post_tag';
840 $tax = get_taxonomy($taxonomy);
842 if ( !current_user_can( $tax->cap->edit_terms ) )
845 $x = new WP_Ajax_Response();
847 $tag = wp_insert_term($_POST['tag-name'], $taxonomy, $_POST );
849 if ( !$tag || is_wp_error($tag) || (!$tag = get_term( $tag['term_id'], $taxonomy )) ) {
850 $message = __('An error has occurred. Please reload the page and try again.');
851 if ( is_wp_error($tag) && $tag->get_error_message() )
852 $message = $tag->get_error_message();
855 'what' => 'taxonomy',
856 'data' => new WP_Error('error', $message )
861 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
864 if ( is_taxonomy_hierarchical($taxonomy) ) {
865 $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
867 $wp_list_table->single_row( $tag, $level );
868 $noparents = ob_get_clean();
872 $wp_list_table->single_row( $tag );
873 $parents = ob_get_clean();
876 'what' => 'taxonomy',
877 'supplemental' => compact('parents', 'noparents')
881 'position' => $level,
882 'supplemental' => (array) $tag
888 * Ajax handler for getting a tagcloud.
892 function wp_ajax_get_tagcloud() {
893 if ( ! isset( $_POST['tax'] ) ) {
897 $taxonomy = sanitize_key( $_POST['tax'] );
898 $tax = get_taxonomy( $taxonomy );
903 if ( ! current_user_can( $tax->cap->assign_terms ) ) {
907 $tags = get_terms( $taxonomy, array( 'number' => 45, 'orderby' => 'count', 'order' => 'DESC' ) );
909 if ( empty( $tags ) )
910 wp_die( $tax->labels->not_found );
912 if ( is_wp_error( $tags ) )
913 wp_die( $tags->get_error_message() );
915 foreach ( $tags as $key => $tag ) {
916 $tags[ $key ]->link = '#';
917 $tags[ $key ]->id = $tag->term_id;
920 // We need raw tag names here, so don't filter the output
921 $return = wp_generate_tag_cloud( $tags, array('filter' => 0) );
923 if ( empty($return) )
932 * Ajax handler for getting comments.
936 * @global WP_List_Table $wp_list_table
937 * @global int $post_id
939 * @param string $action Action to perform.
941 function wp_ajax_get_comments( $action ) {
942 global $wp_list_table, $post_id;
943 if ( empty( $action ) )
944 $action = 'get-comments';
946 check_ajax_referer( $action );
948 if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
949 $id = absint( $_REQUEST['p'] );
950 if ( ! empty( $id ) )
954 if ( empty( $post_id ) )
957 $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
959 if ( ! current_user_can( 'edit_post', $post_id ) )
962 $wp_list_table->prepare_items();
964 if ( !$wp_list_table->has_items() )
967 $x = new WP_Ajax_Response();
969 foreach ( $wp_list_table->items as $comment ) {
970 if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved )
972 get_comment( $comment );
973 $wp_list_table->single_row( $comment );
975 $comment_list_item = ob_get_clean();
978 'what' => 'comments',
979 'data' => $comment_list_item
985 * Ajax handler for replying to a comment.
989 * @global WP_List_Table $wp_list_table
991 * @param string $action Action to perform.
993 function wp_ajax_replyto_comment( $action ) {
994 global $wp_list_table;
995 if ( empty( $action ) )
996 $action = 'replyto-comment';
998 check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
1000 $comment_post_ID = (int) $_POST['comment_post_ID'];
1001 $post = get_post( $comment_post_ID );
1005 if ( !current_user_can( 'edit_post', $comment_post_ID ) )
1008 if ( empty( $post->post_status ) )
1010 elseif ( in_array($post->post_status, array('draft', 'pending', 'trash') ) )
1011 wp_die( __('ERROR: you are replying to a comment on a draft post.') );
1013 $user = wp_get_current_user();
1014 if ( $user->exists() ) {
1015 $user_ID = $user->ID;
1016 $comment_author = wp_slash( $user->display_name );
1017 $comment_author_email = wp_slash( $user->user_email );
1018 $comment_author_url = wp_slash( $user->user_url );
1019 $comment_content = trim( $_POST['content'] );
1020 $comment_type = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
1021 if ( current_user_can( 'unfiltered_html' ) ) {
1022 if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) )
1023 $_POST['_wp_unfiltered_html_comment'] = '';
1025 if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
1026 kses_remove_filters(); // start with a clean slate
1027 kses_init_filters(); // set up the filters
1031 wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
1034 if ( '' == $comment_content )
1035 wp_die( __( 'ERROR: please type a comment.' ) );
1037 $comment_parent = 0;
1038 if ( isset( $_POST['comment_ID'] ) )
1039 $comment_parent = absint( $_POST['comment_ID'] );
1040 $comment_auto_approved = false;
1041 $commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
1043 // Automatically approve parent comment.
1044 if ( !empty($_POST['approve_parent']) ) {
1045 $parent = get_comment( $comment_parent );
1047 if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
1048 if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
1052 if ( wp_set_comment_status( $parent, 'approve' ) )
1053 $comment_auto_approved = true;
1057 $comment_id = wp_new_comment( $commentdata );
1058 $comment = get_comment($comment_id);
1059 if ( ! $comment ) wp_die( 1 );
1061 $position = ( isset($_POST['position']) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1064 if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
1065 require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
1066 _wp_dashboard_recent_comments_row( $comment );
1068 if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
1069 $wp_list_table = _get_list_table('WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1071 $wp_list_table = _get_list_table('WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1073 $wp_list_table->single_row( $comment );
1075 $comment_list_item = ob_get_clean();
1078 'what' => 'comment',
1079 'id' => $comment->comment_ID,
1080 'data' => $comment_list_item,
1081 'position' => $position
1084 $counts = wp_count_comments();
1085 $response['supplemental'] = array(
1086 'in_moderation' => $counts->moderated,
1087 'i18n_comments_text' => sprintf(
1088 _n( '%s Comment', '%s Comments', $counts->approved ),
1089 number_format_i18n( $counts->approved )
1091 'i18n_moderation_text' => sprintf(
1092 _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
1093 number_format_i18n( $counts->moderated )
1097 if ( $comment_auto_approved ) {
1098 $response['supplemental']['parent_approved'] = $parent->comment_ID;
1099 $response['supplemental']['parent_post_id'] = $parent->comment_post_ID;
1102 $x = new WP_Ajax_Response();
1103 $x->add( $response );
1108 * Ajax handler for editing a comment.
1112 * @global WP_List_Table $wp_list_table
1114 function wp_ajax_edit_comment() {
1115 global $wp_list_table;
1117 check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1119 $comment_id = (int) $_POST['comment_ID'];
1120 if ( ! current_user_can( 'edit_comment', $comment_id ) )
1123 if ( '' == $_POST['content'] )
1124 wp_die( __( 'ERROR: please type a comment.' ) );
1126 if ( isset( $_POST['status'] ) )
1127 $_POST['comment_status'] = $_POST['status'];
1130 $position = ( isset($_POST['position']) && (int) $_POST['position']) ? (int) $_POST['position'] : '-1';
1131 $checkbox = ( isset($_POST['checkbox']) && true == $_POST['checkbox'] ) ? 1 : 0;
1132 $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1134 $comment = get_comment( $comment_id );
1135 if ( empty( $comment->comment_ID ) )
1139 $wp_list_table->single_row( $comment );
1140 $comment_list_item = ob_get_clean();
1142 $x = new WP_Ajax_Response();
1145 'what' => 'edit_comment',
1146 'id' => $comment->comment_ID,
1147 'data' => $comment_list_item,
1148 'position' => $position
1155 * Ajax handler for adding a menu item.
1159 function wp_ajax_add_menu_item() {
1160 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1162 if ( ! current_user_can( 'edit_theme_options' ) )
1165 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1167 // For performance reasons, we omit some object properties from the checklist.
1168 // The following is a hacky way to restore them when adding non-custom items.
1170 $menu_items_data = array();
1171 foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1173 ! empty( $menu_item_data['menu-item-type'] ) &&
1174 'custom' != $menu_item_data['menu-item-type'] &&
1175 ! empty( $menu_item_data['menu-item-object-id'] )
1177 switch( $menu_item_data['menu-item-type'] ) {
1179 $_object = get_post( $menu_item_data['menu-item-object-id'] );
1182 case 'post_type_archive' :
1183 $_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1187 $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1191 $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1192 $_menu_item = reset( $_menu_items );
1194 // Restore the missing menu item properties
1195 $menu_item_data['menu-item-description'] = $_menu_item->description;
1198 $menu_items_data[] = $menu_item_data;
1201 $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1202 if ( is_wp_error( $item_ids ) )
1205 $menu_items = array();
1207 foreach ( (array) $item_ids as $menu_item_id ) {
1208 $menu_obj = get_post( $menu_item_id );
1209 if ( ! empty( $menu_obj->ID ) ) {
1210 $menu_obj = wp_setup_nav_menu_item( $menu_obj );
1211 $menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1212 $menu_items[] = $menu_obj;
1216 /** This filter is documented in wp-admin/includes/nav-menu.php */
1217 $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1219 if ( ! class_exists( $walker_class_name ) )
1222 if ( ! empty( $menu_items ) ) {
1227 'link_before' => '',
1228 'walker' => new $walker_class_name,
1230 echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1236 * Ajax handler for adding meta.
1240 function wp_ajax_add_meta() {
1241 check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1243 $pid = (int) $_POST['post_id'];
1244 $post = get_post( $pid );
1246 if ( isset($_POST['metakeyselect']) || isset($_POST['metakeyinput']) ) {
1247 if ( !current_user_can( 'edit_post', $pid ) )
1249 if ( isset($_POST['metakeyselect']) && '#NONE#' == $_POST['metakeyselect'] && empty($_POST['metakeyinput']) )
1252 // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1253 if ( $post->post_status == 'auto-draft' ) {
1254 $post_data = array();
1255 $post_data['action'] = 'draft'; // Warning fix
1256 $post_data['post_ID'] = $pid;
1257 $post_data['post_type'] = $post->post_type;
1258 $post_data['post_status'] = 'draft';
1259 $now = current_time('timestamp', 1);
1260 $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) );
1262 $pid = edit_post( $post_data );
1264 if ( is_wp_error( $pid ) ) {
1265 $x = new WP_Ajax_Response( array(
1272 if ( !$mid = add_meta( $pid ) )
1273 wp_die( __( 'Please provide a custom field value.' ) );
1277 } elseif ( ! $mid = add_meta( $pid ) ) {
1278 wp_die( __( 'Please provide a custom field value.' ) );
1281 $meta = get_metadata_by_mid( 'post', $mid );
1282 $pid = (int) $meta->post_id;
1283 $meta = get_object_vars( $meta );
1284 $x = new WP_Ajax_Response( array(
1287 'data' => _list_meta_row( $meta, $c ),
1289 'supplemental' => array('postid' => $pid)
1292 $mid = (int) key( $_POST['meta'] );
1293 $key = wp_unslash( $_POST['meta'][$mid]['key'] );
1294 $value = wp_unslash( $_POST['meta'][$mid]['value'] );
1295 if ( '' == trim($key) )
1296 wp_die( __( 'Please provide a custom field name.' ) );
1297 if ( '' == trim($value) )
1298 wp_die( __( 'Please provide a custom field value.' ) );
1299 if ( ! $meta = get_metadata_by_mid( 'post', $mid ) )
1300 wp_die( 0 ); // if meta doesn't exist
1301 if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1302 ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1303 ! current_user_can( 'edit_post_meta', $meta->post_id, $key ) )
1305 if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1306 if ( !$u = update_metadata_by_mid( 'post', $mid, $value, $key ) )
1307 wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1310 $x = new WP_Ajax_Response( array(
1312 'id' => $mid, 'old_id' => $mid,
1313 'data' => _list_meta_row( array(
1315 'meta_value' => $value,
1319 'supplemental' => array('postid' => $meta->post_id)
1326 * Ajax handler for adding a user.
1330 * @global WP_List_Table $wp_list_table
1332 * @param string $action Action to perform.
1334 function wp_ajax_add_user( $action ) {
1335 global $wp_list_table;
1336 if ( empty( $action ) )
1337 $action = 'add-user';
1339 check_ajax_referer( $action );
1340 if ( ! current_user_can('create_users') )
1342 if ( ! $user_id = edit_user() ) {
1344 } elseif ( is_wp_error( $user_id ) ) {
1345 $x = new WP_Ajax_Response( array(
1351 $user_object = get_userdata( $user_id );
1353 $wp_list_table = _get_list_table('WP_Users_List_Table');
1355 $role = current( $user_object->roles );
1357 $x = new WP_Ajax_Response( array(
1360 'data' => $wp_list_table->single_row( $user_object, '', $role ),
1361 'supplemental' => array(
1362 'show-link' => sprintf(
1363 /* translators: %s: the new user */
1364 __( 'User %s added' ),
1365 '<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1374 * Ajax handler for closed post boxes.
1378 function wp_ajax_closed_postboxes() {
1379 check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1380 $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed']) : array();
1381 $closed = array_filter($closed);
1383 $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden']) : array();
1384 $hidden = array_filter($hidden);
1386 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1388 if ( $page != sanitize_key( $page ) )
1391 if ( ! $user = wp_get_current_user() )
1394 if ( is_array($closed) )
1395 update_user_option($user->ID, "closedpostboxes_$page", $closed, true);
1397 if ( is_array($hidden) ) {
1398 $hidden = array_diff( $hidden, array('submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu') ); // postboxes that are always shown
1399 update_user_option($user->ID, "metaboxhidden_$page", $hidden, true);
1406 * Ajax handler for hidden columns.
1410 function wp_ajax_hidden_columns() {
1411 check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1412 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1414 if ( $page != sanitize_key( $page ) )
1417 if ( ! $user = wp_get_current_user() )
1420 $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1421 update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true );
1427 * Ajax handler for updating whether to display the welcome panel.
1431 function wp_ajax_update_welcome_panel() {
1432 check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1434 if ( ! current_user_can( 'edit_theme_options' ) )
1437 update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1443 * Ajax handler for retrieving menu meta boxes.
1447 function wp_ajax_menu_get_metabox() {
1448 if ( ! current_user_can( 'edit_theme_options' ) )
1451 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1453 if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1455 $callback = 'wp_nav_menu_item_post_type_meta_box';
1456 $items = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1457 } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1459 $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1460 $items = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1463 if ( ! empty( $_POST['item-object'] ) && isset( $items[$_POST['item-object']] ) ) {
1464 $menus_meta_box_object = $items[ $_POST['item-object'] ];
1466 /** This filter is documented in wp-admin/includes/nav-menu.php */
1467 $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1469 call_user_func_array($callback, array(
1472 'id' => 'add-' . $item->name,
1473 'title' => $item->labels->name,
1474 'callback' => $callback,
1479 $markup = ob_get_clean();
1481 echo wp_json_encode(array(
1482 'replace-id' => $type . '-' . $item->name,
1483 'markup' => $markup,
1491 * Ajax handler for internal linking.
1495 function wp_ajax_wp_link_ajax() {
1496 check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1500 if ( isset( $_POST['search'] ) ) {
1501 $args['s'] = wp_unslash( $_POST['search'] );
1504 if ( isset( $_POST['term'] ) ) {
1505 $args['s'] = wp_unslash( $_POST['term'] );
1508 $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1510 require(ABSPATH . WPINC . '/class-wp-editor.php');
1511 $results = _WP_Editors::wp_link_query( $args );
1513 if ( ! isset( $results ) )
1516 echo wp_json_encode( $results );
1523 * Ajax handler for menu locations save.
1527 function wp_ajax_menu_locations_save() {
1528 if ( ! current_user_can( 'edit_theme_options' ) )
1530 check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1531 if ( ! isset( $_POST['menu-locations'] ) )
1533 set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1538 * Ajax handler for saving the meta box order.
1542 function wp_ajax_meta_box_order() {
1543 check_ajax_referer( 'meta-box-order' );
1544 $order = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1545 $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1547 if ( $page_columns != 'auto' )
1548 $page_columns = (int) $page_columns;
1550 $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1552 if ( $page != sanitize_key( $page ) )
1555 if ( ! $user = wp_get_current_user() )
1559 update_user_option($user->ID, "meta-box-order_$page", $order, true);
1561 if ( $page_columns )
1562 update_user_option($user->ID, "screen_layout_$page", $page_columns, true);
1568 * Ajax handler for menu quick searching.
1572 function wp_ajax_menu_quick_search() {
1573 if ( ! current_user_can( 'edit_theme_options' ) )
1576 require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1578 _wp_ajax_menu_quick_search( $_POST );
1584 * Ajax handler to retrieve a permalink.
1588 function wp_ajax_get_permalink() {
1589 check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1590 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1591 wp_die( get_preview_post_link( $post_id ) );
1595 * Ajax handler to retrieve a sample permalink.
1599 function wp_ajax_sample_permalink() {
1600 check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1601 $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0;
1602 $title = isset($_POST['new_title'])? $_POST['new_title'] : '';
1603 $slug = isset($_POST['new_slug'])? $_POST['new_slug'] : null;
1604 wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1608 * Ajax handler for Quick Edit saving a post from a list table.
1612 * @global WP_List_Table $wp_list_table
1614 function wp_ajax_inline_save() {
1615 global $wp_list_table, $mode;
1617 check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1619 if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
1622 if ( 'page' == $_POST['post_type'] ) {
1623 if ( ! current_user_can( 'edit_page', $post_ID ) )
1624 wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
1626 if ( ! current_user_can( 'edit_post', $post_ID ) )
1627 wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1630 if ( $last = wp_check_post_lock( $post_ID ) ) {
1631 $last_user = get_userdata( $last );
1632 $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1633 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 ) );
1639 $post = get_post( $post_ID, ARRAY_A );
1641 // Since it's coming from the database.
1642 $post = wp_slash($post);
1644 $data['content'] = $post['post_content'];
1645 $data['excerpt'] = $post['post_excerpt'];
1648 $data['user_ID'] = get_current_user_id();
1650 if ( isset($data['post_parent']) )
1651 $data['parent_id'] = $data['post_parent'];
1654 if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1655 $data['visibility'] = 'private';
1656 $data['post_status'] = 'private';
1658 $data['post_status'] = $data['_status'];
1661 if ( empty($data['comment_status']) )
1662 $data['comment_status'] = 'closed';
1663 if ( empty($data['ping_status']) )
1664 $data['ping_status'] = 'closed';
1666 // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1667 if ( ! empty( $data['tax_input'] ) ) {
1668 foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1669 $tax_object = get_taxonomy( $taxonomy );
1670 /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1671 if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1672 unset( $data['tax_input'][ $taxonomy ] );
1677 // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1678 if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1679 $post['post_status'] = 'publish';
1680 $data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1686 $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1688 $mode = $_POST['post_view'] === 'excerpt' ? 'excerpt' : 'list';
1691 if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
1692 $request_post = array( get_post( $_POST['post_ID'] ) );
1693 $parent = $request_post[0]->post_parent;
1695 while ( $parent > 0 ) {
1696 $parent_post = get_post( $parent );
1697 $parent = $parent_post->post_parent;
1702 $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1708 * Ajax handler for quick edit saving for a term.
1712 * @global WP_List_Table $wp_list_table
1714 function wp_ajax_inline_save_tax() {
1715 global $wp_list_table;
1717 check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1719 $taxonomy = sanitize_key( $_POST['taxonomy'] );
1720 $tax = get_taxonomy( $taxonomy );
1724 if ( ! current_user_can( $tax->cap->edit_terms ) )
1727 $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1729 if ( ! isset($_POST['tax_ID']) || ! ( $id = (int) $_POST['tax_ID'] ) )
1732 $tag = get_term( $id, $taxonomy );
1733 $_POST['description'] = $tag->description;
1735 $updated = wp_update_term($id, $taxonomy, $_POST);
1736 if ( $updated && !is_wp_error($updated) ) {
1737 $tag = get_term( $updated['term_id'], $taxonomy );
1738 if ( !$tag || is_wp_error( $tag ) ) {
1739 if ( is_wp_error($tag) && $tag->get_error_message() )
1740 wp_die( $tag->get_error_message() );
1741 wp_die( __( 'Item not updated.' ) );
1744 if ( is_wp_error($updated) && $updated->get_error_message() )
1745 wp_die( $updated->get_error_message() );
1746 wp_die( __( 'Item not updated.' ) );
1749 $parent = $tag->parent;
1750 while ( $parent > 0 ) {
1751 $parent_tag = get_term( $parent, $taxonomy );
1752 $parent = $parent_tag->parent;
1755 $wp_list_table->single_row( $tag, $level );
1760 * Ajax handler for querying posts for the Find Posts modal.
1762 * @see window.findPosts
1766 function wp_ajax_find_posts() {
1767 check_ajax_referer( 'find-posts' );
1769 $post_types = get_post_types( array( 'public' => true ), 'objects' );
1770 unset( $post_types['attachment'] );
1772 $s = wp_unslash( $_POST['ps'] );
1774 'post_type' => array_keys( $post_types ),
1775 'post_status' => 'any',
1776 'posts_per_page' => 50,
1781 $posts = get_posts( $args );
1784 wp_send_json_error( __( 'No items found.' ) );
1787 $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>';
1789 foreach ( $posts as $post ) {
1790 $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1791 $alt = ( 'alternate' == $alt ) ? '' : 'alternate';
1793 switch ( $post->post_status ) {
1796 $stat = __('Published');
1799 $stat = __('Scheduled');
1802 $stat = __('Pending Review');
1805 $stat = __('Draft');
1809 if ( '0000-00-00 00:00:00' == $post->post_date ) {
1812 /* translators: date format in table columns, see https://secure.php.net/date */
1813 $time = mysql2date(__('Y/m/d'), $post->post_date);
1816 $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>';
1817 $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";
1820 $html .= '</tbody></table>';
1822 wp_send_json_success( $html );
1826 * Ajax handler for saving the widgets order.
1830 function wp_ajax_widgets_order() {
1831 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1833 if ( !current_user_can('edit_theme_options') )
1836 unset( $_POST['savewidgets'], $_POST['action'] );
1838 // Save widgets order for all sidebars.
1839 if ( is_array($_POST['sidebars']) ) {
1840 $sidebars = array();
1841 foreach ( $_POST['sidebars'] as $key => $val ) {
1843 if ( !empty($val) ) {
1844 $val = explode(',', $val);
1845 foreach ( $val as $k => $v ) {
1846 if ( strpos($v, 'widget-') === false )
1849 $sb[$k] = substr($v, strpos($v, '_') + 1);
1852 $sidebars[$key] = $sb;
1854 wp_set_sidebars_widgets($sidebars);
1862 * Ajax handler for saving a widget.
1866 * @global array $wp_registered_widgets
1867 * @global array $wp_registered_widget_controls
1868 * @global array $wp_registered_widget_updates
1870 function wp_ajax_save_widget() {
1871 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
1873 check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
1875 if ( !current_user_can('edit_theme_options') || !isset($_POST['id_base']) )
1878 unset( $_POST['savewidgets'], $_POST['action'] );
1881 * Fires early when editing the widgets displayed in sidebars.
1885 do_action( 'load-widgets.php' );
1888 * Fires early when editing the widgets displayed in sidebars.
1892 do_action( 'widgets.php' );
1894 /** This action is documented in wp-admin/widgets.php */
1895 do_action( 'sidebar_admin_setup' );
1897 $id_base = $_POST['id_base'];
1898 $widget_id = $_POST['widget-id'];
1899 $sidebar_id = $_POST['sidebar'];
1900 $multi_number = !empty($_POST['multi_number']) ? (int) $_POST['multi_number'] : 0;
1901 $settings = isset($_POST['widget-' . $id_base]) && is_array($_POST['widget-' . $id_base]) ? $_POST['widget-' . $id_base] : false;
1902 $error = '<p>' . __('An error has occurred. Please reload the page and try again.') . '</p>';
1904 $sidebars = wp_get_sidebars_widgets();
1905 $sidebar = isset($sidebars[$sidebar_id]) ? $sidebars[$sidebar_id] : array();
1908 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1910 if ( !isset($wp_registered_widgets[$widget_id]) )
1913 $sidebar = array_diff( $sidebar, array($widget_id) );
1914 $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1');
1916 /** This action is documented in wp-admin/widgets.php */
1917 do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
1919 } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) {
1920 if ( !$multi_number )
1923 $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
1924 $widget_id = $id_base . '-' . $multi_number;
1925 $sidebar[] = $widget_id;
1927 $_POST['widget-id'] = $sidebar;
1929 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1931 if ( $name == $id_base ) {
1932 if ( !is_callable( $control['callback'] ) )
1936 call_user_func_array( $control['callback'], $control['params'] );
1942 if ( isset($_POST['delete_widget']) && $_POST['delete_widget'] ) {
1943 $sidebars[$sidebar_id] = $sidebar;
1944 wp_set_sidebars_widgets($sidebars);
1945 echo "deleted:$widget_id";
1949 if ( !empty($_POST['add_new']) )
1952 if ( $form = $wp_registered_widget_controls[$widget_id] )
1953 call_user_func_array( $form['callback'], $form['params'] );
1959 * Ajax handler for saving a widget.
1963 * @global WP_Customize_Manager $wp_customize
1965 function wp_ajax_update_widget() {
1966 global $wp_customize;
1967 $wp_customize->widgets->wp_ajax_update_widget();
1971 * Ajax handler for removing inactive widgets.
1975 function wp_ajax_delete_inactive_widgets() {
1976 check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
1978 if ( ! current_user_can( 'edit_theme_options' ) ) {
1982 unset( $_POST['removeinactivewidgets'], $_POST['action'] );
1984 do_action( 'load-widgets.php' );
1985 do_action( 'widgets.php' );
1986 do_action( 'sidebar_admin_setup' );
1988 $sidebars_widgets = wp_get_sidebars_widgets();
1990 foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
1991 $pieces = explode( '-', $widget_id );
1992 $multi_number = array_pop( $pieces );
1993 $id_base = implode( '-', $pieces );
1994 $widget = get_option( 'widget_' . $id_base );
1995 unset( $widget[$multi_number] );
1996 update_option( 'widget_' . $id_base, $widget );
1997 unset( $sidebars_widgets['wp_inactive_widgets'][$key] );
2000 wp_set_sidebars_widgets( $sidebars_widgets );
2006 * Ajax handler for uploading attachments
2010 function wp_ajax_upload_attachment() {
2011 check_ajax_referer( 'media-form' );
2013 * This function does not use wp_send_json_success() / wp_send_json_error()
2014 * as the html4 Plupload handler requires a text/html content-type for older IE.
2015 * See https://core.trac.wordpress.org/ticket/31037
2018 if ( ! current_user_can( 'upload_files' ) ) {
2019 echo wp_json_encode( array(
2022 'message' => __( 'Sorry, you are not allowed to upload files.' ),
2023 'filename' => $_FILES['async-upload']['name'],
2030 if ( isset( $_REQUEST['post_id'] ) ) {
2031 $post_id = $_REQUEST['post_id'];
2032 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2033 echo wp_json_encode( array(
2036 'message' => __( "You don't have permission to attach files to this post." ),
2037 'filename' => $_FILES['async-upload']['name'],
2047 $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
2049 // If the context is custom header or background, make sure the uploaded file is an image.
2050 if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
2051 $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2052 if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2053 echo wp_json_encode( array(
2056 'message' => __( 'The uploaded file is not a valid image. Please try again.' ),
2057 'filename' => $_FILES['async-upload']['name'],
2065 $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2067 if ( is_wp_error( $attachment_id ) ) {
2068 echo wp_json_encode( array(
2071 'message' => $attachment_id->get_error_message(),
2072 'filename' => $_FILES['async-upload']['name'],
2079 if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2080 if ( 'custom-background' === $post_data['context'] )
2081 update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2083 if ( 'custom-header' === $post_data['context'] )
2084 update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2087 if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) )
2090 echo wp_json_encode( array(
2092 'data' => $attachment,
2099 * Ajax handler for image editing.
2103 function wp_ajax_image_editor() {
2104 $attachment_id = intval($_POST['postid']);
2105 if ( empty($attachment_id) || !current_user_can('edit_post', $attachment_id) )
2108 check_ajax_referer( "image_editor-$attachment_id" );
2109 include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
2112 switch ( $_POST['do'] ) {
2114 $msg = wp_save_image($attachment_id);
2115 $msg = wp_json_encode($msg);
2119 $msg = wp_save_image($attachment_id);
2122 $msg = wp_restore_image($attachment_id);
2126 wp_image_editor($attachment_id, $msg);
2131 * Ajax handler for setting the featured image.
2135 function wp_ajax_set_post_thumbnail() {
2136 $json = ! empty( $_REQUEST['json'] ); // New-style request
2138 $post_ID = intval( $_POST['post_id'] );
2139 if ( ! current_user_can( 'edit_post', $post_ID ) )
2142 $thumbnail_id = intval( $_POST['thumbnail_id'] );
2145 check_ajax_referer( "update-post_$post_ID" );
2147 check_ajax_referer( "set_post_thumbnail-$post_ID" );
2149 if ( $thumbnail_id == '-1' ) {
2150 if ( delete_post_thumbnail( $post_ID ) ) {
2151 $return = _wp_post_thumbnail_html( null, $post_ID );
2152 $json ? wp_send_json_success( $return ) : wp_die( $return );
2158 if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2159 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2160 $json ? wp_send_json_success( $return ) : wp_die( $return );
2167 * Ajax handler for retrieving HTML for the featured image.
2171 function wp_ajax_get_post_thumbnail_html() {
2172 $post_ID = intval( $_POST['post_id'] );
2174 check_ajax_referer( "update-post_$post_ID" );
2176 if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2180 $thumbnail_id = intval( $_POST['thumbnail_id'] );
2182 // For backward compatibility, -1 refers to no featured image.
2183 if ( -1 === $thumbnail_id ) {
2184 $thumbnail_id = null;
2187 $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2188 wp_send_json_success( $return );
2192 * Ajax handler for setting the featured image for an attachment.
2196 * @see set_post_thumbnail()
2198 function wp_ajax_set_attachment_thumbnail() {
2199 if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2200 wp_send_json_error();
2203 $thumbnail_id = (int) $_POST['thumbnail_id'];
2204 if ( empty( $thumbnail_id ) ) {
2205 wp_send_json_error();
2208 $post_ids = array();
2209 // For each URL, try to find its corresponding post ID.
2210 foreach ( $_POST['urls'] as $url ) {
2211 $post_id = attachment_url_to_postid( $url );
2212 if ( ! empty( $post_id ) ) {
2213 $post_ids[] = $post_id;
2217 if ( empty( $post_ids ) ) {
2218 wp_send_json_error();
2222 // For each found attachment, set its thumbnail.
2223 foreach ( $post_ids as $post_id ) {
2224 if ( ! current_user_can( 'edit_post', $post_id ) ) {
2228 if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2233 if ( 0 === $success ) {
2234 wp_send_json_error();
2236 wp_send_json_success();
2239 wp_send_json_error();
2243 * Ajax handler for date formatting.
2247 function wp_ajax_date_format() {
2248 wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2252 * Ajax handler for time formatting.
2256 function wp_ajax_time_format() {
2257 wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2261 * Ajax handler for saving posts from the fullscreen editor.
2266 function wp_ajax_wp_fullscreen_save_post() {
2267 $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2272 $post = get_post( $post_id );
2274 check_ajax_referer('update-post_' . $post_id, '_wpnonce');
2276 $post_id = edit_post();
2278 if ( is_wp_error( $post_id ) ) {
2279 wp_send_json_error();
2283 $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2284 $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2286 $last_date = date_i18n( __( 'F j, Y' ) );
2287 $last_time = date_i18n( __( 'g:i a' ) );
2290 if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2291 $last_user = get_userdata( $last_id );
2292 $last_edited = sprintf( __('Last edited by %1$s on %2$s at %3$s'), esc_html( $last_user->display_name ), $last_date, $last_time );
2294 $last_edited = sprintf( __('Last edited on %1$s at %2$s'), $last_date, $last_time );
2297 wp_send_json_success( array( 'last_edited' => $last_edited ) );
2301 * Ajax handler for removing a post lock.
2305 function wp_ajax_wp_remove_post_lock() {
2306 if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) )
2308 $post_id = (int) $_POST['post_ID'];
2309 if ( ! $post = get_post( $post_id ) )
2312 check_ajax_referer( 'update-post_' . $post_id );
2314 if ( ! current_user_can( 'edit_post', $post_id ) )
2317 $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2318 if ( $active_lock[1] != get_current_user_id() )
2322 * Filters the post lock window duration.
2326 * @param int $interval The interval in seconds the post lock duration
2327 * should last, plus 5 seconds. Default 150.
2329 $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2330 update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2335 * Ajax handler for dismissing a WordPress pointer.
2339 function wp_ajax_dismiss_wp_pointer() {
2340 $pointer = $_POST['pointer'];
2341 if ( $pointer != sanitize_key( $pointer ) )
2344 // check_ajax_referer( 'dismiss-pointer_' . $pointer );
2346 $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2348 if ( in_array( $pointer, $dismissed ) )
2351 $dismissed[] = $pointer;
2352 $dismissed = implode( ',', $dismissed );
2354 update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2359 * Ajax handler for getting an attachment.
2363 function wp_ajax_get_attachment() {
2364 if ( ! isset( $_REQUEST['id'] ) )
2365 wp_send_json_error();
2367 if ( ! $id = absint( $_REQUEST['id'] ) )
2368 wp_send_json_error();
2370 if ( ! $post = get_post( $id ) )
2371 wp_send_json_error();
2373 if ( 'attachment' != $post->post_type )
2374 wp_send_json_error();
2376 if ( ! current_user_can( 'upload_files' ) )
2377 wp_send_json_error();
2379 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2380 wp_send_json_error();
2382 wp_send_json_success( $attachment );
2386 * Ajax handler for querying attachments.
2390 function wp_ajax_query_attachments() {
2391 if ( ! current_user_can( 'upload_files' ) )
2392 wp_send_json_error();
2394 $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2396 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
2397 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
2399 foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2400 if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2401 $keys[] = $t->query_var;
2405 $query = array_intersect_key( $query, array_flip( $keys ) );
2406 $query['post_type'] = 'attachment';
2408 && ! empty( $_REQUEST['query']['post_status'] )
2409 && 'trash' === $_REQUEST['query']['post_status'] ) {
2410 $query['post_status'] = 'trash';
2412 $query['post_status'] = 'inherit';
2415 if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
2416 $query['post_status'] .= ',private';
2419 * Filters the arguments passed to WP_Query during an Ajax
2420 * call for querying attachments.
2424 * @see WP_Query::parse_query()
2426 * @param array $query An array of query variables.
2428 $query = apply_filters( 'ajax_query_attachments_args', $query );
2429 $query = new WP_Query( $query );
2431 $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2432 $posts = array_filter( $posts );
2434 wp_send_json_success( $posts );
2438 * Ajax handler for updating attachment attributes.
2442 function wp_ajax_save_attachment() {
2443 if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
2444 wp_send_json_error();
2446 if ( ! $id = absint( $_REQUEST['id'] ) )
2447 wp_send_json_error();
2449 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2451 if ( ! current_user_can( 'edit_post', $id ) )
2452 wp_send_json_error();
2454 $changes = $_REQUEST['changes'];
2455 $post = get_post( $id, ARRAY_A );
2457 if ( 'attachment' != $post['post_type'] )
2458 wp_send_json_error();
2460 if ( isset( $changes['parent'] ) )
2461 $post['post_parent'] = $changes['parent'];
2463 if ( isset( $changes['title'] ) )
2464 $post['post_title'] = $changes['title'];
2466 if ( isset( $changes['caption'] ) )
2467 $post['post_excerpt'] = $changes['caption'];
2469 if ( isset( $changes['description'] ) )
2470 $post['post_content'] = $changes['description'];
2472 if ( MEDIA_TRASH && isset( $changes['status'] ) )
2473 $post['post_status'] = $changes['status'];
2475 if ( isset( $changes['alt'] ) ) {
2476 $alt = wp_unslash( $changes['alt'] );
2477 if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2478 $alt = wp_strip_all_tags( $alt, true );
2479 update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2483 if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2485 $id3data = wp_get_attachment_metadata( $post['ID'] );
2486 if ( ! is_array( $id3data ) ) {
2490 foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2491 if ( isset( $changes[ $key ] ) ) {
2493 $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2498 wp_update_attachment_metadata( $id, $id3data );
2502 if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2503 wp_delete_post( $id );
2505 wp_update_post( $post );
2508 wp_send_json_success();
2512 * Ajax handler for saving backward compatible attachment attributes.
2516 function wp_ajax_save_attachment_compat() {
2517 if ( ! isset( $_REQUEST['id'] ) )
2518 wp_send_json_error();
2520 if ( ! $id = absint( $_REQUEST['id'] ) )
2521 wp_send_json_error();
2523 if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
2524 wp_send_json_error();
2525 $attachment_data = $_REQUEST['attachments'][ $id ];
2527 check_ajax_referer( 'update-post_' . $id, 'nonce' );
2529 if ( ! current_user_can( 'edit_post', $id ) )
2530 wp_send_json_error();
2532 $post = get_post( $id, ARRAY_A );
2534 if ( 'attachment' != $post['post_type'] )
2535 wp_send_json_error();
2537 /** This filter is documented in wp-admin/includes/media.php */
2538 $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2540 if ( isset( $post['errors'] ) ) {
2541 $errors = $post['errors']; // @todo return me and display me!
2542 unset( $post['errors'] );
2545 wp_update_post( $post );
2547 foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2548 if ( isset( $attachment_data[ $taxonomy ] ) )
2549 wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2552 if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
2553 wp_send_json_error();
2555 wp_send_json_success( $attachment );
2559 * Ajax handler for saving the attachment order.
2563 function wp_ajax_save_attachment_order() {
2564 if ( ! isset( $_REQUEST['post_id'] ) )
2565 wp_send_json_error();
2567 if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
2568 wp_send_json_error();
2570 if ( empty( $_REQUEST['attachments'] ) )
2571 wp_send_json_error();
2573 check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2575 $attachments = $_REQUEST['attachments'];
2577 if ( ! current_user_can( 'edit_post', $post_id ) )
2578 wp_send_json_error();
2580 foreach ( $attachments as $attachment_id => $menu_order ) {
2581 if ( ! current_user_can( 'edit_post', $attachment_id ) )
2583 if ( ! $attachment = get_post( $attachment_id ) )
2585 if ( 'attachment' != $attachment->post_type )
2588 wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
2591 wp_send_json_success();
2595 * Ajax handler for sending an attachment to the editor.
2597 * Generates the HTML to send an attachment to the editor.
2598 * Backward compatible with the {@see 'media_send_to_editor'} filter
2599 * and the chain of filters that follow.
2603 function wp_ajax_send_attachment_to_editor() {
2604 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2606 $attachment = wp_unslash( $_POST['attachment'] );
2608 $id = intval( $attachment['id'] );
2610 if ( ! $post = get_post( $id ) )
2611 wp_send_json_error();
2613 if ( 'attachment' != $post->post_type )
2614 wp_send_json_error();
2616 if ( current_user_can( 'edit_post', $id ) ) {
2617 // If this attachment is unattached, attach it. Primarily a back compat thing.
2618 if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2619 wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
2623 $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
2624 $rel = ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url );
2626 remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2628 if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2629 $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2630 $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2631 $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2633 // No whitespace-only captions.
2634 $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2635 if ( '' === trim( $caption ) ) {
2639 $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2640 $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
2641 } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
2642 $html = stripslashes_deep( $_POST['html'] );
2644 $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2645 $rel = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized
2647 if ( ! empty( $url ) ) {
2648 $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2652 /** This filter is documented in wp-admin/includes/media.php */
2653 $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2655 wp_send_json_success( $html );
2659 * Ajax handler for sending a link to the editor.
2661 * Generates the HTML to send a non-image embed link to the editor.
2663 * Backward compatible with the following filters:
2664 * - file_send_to_editor_url
2665 * - audio_send_to_editor_url
2666 * - video_send_to_editor_url
2670 * @global WP_Post $post
2671 * @global WP_Embed $wp_embed
2673 function wp_ajax_send_link_to_editor() {
2674 global $post, $wp_embed;
2676 check_ajax_referer( 'media-send-to-editor', 'nonce' );
2678 if ( ! $src = wp_unslash( $_POST['src'] ) )
2679 wp_send_json_error();
2681 if ( ! strpos( $src, '://' ) )
2682 $src = 'http://' . $src;
2684 if ( ! $src = esc_url_raw( $src ) )
2685 wp_send_json_error();
2687 if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
2688 $link_text = wp_basename( $src );
2690 $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2692 // Ping WordPress for an embed.
2693 $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
2695 // Fallback that WordPress creates when no oEmbed was found.
2696 $fallback = $wp_embed->maybe_make_link( $src );
2698 if ( $check_embed !== $fallback ) {
2699 // TinyMCE view for [embed] will parse this
2700 $html = '[embed]' . $src . '[/embed]';
2701 } elseif ( $link_text ) {
2702 $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2707 // Figure out what filter to run:
2709 if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2710 && ( 'audio' == $ext_type || 'video' == $ext_type ) )
2713 /** This filter is documented in wp-admin/includes/media.php */
2714 $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
2716 wp_send_json_success( $html );
2720 * Ajax handler for the Heartbeat API.
2722 * Runs when the user is logged in.
2726 function wp_ajax_heartbeat() {
2727 if ( empty( $_POST['_nonce'] ) ) {
2728 wp_send_json_error();
2731 $response = $data = array();
2732 $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
2734 // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
2735 if ( ! empty( $_POST['screen_id'] ) ) {
2736 $screen_id = sanitize_key($_POST['screen_id']);
2738 $screen_id = 'front';
2741 if ( ! empty( $_POST['data'] ) ) {
2742 $data = wp_unslash( (array) $_POST['data'] );
2745 if ( 1 !== $nonce_state ) {
2746 $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
2748 if ( false === $nonce_state ) {
2749 // User is logged in but nonces have expired.
2750 $response['nonces_expired'] = true;
2751 wp_send_json( $response );
2755 if ( ! empty( $data ) ) {
2757 * Filters the Heartbeat response received.
2761 * @param array $response The Heartbeat response.
2762 * @param array $data The $_POST data sent.
2763 * @param string $screen_id The screen id.
2765 $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
2769 * Filters the Heartbeat response sent.
2773 * @param array $response The Heartbeat response.
2774 * @param string $screen_id The screen id.
2776 $response = apply_filters( 'heartbeat_send', $response, $screen_id );
2779 * Fires when Heartbeat ticks in logged-in environments.
2781 * Allows the transport to be easily replaced with long-polling.
2785 * @param array $response The Heartbeat response.
2786 * @param string $screen_id The screen id.
2788 do_action( 'heartbeat_tick', $response, $screen_id );
2790 // Send the current time according to the server
2791 $response['server_time'] = time();
2793 wp_send_json( $response );
2797 * Ajax handler for getting revision diffs.
2801 function wp_ajax_get_revision_diffs() {
2802 require ABSPATH . 'wp-admin/includes/revision.php';
2804 if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
2805 wp_send_json_error();
2807 if ( ! current_user_can( 'edit_post', $post->ID ) )
2808 wp_send_json_error();
2810 // Really just pre-loading the cache here.
2811 if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
2812 wp_send_json_error();
2815 @set_time_limit( 0 );
2817 foreach ( $_REQUEST['compare'] as $compare_key ) {
2818 list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
2821 'id' => $compare_key,
2822 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
2825 wp_send_json_success( $return );
2829 * Ajax handler for auto-saving the selected color scheme for
2830 * a user's own profile.
2834 * @global array $_wp_admin_css_colors
2836 function wp_ajax_save_user_color_scheme() {
2837 global $_wp_admin_css_colors;
2839 check_ajax_referer( 'save-color-scheme', 'nonce' );
2841 $color_scheme = sanitize_key( $_POST['color_scheme'] );
2843 if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
2844 wp_send_json_error();
2847 $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
2848 update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
2850 wp_send_json_success( array(
2851 'previousScheme' => 'admin-color-' . $previous_color_scheme,
2852 'currentScheme' => 'admin-color-' . $color_scheme
2857 * Ajax handler for getting themes from themes_api().
2861 * @global array $themes_allowedtags
2862 * @global array $theme_field_defaults
2864 function wp_ajax_query_themes() {
2865 global $themes_allowedtags, $theme_field_defaults;
2867 if ( ! current_user_can( 'install_themes' ) ) {
2868 wp_send_json_error();
2871 $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
2873 'fields' => $theme_field_defaults
2876 if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
2877 $user = get_user_option( 'wporg_favorites' );
2879 $args['user'] = $user;
2883 $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
2885 /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
2886 $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
2888 $api = themes_api( 'query_themes', $args );
2890 if ( is_wp_error( $api ) ) {
2891 wp_send_json_error();
2894 $update_php = network_admin_url( 'update.php?action=install-theme' );
2895 foreach ( $api->themes as &$theme ) {
2896 $theme->install_url = add_query_arg( array(
2897 'theme' => $theme->slug,
2898 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
2901 if ( current_user_can( 'switch_themes' ) ) {
2902 if ( is_multisite() ) {
2903 $theme->activate_url = add_query_arg( array(
2904 'action' => 'enable',
2905 '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
2906 'theme' => $theme->slug,
2907 ), network_admin_url( 'themes.php' ) );
2909 $theme->activate_url = add_query_arg( array(
2910 'action' => 'activate',
2911 '_wpnonce' => wp_create_nonce( 'switch-theme_' . $theme->slug ),
2912 'stylesheet' => $theme->slug,
2913 ), admin_url( 'themes.php' ) );
2917 if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
2918 $theme->customize_url = add_query_arg( array(
2919 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
2920 ), wp_customize_url( $theme->slug ) );
2923 $theme->name = wp_kses( $theme->name, $themes_allowedtags );
2924 $theme->author = wp_kses( $theme->author, $themes_allowedtags );
2925 $theme->version = wp_kses( $theme->version, $themes_allowedtags );
2926 $theme->description = wp_kses( $theme->description, $themes_allowedtags );
2927 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) );
2928 $theme->num_ratings = number_format_i18n( $theme->num_ratings );
2929 $theme->preview_url = set_url_scheme( $theme->preview_url );
2932 wp_send_json_success( $api );
2936 * Apply [embed] Ajax handlers to a string.
2940 * @global WP_Post $post Global $post.
2941 * @global WP_Embed $wp_embed Embed API instance.
2942 * @global WP_Scripts $wp_scripts
2944 function wp_ajax_parse_embed() {
2945 global $post, $wp_embed;
2947 if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
2948 wp_send_json_error();
2951 if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
2952 wp_send_json_error();
2955 $shortcode = wp_unslash( $_POST['shortcode'] );
2957 preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
2958 $atts = shortcode_parse_atts( $matches[3] );
2959 if ( ! empty( $matches[5] ) ) {
2961 } elseif ( ! empty( $atts['src'] ) ) {
2962 $url = $atts['src'];
2968 setup_postdata( $post );
2970 $wp_embed->return_false_on_fail = true;
2972 if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
2973 // Admin is ssl and the user pasted non-ssl URL.
2974 // Check if the provider supports ssl embeds and use that for the preview.
2975 $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
2976 $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
2979 $no_ssl_support = true;
2983 if ( $url && ! $parsed ) {
2984 $parsed = $wp_embed->run_shortcode( $shortcode );
2988 wp_send_json_error( array(
2989 'type' => 'not-embeddable',
2990 'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
2994 if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
2996 $mce_styles = wpview_media_sandbox_styles();
2997 foreach ( $mce_styles as $style ) {
2998 $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
3001 $html = do_shortcode( $parsed );
3004 if ( ! empty( $wp_scripts ) ) {
3005 $wp_scripts->done = array();
3008 wp_print_scripts( 'wp-mediaelement' );
3009 $scripts = ob_get_clean();
3011 $parsed = $styles . $html . $scripts;
3015 if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3016 preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3017 // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3018 wp_send_json_error( array(
3019 'type' => 'not-ssl',
3020 'message' => __( 'This preview is unavailable in the editor.' ),
3024 wp_send_json_success( array(
3026 'attr' => $wp_embed->last_attr
3033 * @global WP_Post $post
3034 * @global WP_Scripts $wp_scripts
3036 function wp_ajax_parse_media_shortcode() {
3037 global $post, $wp_scripts;
3039 if ( empty( $_POST['shortcode'] ) ) {
3040 wp_send_json_error();
3043 $shortcode = wp_unslash( $_POST['shortcode'] );
3045 if ( ! empty( $_POST['post_ID'] ) ) {
3046 $post = get_post( (int) $_POST['post_ID'] );
3049 // the embed shortcode requires a post
3050 if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3051 if ( 'embed' === $shortcode ) {
3052 wp_send_json_error();
3055 setup_postdata( $post );
3058 $parsed = do_shortcode( $shortcode );
3060 if ( empty( $parsed ) ) {
3061 wp_send_json_error( array(
3062 'type' => 'no-items',
3063 'message' => __( 'No items found.' ),
3068 $styles = wpview_media_sandbox_styles();
3070 foreach ( $styles as $style ) {
3071 $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3074 if ( ! empty( $wp_scripts ) ) {
3075 $wp_scripts->done = array();
3082 if ( 'playlist' === $_REQUEST['type'] ) {
3083 wp_underscore_playlist_templates();
3085 wp_print_scripts( 'wp-playlist' );
3087 wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
3090 wp_send_json_success( array(
3092 'body' => ob_get_clean()
3097 * Ajax handler for destroying multiple open sessions for a user.
3101 function wp_ajax_destroy_sessions() {
3102 $user = get_userdata( (int) $_POST['user_id'] );
3104 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3106 } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3112 wp_send_json_error( array(
3113 'message' => __( 'Could not log out user sessions. Please try again.' ),
3117 $sessions = WP_Session_Tokens::get_instance( $user->ID );
3119 if ( $user->ID === get_current_user_id() ) {
3120 $sessions->destroy_others( wp_get_session_token() );
3121 $message = __( 'You are now logged out everywhere else.' );
3123 $sessions->destroy_all();
3124 /* translators: 1: User's display name. */
3125 $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3128 wp_send_json_success( array( 'message' => $message ) );
3132 * Ajax handler for saving a post from Press This.
3136 * @global WP_Press_This $wp_press_this
3138 function wp_ajax_press_this_save_post() {
3139 if ( empty( $GLOBALS['wp_press_this'] ) ) {
3140 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3143 $GLOBALS['wp_press_this']->save_post();
3147 * Ajax handler for creating new category from Press This.
3151 * @global WP_Press_This $wp_press_this
3153 function wp_ajax_press_this_add_category() {
3154 if ( empty( $GLOBALS['wp_press_this'] ) ) {
3155 include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' );
3158 $GLOBALS['wp_press_this']->add_category();
3162 * Ajax handler for cropping an image.
3166 * @global WP_Site_Icon $wp_site_icon
3168 function wp_ajax_crop_image() {
3169 $attachment_id = absint( $_POST['id'] );
3171 check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3172 if ( ! current_user_can( 'customize' ) ) {
3173 wp_send_json_error();
3176 $context = str_replace( '_', '-', $_POST['context'] );
3177 $data = array_map( 'absint', $_POST['cropDetails'] );
3178 $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3180 if ( ! $cropped || is_wp_error( $cropped ) ) {
3181 wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3184 switch ( $context ) {
3186 require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
3187 global $wp_site_icon;
3189 // Skip creating a new attachment if the attachment is a Site Icon.
3190 if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3192 // Delete the temporary cropped file, we don't need it.
3193 wp_delete_file( $cropped );
3195 // Additional sizes in wp_prepare_attachment_for_js().
3196 add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3200 /** This filter is documented in wp-admin/custom-header.php */
3201 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3202 $object = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3203 unset( $object['ID'] );
3205 // Update the attachment.
3206 add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3207 $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3208 remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3210 // Additional sizes in wp_prepare_attachment_for_js().
3211 add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3217 * Fires before a cropped image is saved.
3219 * Allows to add filters to modify the way a cropped image is saved.
3223 * @param string $context The Customizer control requesting the cropped image.
3224 * @param int $attachment_id The attachment ID of the original image.
3225 * @param string $cropped Path to the cropped image file.
3227 do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3229 /** This filter is documented in wp-admin/custom-header.php */
3230 $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3232 $parent_url = wp_get_attachment_url( $attachment_id );
3233 $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
3235 $size = @getimagesize( $cropped );
3236 $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3239 'post_title' => basename( $cropped ),
3240 'post_content' => $url,
3241 'post_mime_type' => $image_type,
3243 'context' => $context,
3246 $attachment_id = wp_insert_attachment( $object, $cropped );
3247 $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
3250 * Filters the cropped image attachment metadata.
3254 * @see wp_generate_attachment_metadata()
3256 * @param array $metadata Attachment metadata.
3258 $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3259 wp_update_attachment_metadata( $attachment_id, $metadata );
3262 * Filters the attachment ID for a cropped image.
3266 * @param int $attachment_id The attachment ID of the cropped image.
3267 * @param string $context The Customizer control requesting the cropped image.
3269 $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3272 wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
3276 * Ajax handler for generating a password.
3280 function wp_ajax_generate_password() {
3281 wp_send_json_success( wp_generate_password( 24 ) );
3285 * Ajax handler for saving the user's WordPress.org username.
3289 function wp_ajax_save_wporg_username() {
3290 if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
3291 wp_send_json_error();
3294 check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
3296 $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
3298 if ( ! $username ) {
3299 wp_send_json_error();
3302 wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
3306 * Ajax handler for installing a theme.
3310 * @see Theme_Upgrader
3312 function wp_ajax_install_theme() {
3313 check_ajax_referer( 'updates' );
3315 if ( empty( $_POST['slug'] ) ) {
3316 wp_send_json_error( array(
3318 'errorCode' => 'no_theme_specified',
3319 'errorMessage' => __( 'No theme specified.' ),
3323 $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
3326 'install' => 'theme',
3330 if ( ! current_user_can( 'install_themes' ) ) {
3331 $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
3332 wp_send_json_error( $status );
3335 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3336 include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3338 $api = themes_api( 'theme_information', array(
3340 'fields' => array( 'sections' => false ),
3343 if ( is_wp_error( $api ) ) {
3344 $status['errorMessage'] = $api->get_error_message();
3345 wp_send_json_error( $status );
3348 $skin = new WP_Ajax_Upgrader_Skin();
3349 $upgrader = new Theme_Upgrader( $skin );
3350 $result = $upgrader->install( $api->download_link );
3352 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3353 $status['debug'] = $skin->get_upgrade_messages();
3356 if ( is_wp_error( $result ) ) {
3357 $status['errorCode'] = $result->get_error_code();
3358 $status['errorMessage'] = $result->get_error_message();
3359 wp_send_json_error( $status );
3360 } elseif ( is_wp_error( $skin->result ) ) {
3361 $status['errorCode'] = $skin->result->get_error_code();
3362 $status['errorMessage'] = $skin->result->get_error_message();
3363 wp_send_json_error( $status );
3364 } elseif ( $skin->get_errors()->get_error_code() ) {
3365 $status['errorMessage'] = $skin->get_error_messages();
3366 wp_send_json_error( $status );
3367 } elseif ( is_null( $result ) ) {
3368 global $wp_filesystem;
3370 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3371 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3373 // Pass through the error from WP_Filesystem if one was raised.
3374 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3375 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3378 wp_send_json_error( $status );
3381 $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
3383 if ( current_user_can( 'switch_themes' ) ) {
3384 if ( is_multisite() ) {
3385 $status['activateUrl'] = add_query_arg( array(
3386 'action' => 'enable',
3387 '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
3389 ), network_admin_url( 'themes.php' ) );
3391 $status['activateUrl'] = add_query_arg( array(
3392 'action' => 'activate',
3393 '_wpnonce' => wp_create_nonce( 'switch-theme_' . $slug ),
3394 'stylesheet' => $slug,
3395 ), admin_url( 'themes.php' ) );
3399 if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3400 $status['customizeUrl'] = add_query_arg( array(
3401 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3402 ), wp_customize_url( $slug ) );
3406 * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
3407 * on post-install status.
3409 wp_send_json_success( $status );
3413 * Ajax handler for updating a theme.
3417 * @see Theme_Upgrader
3419 function wp_ajax_update_theme() {
3420 check_ajax_referer( 'updates' );
3422 if ( empty( $_POST['slug'] ) ) {
3423 wp_send_json_error( array(
3425 'errorCode' => 'no_theme_specified',
3426 'errorMessage' => __( 'No theme specified.' ),
3430 $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) );
3432 'update' => 'theme',
3433 'slug' => $stylesheet,
3437 if ( ! current_user_can( 'update_themes' ) ) {
3438 $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
3439 wp_send_json_error( $status );
3442 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3444 $current = get_site_transient( 'update_themes' );
3445 if ( empty( $current ) ) {
3449 $skin = new WP_Ajax_Upgrader_Skin();
3450 $upgrader = new Theme_Upgrader( $skin );
3451 $result = $upgrader->bulk_upgrade( array( $stylesheet ) );
3453 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3454 $status['debug'] = $skin->get_upgrade_messages();
3457 if ( is_wp_error( $skin->result ) ) {
3458 $status['errorCode'] = $skin->result->get_error_code();
3459 $status['errorMessage'] = $skin->result->get_error_message();
3460 wp_send_json_error( $status );
3461 } elseif ( $skin->get_errors()->get_error_code() ) {
3462 $status['errorMessage'] = $skin->get_error_messages();
3463 wp_send_json_error( $status );
3464 } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
3466 // Theme is already at the latest version.
3467 if ( true === $result[ $stylesheet ] ) {
3468 $status['errorMessage'] = $upgrader->strings['up_to_date'];
3469 wp_send_json_error( $status );
3472 $theme = wp_get_theme( $stylesheet );
3473 if ( $theme->get( 'Version' ) ) {
3474 $status['newVersion'] = $theme->get( 'Version' );
3477 wp_send_json_success( $status );
3478 } elseif ( false === $result ) {
3479 global $wp_filesystem;
3481 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3482 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3484 // Pass through the error from WP_Filesystem if one was raised.
3485 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3486 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3489 wp_send_json_error( $status );
3492 // An unhandled error occurred.
3493 $status['errorMessage'] = __( 'Update failed.' );
3494 wp_send_json_error( $status );
3498 * Ajax handler for deleting a theme.
3502 * @see delete_theme()
3504 function wp_ajax_delete_theme() {
3505 check_ajax_referer( 'updates' );
3507 if ( empty( $_POST['slug'] ) ) {
3508 wp_send_json_error( array(
3510 'errorCode' => 'no_theme_specified',
3511 'errorMessage' => __( 'No theme specified.' ),
3515 $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) );
3517 'delete' => 'theme',
3518 'slug' => $stylesheet,
3521 if ( ! current_user_can( 'delete_themes' ) ) {
3522 $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
3523 wp_send_json_error( $status );
3526 if ( ! wp_get_theme( $stylesheet )->exists() ) {
3527 $status['errorMessage'] = __( 'The requested theme does not exist.' );
3528 wp_send_json_error( $status );
3531 // Check filesystem credentials. `delete_theme()` will bail otherwise.
3532 $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
3534 $credentials = request_filesystem_credentials( $url );
3536 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3537 global $wp_filesystem;
3539 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3540 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3542 // Pass through the error from WP_Filesystem if one was raised.
3543 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3544 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3547 wp_send_json_error( $status );
3550 include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3552 $result = delete_theme( $stylesheet );
3554 if ( is_wp_error( $result ) ) {
3555 $status['errorMessage'] = $result->get_error_message();
3556 wp_send_json_error( $status );
3557 } elseif ( false === $result ) {
3558 $status['errorMessage'] = __( 'Theme could not be deleted.' );
3559 wp_send_json_error( $status );
3562 wp_send_json_success( $status );
3566 * Ajax handler for installing a plugin.
3570 * @see Plugin_Upgrader
3572 function wp_ajax_install_plugin() {
3573 check_ajax_referer( 'updates' );
3575 if ( empty( $_POST['slug'] ) ) {
3576 wp_send_json_error( array(
3578 'errorCode' => 'no_plugin_specified',
3579 'errorMessage' => __( 'No plugin specified.' ),
3584 'install' => 'plugin',
3585 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3588 if ( ! current_user_can( 'install_plugins' ) ) {
3589 $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
3590 wp_send_json_error( $status );
3593 include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3594 include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
3596 $api = plugins_api( 'plugin_information', array(
3597 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3599 'sections' => false,
3603 if ( is_wp_error( $api ) ) {
3604 $status['errorMessage'] = $api->get_error_message();
3605 wp_send_json_error( $status );
3608 $status['pluginName'] = $api->name;
3610 $skin = new WP_Ajax_Upgrader_Skin();
3611 $upgrader = new Plugin_Upgrader( $skin );
3612 $result = $upgrader->install( $api->download_link );
3614 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3615 $status['debug'] = $skin->get_upgrade_messages();
3618 if ( is_wp_error( $result ) ) {
3619 $status['errorCode'] = $result->get_error_code();
3620 $status['errorMessage'] = $result->get_error_message();
3621 wp_send_json_error( $status );
3622 } elseif ( is_wp_error( $skin->result ) ) {
3623 $status['errorCode'] = $skin->result->get_error_code();
3624 $status['errorMessage'] = $skin->result->get_error_message();
3625 wp_send_json_error( $status );
3626 } elseif ( $skin->get_errors()->get_error_code() ) {
3627 $status['errorMessage'] = $skin->get_error_messages();
3628 wp_send_json_error( $status );
3629 } elseif ( is_null( $result ) ) {
3630 global $wp_filesystem;
3632 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3633 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3635 // Pass through the error from WP_Filesystem if one was raised.
3636 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3637 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3640 wp_send_json_error( $status );
3643 $install_status = install_plugin_install_status( $api );
3645 if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) {
3646 $status['activateUrl'] = add_query_arg( array(
3647 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
3648 'action' => 'activate',
3649 'plugin' => $install_status['file'],
3650 ), network_admin_url( 'plugins.php' ) );
3653 if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
3654 $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
3657 wp_send_json_success( $status );
3661 * Ajax handler for updating a plugin.
3665 * @see Plugin_Upgrader
3667 function wp_ajax_update_plugin() {
3668 check_ajax_referer( 'updates' );
3670 if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
3671 wp_send_json_error( array(
3673 'errorCode' => 'no_plugin_specified',
3674 'errorMessage' => __( 'No plugin specified.' ),
3678 $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
3681 'update' => 'plugin',
3682 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3687 if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
3688 $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
3689 wp_send_json_error( $status );
3692 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3693 $status['plugin'] = $plugin;
3694 $status['pluginName'] = $plugin_data['Name'];
3696 if ( $plugin_data['Version'] ) {
3697 /* translators: %s: Plugin version */
3698 $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3701 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
3703 wp_update_plugins();
3705 $skin = new WP_Ajax_Upgrader_Skin();
3706 $upgrader = new Plugin_Upgrader( $skin );
3707 $result = $upgrader->bulk_upgrade( array( $plugin ) );
3709 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3710 $status['debug'] = $skin->get_upgrade_messages();
3713 if ( is_wp_error( $skin->result ) ) {
3714 $status['errorCode'] = $skin->result->get_error_code();
3715 $status['errorMessage'] = $skin->result->get_error_message();
3716 wp_send_json_error( $status );
3717 } elseif ( $skin->get_errors()->get_error_code() ) {
3718 $status['errorMessage'] = $skin->get_error_messages();
3719 wp_send_json_error( $status );
3720 } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
3721 $plugin_update_data = current( $result );
3724 * If the `update_plugins` site transient is empty (e.g. when you update
3725 * two plugins in quick succession before the transient repopulates),
3726 * this may be the return.
3728 * Preferably something can be done to ensure `update_plugins` isn't empty.
3729 * For now, surface some sort of error here.
3731 if ( true === $plugin_update_data ) {
3732 $status['errorMessage'] = __( 'Plugin update failed.' );
3733 wp_send_json_error( $status );
3736 $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
3737 $plugin_data = reset( $plugin_data );
3739 if ( $plugin_data['Version'] ) {
3740 /* translators: %s: Plugin version */
3741 $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
3743 wp_send_json_success( $status );
3744 } elseif ( false === $result ) {
3745 global $wp_filesystem;
3747 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3748 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3750 // Pass through the error from WP_Filesystem if one was raised.
3751 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3752 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3755 wp_send_json_error( $status );
3758 // An unhandled error occurred.
3759 $status['errorMessage'] = __( 'Plugin update failed.' );
3760 wp_send_json_error( $status );
3764 * Ajax handler for deleting a plugin.
3768 * @see delete_plugins()
3770 function wp_ajax_delete_plugin() {
3771 check_ajax_referer( 'updates' );
3773 if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
3774 wp_send_json_error( array(
3776 'errorCode' => 'no_plugin_specified',
3777 'errorMessage' => __( 'No plugin specified.' ),
3781 $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
3784 'delete' => 'plugin',
3785 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3788 if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
3789 $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
3790 wp_send_json_error( $status );
3793 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
3794 $status['plugin'] = $plugin;
3795 $status['pluginName'] = $plugin_data['Name'];
3797 if ( is_plugin_active( $plugin ) ) {
3798 $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
3799 wp_send_json_error( $status );
3802 // Check filesystem credentials. `delete_plugins()` will bail otherwise.
3803 $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
3805 $credentials = request_filesystem_credentials( $url );
3807 if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3808 global $wp_filesystem;
3810 $status['errorCode'] = 'unable_to_connect_to_filesystem';
3811 $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3813 // Pass through the error from WP_Filesystem if one was raised.
3814 if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
3815 $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3818 wp_send_json_error( $status );
3821 $result = delete_plugins( array( $plugin ) );
3823 if ( is_wp_error( $result ) ) {
3824 $status['errorMessage'] = $result->get_error_message();
3825 wp_send_json_error( $status );
3826 } elseif ( false === $result ) {
3827 $status['errorMessage'] = __( 'Plugin could not be deleted.' );
3828 wp_send_json_error( $status );
3831 wp_send_json_success( $status );
3835 * Ajax handler for searching plugins.
3839 * @global string $s Search term.
3841 function wp_ajax_search_plugins() {
3842 check_ajax_referer( 'updates' );
3844 $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3845 if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
3846 set_current_screen( $pagenow );
3849 /** @var WP_Plugins_List_Table $wp_list_table */
3850 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table', array(
3851 'screen' => get_current_screen(),
3856 if ( ! $wp_list_table->ajax_user_can() ) {
3857 $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3858 wp_send_json_error( $status );
3861 // Set the correct requester, so pagination works.
3862 $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3863 '_ajax_nonce' => null,
3865 ) ), network_admin_url( 'plugins.php', 'relative' ) );
3867 $GLOBALS['s'] = wp_unslash( $_POST['s'] );
3869 $wp_list_table->prepare_items();
3872 $wp_list_table->display();
3873 $status['count'] = count( $wp_list_table->items );
3874 $status['items'] = ob_get_clean();
3876 wp_send_json_success( $status );
3880 * Ajax handler for searching plugins to install.
3884 function wp_ajax_search_install_plugins() {
3885 check_ajax_referer( 'updates' );
3887 $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
3888 if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
3889 set_current_screen( $pagenow );
3892 /** @var WP_Plugin_Install_List_Table $wp_list_table */
3893 $wp_list_table = _get_list_table( 'WP_Plugin_Install_List_Table', array(
3894 'screen' => get_current_screen(),
3899 if ( ! $wp_list_table->ajax_user_can() ) {
3900 $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
3901 wp_send_json_error( $status );
3904 // Set the correct requester, so pagination works.
3905 $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array(
3906 '_ajax_nonce' => null,
3908 ) ), network_admin_url( 'plugin-install.php', 'relative' ) );
3910 $wp_list_table->prepare_items();
3913 $wp_list_table->display();
3914 $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
3915 $status['items'] = ob_get_clean();
3917 wp_send_json_success( $status );