: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( ! current_user_can( 'upload_files' ) ) {
$attachment = wp_prepare_attachment_for_js( $id );
wp_send_json_success( $attachment );
* Handles querying attachments via AJAX.
function wp_ajax_query_attachments() {
if ( ! current_user_can( 'upload_files' ) ) {
$query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
$query = array_intersect_key( $query, array_flip( $keys ) );
$query['post_type'] = 'attachment';
! empty( $_REQUEST['query']['post_status'] ) &&
'trash' === $_REQUEST['query']['post_status']
$query['post_status'] = 'trash';
$query['post_status'] = 'inherit';
if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
$query['post_status'] .= ',private';
// Filter query clauses to include filenames.
if ( isset( $query['s'] ) ) {
add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
* Filters the arguments passed to WP_Query during an Ajax
* call for querying attachments.
* @see WP_Query::parse_query()
* @param array $query An array of query variables.
$query = apply_filters( 'ajax_query_attachments_args', $query );
$attachments_query = new WP_Query( $query );
update_post_parent_caches( $attachments_query->posts );
$posts = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
$posts = array_filter( $posts );
$total_posts = $attachments_query->found_posts;
if ( $total_posts < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count.
unset( $query['paged'] );
$count_query = new WP_Query();
$count_query->query( $query );
$total_posts = $count_query->found_posts;
$posts_per_page = (int) $attachments_query->get( 'posts_per_page' );
$max_pages = $posts_per_page ? (int) ceil( $total_posts / $posts_per_page ) : 0;
header( 'X-WP-Total: ' . (int) $total_posts );
header( 'X-WP-TotalPages: ' . $max_pages );
wp_send_json_success( $posts );
* Handles updating attachment attributes via AJAX.
function wp_ajax_save_attachment() {
if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
$id = absint( $_REQUEST['id'] );
check_ajax_referer( 'update-post_' . $id, 'nonce' );
if ( ! current_user_can( 'edit_post', $id ) ) {
$changes = $_REQUEST['changes'];
$post = get_post( $id, ARRAY_A );
if ( 'attachment' !== $post['post_type'] ) {
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 ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
$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'] ) ) {
$id3data = wp_get_attachment_metadata( $post['ID'] );
if ( ! is_array( $id3data ) ) {
foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
if ( isset( $changes[ $key ] ) ) {
$id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
wp_update_attachment_metadata( $id, $id3data );
if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
* Handles saving backward compatible attachment attributes via AJAX.
function wp_ajax_save_attachment_compat() {
if ( ! isset( $_REQUEST['id'] ) ) {
$id = absint( $_REQUEST['id'] );
if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
$attachment_data = $_REQUEST['attachments'][ $id ];
check_ajax_referer( 'update-post_' . $id, 'nonce' );
if ( ! current_user_can( 'edit_post', $id ) ) {
$post = get_post( $id, ARRAY_A );
if ( 'attachment' !== $post['post_type'] ) {
/** 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'] );
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 );
$attachment = wp_prepare_attachment_for_js( $id );
wp_send_json_success( $attachment );
* Handles saving the attachment order via AJAX.
function wp_ajax_save_attachment_order() {
if ( ! isset( $_REQUEST['post_id'] ) ) {
$post_id = absint( $_REQUEST['post_id'] );
if ( empty( $_REQUEST['attachments'] ) ) {
check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
$attachments = $_REQUEST['attachments'];
if ( ! current_user_can( 'edit_post', $post_id ) ) {
foreach ( $attachments as $attachment_id => $menu_order ) {
if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
$attachment = get_post( $attachment_id );
if ( 'attachment' !== $attachment->post_type ) {
'menu_order' => $menu_order,
* Handles sending an attachment to the editor via AJAX.
* Generates the HTML to send an attachment to the editor.
* Backward compatible with the {@see 'media_send_to_editor'} filter
* and the chain of filters that follow.
function wp_ajax_send_attachment_to_editor() {
check_ajax_referer( 'media-send-to-editor', 'nonce' );
$attachment = wp_unslash( $_POST['attachment'] );
$id = (int) $attachment['id'];
if ( 'attachment' !== $post->post_type ) {
if ( current_user_can( 'edit_post', $id ) ) {
// If this attachment is unattached, attach it. Primarily a back compat thing.
$insert_into_post_id = (int) $_POST['post_id'];
if ( 0 === $post->post_parent && $insert_into_post_id ) {
'post_parent' => $insert_into_post_id,
$url = empty( $attachment['url'] ) ? '' : $attachment['url'];
$rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $id ) === $url );
remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
$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 ) ) {
$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, $rel, $size, $alt );
} elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
$html = stripslashes_deep( $_POST['html'] );
$html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
$rel = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
$html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
/** 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 );
* Handles sending a link to the editor via AJAX.
* Generates the HTML to send a non-image embed link to the editor.
* Backward compatible with the following filters:
* - file_send_to_editor_url
* - audio_send_to_editor_url
* - video_send_to_editor_url
* @global WP_Post $post Global post object.
* @global WP_Embed $wp_embed WordPress Embed object.
function wp_ajax_send_link_to_editor() {
check_ajax_referer( 'media-send-to-editor', 'nonce' );
$src = wp_unslash( $_POST['src'] );
if ( ! strpos( $src, '://' ) ) {
$src = sanitize_url( $src );
$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>';
// Figure out what filter to run:
$ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
$ext_type = wp_ext2type( $ext );
if ( 'audio' === $ext_type || 'video' === $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 );
* Handles the Heartbeat API via AJAX.
* Runs when the user is logged in.
function wp_ajax_heartbeat() {
if ( empty( $_POST['_nonce'] ) ) {
$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'] );
if ( ! empty( $_POST['data'] ) ) {
$data = wp_unslash( (array) $_POST['data'] );
if ( 1 !== $nonce_state ) {
* Filters the nonces to send to the New/Edit Post screen.
* @param array $response The Heartbeat response.
* @param array $data The $_POST data sent.
* @param string $screen_id The screen ID.
$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 ) ) {
* Filters the Heartbeat response received.
* @param array $response The Heartbeat response.
* @param array $data The $_POST data sent.