+
+/**
+ * Ajax handler for getting an attachment.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_get_attachment() {
+ if ( ! isset( $_REQUEST['id'] ) )
+ wp_send_json_error();
+
+ if ( ! $id = absint( $_REQUEST['id'] ) )
+ wp_send_json_error();
+
+ if ( ! $post = get_post( $id ) )
+ wp_send_json_error();
+
+ if ( 'attachment' != $post->post_type )
+ wp_send_json_error();
+
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_send_json_error();
+
+ if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
+ wp_send_json_error();
+
+ wp_send_json_success( $attachment );
+}
+
+/**
+ * Ajax handler for querying attachments.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_query_attachments() {
+ if ( ! current_user_can( 'upload_files' ) )
+ wp_send_json_error();
+
+ $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
+ $keys = array(
+ 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
+ 'post_parent', 'post__in', 'post__not_in', 'year', 'monthnum'
+ );
+ foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
+ if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
+ $keys[] = $t->query_var;
+ }
+ }
+
+ $query = array_intersect_key( $query, array_flip( $keys ) );
+ $query['post_type'] = 'attachment';
+ if ( MEDIA_TRASH
+ && ! empty( $_REQUEST['query']['post_status'] )
+ && 'trash' === $_REQUEST['query']['post_status'] ) {
+ $query['post_status'] = 'trash';
+ } else {
+ $query['post_status'] = 'inherit';
+ }
+
+ if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) )
+ $query['post_status'] .= ',private';
+
+ /**
+ * Filter the arguments passed to WP_Query during an AJAX
+ * call for querying attachments.
+ *
+ * @since 3.7.0
+ *
+ * @see WP_Query::parse_query()
+ *
+ * @param array $query An array of query variables.
+ */
+ $query = apply_filters( 'ajax_query_attachments_args', $query );
+ $query = new WP_Query( $query );
+
+ $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
+ $posts = array_filter( $posts );
+
+ wp_send_json_success( $posts );
+}
+
+/**
+ * Ajax handler for updating attachment attributes.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_save_attachment() {
+ if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) )
+ wp_send_json_error();
+
+ if ( ! $id = absint( $_REQUEST['id'] ) )
+ wp_send_json_error();
+
+ check_ajax_referer( 'update-post_' . $id, 'nonce' );
+
+ if ( ! current_user_can( 'edit_post', $id ) )
+ wp_send_json_error();
+
+ $changes = $_REQUEST['changes'];
+ $post = get_post( $id, ARRAY_A );
+
+ if ( 'attachment' != $post['post_type'] )
+ wp_send_json_error();
+
+ if ( isset( $changes['parent'] ) )
+ $post['post_parent'] = $changes['parent'];
+
+ if ( isset( $changes['title'] ) )
+ $post['post_title'] = $changes['title'];
+
+ if ( isset( $changes['caption'] ) )
+ $post['post_excerpt'] = $changes['caption'];
+
+ if ( isset( $changes['description'] ) )
+ $post['post_content'] = $changes['description'];
+
+ if ( MEDIA_TRASH && isset( $changes['status'] ) )
+ $post['post_status'] = $changes['status'];
+
+ if ( isset( $changes['alt'] ) ) {
+ $alt = wp_unslash( $changes['alt'] );
+ if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
+ $alt = wp_strip_all_tags( $alt, true );
+ update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
+ }
+ }
+
+ if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
+ $changed = false;
+ $id3data = wp_get_attachment_metadata( $post['ID'] );
+ if ( ! is_array( $id3data ) ) {
+ $changed = true;
+ $id3data = array();
+ }
+ foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
+ if ( isset( $changes[ $key ] ) ) {
+ $changed = true;
+ $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
+ }
+ }
+
+ if ( $changed ) {
+ wp_update_attachment_metadata( $id, $id3data );
+ }
+ }
+
+ if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
+ wp_delete_post( $id );
+ } else {
+ wp_update_post( $post );
+ }
+
+ wp_send_json_success();
+}
+
+/**
+ * Ajax handler for saving backwards compatible attachment attributes.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_save_attachment_compat() {
+ if ( ! isset( $_REQUEST['id'] ) )
+ wp_send_json_error();
+
+ if ( ! $id = absint( $_REQUEST['id'] ) )
+ wp_send_json_error();
+
+ if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) )
+ wp_send_json_error();
+ $attachment_data = $_REQUEST['attachments'][ $id ];
+
+ check_ajax_referer( 'update-post_' . $id, 'nonce' );
+
+ if ( ! current_user_can( 'edit_post', $id ) )
+ wp_send_json_error();
+
+ $post = get_post( $id, ARRAY_A );
+
+ if ( 'attachment' != $post['post_type'] )
+ wp_send_json_error();
+
+ /** This filter is documented in wp-admin/includes/media.php */
+ $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
+
+ if ( isset( $post['errors'] ) ) {
+ $errors = $post['errors']; // @todo return me and display me!
+ unset( $post['errors'] );
+ }
+
+ wp_update_post( $post );
+
+ foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
+ if ( isset( $attachment_data[ $taxonomy ] ) )
+ wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
+ }
+
+ if ( ! $attachment = wp_prepare_attachment_for_js( $id ) )
+ wp_send_json_error();
+
+ wp_send_json_success( $attachment );
+}
+
+/**
+ * Ajax handler for saving the attachment order.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_save_attachment_order() {
+ if ( ! isset( $_REQUEST['post_id'] ) )
+ wp_send_json_error();
+
+ if ( ! $post_id = absint( $_REQUEST['post_id'] ) )
+ wp_send_json_error();
+
+ if ( empty( $_REQUEST['attachments'] ) )
+ wp_send_json_error();
+
+ check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
+
+ $attachments = $_REQUEST['attachments'];
+
+ if ( ! current_user_can( 'edit_post', $post_id ) )
+ wp_send_json_error();
+
+ foreach ( $attachments as $attachment_id => $menu_order ) {
+ if ( ! current_user_can( 'edit_post', $attachment_id ) )
+ continue;
+ if ( ! $attachment = get_post( $attachment_id ) )
+ continue;
+ if ( 'attachment' != $attachment->post_type )
+ continue;
+
+ wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order ) );
+ }
+
+ wp_send_json_success();
+}
+
+/**
+ * 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
+ * and the chain of filters that follow.
+ *
+ * @since 3.5.0
+ */
+function wp_ajax_send_attachment_to_editor() {
+ check_ajax_referer( 'media-send-to-editor', 'nonce' );
+
+ $attachment = wp_unslash( $_POST['attachment'] );
+
+ $id = intval( $attachment['id'] );
+
+ if ( ! $post = get_post( $id ) )
+ wp_send_json_error();
+
+ if ( 'attachment' != $post->post_type )
+ wp_send_json_error();
+
+ if ( current_user_can( 'edit_post', $id ) ) {
+ // If this attachment is unattached, attach it. Primarily a back compat thing.
+ if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
+ wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id ) );
+ }
+ }
+
+ $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 = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
+ }
+
+ remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
+
+ if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
+ $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
+ $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
+ $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
+
+ // No whitespace-only captions.
+ $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
+ if ( '' === trim( $caption ) ) {
+ $caption = '';
+ }
+
+ $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
+ $html = get_image_send_to_editor( $id, $caption, $title, $align, $url, (bool) $rel, $size, $alt );
+ } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
+ $html = stripslashes_deep( $_POST['html'] );
+ }
+
+ /** This filter is documented in wp-admin/includes/media.php */
+ $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
+
+ wp_send_json_success( $html );
+}
+
+/**
+ * Ajax handler for sending a link to the editor.
+ *
+ * Generates the HTML to send a non-image embed link to the editor.
+ *
+ * Backwards 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;
+
+ check_ajax_referer( 'media-send-to-editor', 'nonce' );
+
+ if ( ! $src = wp_unslash( $_POST['src'] ) )
+ wp_send_json_error();
+
+ if ( ! strpos( $src, '://' ) )
+ $src = 'http://' . $src;
+
+ if ( ! $src = esc_url_raw( $src ) )
+ wp_send_json_error();
+
+ if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) )
+ $link_text = wp_basename( $src );
+
+ $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
+
+ // Ping WordPress for an embed.
+ $check_embed = $wp_embed->run_shortcode( '[embed]'. $src .'[/embed]' );
+
+ // Fallback that WordPress creates when no oEmbed was found.
+ $fallback = $wp_embed->maybe_make_link( $src );
+
+ if ( $check_embed !== $fallback ) {
+ // TinyMCE view for [embed] will parse this
+ $html = '[embed]' . $src . '[/embed]';
+ } elseif ( $link_text ) {
+ $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
+ } else {
+ $html = '';
+ }
+
+ // Figure out what filter to run:
+ $type = 'file';
+ if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
+ && ( 'audio' == $ext_type || 'video' == $ext_type ) )
+ $type = $ext_type;
+
+ /** This filter is documented in wp-admin/includes/media.php */
+ $html = apply_filters( $type . '_send_to_editor_url', $html, $src, $link_text );
+
+ wp_send_json_success( $html );
+}
+
+/**
+ * Ajax handler for the Heartbeat API.
+ *
+ * Runs when the user is logged in.
+ *
+ * @since 3.6.0
+ */
+function wp_ajax_heartbeat() {
+ if ( empty( $_POST['_nonce'] ) ) {
+ wp_send_json_error();
+ }
+
+ $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'] ) ) {
+ $screen_id = sanitize_key($_POST['screen_id']);
+ } else {
+ $screen_id = 'front';
+ }
+
+ 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.
+ *
+ * @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.
+ */
+ $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
+ }
+
+ /**
+ * Filter 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.
+ */
+ $response = apply_filters( 'heartbeat_send', $response, $screen_id );
+
+ /**
+ * Fires when Heartbeat ticks in logged-in environments.
+ *
+ * Allows the transport to be easily replaced with long-polling.
+ *
+ * @since 3.6.0
+ *
+ * @param array|object $response The Heartbeat response object or array.
+ * @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 );
+}
+
+/**
+ * Ajax handler for getting revision diffs.
+ *
+ * @since 3.6.0
+ */
+function wp_ajax_get_revision_diffs() {
+ require ABSPATH . 'wp-admin/includes/revision.php';
+
+ if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) )
+ wp_send_json_error();
+
+ if ( ! current_user_can( 'read_post', $post->ID ) )
+ wp_send_json_error();
+
+ // Really just pre-loading the cache here.
+ if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) )
+ wp_send_json_error();
+
+ $return = array();
+ @set_time_limit( 0 );
+
+ foreach ( $_REQUEST['compare'] as $compare_key ) {
+ list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
+
+ $return[] = array(
+ 'id' => $compare_key,
+ 'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
+ );
+ }
+ wp_send_json_success( $return );
+}
+
+/**
+ * Ajax handler for auto-saving the selected color scheme for
+ * 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;
+
+ check_ajax_referer( 'save-color-scheme', 'nonce' );
+
+ $color_scheme = sanitize_key( $_POST['color_scheme'] );
+
+ if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
+ wp_send_json_error();
+ }
+
+ $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
+ update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
+
+ wp_send_json_success( array(
+ 'previousScheme' => 'admin-color-' . $previous_color_scheme,
+ 'currentScheme' => 'admin-color-' . $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;
+
+ if ( ! current_user_can( 'install_themes' ) ) {
+ wp_send_json_error();
+ }
+
+ $args = wp_parse_args( wp_unslash( $_REQUEST['request'] ), array(
+ 'per_page' => 20,
+ 'fields' => $theme_field_defaults
+ ) );
+
+ $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
+
+ /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
+ $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
+
+ $api = themes_api( 'query_themes', $args );
+
+ if ( is_wp_error( $api ) ) {
+ wp_send_json_error();
+ }
+
+ $update_php = network_admin_url( 'update.php?action=install-theme' );
+ foreach ( $api->themes as &$theme ) {
+ $theme->install_url = add_query_arg( array(
+ 'theme' => $theme->slug,
+ '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug )
+ ), $update_php );
+
+ $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->preview_url = set_url_scheme( $theme->preview_url );
+ }
+
+ wp_send_json_success( $api );
+}
+
+/**
+ * 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_Scripts $wp_scripts
+ */
+function wp_ajax_parse_embed() {
+ global $post, $wp_embed;
+
+ if ( ! $post = get_post( (int) $_POST['post_ID'] ) ) {
+ wp_send_json_error();
+ }
+
+ if ( empty( $_POST['shortcode'] ) || ! current_user_can( 'edit_post', $post->ID ) ) {
+ wp_send_json_error();
+ }
+
+ $shortcode = wp_unslash( $_POST['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() && 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 );
+ $parsed = $wp_embed->run_shortcode( $ssl_shortcode );
+
+ if ( ! $parsed ) {
+ $no_ssl_support = true;
+ }
+ }
+
+ if ( $url && ! $parsed ) {
+ $parsed = $wp_embed->run_shortcode( $shortcode );
+ }
+
+ if ( ! $parsed ) {
+ wp_send_json_error( array(
+ 'type' => 'not-embeddable',
+ 'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
+ ) );
+ }
+
+ if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
+ $styles = '';
+ $mce_styles = wpview_media_sandbox_styles();
+ foreach ( $mce_styles as $style ) {
+ $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
+ }
+
+ $html = do_shortcode( $parsed );
+
+ global $wp_scripts;
+ if ( ! empty( $wp_scripts ) ) {
+ $wp_scripts->done = array();
+ }
+ ob_start();
+ wp_print_scripts( 'wp-mediaelement' );
+ $scripts = ob_get_clean();
+
+ $parsed = $styles . $html . $scripts;
+ }
+
+
+ if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
+ preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
+ // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
+ wp_send_json_error( array(
+ 'type' => 'not-ssl',
+ 'message' => __( 'This preview is unavailable in the editor.' ),
+ ) );
+ }
+
+ wp_send_json_success( array(
+ '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;
+
+ if ( empty( $_POST['shortcode'] ) ) {
+ wp_send_json_error();
+ }
+
+ $shortcode = wp_unslash( $_POST['shortcode'] );
+
+ if ( ! empty( $_POST['post_ID'] ) ) {
+ $post = get_post( (int) $_POST['post_ID'] );
+ }
+
+ // the embed shortcode requires a post
+ if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
+ if ( 'embed' === $shortcode ) {
+ wp_send_json_error();
+ }
+ } else {
+ setup_postdata( $post );
+ }
+
+ $parsed = do_shortcode( $shortcode );
+
+ if ( empty( $parsed ) ) {
+ wp_send_json_error( array(
+ 'type' => 'no-items',
+ 'message' => __( 'No items found.' ),
+ ) );
+ }
+
+ $head = '';
+ $styles = wpview_media_sandbox_styles();
+
+ foreach ( $styles as $style ) {
+ $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
+ }
+
+ if ( ! empty( $wp_scripts ) ) {
+ $wp_scripts->done = array();
+ }
+
+ ob_start();
+
+ echo $parsed;
+
+ if ( 'playlist' === $_REQUEST['type'] ) {
+ wp_underscore_playlist_templates();
+
+ wp_print_scripts( 'wp-playlist' );
+ } else {
+ wp_print_scripts( array( 'froogaloop', 'wp-mediaelement' ) );
+ }
+
+ wp_send_json_success( array(
+ 'head' => $head,
+ 'body' => ob_get_clean()
+ ) );
+}
+
+/**
+ * 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 ) ) {
+ $user = false;
+ } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
+ $user = false;
+ }
+ }
+
+ if ( ! $user ) {
+ wp_send_json_error( array(
+ 'message' => __( 'Could not log out user sessions. Please try again.' ),
+ ) );
+ }
+
+ $sessions = WP_Session_Tokens::get_instance( $user->ID );
+
+ if ( $user->ID === get_current_user_id() ) {
+ $sessions->destroy_others( wp_get_session_token() );
+ $message = __( 'You are now logged out everywhere else.' );
+ } else {
+ $sessions->destroy_all();
+ /* translators: 1: User's display name. */
+ $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
+ }
+
+ wp_send_json_success( array( 'message' => $message ) );
+}
+
+
+/**
+ * AJAX handler for updating a plugin.
+ *
+ * @since 4.2.0
+ *
+ * @see Plugin_Upgrader
+ */
+function wp_ajax_update_plugin() {
+ global $wp_filesystem;
+
+ $plugin = urldecode( $_POST['plugin'] );
+
+ $status = array(
+ 'update' => 'plugin',
+ 'plugin' => $plugin,
+ 'slug' => sanitize_key( $_POST['slug'] ),
+ 'oldVersion' => '',
+ 'newVersion' => '',
+ );
+
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+ if ( $plugin_data['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 for this site.' );
+ wp_send_json_error( $status );
+ }
+
+ check_ajax_referer( 'updates' );
+
+ include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
+
+ wp_update_plugins();
+
+ $skin = new Automatic_Upgrader_Skin();
+ $upgrader = new Plugin_Upgrader( $skin );
+ $result = $upgrader->bulk_upgrade( array( $plugin ) );
+
+ if ( is_array( $result ) && empty( $result[$plugin] ) && is_wp_error( $skin->result ) ) {
+ $result = $skin->result;
+ }
+
+ if ( is_array( $result ) && !empty( $result[ $plugin ] ) ) {
+ $plugin_update_data = current( $result );
+
+ /*
+ * If the `update_plugins` site transient is empty (e.g. when you update
+ * two plugins in quick succession before the transient repopulates),
+ * this may be the return.
+ *
+ * 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 );
+ }
+
+ $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
+ $plugin_data = reset( $plugin_data );
+
+ if ( $plugin_data['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.' );
+
+ // Pass through the error from WP_Filesystem if one was raised
+ if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->get_error_code() ) {
+ $status['error'] = $wp_filesystem->errors->get_error_message();
+ }
+
+ wp_send_json_error( $status );
+
+ }
+}
+
+/**
+ * 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 = get_post( $attachment_id )->guid;
+ $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 );
+
+ /**
+ * Filter 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 );
+
+ /**
+ * Filter 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 ) );
+}