X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/7f1521bf193b382565eb753043c161f4cb3fcda7..607b7e02d77e7326161e8ec15639052d2040f745:/wp-admin/includes/ajax-actions.php diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php index 40f8f5d1..dc965d63 100644 --- a/wp-admin/includes/ajax-actions.php +++ b/wp-admin/includes/ajax-actions.php @@ -1,9 +1,10 @@ cache_oembed( $_GET['post'] ); @@ -284,7 +290,7 @@ function wp_ajax_autocomplete_user() { foreach ( $users as $user ) { $return[] = array( /* translators: 1: user_login, 2: user_email */ - 'label' => sprintf( __( '%1$s (%2$s)' ), $user->user_login, $user->user_email ), + 'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ), 'value' => $user->$field, ); } @@ -329,12 +335,13 @@ function wp_ajax_logged_in() { /** * Sends back current comment total and new page links if they need to be updated. * - * Contrary to normal success AJAX response ("1"), die with time() on success. + * Contrary to normal success Ajax response ("1"), die with time() on success. * + * @access private * @since 2.7.0 * * @param int $comment_id - * @return die + * @param int $delta */ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) { $total = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0; @@ -343,8 +350,44 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) { $url = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : ''; // JS didn't send us everything we need to know. Just die with success message - if ( !$total || !$per_page || !$page || !$url ) - wp_die( time() ); + if ( ! $total || ! $per_page || ! $page || ! $url ) { + $time = time(); + $comment = get_comment( $comment_id ); + $comment_status = ''; + $comment_link = ''; + + if ( $comment ) { + $comment_status = $comment->comment_approved; + } + + if ( 1 === (int) $comment_status ) { + $comment_link = get_comment_link( $comment ); + } + + $counts = wp_count_comments(); + + $x = new WP_Ajax_Response( array( + 'what' => 'comment', + // Here for completeness - not used. + 'id' => $comment_id, + 'supplemental' => array( + 'status' => $comment_status, + 'postId' => $comment ? $comment->comment_post_ID : '', + 'time' => $time, + 'in_moderation' => $counts->moderated, + 'i18n_comments_text' => sprintf( + _n( '%s Comment', '%s Comments', $counts->approved ), + number_format_i18n( $counts->approved ) + ), + 'i18n_moderation_text' => sprintf( + _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ), + number_format_i18n( $counts->moderated ) + ), + 'comment_link' => $comment_link, + ) + ) ); + $x->send(); + } $total += $delta; if ( $total < 0 ) @@ -353,7 +396,8 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) { // Only do the expensive stuff on a page-break, and about 1 other time per page if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) { $post_id = 0; - $status = 'total_comments'; // What type of comment count are we looking for? + // What type of comment count are we looking for? + $status = 'all'; $parsed = parse_url( $url ); if ( isset( $parsed['query'] ) ) { parse_str( $parsed['query'], $query_vars ); @@ -361,24 +405,33 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) { $status = $query_vars['comment_status']; if ( !empty( $query_vars['p'] ) ) $post_id = (int) $query_vars['p']; + if ( ! empty( $query_vars['comment_type'] ) ) + $type = $query_vars['comment_type']; } - $comment_count = wp_count_comments($post_id); + if ( empty( $type ) ) { + // Only use the comment count if not filtering by a comment_type. + $comment_count = wp_count_comments($post_id); - // We're looking for a known type of comment count. - if ( isset( $comment_count->$status ) ) - $total = $comment_count->$status; - // Else use the decremented value from above. + // We're looking for a known type of comment count. + if ( isset( $comment_count->$status ) ) { + $total = $comment_count->$status; + } + } + // Else use the decremented value from above. } // The time since the last comment count. $time = time(); + $comment = get_comment( $comment_id ); $x = new WP_Ajax_Response( array( 'what' => 'comment', // Here for completeness - not used. 'id' => $comment_id, 'supplemental' => array( + 'status' => $comment ? $comment->comment_approved : '', + 'postId' => $comment ? $comment->comment_post_ID : '', 'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ), 'total_pages' => ceil( $total / $per_page ), 'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ), @@ -396,6 +449,7 @@ function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) { /** * Ajax handler for adding a hierarchical term. * + * @access private * @since 3.1.0 */ function _wp_ajax_add_hierarchical_term() { @@ -430,10 +484,13 @@ function _wp_ajax_add_hierarchical_term() { $checked_categories[] = $cat_id; if ( $parent ) // Do these all at once in a second continue; + ob_start(); - wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids )); - $data = ob_get_contents(); - ob_end_clean(); + + wp_terms_checklist( 0, array( 'taxonomy' => $taxonomy->name, 'descendants_and_self' => $cat_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids )); + + $data = ob_get_clean(); + $add = array( 'what' => $taxonomy->name, 'id' => $cat_id, @@ -454,9 +511,11 @@ function _wp_ajax_add_hierarchical_term() { } ob_start(); - wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids)); - $data = ob_get_contents(); - ob_end_clean(); + + wp_terms_checklist( 0, array('taxonomy' => $taxonomy->name, 'descendants_and_self' => $term_id, 'selected_cats' => $checked_categories, 'popular_cats' => $popular_ids)); + + $data = ob_get_clean(); + $add = array( 'what' => $taxonomy->name, 'id' => $term_id, @@ -466,12 +525,14 @@ function _wp_ajax_add_hierarchical_term() { } ob_start(); - wp_dropdown_categories( array( - 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name', - 'hierarchical' => 1, 'show_option_none' => '— '.$taxonomy->labels->parent_item.' —' - ) ); - $sup = ob_get_contents(); - ob_end_clean(); + + wp_dropdown_categories( array( + 'taxonomy' => $taxonomy->name, 'hide_empty' => 0, 'name' => 'new'.$taxonomy->name.'_parent', 'orderby' => 'name', + 'hierarchical' => 1, 'show_option_none' => '— '.$taxonomy->labels->parent_item.' —' + ) ); + + $sup = ob_get_clean(); + $add['supplemental'] = array( 'newcat_parent' => $sup ); $x = new WP_Ajax_Response( $add ); @@ -492,31 +553,31 @@ function wp_ajax_delete_comment() { wp_die( -1 ); check_ajax_referer( "delete-comment_$id" ); - $status = wp_get_comment_status( $comment->comment_ID ); + $status = wp_get_comment_status( $comment ); $delta = -1; if ( isset($_POST['trash']) && 1 == $_POST['trash'] ) { if ( 'trash' == $status ) wp_die( time() ); - $r = wp_trash_comment( $comment->comment_ID ); + $r = wp_trash_comment( $comment ); } elseif ( isset($_POST['untrash']) && 1 == $_POST['untrash'] ) { if ( 'trash' != $status ) wp_die( time() ); - $r = wp_untrash_comment( $comment->comment_ID ); + $r = wp_untrash_comment( $comment ); if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) // undo trash, not in trash $delta = 1; } elseif ( isset($_POST['spam']) && 1 == $_POST['spam'] ) { if ( 'spam' == $status ) wp_die( time() ); - $r = wp_spam_comment( $comment->comment_ID ); + $r = wp_spam_comment( $comment ); } elseif ( isset($_POST['unspam']) && 1 == $_POST['unspam'] ) { if ( 'spam' != $status ) wp_die( time() ); - $r = wp_unspam_comment( $comment->comment_ID ); + $r = wp_unspam_comment( $comment ); if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) // undo spam, not in spam $delta = 1; } elseif ( isset($_POST['delete']) && 1 == $_POST['delete'] ) { - $r = wp_delete_comment( $comment->comment_ID ); + $r = wp_delete_comment( $comment ); } else { wp_die( -1 ); } @@ -660,6 +721,11 @@ function wp_ajax_untrash_post( $action ) { wp_ajax_trash_post( $action ); } +/** + * @since 3.1.0 + * + * @param string $action + */ function wp_ajax_delete_page( $action ) { if ( empty( $action ) ) $action = 'delete-page'; @@ -697,15 +763,16 @@ function wp_ajax_dim_comment() { if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) ) wp_die( -1 ); - $current = wp_get_comment_status( $comment->comment_ID ); + $current = wp_get_comment_status( $comment ); if ( isset( $_POST['new'] ) && $_POST['new'] == $current ) wp_die( time() ); check_ajax_referer( "approve-comment_$id" ); - if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) - $result = wp_set_comment_status( $comment->comment_ID, 'approve', true ); - else - $result = wp_set_comment_status( $comment->comment_ID, 'hold', true ); + if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) { + $result = wp_set_comment_status( $comment, 'approve', true ); + } else { + $result = wp_set_comment_status( $comment, 'hold', true ); + } if ( is_wp_error($result) ) { $x = new WP_Ajax_Response( array( @@ -762,6 +829,8 @@ function wp_ajax_add_link_category( $action ) { * Ajax handler to add a tag. * * @since 3.1.0 + * + * @global WP_List_Table $wp_list_table */ function wp_ajax_add_tag() { global $wp_list_table; @@ -806,12 +875,12 @@ function wp_ajax_add_tag() { $x->add( array( 'what' => 'taxonomy', 'supplemental' => compact('parents', 'noparents') - ) ); + ) ); $x->add( array( 'what' => 'term', 'position' => $level, 'supplemental' => (array) $tag - ) ); + ) ); $x->send(); } @@ -864,6 +933,9 @@ function wp_ajax_get_tagcloud() { * * @since 3.1.0 * + * @global WP_List_Table $wp_list_table + * @global int $post_id + * * @param string $action Action to perform. */ function wp_ajax_get_comments( $action ) { @@ -895,13 +967,12 @@ function wp_ajax_get_comments( $action ) { $x = new WP_Ajax_Response(); ob_start(); foreach ( $wp_list_table->items as $comment ) { - if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) + if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved ) continue; get_comment( $comment ); $wp_list_table->single_row( $comment ); } - $comment_list_item = ob_get_contents(); - ob_end_clean(); + $comment_list_item = ob_get_clean(); $x->add( array( 'what' => 'comments', @@ -915,6 +986,8 @@ function wp_ajax_get_comments( $action ) { * * @since 3.1.0 * + * @global WP_List_Table $wp_list_table + * * @param string $action Action to perform. */ function wp_ajax_replyto_comment( $action ) { @@ -972,7 +1045,11 @@ function wp_ajax_replyto_comment( $action ) { $parent = get_comment( $comment_parent ); if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) { - if ( wp_set_comment_status( $parent->comment_ID, 'approve' ) ) + if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) { + wp_die( -1 ); + } + + if ( wp_set_comment_status( $parent, 'approve' ) ) $comment_auto_approved = true; } } @@ -1004,8 +1081,23 @@ function wp_ajax_replyto_comment( $action ) { 'position' => $position ); - if ( $comment_auto_approved ) - $response['supplemental'] = array( 'parent_approved' => $parent->comment_ID ); + $counts = wp_count_comments(); + $response['supplemental'] = array( + 'in_moderation' => $counts->moderated, + 'i18n_comments_text' => sprintf( + _n( '%s Comment', '%s Comments', $counts->approved ), + number_format_i18n( $counts->approved ) + ), + 'i18n_moderation_text' => sprintf( + _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ), + number_format_i18n( $counts->moderated ) + ) + ); + + if ( $comment_auto_approved ) { + $response['supplemental']['parent_approved'] = $parent->comment_ID; + $response['supplemental']['parent_post_id'] = $parent->comment_post_ID; + } $x = new WP_Ajax_Response(); $x->add( $response ); @@ -1016,6 +1108,8 @@ function wp_ajax_replyto_comment( $action ) { * Ajax handler for editing a comment. * * @since 3.1.0 + * + * @global WP_List_Table $wp_list_table */ function wp_ajax_edit_comment() { global $wp_list_table; @@ -1085,6 +1179,10 @@ function wp_ajax_add_menu_item() { $_object = get_post( $menu_item_data['menu-item-object-id'] ); break; + case 'post_type_archive' : + $_object = get_post_type_object( $menu_item_data['menu-item-object'] ); + break; + case 'taxonomy' : $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] ); break; @@ -1153,16 +1251,16 @@ function wp_ajax_add_meta() { // If the post is an autodraft, save the post as a draft and then attempt to save the meta. if ( $post->post_status == 'auto-draft' ) { - $save_POST = $_POST; // Backup $_POST - $_POST = array(); // Make it empty for edit_post() - $_POST['action'] = 'draft'; // Warning fix - $_POST['post_ID'] = $pid; - $_POST['post_type'] = $post->post_type; - $_POST['post_status'] = 'draft'; + $post_data = array(); + $post_data['action'] = 'draft'; // Warning fix + $post_data['post_ID'] = $pid; + $post_data['post_type'] = $post->post_type; + $post_data['post_status'] = 'draft'; $now = current_time('timestamp', 1); - $_POST['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( get_option( 'date_format' ), $now ), date( get_option( 'time_format' ), $now ) ); + $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) ); - if ( $pid = edit_post() ) { + $pid = edit_post( $post_data ); + if ( $pid ) { if ( is_wp_error( $pid ) ) { $x = new WP_Ajax_Response( array( 'what' => 'meta', @@ -1170,7 +1268,7 @@ function wp_ajax_add_meta() { ) ); $x->send(); } - $_POST = $save_POST; // Now we can restore original $_POST again + if ( !$mid = add_meta( $pid ) ) wp_die( __( 'Please provide a custom field value.' ) ); } else { @@ -1229,6 +1327,8 @@ function wp_ajax_add_meta() { * * @since 3.1.0 * + * @global WP_List_Table $wp_list_table + * * @param string $action Action to perform. */ function wp_ajax_add_user( $action ) { @@ -1259,7 +1359,11 @@ function wp_ajax_add_user( $action ) { 'id' => $user_id, 'data' => $wp_list_table->single_row( $user_object, '', $role ), 'supplemental' => array( - 'show-link' => sprintf(__( 'User %s added' ), "user-$user_id", $user_object->user_login), + 'show-link' => sprintf( + /* translators: %s: the new user */ + __( 'User %s added' ), + '' . $user_object->user_login . '' + ), 'role' => $role, ) ) ); @@ -1305,7 +1409,6 @@ function wp_ajax_closed_postboxes() { */ function wp_ajax_hidden_columns() { check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' ); - $hidden = explode( ',', isset( $_POST['hidden'] ) ? $_POST['hidden'] : '' ); $page = isset( $_POST['page'] ) ? $_POST['page'] : ''; if ( $page != sanitize_key( $page ) ) @@ -1314,8 +1417,8 @@ function wp_ajax_hidden_columns() { if ( ! $user = wp_get_current_user() ) wp_die( -1 ); - if ( is_array($hidden) ) - update_user_option($user->ID, "manage{$page}columnshidden", $hidden, true); + $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array(); + update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true ); wp_die( 1 ); } @@ -1394,8 +1497,14 @@ function wp_ajax_wp_link_ajax() { $args = array(); - if ( isset( $_POST['search'] ) ) + if ( isset( $_POST['search'] ) ) { $args['s'] = wp_unslash( $_POST['search'] ); + } + + if ( isset( $_POST['term'] ) ) { + $args['s'] = wp_unslash( $_POST['term'] ); + } + $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1; require(ABSPATH . WPINC . '/class-wp-editor.php'); @@ -1479,7 +1588,7 @@ function wp_ajax_menu_quick_search() { function wp_ajax_get_permalink() { check_ajax_referer( 'getpermalink', 'getpermalinknonce' ); $post_id = isset($_POST['post_id'])? intval($_POST['post_id']) : 0; - wp_die( add_query_arg( array( 'preview' => 'true' ), get_permalink( $post_id ) ) ); + wp_die( get_preview_post_link( $post_id ) ); } /** @@ -1499,9 +1608,11 @@ function wp_ajax_sample_permalink() { * Ajax handler for Quick Edit saving a post from a list table. * * @since 3.1.0 + * + * @global WP_List_Table $wp_list_table */ function wp_ajax_inline_save() { - global $wp_list_table; + global $wp_list_table, $mode; check_ajax_referer( 'inlineeditnonce', '_inline_edit' ); @@ -1510,10 +1621,10 @@ function wp_ajax_inline_save() { if ( 'page' == $_POST['post_type'] ) { if ( ! current_user_can( 'edit_page', $post_ID ) ) - wp_die( __( 'You are not allowed to edit this page.' ) ); + wp_die( __( 'Sorry, you are not allowed to edit this page.' ) ); } else { if ( ! current_user_can( 'edit_post', $post_ID ) ) - wp_die( __( 'You are not allowed to edit this post.' ) ); + wp_die( __( 'Sorry, you are not allowed to edit this post.' ) ); } if ( $last = wp_check_post_lock( $post_ID ) ) { @@ -1540,10 +1651,12 @@ function wp_ajax_inline_save() { $data['parent_id'] = $data['post_parent']; // Status. - if ( isset($data['keep_private']) && 'private' == $data['keep_private'] ) + if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) { + $data['visibility'] = 'private'; $data['post_status'] = 'private'; - else + } else { $data['post_status'] = $data['_status']; + } if ( empty($data['comment_status']) ) $data['comment_status'] = 'closed'; @@ -1572,14 +1685,18 @@ function wp_ajax_inline_save() { $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) ); - $level = 0; - $request_post = array( get_post( $_POST['post_ID'] ) ); - $parent = $request_post[0]->post_parent; + $mode = $_POST['post_view'] === 'excerpt' ? 'excerpt' : 'list'; - while ( $parent > 0 ) { - $parent_post = get_post( $parent ); - $parent = $parent_post->post_parent; - $level++; + $level = 0; + if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) { + $request_post = array( get_post( $_POST['post_ID'] ) ); + $parent = $request_post[0]->post_parent; + + while ( $parent > 0 ) { + $parent_post = get_post( $parent ); + $parent = $parent_post->post_parent; + $level++; + } } $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level ); @@ -1591,6 +1708,8 @@ function wp_ajax_inline_save() { * Ajax handler for quick edit saving for a term. * * @since 3.1.0 + * + * @global WP_List_Table $wp_list_table */ function wp_ajax_inline_save_tax() { global $wp_list_table; @@ -1690,7 +1809,7 @@ function wp_ajax_find_posts() { if ( '0000-00-00 00:00:00' == $post->post_date ) { $time = ''; } else { - /* translators: date format in table columns, see http://php.net/date */ + /* translators: date format in table columns, see https://secure.php.net/date */ $time = mysql2date(__('Y/m/d'), $post->post_date); } @@ -1743,6 +1862,10 @@ function wp_ajax_widgets_order() { * Ajax handler for saving a widget. * * @since 3.1.0 + * + * @global array $wp_registered_widgets + * @global array $wp_registered_widget_controls + * @global array $wp_registered_widget_updates */ function wp_ajax_save_widget() { global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates; @@ -1789,6 +1912,10 @@ function wp_ajax_save_widget() { $sidebar = array_diff( $sidebar, array($widget_id) ); $_POST = array('sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1'); + + /** This action is documented in wp-admin/widgets.php */ + do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base ); + } elseif ( $settings && preg_match( '/__i__|%i%/', key($settings) ) ) { if ( !$multi_number ) wp_die( $error ); @@ -1832,12 +1959,49 @@ function wp_ajax_save_widget() { * Ajax handler for saving a widget. * * @since 3.9.0 + * + * @global WP_Customize_Manager $wp_customize */ function wp_ajax_update_widget() { global $wp_customize; $wp_customize->widgets->wp_ajax_update_widget(); } +/** + * Ajax handler for removing inactive widgets. + * + * @since 4.4.0 + */ +function wp_ajax_delete_inactive_widgets() { + check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' ); + + if ( ! current_user_can( 'edit_theme_options' ) ) { + wp_die( -1 ); + } + + unset( $_POST['removeinactivewidgets'], $_POST['action'] ); + + do_action( 'load-widgets.php' ); + do_action( 'widgets.php' ); + do_action( 'sidebar_admin_setup' ); + + $sidebars_widgets = wp_get_sidebars_widgets(); + + foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) { + $pieces = explode( '-', $widget_id ); + $multi_number = array_pop( $pieces ); + $id_base = implode( '-', $pieces ); + $widget = get_option( 'widget_' . $id_base ); + unset( $widget[$multi_number] ); + update_option( 'widget_' . $id_base, $widget ); + unset( $sidebars_widgets['wp_inactive_widgets'][$key] ); + } + + wp_set_sidebars_widgets( $sidebars_widgets ); + + wp_die(); +} + /** * Ajax handler for uploading attachments * @@ -1855,7 +2019,7 @@ function wp_ajax_upload_attachment() { echo wp_json_encode( array( 'success' => false, 'data' => array( - 'message' => __( "You don't have permission to upload files." ), + 'message' => __( 'Sorry, you are not allowed to upload files.' ), 'filename' => $_FILES['async-upload']['name'], ) ) ); @@ -2000,7 +2164,32 @@ function wp_ajax_set_post_thumbnail() { } /** - * AJAX handler for setting the featured image for an attachment. + * Ajax handler for retrieving HTML for the featured image. + * + * @since 4.6.0 + */ +function wp_ajax_get_post_thumbnail_html() { + $post_ID = intval( $_POST['post_id'] ); + + check_ajax_referer( "update-post_$post_ID" ); + + if ( ! current_user_can( 'edit_post', $post_ID ) ) { + wp_die( -1 ); + } + + $thumbnail_id = intval( $_POST['thumbnail_id'] ); + + // For backward compatibility, -1 refers to no featured image. + if ( -1 === $thumbnail_id ) { + $thumbnail_id = null; + } + + $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID ); + wp_send_json_success( $return ); +} + +/** + * Ajax handler for setting the featured image for an attachment. * * @since 4.0.0 * @@ -2072,6 +2261,7 @@ function wp_ajax_time_format() { * Ajax handler for saving posts from the fullscreen editor. * * @since 3.1.0 + * @deprecated 4.3.0 */ function wp_ajax_wp_fullscreen_save_post() { $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0; @@ -2090,11 +2280,11 @@ function wp_ajax_wp_fullscreen_save_post() { } if ( $post ) { - $last_date = mysql2date( get_option('date_format'), $post->post_modified ); - $last_time = mysql2date( get_option('time_format'), $post->post_modified ); + $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified ); + $last_time = mysql2date( __( 'g:i a' ), $post->post_modified ); } else { - $last_date = date_i18n( get_option('date_format') ); - $last_time = date_i18n( get_option('time_format') ); + $last_date = date_i18n( __( 'F j, Y' ) ); + $last_time = date_i18n( __( 'g:i a' ) ); } if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) { @@ -2129,7 +2319,7 @@ function wp_ajax_wp_remove_post_lock() { wp_die( 0 ); /** - * Filter the post lock window duration. + * Filters the post lock window duration. * * @since 3.3.0 * @@ -2226,7 +2416,7 @@ function wp_ajax_query_attachments() { $query['post_status'] .= ',private'; /** - * Filter the arguments passed to WP_Query during an AJAX + * Filters the arguments passed to WP_Query during an Ajax * call for querying attachments. * * @since 3.7.0 @@ -2319,7 +2509,7 @@ function wp_ajax_save_attachment() { } /** - * Ajax handler for saving backwards compatible attachment attributes. + * Ajax handler for saving backward compatible attachment attributes. * * @since 3.5.0 */ @@ -2405,7 +2595,7 @@ function wp_ajax_save_attachment_order() { * Ajax handler for sending an attachment to the editor. * * Generates the HTML to send an attachment to the editor. - * Backwards compatible with the media_send_to_editor filter + * Backward compatible with the {@see 'media_send_to_editor'} filter * and the chain of filters that follow. * * @since 3.5.0 @@ -2430,14 +2620,8 @@ function wp_ajax_send_attachment_to_editor() { } } - $rel = $url = ''; - $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : ''; - if ( ! empty( $attachment['url'] ) ) { - $url = $attachment['url']; - if ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url ) - $rel = ' rel="attachment wp-att-' . $id . '"'; - $html = '' . $html . ''; - } + $url = empty( $attachment['url'] ) ? '' : $attachment['url']; + $rel = ( strpos( $url, 'attachment_id') || get_attachment_link( $id ) == $url ); remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' ); @@ -2453,9 +2637,16 @@ function wp_ajax_send_attachment_to_editor() { } $title = ''; // We no longer insert title tags into tags, as they are redundant. - $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt ); + $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt ); } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) { $html = stripslashes_deep( $_POST['html'] ); + } else { + $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : ''; + $rel = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized + + if ( ! empty( $url ) ) { + $html = '' . $html . ''; + } } /** This filter is documented in wp-admin/includes/media.php */ @@ -2469,12 +2660,15 @@ function wp_ajax_send_attachment_to_editor() { * * Generates the HTML to send a non-image embed link to the editor. * - * Backwards compatible with the following filters: + * Backward compatible with the following filters: * - file_send_to_editor_url * - audio_send_to_editor_url * - video_send_to_editor_url * * @since 3.5.0 + * + * @global WP_Post $post + * @global WP_Embed $wp_embed */ function wp_ajax_send_link_to_editor() { global $post, $wp_embed; @@ -2530,45 +2724,54 @@ function wp_ajax_send_link_to_editor() { * @since 3.6.0 */ function wp_ajax_heartbeat() { - if ( empty( $_POST['_nonce'] ) ) + if ( empty( $_POST['_nonce'] ) ) { wp_send_json_error(); - - $response = array(); - - if ( false === wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' ) ) { - // User is logged in but nonces have expired. - $response['nonces_expired'] = true; - wp_send_json($response); } + $response = $data = array(); + $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' ); + // screen_id is the same as $current_screen->id and the JS global 'pagenow'. - if ( ! empty($_POST['screen_id']) ) + if ( ! empty( $_POST['screen_id'] ) ) { $screen_id = sanitize_key($_POST['screen_id']); - else + } else { $screen_id = 'front'; + } - if ( ! empty($_POST['data']) ) { + if ( ! empty( $_POST['data'] ) ) { $data = wp_unslash( (array) $_POST['data'] ); + } + if ( 1 !== $nonce_state ) { + $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id ); + + if ( false === $nonce_state ) { + // User is logged in but nonces have expired. + $response['nonces_expired'] = true; + wp_send_json( $response ); + } + } + + if ( ! empty( $data ) ) { /** - * Filter the Heartbeat response received. + * Filters the Heartbeat response received. * * @since 3.6.0 * - * @param array|object $response The Heartbeat response object or array. - * @param array $data The $_POST data sent. - * @param string $screen_id The screen id. + * @param array $response The Heartbeat response. + * @param array $data The $_POST data sent. + * @param string $screen_id The screen id. */ $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id ); } /** - * Filter the Heartbeat response sent. + * Filters the Heartbeat response sent. * * @since 3.6.0 * - * @param array|object $response The Heartbeat response object or array. - * @param string $screen_id The screen id. + * @param array $response The Heartbeat response. + * @param string $screen_id The screen id. */ $response = apply_filters( 'heartbeat_send', $response, $screen_id ); @@ -2579,15 +2782,15 @@ function wp_ajax_heartbeat() { * * @since 3.6.0 * - * @param array|object $response The Heartbeat response object or array. - * @param string $screen_id The screen id. + * @param array $response The Heartbeat response. + * @param string $screen_id The screen id. */ do_action( 'heartbeat_tick', $response, $screen_id ); // Send the current time according to the server $response['server_time'] = time(); - wp_send_json($response); + wp_send_json( $response ); } /** @@ -2601,7 +2804,7 @@ function wp_ajax_get_revision_diffs() { if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) wp_send_json_error(); - if ( ! current_user_can( 'read_post', $post->ID ) ) + if ( ! current_user_can( 'edit_post', $post->ID ) ) wp_send_json_error(); // Really just pre-loading the cache here. @@ -2627,6 +2830,8 @@ function wp_ajax_get_revision_diffs() { * a user's own profile. * * @since 3.8.0 + * + * @global array $_wp_admin_css_colors */ function wp_ajax_save_user_color_scheme() { global $_wp_admin_css_colors; @@ -2652,6 +2857,9 @@ function wp_ajax_save_user_color_scheme() { * Ajax handler for getting themes from themes_api(). * * @since 3.9.0 + * + * @global array $themes_allowedtags + * @global array $theme_field_defaults */ function wp_ajax_query_themes() { global $themes_allowedtags, $theme_field_defaults; @@ -2665,6 +2873,13 @@ function wp_ajax_query_themes() { 'fields' => $theme_field_defaults ) ); + if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) { + $user = get_user_option( 'wporg_favorites' ); + if ( $user ) { + $args['user'] = $user; + } + } + $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search'; /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */ @@ -2683,11 +2898,34 @@ function wp_ajax_query_themes() { '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ) ), $update_php ); + if ( current_user_can( 'switch_themes' ) ) { + if ( is_multisite() ) { + $theme->activate_url = add_query_arg( array( + 'action' => 'enable', + '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ), + 'theme' => $theme->slug, + ), network_admin_url( 'themes.php' ) ); + } else { + $theme->activate_url = add_query_arg( array( + 'action' => 'activate', + '_wpnonce' => wp_create_nonce( 'switch-theme_' . $theme->slug ), + 'stylesheet' => $theme->slug, + ), admin_url( 'themes.php' ) ); + } + } + + if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { + $theme->customize_url = add_query_arg( array( + 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ), + ), wp_customize_url( $theme->slug ) ); + } + $theme->name = wp_kses( $theme->name, $themes_allowedtags ); $theme->author = wp_kses( $theme->author, $themes_allowedtags ); $theme->version = wp_kses( $theme->version, $themes_allowedtags ); $theme->description = wp_kses( $theme->description, $themes_allowedtags ); - $theme->num_ratings = sprintf( _n( '(based on %s rating)', '(based on %s ratings)', $theme->num_ratings ), number_format_i18n( $theme->num_ratings ) ); + $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); + $theme->num_ratings = number_format_i18n( $theme->num_ratings ); $theme->preview_url = set_url_scheme( $theme->preview_url ); } @@ -2695,12 +2933,13 @@ function wp_ajax_query_themes() { } /** - * Apply [embed] AJAX handlers to a string. + * Apply [embed] Ajax handlers to a string. * * @since 4.0.0 * - * @global WP_Post $post Global $post. - * @global WP_Embed $wp_embed Embed API instance. + * @global WP_Post $post Global $post. + * @global WP_Embed $wp_embed Embed API instance. + * @global WP_Scripts $wp_scripts */ function wp_ajax_parse_embed() { global $post, $wp_embed; @@ -2714,14 +2953,23 @@ function wp_ajax_parse_embed() { } $shortcode = wp_unslash( $_POST['shortcode'] ); - $url = str_replace( '[embed]', '', str_replace( '[/embed]', '', $shortcode ) ); + + preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches ); + $atts = shortcode_parse_atts( $matches[3] ); + if ( ! empty( $matches[5] ) ) { + $url = $matches[5]; + } elseif ( ! empty( $atts['src'] ) ) { + $url = $atts['src']; + } else { + $url = ''; + } $parsed = false; setup_postdata( $post ); $wp_embed->return_false_on_fail = true; - if ( is_ssl() && preg_match( '%^\\[embed[^\\]]*\\]http://%i', $shortcode ) ) { + if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) { // Admin is ssl and the user pasted non-ssl URL. // Check if the provider supports ssl embeds and use that for the preview. $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode ); @@ -2732,7 +2980,7 @@ function wp_ajax_parse_embed() { } } - if ( ! $parsed ) { + if ( $url && ! $parsed ) { $parsed = $wp_embed->run_shortcode( $shortcode ); } @@ -2774,10 +3022,17 @@ function wp_ajax_parse_embed() { } wp_send_json_success( array( - 'body' => $parsed + 'body' => $parsed, + 'attr' => $wp_embed->last_attr ) ); } +/** + * @since 4.0.0 + * + * @global WP_Post $post + * @global WP_Scripts $wp_scripts + */ function wp_ajax_parse_media_shortcode() { global $post, $wp_scripts; @@ -2839,12 +3094,11 @@ function wp_ajax_parse_media_shortcode() { } /** - * AJAX handler for destroying multiple open sessions for a user. + * Ajax handler for destroying multiple open sessions for a user. * * @since 4.1.0 */ function wp_ajax_destroy_sessions() { - $user = get_userdata( (int) $_POST['user_id'] ); if ( $user ) { if ( ! current_user_can( 'edit_user', $user->ID ) ) { @@ -2874,47 +3128,596 @@ function wp_ajax_destroy_sessions() { wp_send_json_success( array( 'message' => $message ) ); } +/** + * Ajax handler for saving a post from Press This. + * + * @since 4.2.0 + * + * @global WP_Press_This $wp_press_this + */ +function wp_ajax_press_this_save_post() { + if ( empty( $GLOBALS['wp_press_this'] ) ) { + include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' ); + } + + $GLOBALS['wp_press_this']->save_post(); +} + +/** + * Ajax handler for creating new category from Press This. + * + * @since 4.2.0 + * + * @global WP_Press_This $wp_press_this + */ +function wp_ajax_press_this_add_category() { + if ( empty( $GLOBALS['wp_press_this'] ) ) { + include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' ); + } + + $GLOBALS['wp_press_this']->add_category(); +} + +/** + * Ajax handler for cropping an image. + * + * @since 4.3.0 + * + * @global WP_Site_Icon $wp_site_icon + */ +function wp_ajax_crop_image() { + $attachment_id = absint( $_POST['id'] ); + + check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' ); + if ( ! current_user_can( 'customize' ) ) { + wp_send_json_error(); + } + + $context = str_replace( '_', '-', $_POST['context'] ); + $data = array_map( 'absint', $_POST['cropDetails'] ); + $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] ); + + if ( ! $cropped || is_wp_error( $cropped ) ) { + wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) ); + } + + switch ( $context ) { + case 'site-icon': + require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php'; + global $wp_site_icon; + + // Skip creating a new attachment if the attachment is a Site Icon. + if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) { + + // Delete the temporary cropped file, we don't need it. + wp_delete_file( $cropped ); + + // Additional sizes in wp_prepare_attachment_for_js(). + add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) ); + break; + } + + /** This filter is documented in wp-admin/custom-header.php */ + $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication. + $object = $wp_site_icon->create_attachment_object( $cropped, $attachment_id ); + unset( $object['ID'] ); + + // Update the attachment. + add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) ); + $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped ); + remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) ); + + // Additional sizes in wp_prepare_attachment_for_js(). + add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) ); + break; + + default: + + /** + * Fires before a cropped image is saved. + * + * Allows to add filters to modify the way a cropped image is saved. + * + * @since 4.3.0 + * + * @param string $context The Customizer control requesting the cropped image. + * @param int $attachment_id The attachment ID of the original image. + * @param string $cropped Path to the cropped image file. + */ + do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped ); + + /** This filter is documented in wp-admin/custom-header.php */ + $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication. + + $parent_url = wp_get_attachment_url( $attachment_id ); + $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url ); + + $size = @getimagesize( $cropped ); + $image_type = ( $size ) ? $size['mime'] : 'image/jpeg'; + + $object = array( + 'post_title' => basename( $cropped ), + 'post_content' => $url, + 'post_mime_type' => $image_type, + 'guid' => $url, + 'context' => $context, + ); + + $attachment_id = wp_insert_attachment( $object, $cropped ); + $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped ); + + /** + * Filters the cropped image attachment metadata. + * + * @since 4.3.0 + * + * @see wp_generate_attachment_metadata() + * + * @param array $metadata Attachment metadata. + */ + $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata ); + wp_update_attachment_metadata( $attachment_id, $metadata ); + + /** + * Filters the attachment ID for a cropped image. + * + * @since 4.3.0 + * + * @param int $attachment_id The attachment ID of the cropped image. + * @param string $context The Customizer control requesting the cropped image. + */ + $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context ); + } + + wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) ); +} + +/** + * Ajax handler for generating a password. + * + * @since 4.4.0 + */ +function wp_ajax_generate_password() { + wp_send_json_success( wp_generate_password( 24 ) ); +} + +/** + * Ajax handler for saving the user's WordPress.org username. + * + * @since 4.4.0 + */ +function wp_ajax_save_wporg_username() { + if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error(); + } + + check_ajax_referer( 'save_wporg_username_' . get_current_user_id() ); + + $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false; + + if ( ! $username ) { + wp_send_json_error(); + } + + wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) ); +} + +/** + * Ajax handler for installing a theme. + * + * @since 4.6.0 + * + * @see Theme_Upgrader + */ +function wp_ajax_install_theme() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_theme_specified', + 'errorMessage' => __( 'No theme specified.' ), + ) ); + } + + $slug = sanitize_key( wp_unslash( $_POST['slug'] ) ); + + $status = array( + 'install' => 'theme', + 'slug' => $slug, + ); + + if ( ! current_user_can( 'install_themes' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' ); + wp_send_json_error( $status ); + } + + include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); + include_once( ABSPATH . 'wp-admin/includes/theme.php' ); + + $api = themes_api( 'theme_information', array( + 'slug' => $slug, + 'fields' => array( 'sections' => false ), + ) ); + + if ( is_wp_error( $api ) ) { + $status['errorMessage'] = $api->get_error_message(); + wp_send_json_error( $status ); + } + + $skin = new WP_Ajax_Upgrader_Skin(); + $upgrader = new Theme_Upgrader( $skin ); + $result = $upgrader->install( $api->download_link ); + + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + $status['debug'] = $skin->get_upgrade_messages(); + } + + if ( is_wp_error( $result ) ) { + $status['errorCode'] = $result->get_error_code(); + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( is_wp_error( $skin->result ) ) { + $status['errorCode'] = $skin->result->get_error_code(); + $status['errorMessage'] = $skin->result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( $skin->get_errors()->get_error_code() ) { + $status['errorMessage'] = $skin->get_error_messages(); + wp_send_json_error( $status ); + } elseif ( is_null( $result ) ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + + wp_send_json_error( $status ); + } + + $status['themeName'] = wp_get_theme( $slug )->get( 'Name' ); + + if ( current_user_can( 'switch_themes' ) ) { + if ( is_multisite() ) { + $status['activateUrl'] = add_query_arg( array( + 'action' => 'enable', + '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ), + 'theme' => $slug, + ), network_admin_url( 'themes.php' ) ); + } else { + $status['activateUrl'] = add_query_arg( array( + 'action' => 'activate', + '_wpnonce' => wp_create_nonce( 'switch-theme_' . $slug ), + 'stylesheet' => $slug, + ), admin_url( 'themes.php' ) ); + } + } + + if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { + $status['customizeUrl'] = add_query_arg( array( + 'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ), + ), wp_customize_url( $slug ) ); + } + + /* + * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check + * on post-install status. + */ + wp_send_json_success( $status ); +} + +/** + * Ajax handler for updating a theme. + * + * @since 4.6.0 + * + * @see Theme_Upgrader + */ +function wp_ajax_update_theme() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_theme_specified', + 'errorMessage' => __( 'No theme specified.' ), + ) ); + } + + $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) ); + $status = array( + 'update' => 'theme', + 'slug' => $stylesheet, + 'newVersion' => '', + ); + + if ( ! current_user_can( 'update_themes' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' ); + wp_send_json_error( $status ); + } + + include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); + + $current = get_site_transient( 'update_themes' ); + if ( empty( $current ) ) { + wp_update_themes(); + } + + $skin = new WP_Ajax_Upgrader_Skin(); + $upgrader = new Theme_Upgrader( $skin ); + $result = $upgrader->bulk_upgrade( array( $stylesheet ) ); + + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + $status['debug'] = $skin->get_upgrade_messages(); + } + + if ( is_wp_error( $skin->result ) ) { + $status['errorCode'] = $skin->result->get_error_code(); + $status['errorMessage'] = $skin->result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( $skin->get_errors()->get_error_code() ) { + $status['errorMessage'] = $skin->get_error_messages(); + wp_send_json_error( $status ); + } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) { + + // Theme is already at the latest version. + if ( true === $result[ $stylesheet ] ) { + $status['errorMessage'] = $upgrader->strings['up_to_date']; + wp_send_json_error( $status ); + } + + $theme = wp_get_theme( $stylesheet ); + if ( $theme->get( 'Version' ) ) { + $status['newVersion'] = $theme->get( 'Version' ); + } + + wp_send_json_success( $status ); + } elseif ( false === $result ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + + wp_send_json_error( $status ); + } + + // An unhandled error occurred. + $status['errorMessage'] = __( 'Update failed.' ); + wp_send_json_error( $status ); +} /** - * AJAX handler for updating a plugin. + * Ajax handler for deleting a theme. + * + * @since 4.6.0 + * + * @see delete_theme() + */ +function wp_ajax_delete_theme() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_theme_specified', + 'errorMessage' => __( 'No theme specified.' ), + ) ); + } + + $stylesheet = sanitize_key( wp_unslash( $_POST['slug'] ) ); + $status = array( + 'delete' => 'theme', + 'slug' => $stylesheet, + ); + + if ( ! current_user_can( 'delete_themes' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' ); + wp_send_json_error( $status ); + } + + if ( ! wp_get_theme( $stylesheet )->exists() ) { + $status['errorMessage'] = __( 'The requested theme does not exist.' ); + wp_send_json_error( $status ); + } + + // Check filesystem credentials. `delete_theme()` will bail otherwise. + $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet ); + ob_start(); + $credentials = request_filesystem_credentials( $url ); + ob_end_clean(); + if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + + wp_send_json_error( $status ); + } + + include_once( ABSPATH . 'wp-admin/includes/theme.php' ); + + $result = delete_theme( $stylesheet ); + + if ( is_wp_error( $result ) ) { + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( false === $result ) { + $status['errorMessage'] = __( 'Theme could not be deleted.' ); + wp_send_json_error( $status ); + } + + wp_send_json_success( $status ); +} + +/** + * Ajax handler for installing a plugin. + * + * @since 4.6.0 + * + * @see Plugin_Upgrader + */ +function wp_ajax_install_plugin() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_plugin_specified', + 'errorMessage' => __( 'No plugin specified.' ), + ) ); + } + + $status = array( + 'install' => 'plugin', + 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), + ); + + if ( ! current_user_can( 'install_plugins' ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' ); + wp_send_json_error( $status ); + } + + include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); + include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); + + $api = plugins_api( 'plugin_information', array( + 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), + 'fields' => array( + 'sections' => false, + ), + ) ); + + if ( is_wp_error( $api ) ) { + $status['errorMessage'] = $api->get_error_message(); + wp_send_json_error( $status ); + } + + $status['pluginName'] = $api->name; + + $skin = new WP_Ajax_Upgrader_Skin(); + $upgrader = new Plugin_Upgrader( $skin ); + $result = $upgrader->install( $api->download_link ); + + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + $status['debug'] = $skin->get_upgrade_messages(); + } + + if ( is_wp_error( $result ) ) { + $status['errorCode'] = $result->get_error_code(); + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( is_wp_error( $skin->result ) ) { + $status['errorCode'] = $skin->result->get_error_code(); + $status['errorMessage'] = $skin->result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( $skin->get_errors()->get_error_code() ) { + $status['errorMessage'] = $skin->get_error_messages(); + wp_send_json_error( $status ); + } elseif ( is_null( $result ) ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + + wp_send_json_error( $status ); + } + + $install_status = install_plugin_install_status( $api ); + + if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) { + $status['activateUrl'] = add_query_arg( array( + '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ), + 'action' => 'activate', + 'plugin' => $install_status['file'], + ), network_admin_url( 'plugins.php' ) ); + } + + if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) { + $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] ); + } + + wp_send_json_success( $status ); +} + +/** + * Ajax handler for updating a plugin. * * @since 4.2.0 * * @see Plugin_Upgrader */ function wp_ajax_update_plugin() { - $plugin = urldecode( $_POST['plugin'] ); + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_plugin_specified', + 'errorMessage' => __( 'No plugin specified.' ), + ) ); + } + + $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) ); $status = array( 'update' => 'plugin', - 'plugin' => $plugin, - 'slug' => sanitize_key( $_POST['slug'] ), + 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), 'oldVersion' => '', 'newVersion' => '', ); - $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); + + if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' ); + wp_send_json_error( $status ); + } + + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); + $status['plugin'] = $plugin; + $status['pluginName'] = $plugin_data['Name']; + if ( $plugin_data['Version'] ) { + /* translators: %s: Plugin version */ $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); } - if ( ! current_user_can( 'update_plugins' ) ) { - $status['error'] = __( 'You do not have sufficient permissions to update plugins on this site.' ); - wp_send_json_error( $status ); - } + include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; - check_ajax_referer( 'updates' ); + wp_update_plugins(); - include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); + $skin = new WP_Ajax_Upgrader_Skin(); + $upgrader = new Plugin_Upgrader( $skin ); + $result = $upgrader->bulk_upgrade( array( $plugin ) ); - $current = get_site_transient( 'update_plugins' ); - if ( empty( $current ) ) { - wp_update_plugins(); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + $status['debug'] = $skin->get_upgrade_messages(); } - $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); - $result = $upgrader->bulk_upgrade( array( $plugin ) ); - - if ( is_array( $result ) ) { + if ( is_wp_error( $skin->result ) ) { + $status['errorCode'] = $skin->result->get_error_code(); + $status['errorMessage'] = $skin->result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( $skin->get_errors()->get_error_code() ) { + $status['errorMessage'] = $skin->get_error_messages(); + wp_send_json_error( $status ); + } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) { $plugin_update_data = current( $result ); /* @@ -2925,49 +3728,191 @@ function wp_ajax_update_plugin() { * Preferably something can be done to ensure `update_plugins` isn't empty. * For now, surface some sort of error here. */ - if ( $plugin_update_data === true ) { - wp_send_json_error( $status ); + if ( true === $plugin_update_data ) { + $status['errorMessage'] = __( 'Plugin update failed.' ); + wp_send_json_error( $status ); } - $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); + $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] ); + $plugin_data = reset( $plugin_data ); if ( $plugin_data['Version'] ) { + /* translators: %s: Plugin version */ $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] ); } - wp_send_json_success( $status ); - } else if ( is_wp_error( $result ) ) { - $status['error'] = $result->get_error_message(); - wp_send_json_error( $status ); - } else if ( is_bool( $result ) && ! $result ) { - $status['errorCode'] = 'unable_to_connect_to_filesystem'; - $status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + } elseif ( false === $result ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + wp_send_json_error( $status ); } + + // An unhandled error occurred. + $status['errorMessage'] = __( 'Plugin update failed.' ); + wp_send_json_error( $status ); } /** - * AJAX handler for saving a post from Press This. + * Ajax handler for deleting a plugin. * - * @since 4.2.0 + * @since 4.6.0 + * + * @see delete_plugins() */ -function wp_ajax_press_this_save_post() { - if ( empty( $GLOBALS['wp_press_this'] ) ) { - include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' ); +function wp_ajax_delete_plugin() { + check_ajax_referer( 'updates' ); + + if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) { + wp_send_json_error( array( + 'slug' => '', + 'errorCode' => 'no_plugin_specified', + 'errorMessage' => __( 'No plugin specified.' ), + ) ); } - $GLOBALS['wp_press_this']->save_post(); + $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) ); + + $status = array( + 'delete' => 'plugin', + 'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ), + ); + + if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' ); + wp_send_json_error( $status ); + } + + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin ); + $status['plugin'] = $plugin; + $status['pluginName'] = $plugin_data['Name']; + + if ( is_plugin_active( $plugin ) ) { + $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' ); + wp_send_json_error( $status ); + } + + // Check filesystem credentials. `delete_plugins()` will bail otherwise. + $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' ); + ob_start(); + $credentials = request_filesystem_credentials( $url ); + ob_end_clean(); + if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { + global $wp_filesystem; + + $status['errorCode'] = 'unable_to_connect_to_filesystem'; + $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' ); + + // Pass through the error from WP_Filesystem if one was raised. + if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) { + $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() ); + } + + wp_send_json_error( $status ); + } + + $result = delete_plugins( array( $plugin ) ); + + if ( is_wp_error( $result ) ) { + $status['errorMessage'] = $result->get_error_message(); + wp_send_json_error( $status ); + } elseif ( false === $result ) { + $status['errorMessage'] = __( 'Plugin could not be deleted.' ); + wp_send_json_error( $status ); + } + + wp_send_json_success( $status ); } /** - * AJAX handler for creating new category from Press This. + * Ajax handler for searching plugins. * - * @since 4.2.0 + * @since 4.6.0 + * + * @global string $s Search term. */ -function wp_ajax_press_this_add_category() { - if ( empty( $GLOBALS['wp_press_this'] ) ) { - include( ABSPATH . 'wp-admin/includes/class-wp-press-this.php' ); +function wp_ajax_search_plugins() { + check_ajax_referer( 'updates' ); + + $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : ''; + if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) { + set_current_screen( $pagenow ); } - $GLOBALS['wp_press_this']->add_category(); + /** @var WP_Plugins_List_Table $wp_list_table */ + $wp_list_table = _get_list_table( 'WP_Plugins_List_Table', array( + 'screen' => get_current_screen(), + ) ); + + $status = array(); + + if ( ! $wp_list_table->ajax_user_can() ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' ); + wp_send_json_error( $status ); + } + + // Set the correct requester, so pagination works. + $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array( + '_ajax_nonce' => null, + 'action' => null, + ) ), network_admin_url( 'plugins.php', 'relative' ) ); + + $GLOBALS['s'] = wp_unslash( $_POST['s'] ); + + $wp_list_table->prepare_items(); + + ob_start(); + $wp_list_table->display(); + $status['count'] = count( $wp_list_table->items ); + $status['items'] = ob_get_clean(); + + wp_send_json_success( $status ); +} + +/** + * Ajax handler for searching plugins to install. + * + * @since 4.6.0 + */ +function wp_ajax_search_install_plugins() { + check_ajax_referer( 'updates' ); + + $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : ''; + if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) { + set_current_screen( $pagenow ); + } + + /** @var WP_Plugin_Install_List_Table $wp_list_table */ + $wp_list_table = _get_list_table( 'WP_Plugin_Install_List_Table', array( + 'screen' => get_current_screen(), + ) ); + + $status = array(); + + if ( ! $wp_list_table->ajax_user_can() ) { + $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' ); + wp_send_json_error( $status ); + } + + // Set the correct requester, so pagination works. + $_SERVER['REQUEST_URI'] = add_query_arg( array_diff_key( $_POST, array( + '_ajax_nonce' => null, + 'action' => null, + ) ), network_admin_url( 'plugin-install.php', 'relative' ) ); + + $wp_list_table->prepare_items(); + + ob_start(); + $wp_list_table->display(); + $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' ); + $status['items'] = ob_get_clean(); + + wp_send_json_success( $status ); }