\n";
}
}
/**
* Retrieves the image HTML to send to the editor.
*
* @since 2.5.0
*
* @param int $id Image attachment ID.
* @param string $caption Image caption.
* @param string $title Image title attribute.
* @param string $align Image CSS alignment property.
* @param string $url Optional. Image src URL. Default empty.
* @param bool|string $rel Optional. Value for rel attribute or whether to add a default value. Default false.
* @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of
* width and height values in pixels (in that order). Default 'medium'.
* @param string $alt Optional. Image alt attribute. Default empty.
* @return string The HTML output to insert into the editor.
*/
function get_image_send_to_editor( $id, $caption, $title, $align, $url = '', $rel = false, $size = 'medium', $alt = '' ) {
$html = get_image_tag( $id, $alt, '', $align, $size );
if ( $rel ) {
if ( is_string( $rel ) ) {
$rel = ' rel="' . esc_attr( $rel ) . '"';
} else {
$rel = ' rel="attachment wp-att-' . (int) $id . '"';
}
} else {
$rel = '';
}
if ( $url ) {
$html = '' . $html . '';
}
/**
* Filters the image HTML markup to send to the editor when inserting an image.
*
* @since 2.5.0
* @since 5.6.0 The `$rel` parameter was added.
*
* @param string $html The image HTML markup to send.
* @param int $id The attachment ID.
* @param string $caption The image caption.
* @param string $title The image title.
* @param string $align The image alignment.
* @param string $url The image source URL.
* @param string|int[] $size Requested image size. Can be any registered image size name, or
* an array of width and height values in pixels (in that order).
* @param string $alt The image alternative, or alt, text.
* @param string $rel The image rel attribute.
*/
$html = apply_filters( 'image_send_to_editor', $html, $id, $caption, $title, $align, $url, $size, $alt, $rel );
return $html;
}
/**
* Adds image shortcode with caption to editor.
*
* @since 2.6.0
*
* @param string $html The image HTML markup to send.
* @param int $id Image attachment ID.
* @param string $caption Image caption.
* @param string $title Image title attribute (not used).
* @param string $align Image CSS alignment property.
* @param string $url Image source URL (not used).
* @param string $size Image size (not used).
* @param string $alt Image `alt` attribute (not used).
* @return string The image HTML markup with caption shortcode.
*/
function image_add_caption( $html, $id, $caption, $title, $align, $url, $size, $alt = '' ) {
/**
* Filters the caption text.
*
* Note: If the caption text is empty, the caption shortcode will not be appended
* to the image HTML when inserted into the editor.
*
* Passing an empty value also prevents the {@see 'image_add_caption_shortcode'}
* Filters from being evaluated at the end of image_add_caption().
*
* @since 4.1.0
*
* @param string $caption The original caption text.
* @param int $id The attachment ID.
*/
$caption = apply_filters( 'image_add_caption_text', $caption, $id );
/**
* Filters whether to disable captions.
*
* Prevents image captions from being appended to image HTML when inserted into the editor.
*
* @since 2.6.0
*
* @param bool $bool Whether to disable appending captions. Returning true from the filter
* will disable captions. Default empty string.
*/
if ( empty( $caption ) || apply_filters( 'disable_captions', '' ) ) {
return $html;
}
$id = ( 0 < (int) $id ) ? 'attachment_' . $id : '';
if ( ! preg_match( '/width=["\']([0-9]+)/', $html, $matches ) ) {
return $html;
}
$width = $matches[1];
$caption = str_replace( array( "\r\n", "\r" ), "\n", $caption );
$caption = preg_replace_callback( '/<[a-zA-Z0-9]+(?: [^<>]+>)*/', '_cleanup_image_add_caption', $caption );
// Convert any remaining line breaks to .
$caption = preg_replace( '/[ \n\t]*\n[ \t]*/', ' ', $caption );
$html = preg_replace( '/(class=["\'][^\'"]*)align(none|left|right|center)\s?/', '$1', $html );
if ( empty( $align ) ) {
$align = 'none';
}
$shcode = '[caption id="' . $id . '" align="align' . $align . '" width="' . $width . '"]' . $html . ' ' . $caption . '[/caption]';
/**
* Filters the image HTML markup including the caption shortcode.
*
* @since 2.6.0
*
* @param string $shcode The image HTML markup with caption shortcode.
* @param string $html The image HTML markup.
*/
return apply_filters( 'image_add_caption_shortcode', $shcode, $html );
}
/**
* Private preg_replace callback used in image_add_caption().
*
* @access private
* @since 3.4.0
*
* @param array $matches Single regex match.
* @return string Cleaned up HTML for caption.
*/
function _cleanup_image_add_caption( $matches ) {
// Remove any line breaks from inside the tags.
return preg_replace( '/[\r\n\t]+/', ' ', $matches[0] );
}
/**
* Adds image HTML to editor.
*
* @since 2.5.0
*
* @param string $html
*/
function media_send_to_editor( $html ) {
?>
false ) ) {
$time = current_time( 'mysql' );
$post = get_post( $post_id );
if ( $post ) {
// The post date doesn't usually matter for pages, so don't backdate this upload.
if ( 'page' !== $post->post_type && substr( $post->post_date, 0, 4 ) > 0 ) {
$time = $post->post_date;
}
}
$file = wp_handle_upload( $_FILES[ $file_id ], $overrides, $time );
if ( isset( $file['error'] ) ) {
return new WP_Error( 'upload_error', $file['error'] );
}
$name = $_FILES[ $file_id ]['name'];
$ext = pathinfo( $name, PATHINFO_EXTENSION );
$name = wp_basename( $name, ".$ext" );
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
$title = sanitize_text_field( $name );
$content = '';
$excerpt = '';
if ( preg_match( '#^audio#', $type ) ) {
$meta = wp_read_audio_metadata( $file );
if ( ! empty( $meta['title'] ) ) {
$title = $meta['title'];
}
if ( ! empty( $title ) ) {
if ( ! empty( $meta['album'] ) && ! empty( $meta['artist'] ) ) {
/* translators: 1: Audio track title, 2: Album title, 3: Artist name. */
$content .= sprintf( __( '"%1$s" from %2$s by %3$s.' ), $title, $meta['album'], $meta['artist'] );
} elseif ( ! empty( $meta['album'] ) ) {
/* translators: 1: Audio track title, 2: Album title. */
$content .= sprintf( __( '"%1$s" from %2$s.' ), $title, $meta['album'] );
} elseif ( ! empty( $meta['artist'] ) ) {
/* translators: 1: Audio track title, 2: Artist name. */
$content .= sprintf( __( '"%1$s" by %2$s.' ), $title, $meta['artist'] );
} else {
/* translators: %s: Audio track title. */
$content .= sprintf( __( '"%s".' ), $title );
}
} elseif ( ! empty( $meta['album'] ) ) {
if ( ! empty( $meta['artist'] ) ) {
/* translators: 1: Audio album title, 2: Artist name. */
$content .= sprintf( __( '%1$s by %2$s.' ), $meta['album'], $meta['artist'] );
} else {
$content .= $meta['album'] . '.';
}
} elseif ( ! empty( $meta['artist'] ) ) {
$content .= $meta['artist'] . '.';
}
if ( ! empty( $meta['year'] ) ) {
/* translators: Audio file track information. %d: Year of audio track release. */
$content .= ' ' . sprintf( __( 'Released: %d.' ), $meta['year'] );
}
if ( ! empty( $meta['track_number'] ) ) {
$track_number = explode( '/', $meta['track_number'] );
if ( is_numeric( $track_number[0] ) ) {
if ( isset( $track_number[1] ) && is_numeric( $track_number[1] ) ) {
$content .= ' ' . sprintf(
/* translators: Audio file track information. 1: Audio track number, 2: Total audio tracks. */
__( 'Track %1$s of %2$s.' ),
number_format_i18n( $track_number[0] ),
number_format_i18n( $track_number[1] )
);
} else {
$content .= ' ' . sprintf(
/* translators: Audio file track information. %s: Audio track number. */
__( 'Track %s.' ),
number_format_i18n( $track_number[0] )
);
}
}
}
if ( ! empty( $meta['genre'] ) ) {
/* translators: Audio file genre information. %s: Audio genre name. */
$content .= ' ' . sprintf( __( 'Genre: %s.' ), $meta['genre'] );
}
// Use image exif/iptc data for title and caption defaults if possible.
} elseif ( str_starts_with( $type, 'image/' ) ) {
$image_meta = wp_read_image_metadata( $file );
if ( $image_meta ) {
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$title = $image_meta['title'];
}
if ( trim( $image_meta['caption'] ) ) {
$excerpt = $image_meta['caption'];
}
}
}
// Construct the attachment array.
$attachment = array_merge(
array(
'post_mime_type' => $type,
'guid' => $url,
'post_parent' => $post_id,
'post_title' => $title,
'post_content' => $content,
'post_excerpt' => $excerpt,
),
$post_data
);
// This should never be set as it would then overwrite an existing attachment.
unset( $attachment['ID'] );
// Save the data.
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );
if ( ! is_wp_error( $attachment_id ) ) {
/*
* Set a custom header with the attachment_id.
* Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
*/
if ( ! headers_sent() ) {
header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
}
/*
* The image sub-sizes are created during wp_generate_attachment_metadata().
* This is generally slow and may cause timeouts or out of memory errors.
*/
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
}
return $attachment_id;
}
/**
* Handles a side-loaded file in the same way as an uploaded file is handled by media_handle_upload().
*
* @since 2.6.0
* @since 5.3.0 The `$post_id` parameter was made optional.
*
* @param string[] $file_array Array that represents a `$_FILES` upload array.
* @param int $post_id Optional. The post ID the media is associated with.
* @param string $desc Optional. Description of the side-loaded file. Default null.
* @param array $post_data Optional. Post data to override. Default empty array.
* @return int|WP_Error The ID of the attachment or a WP_Error on failure.
*/
function media_handle_sideload( $file_array, $post_id = 0, $desc = null, $post_data = array() ) {
$overrides = array( 'test_form' => false );
if ( isset( $post_data['post_date'] ) && substr( $post_data['post_date'], 0, 4 ) > 0 ) {
$time = $post_data['post_date'];
} else {
$post = get_post( $post_id );
if ( $post && substr( $post->post_date, 0, 4 ) > 0 ) {
$time = $post->post_date;
} else {
$time = current_time( 'mysql' );
}
}
$file = wp_handle_sideload( $file_array, $overrides, $time );
if ( isset( $file['error'] ) ) {
return new WP_Error( 'upload_error', $file['error'] );
}
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
$title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) );
$content = '';
// Use image exif/iptc data for title and caption defaults if possible.
$image_meta = wp_read_image_metadata( $file );
if ( $image_meta ) {
if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$title = $image_meta['title'];
}
if ( trim( $image_meta['caption'] ) ) {
$content = $image_meta['caption'];
}
}
if ( isset( $desc ) ) {
$title = $desc;
}
// Construct the attachment array.
$attachment = array_merge(
array(
'post_mime_type' => $type,
'guid' => $url,
'post_parent' => $post_id,
'post_title' => $title,
'post_content' => $content,
),
$post_data
);
// This should never be set as it would then overwrite an existing attachment.
unset( $attachment['ID'] );
// Save the attachment metadata.
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );
if ( ! is_wp_error( $attachment_id ) ) {
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
}
return $attachment_id;
}
/**
* Outputs the iframe to display the media upload page.
*
* @since 2.5.0
* @since 5.3.0 Formalized the existing and already documented `...$args` parameter
* by adding it to the function signature.
*
* @global string $body_id
*
* @param callable $content_func Function that outputs the content.
* @param mixed ...$args Optional additional parameters to pass to the callback function when it's called.
*/
function wp_iframe( $content_func, ...$args ) {
global $body_id;
_wp_admin_html_begin();
?>
› —
class="wp-core-ui no-js">
$post ) );
$img = ' ';
$id_attribute = 1 === $instance ? ' id="insert-media-button"' : '';
printf(
'',
$id_attribute,
esc_attr( $editor_id ),
$img . __( 'Add Media' )
);
/**
* Filters the legacy (pre-3.5.0) media buttons.
*
* Use {@see 'media_buttons'} action instead.
*
* @since 2.5.0
* @deprecated 3.5.0 Use {@see 'media_buttons'} action instead.
*
* @param string $string Media buttons context. Default empty.
*/
$legacy_filter = apply_filters_deprecated( 'media_buttons_context', array( '' ), '3.5.0', 'media_buttons' );
if ( $legacy_filter ) {
// #WP22559. Close if a plugin started by closing to open their own tag.
if ( 0 === stripos( trim( $legacy_filter ), '' ) ) {
$legacy_filter .= '';
}
echo $legacy_filter;
}
}
/**
* Retrieves the upload iframe source URL.
*
* @since 3.0.0
*
* @global int $post_ID
*
* @param string $type Media type.
* @param int $post_id Post ID.
* @param string $tab Media upload tab.
* @return string Upload iframe source URL.
*/
function get_upload_iframe_src( $type = null, $post_id = null, $tab = null ) {
global $post_ID;
if ( empty( $post_id ) ) {
$post_id = $post_ID;
}
$upload_iframe_src = add_query_arg( 'post_id', (int) $post_id, admin_url( 'media-upload.php' ) );
if ( $type && 'media' !== $type ) {
$upload_iframe_src = add_query_arg( 'type', $type, $upload_iframe_src );
}
if ( ! empty( $tab ) ) {
$upload_iframe_src = add_query_arg( 'tab', $tab, $upload_iframe_src );
}
/**
* Filters the upload iframe source URL for a specific media type.
*
* The dynamic portion of the hook name, `$type`, refers to the type
* of media uploaded.
*
* Possible hook names include:
*
* - `image_upload_iframe_src`
* - `media_upload_iframe_src`
*
* @since 3.0.0
*
* @param string $upload_iframe_src The upload iframe source URL.
*/
$upload_iframe_src = apply_filters( "{$type}_upload_iframe_src", $upload_iframe_src );
return add_query_arg( 'TB_iframe', true, $upload_iframe_src );
}
/**
* Handles form submissions for the legacy media uploader.
*
* @since 2.5.0
*
* @return null|array|void Array of error messages keyed by attachment ID, null or void on success.
*/
function media_upload_form_handler() {
check_admin_referer( 'media-form' );
$errors = null;
if ( isset( $_POST['send'] ) ) {
$keys = array_keys( $_POST['send'] );
$send_id = (int) reset( $keys );
}
if ( ! empty( $_POST['attachments'] ) ) {
foreach ( $_POST['attachments'] as $attachment_id => $attachment ) {
$post = get_post( $attachment_id, ARRAY_A );
$_post = $post;
if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
continue;
}
if ( isset( $attachment['post_content'] ) ) {
$post['post_content'] = $attachment['post_content'];
}
if ( isset( $attachment['post_title'] ) ) {
$post['post_title'] = $attachment['post_title'];
}
if ( isset( $attachment['post_excerpt'] ) ) {
$post['post_excerpt'] = $attachment['post_excerpt'];
}
if ( isset( $attachment['menu_order'] ) ) {
$post['menu_order'] = $attachment['menu_order'];
}
if ( isset( $send_id ) && $attachment_id == $send_id ) {
if ( isset( $attachment['post_parent'] ) ) {
$post['post_parent'] = $attachment['post_parent'];
}
}
/**
* Filters the attachment fields to be saved.
*
* @since 2.5.0
*
* @see wp_get_attachment_metadata()
*
* @param array $post An array of post data.
* @param array $attachment An array of attachment metadata.
*/
$post = apply_filters( 'attachment_fields_to_save', $post, $attachment );
if ( isset( $attachment['image_alt'] ) ) {
$image_alt = wp_unslash( $attachment['image_alt'] );
if ( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) !== $image_alt ) {
$image_alt = wp_strip_all_tags( $image_alt, true );
// update_post_meta() expects slashed.
update_post_meta( $attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
}
}
if ( isset( $post['errors'] ) ) {
$errors[ $attachment_id ] = $post['errors'];
unset( $post['errors'] );
}
if ( $post != $_post ) {
wp_update_post( $post );
}
foreach ( get_attachment_taxonomies( $post ) as $t ) {
if ( isset( $attachment[ $t ] ) ) {
wp_set_object_terms( $attachment_id, array_map( 'trim', preg_split( '/,+/', $attachment[ $t ] ) ), $t, false );
}
}
}
}
if ( isset( $_POST['insert-gallery'] ) || isset( $_POST['update-gallery'] ) ) {
?>
$html";
}
/**
* Filters the HTML markup for a media item sent to the editor.
*
* @since 2.5.0
*
* @see wp_get_attachment_metadata()
*
* @param string $html HTML markup for a media item sent to the editor.
* @param int $send_id The first key from the $_POST['send'] data.
* @param array $attachment Array of attachment metadata.
*/
$html = apply_filters( 'media_send_to_editor', $html, $send_id, $attachment );
return media_send_to_editor( $html );
}
return $errors;
}
/**
* Handles the process of uploading media.
*
* @since 2.5.0
*
* @return null|string
*/
function wp_media_upload_handler() {
$errors = array();
$id = 0;
if ( isset( $_POST['html-upload'] ) && ! empty( $_FILES ) ) {
check_admin_referer( 'media-form' );
// Upload File button was clicked.
$id = media_handle_upload( 'async-upload', $_REQUEST['post_id'] );
unset( $_FILES );
if ( is_wp_error( $id ) ) {
$errors['upload_error'] = $id;
$id = false;
}
}
if ( ! empty( $_POST['insertonlybutton'] ) ) {
$src = $_POST['src'];
if ( ! empty( $src ) && ! strpos( $src, '://' ) ) {
$src = "http://$src";
}
if ( isset( $_POST['media_type'] ) && 'image' !== $_POST['media_type'] ) {
$title = esc_html( wp_unslash( $_POST['title'] ) );
if ( empty( $title ) ) {
$title = esc_html( wp_basename( $src ) );
}
if ( $title && $src ) {
$html = "$title";
}
$type = 'file';
$ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
if ( $ext ) {
$ext_type = wp_ext2type( $ext );
if ( 'audio' === $ext_type || 'video' === $ext_type ) {
$type = $ext_type;
}
}
/**
* Filters the URL sent to the editor for a specific media type.
*
* The dynamic portion of the hook name, `$type`, refers to the type
* of media being sent.
*
* Possible hook names include:
*
* - `audio_send_to_editor_url`
* - `file_send_to_editor_url`
* - `video_send_to_editor_url`
*
* @since 3.3.0
*
* @param string $html HTML markup sent to the editor.
* @param string $src Media source URL.
* @param string $title Media title.
*/
$html = apply_filters( "{$type}_send_to_editor_url", $html, sanitize_url( $src ), $title );
} else {
$align = '';
$alt = esc_attr( wp_unslash( $_POST['alt'] ) );
if ( isset( $_POST['align'] ) ) {
$align = esc_attr( wp_unslash( $_POST['align'] ) );
$class = " class='align$align'";
}
if ( ! empty( $src ) ) {
$html = "";
}
/**
* Filters the image URL sent to the editor.
*
* @since 2.8.0
*
* @param string $html HTML markup sent to the editor for an image.
* @param string $src Image source URL.
* @param string $alt Image alternate, or alt, text.
* @param string $align The image alignment. Default 'alignnone'. Possible values include
* 'alignleft', 'aligncenter', 'alignright', 'alignnone'.
*/
$html = apply_filters( 'image_send_to_editor_url', $html, sanitize_url( $src ), $alt, $align );
}
return media_send_to_editor( $html );
}
if ( isset( $_POST['save'] ) ) {
$errors['upload_notice'] = __( 'Saved.' );
wp_enqueue_script( 'admin-gallery' );
return wp_iframe( 'media_upload_gallery_form', $errors );
} elseif ( ! empty( $_POST ) ) {
$return = media_upload_form_handler();
if ( is_string( $return ) ) {
return $return;
}
if ( is_array( $return ) ) {
$errors = $return;
}
}
if ( isset( $_GET['tab'] ) && 'type_url' === $_GET['tab'] ) {
$type = 'image';
if ( isset( $_GET['type'] ) && in_array( $_GET['type'], array( 'video', 'audio', 'file' ), true ) ) {
$type = $_GET['type'];
}
return wp_iframe( 'media_upload_type_url_form', $type, $errors, $id );
}
return wp_iframe( 'media_upload_type_form', 'image', $errors, $id );
}
/**
* Downloads an image from the specified URL, saves it as an attachment, and optionally attaches it to a post.
*
* @since 2.6.0
* @since 4.2.0 Introduced the `$return_type` parameter.
* @since 4.8.0 Introduced the 'id' option for the `$return_type` parameter.
* @since 5.3.0 The `$post_id` parameter was made optional.
* @since 5.4.0 The original URL of the attachment is stored in the `_source_url`
* post meta value.
* @since 5.8.0 Added 'webp' to the default list of allowed file extensions.
*
* @param string $file The URL of the image to download.
* @param int $post_id Optional. The post ID the media is to be associated with.
* @param string $desc Optional. Description of the image.
* @param string $return_type Optional. Accepts 'html' (image tag html) or 'src' (URL),
* or 'id' (attachment ID). Default 'html'.
* @return string|int|WP_Error Populated HTML img tag, attachment ID, or attachment source
* on success, WP_Error object otherwise.
*/
function media_sideload_image( $file, $post_id = 0, $desc = null, $return_type = 'html' ) {
if ( ! empty( $file ) ) {
$allowed_extensions = array( 'jpg', 'jpeg', 'jpe', 'png', 'gif', 'webp' );
/**
* Filters the list of allowed file extensions when sideloading an image from a URL.
*
* The default allowed extensions are:
*
* - `jpg`
* - `jpeg`
* - `jpe`
* - `png`
* - `gif`
* - `webp`
*
* @since 5.6.0
* @since 5.8.0 Added 'webp' to the default list of allowed file extensions.
*
* @param string[] $allowed_extensions Array of allowed file extensions.
* @param string $file The URL of the image to download.
*/
$allowed_extensions = apply_filters( 'image_sideload_extensions', $allowed_extensions, $file );
$allowed_extensions = array_map( 'preg_quote', $allowed_extensions );
// Set variables for storage, fix file filename for query strings.
preg_match( '/[^\?]+\.(' . implode( '|', $allowed_extensions ) . ')\b/i', $file, $matches );
if ( ! $matches ) {
return new WP_Error( 'image_sideload_failed', __( 'Invalid image URL.' ) );
}
$file_array = array();
$file_array['name'] = wp_basename( $matches[0] );
// Download file to temp location.
$file_array['tmp_name'] = download_url( $file );
// If error storing temporarily, return the error.
if ( is_wp_error( $file_array['tmp_name'] ) ) {
return $file_array['tmp_name'];
}
// Do the validation and storage stuff.
$id = media_handle_sideload( $file_array, $post_id, $desc );
// If error storing permanently, unlink.
if ( is_wp_error( $id ) ) {
@unlink( $file_array['tmp_name'] );
return $id;
}
// Store the original attachment source in meta.
add_post_meta( $id, '_source_url', $file );
// If attachment ID was requested, return it.
if ( 'id' === $return_type ) {
return $id;
}
$src = wp_get_attachment_url( $id );
}
// Finally, check to make sure the file has been saved, then return the HTML.
if ( ! empty( $src ) ) {
if ( 'src' === $return_type ) {
return $src;
}
$alt = isset( $desc ) ? esc_attr( $desc ) : '';
$html = "";
return $html;
} else {
return new WP_Error( 'image_sideload_failed' );
}
}
/**
* Retrieves the legacy media uploader form in an iframe.
*
* @since 2.5.0
*
* @return string|null
*/
function media_upload_gallery() {
$errors = array();
if ( ! empty( $_POST ) ) {
$return = media_upload_form_handler();
if ( is_string( $return ) ) {
return $return;
}
if ( is_array( $return ) ) {
$errors = $return;
}
}
wp_enqueue_script( 'admin-gallery' );
return wp_iframe( 'media_upload_gallery_form', $errors );
}
/**
* Retrieves the legacy media library form in an iframe.
*
* @since 2.5.0
*
* @return string|null
*/
function media_upload_library() {
$errors = array();
if ( ! empty( $_POST ) ) {
$return = media_upload_form_handler();
if ( is_string( $return ) ) {
return $return;
}
if ( is_array( $return ) ) {
$errors = $return;
}
}
return wp_iframe( 'media_upload_library_form', $errors );
}
/**
* Retrieves HTML for the image alignment radio buttons with the specified one checked.
*
* @since 2.7.0
*
* @param WP_Post $post
* @param string $checked
* @return string
*/
function image_align_input_fields( $post, $checked = '' ) {
if ( empty( $checked ) ) {
$checked = get_user_setting( 'align', 'none' );
}
$alignments = array(
'none' => __( 'None' ),
'left' => __( 'Left' ),
'center' => __( 'Center' ),
'right' => __( 'Right' ),
);
if ( ! array_key_exists( (string) $checked, $alignments ) ) {
$checked = 'none';
}
$output = array();
foreach ( $alignments as $name => $label ) {
$name = esc_attr( $name );
$output[] = "";
}
return implode( "\n", $output );
}
/**
* Retrieves HTML for the size radio buttons with the specified one checked.
*
* @since 2.7.0
*
* @param WP_Post $post
* @param bool|string $check
* @return array
*/
function image_size_input_fields( $post, $check = '' ) {
/**
* Filters the names and labels of the default image sizes.
*
* @since 3.3.0
*
* @param string[] $size_names Array of image size labels keyed by their name. Default values
* include 'Thumbnail', 'Medium', 'Large', and 'Full Size'.
*/
$size_names = apply_filters(
'image_size_names_choose',
array(
'thumbnail' => __( 'Thumbnail' ),
'medium' => __( 'Medium' ),
'large' => __( 'Large' ),
'full' => __( 'Full Size' ),
)
);
if ( empty( $check ) ) {
$check = get_user_setting( 'imgsize', 'medium' );
}
$output = array();
foreach ( $size_names as $size => $label ) {
$downsize = image_downsize( $post->ID, $size );
$checked = '';
// Is this size selectable?
$enabled = ( $downsize[3] || 'full' === $size );
$css_id = "image-size-{$size}-{$post->ID}";
// If this size is the default but that's not available, don't select it.
if ( $size == $check ) {
if ( $enabled ) {
$checked = " checked='checked'";
} else {
$check = '';
}
} elseif ( ! $check && $enabled && 'thumbnail' !== $size ) {
/*
* If $check is not enabled, default to the first available size
* that's bigger than a thumbnail.
*/
$check = $size;
$checked = " checked='checked'";
}
$html = "
ID][image-size]' id='{$css_id}' value='{$size}'$checked />";
$html .= "";
// Only show the dimensions if that choice is available.
if ( $enabled ) {
$html .= " ';
}
$html .= '
';
$output[] = $html;
}
return array(
'label' => __( 'Size' ),
'input' => 'html',
'html' => implode( "\n", $output ),
);
}
/**
* Retrieves HTML for the Link URL buttons with the default link type as specified.
*
* @since 2.7.0
*
* @param WP_Post $post
* @param string $url_type
* @return string
*/
function image_link_input_fields( $post, $url_type = '' ) {
$file = wp_get_attachment_url( $post->ID );
$link = get_attachment_link( $post->ID );
if ( empty( $url_type ) ) {
$url_type = get_user_setting( 'urlbutton', 'post' );
}
$url = '';
if ( 'file' === $url_type ) {
$url = $file;
} elseif ( 'post' === $url_type ) {
$url = $link;
}
return "
';
}
/**
* Outputs a textarea element for inputting an attachment caption.
*
* @since 3.4.0
*
* @param WP_Post $edit_post Attachment WP_Post object.
* @return string HTML markup for the textarea element.
*/
function wp_caption_input_textarea( $edit_post ) {
// Post data is already escaped.
$name = "attachments[{$edit_post->ID}][post_excerpt]";
return '';
}
/**
* Retrieves the image attachment fields to edit form fields.
*
* @since 2.5.0
*
* @param array $form_fields
* @param object $post
* @return array
*/
function image_attachment_fields_to_edit( $form_fields, $post ) {
return $form_fields;
}
/**
* Retrieves the single non-image attachment fields to edit form fields.
*
* @since 2.5.0
*
* @param array $form_fields An array of attachment form fields.
* @param WP_Post $post The WP_Post attachment object.
* @return array Filtered attachment form fields.
*/
function media_single_attachment_fields_to_edit( $form_fields, $post ) {
unset( $form_fields['url'], $form_fields['align'], $form_fields['image-size'] );
return $form_fields;
}
/**
* Retrieves the post non-image attachment fields to edit form fields.
*
* @since 2.8.0
*
* @param array $form_fields An array of attachment form fields.
* @param WP_Post $post The WP_Post attachment object.
* @return array Filtered attachment form fields.
*/
function media_post_single_attachment_fields_to_edit( $form_fields, $post ) {
unset( $form_fields['image_url'] );
return $form_fields;
}
/**
* Retrieves the media element HTML to send to the editor.
*
* @since 2.5.0
*
* @param string $html
* @param int $attachment_id
* @param array $attachment
* @return string
*/
function image_media_send_to_editor( $html, $attachment_id, $attachment ) {
$post = get_post( $attachment_id );
if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
$url = $attachment['url'];
$align = ! empty( $attachment['align'] ) ? $attachment['align'] : 'none';
$size = ! empty( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
$alt = ! empty( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
$rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $attachment_id ) === $url );
return get_image_send_to_editor( $attachment_id, $attachment['post_excerpt'], $attachment['post_title'], $align, $url, $rel, $size, $alt );
}
return $html;
}
/**
* Retrieves the attachment fields to edit form fields.
*
* @since 2.5.0
*
* @param WP_Post $post
* @param array $errors
* @return array
*/
function get_attachment_fields_to_edit( $post, $errors = null ) {
if ( is_int( $post ) ) {
$post = get_post( $post );
}
if ( is_array( $post ) ) {
$post = new WP_Post( (object) $post );
}
$image_url = wp_get_attachment_url( $post->ID );
$edit_post = sanitize_post( $post, 'edit' );
$form_fields = array(
'post_title' => array(
'label' => __( 'Title' ),
'value' => $edit_post->post_title,
),
'image_alt' => array(),
'post_excerpt' => array(
'label' => __( 'Caption' ),
'input' => 'html',
'html' => wp_caption_input_textarea( $edit_post ),
),
'post_content' => array(
'label' => __( 'Description' ),
'value' => $edit_post->post_content,
'input' => 'textarea',
),
'url' => array(
'label' => __( 'Link URL' ),
'input' => 'html',
'html' => image_link_input_fields( $post, get_option( 'image_default_link_type' ) ),
'helps' => __( 'Enter a link URL or click above for presets.' ),
),
'menu_order' => array(
'label' => __( 'Order' ),
'value' => $edit_post->menu_order,
),
'image_url' => array(
'label' => __( 'File URL' ),
'input' => 'html',
'html' => " ",
'value' => wp_get_attachment_url( $post->ID ),
'helps' => __( 'Location of the uploaded file.' ),
),
);
foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
$t = (array) get_taxonomy( $taxonomy );
if ( ! $t['public'] || ! $t['show_ui'] ) {
continue;
}
if ( empty( $t['label'] ) ) {
$t['label'] = $taxonomy;
}
if ( empty( $t['args'] ) ) {
$t['args'] = array();
}
$terms = get_object_term_cache( $post->ID, $taxonomy );
if ( false === $terms ) {
$terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
}
$values = array();
foreach ( $terms as $term ) {
$values[] = $term->slug;
}
$t['value'] = implode( ', ', $values );
$form_fields[ $taxonomy ] = $t;
}
/*
* Merge default fields with their errors, so any key passed with the error
* (e.g. 'error', 'helps', 'value') will replace the default.
* The recursive merge is easily traversed with array casting:
* foreach ( (array) $things as $thing )
*/
$form_fields = array_merge_recursive( $form_fields, (array) $errors );
// This was formerly in image_attachment_fields_to_edit().
if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
$alt = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
if ( empty( $alt ) ) {
$alt = '';
}
$form_fields['post_title']['required'] = true;
$form_fields['image_alt'] = array(
'value' => $alt,
'label' => __( 'Alternative Text' ),
'helps' => __( 'Alt text for the image, e.g. “The Mona Lisa”' ),
);
$form_fields['align'] = array(
'label' => __( 'Alignment' ),
'input' => 'html',
'html' => image_align_input_fields( $post, get_option( 'image_default_align' ) ),
);
$form_fields['image-size'] = image_size_input_fields( $post, get_option( 'image_default_size', 'medium' ) );
} else {
unset( $form_fields['image_alt'] );
}
/**
* Filters the attachment fields to edit.
*
* @since 2.5.0
*
* @param array $form_fields An array of attachment form fields.
* @param WP_Post $post The WP_Post attachment object.
*/
$form_fields = apply_filters( 'attachment_fields_to_edit', $form_fields, $post );
return $form_fields;
}
/**
* Retrieves HTML for media items of post gallery.
*
* The HTML markup retrieved will be created for the progress of SWF Upload
* component. Will also create link for showing and hiding the form to modify
* the image attachment.
*
* @since 2.5.0
*
* @global WP_Query $wp_the_query WordPress Query object.
*
* @param int $post_id Post ID.
* @param array $errors Errors for attachment, if any.
* @return string HTML content for media items of post gallery.
*/
function get_media_items( $post_id, $errors ) {
$attachments = array();
if ( $post_id ) {
$post = get_post( $post_id );
if ( $post && 'attachment' === $post->post_type ) {
$attachments = array( $post->ID => $post );
} else {
$attachments = get_children(
array(
'post_parent' => $post_id,
'post_type' => 'attachment',
'orderby' => 'menu_order ASC, ID',
'order' => 'DESC',
)
);
}
} else {
if ( is_array( $GLOBALS['wp_the_query']->posts ) ) {
foreach ( $GLOBALS['wp_the_query']->posts as $attachment ) {
$attachments[ $attachment->ID ] = $attachment;
}
}
}
$output = '';
foreach ( (array) $attachments as $id => $attachment ) {
if ( 'trash' === $attachment->post_status ) {
continue;
}
$item = get_media_item( $id, array( 'errors' => isset( $errors[ $id ] ) ? $errors[ $id ] : null ) );
if ( $item ) {
$output .= "\n
';
}
}
/**
* Outputs the legacy media upload form.
*
* @since 2.5.0
*
* @global string $type
* @global string $tab
*
* @param array $errors
*/
function media_upload_form( $errors = null ) {
global $type, $tab;
if ( ! _device_can_upload() ) {
echo '
' . sprintf(
/* translators: %s: https://apps.wordpress.org/ */
__( 'The web browser on your device cannot be used to upload files. You may be able to use the native app for your device instead.' ),
'https://apps.wordpress.org/'
) . '
Learn how to describe the purpose of the image%3$s. Leave empty if the image is purely decorative.' ),
/* translators: Localized tutorial, if one exists. W3C Web Accessibility Initiative link has list of existing translations. */
esc_url( __( 'https://www.w3.org/WAI/tutorials/images/decision-tree/' ) ),
'target="_blank"',
sprintf(
' %s',
/* translators: Hidden accessibility text. */
__( '(opens in a new tab)' )
)
);
?>
'strong,em,link,block,del,ins,img,ul,ol,li,code,close' );
$editor_args = array(
'textarea_name' => 'content',
'textarea_rows' => 5,
'media_buttons' => false,
/**
* Filters the TinyMCE argument for the media description field on the attachment details screen.
*
* @since 6.6.0
*
* @param bool $tinymce Whether to activate TinyMCE in media description field. Default false.
*/
'tinymce' => apply_filters( 'activate_tinymce_for_media_description', false ),
'quicktags' => $quicktags_settings,
);
?>
post_content ), 'attachment_content', $editor_args ); ?>
ID );
echo $extras['item'];
echo '' . "\n";
}
/**
* Displays non-editable attachment metadata in the publish meta box.
*
* @since 3.5.0
*/
function attachment_submitbox_metadata() {
$post = get_post();
$attachment_id = $post->ID;
$file = get_attached_file( $attachment_id );
$filename = esc_html( wp_basename( $file ) );
$media_dims = '';
$meta = wp_get_attachment_metadata( $attachment_id );
if ( isset( $meta['width'], $meta['height'] ) ) {
/* translators: 1: A number of pixels wide, 2: A number of pixels tall. */
$media_dims .= "" . sprintf( __( '%1$s by %2$s pixels' ), $meta['width'], $meta['height'] ) . '';
}
/** This filter is documented in wp-admin/includes/media.php */
$media_dims = apply_filters( 'media_meta', $media_dims, $post );
$att_url = wp_get_attachment_url( $attachment_id );
$author = new WP_User( $post->post_author );
$uploaded_by_name = __( '(no author)' );
$uploaded_by_link = '';
if ( $author->exists() ) {
$uploaded_by_name = $author->display_name ? $author->display_name : $author->nickname;
$uploaded_by_link = get_edit_user_link( $author->ID );
}
?>
post_mime_type ) ) {
$fields = array(
'length_formatted' => __( 'Length:' ),
'bitrate' => __( 'Bitrate:' ),
);
/**
* Filters the audio and video metadata fields to be shown in the publish meta box.
*
* The key for each item in the array should correspond to an attachment
* metadata key, and the value should be the desired label.
*
* @since 3.7.0
* @since 4.9.0 Added the `$post` parameter.
*
* @param array $fields An array of the attachment metadata keys and labels.
* @param WP_Post $post WP_Post object for the current attachment.
*/
$fields = apply_filters( 'media_submitbox_misc_sections', $fields, $post );
foreach ( $fields as $key => $label ) {
if ( empty( $meta[ $key ] ) ) {
continue;
}
?>
__( 'Audio Format:' ),
'codec' => __( 'Audio Codec:' ),
);
/**
* Filters the audio attachment metadata fields to be shown in the publish meta box.
*
* The key for each item in the array should correspond to an attachment
* metadata key, and the value should be the desired label.
*
* @since 3.7.0
* @since 4.9.0 Added the `$post` parameter.
*
* @param array $fields An array of the attachment metadata keys and labels.
* @param WP_Post $post WP_Post object for the current attachment.
*/
$audio_fields = apply_filters( 'audio_submitbox_misc_sections', $fields, $post );
foreach ( $audio_fields as $key => $label ) {
if ( empty( $meta['audio'][ $key ] ) ) {
continue;
}
?>
$list ) {
if ( 'length' !== $key && ! empty( $list ) ) {
$metadata[ $key ] = wp_kses_post( reset( $list ) );
// Fix bug in byte stream analysis.
if ( 'terms_of_use' === $key && str_starts_with( $metadata[ $key ], 'yright notice.' ) ) {
$metadata[ $key ] = 'Cop' . $metadata[ $key ];
}
}
}
break;
}
}
if ( ! empty( $data['id3v2']['APIC'] ) ) {
$image = reset( $data['id3v2']['APIC'] );
if ( ! empty( $image['data'] ) ) {
$metadata['image'] = array(
'data' => $image['data'],
'mime' => $image['image_mime'],
'width' => $image['image_width'],
'height' => $image['image_height'],
);
}
} elseif ( ! empty( $data['comments']['picture'] ) ) {
$image = reset( $data['comments']['picture'] );
if ( ! empty( $image['data'] ) ) {
$metadata['image'] = array(
'data' => $image['data'],
'mime' => $image['image_mime'],
);
}
}
}
/**
* Retrieves metadata from a video file's ID3 tags.
*
* @since 3.6.0
*
* @param string $file Path to file.
* @return array|false Returns array of metadata, if found.
*/
function wp_read_video_metadata( $file ) {
if ( ! file_exists( $file ) ) {
return false;
}
$metadata = array();
if ( ! defined( 'GETID3_TEMP_DIR' ) ) {
define( 'GETID3_TEMP_DIR', get_temp_dir() );
}
if ( ! class_exists( 'getID3', false ) ) {
require ABSPATH . WPINC . '/ID3/getid3.php';
}
$id3 = new getID3();
// Required to get the `created_timestamp` value.
$id3->options_audiovideo_quicktime_ReturnAtomData = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
$data = $id3->analyze( $file );
if ( isset( $data['video']['lossless'] ) ) {
$metadata['lossless'] = $data['video']['lossless'];
}
if ( ! empty( $data['video']['bitrate'] ) ) {
$metadata['bitrate'] = (int) $data['video']['bitrate'];
}
if ( ! empty( $data['video']['bitrate_mode'] ) ) {
$metadata['bitrate_mode'] = $data['video']['bitrate_mode'];
}
if ( ! empty( $data['filesize'] ) ) {
$metadata['filesize'] = (int) $data['filesize'];
}
if ( ! empty( $data['mime_type'] ) ) {
$metadata['mime_type'] = $data['mime_type'];
}
if ( ! empty( $data['playtime_seconds'] ) ) {
$metadata['length'] = (int) round( $data['playtime_seconds'] );
}
if ( ! empty( $data['playtime_string'] ) ) {
$metadata['length_formatted'] = $data['playtime_string'];
}
if ( ! empty( $data['video']['resolution_x'] ) ) {
$metadata['width'] = (int) $data['video']['resolution_x'];
}
if ( ! empty( $data['video']['resolution_y'] ) ) {
$metadata['height'] = (int) $data['video']['resolution_y'];
}
if ( ! empty( $data['fileformat'] ) ) {
$metadata['fileformat'] = $data['fileformat'];
}
if ( ! empty( $data['video']['dataformat'] ) ) {
$metadata['dataformat'] = $data['video']['dataformat'];
}
if ( ! empty( $data['video']['encoder'] ) ) {
$metadata['encoder'] = $data['video']['encoder'];
}
if ( ! empty( $data['video']['codec'] ) ) {
$metadata['codec'] = $data['video']['codec'];
}
if ( ! empty( $data['audio'] ) ) {
unset( $data['audio']['streams'] );
$metadata['audio'] = $data['audio'];
}
if ( empty( $metadata['created_timestamp'] ) ) {
$created_timestamp = wp_get_media_creation_timestamp( $data );
if ( false !== $created_timestamp ) {
$metadata['created_timestamp'] = $created_timestamp;
}
}
wp_add_id3_tag_data( $metadata, $data );
$file_format = isset( $metadata['fileformat'] ) ? $metadata['fileformat'] : null;
/**
* Filters the array of metadata retrieved from a video.
*
* In core, usually this selection is what is stored.
* More complete data can be parsed from the `$data` parameter.
*
* @since 4.9.0
*
* @param array $metadata Filtered video metadata.
* @param string $file Path to video file.
* @param string|null $file_format File format of video, as analyzed by getID3.
* Null if unknown.
* @param array $data Raw metadata from getID3.
*/
return apply_filters( 'wp_read_video_metadata', $metadata, $file, $file_format, $data );
}
/**
* Retrieves metadata from an audio file's ID3 tags.
*
* @since 3.6.0
*
* @param string $file Path to file.
* @return array|false Returns array of metadata, if found.
*/
function wp_read_audio_metadata( $file ) {
if ( ! file_exists( $file ) ) {
return false;
}
$metadata = array();
if ( ! defined( 'GETID3_TEMP_DIR' ) ) {
define( 'GETID3_TEMP_DIR', get_temp_dir() );
}
if ( ! class_exists( 'getID3', false ) ) {
require ABSPATH . WPINC . '/ID3/getid3.php';
}
$id3 = new getID3();
// Required to get the `created_timestamp` value.
$id3->options_audiovideo_quicktime_ReturnAtomData = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
$data = $id3->analyze( $file );
if ( ! empty( $data['audio'] ) ) {
unset( $data['audio']['streams'] );
$metadata = $data['audio'];
}
if ( ! empty( $data['fileformat'] ) ) {
$metadata['fileformat'] = $data['fileformat'];
}
if ( ! empty( $data['filesize'] ) ) {
$metadata['filesize'] = (int) $data['filesize'];
}
if ( ! empty( $data['mime_type'] ) ) {
$metadata['mime_type'] = $data['mime_type'];
}
if ( ! empty( $data['playtime_seconds'] ) ) {
$metadata['length'] = (int) round( $data['playtime_seconds'] );
}
if ( ! empty( $data['playtime_string'] ) ) {
$metadata['length_formatted'] = $data['playtime_string'];
}
if ( empty( $metadata['created_timestamp'] ) ) {
$created_timestamp = wp_get_media_creation_timestamp( $data );
if ( false !== $created_timestamp ) {
$metadata['created_timestamp'] = $created_timestamp;
}
}
wp_add_id3_tag_data( $metadata, $data );
$file_format = isset( $metadata['fileformat'] ) ? $metadata['fileformat'] : null;
/**
* Filters the array of metadata retrieved from an audio file.
*
* In core, usually this selection is what is stored.
* More complete data can be parsed from the `$data` parameter.
*
* @since 6.1.0
*
* @param array $metadata Filtered audio metadata.
* @param string $file Path to audio file.
* @param string|null $file_format File format of audio, as analyzed by getID3.
* Null if unknown.
* @param array $data Raw metadata from getID3.
*/
return apply_filters( 'wp_read_audio_metadata', $metadata, $file, $file_format, $data );
}
/**
* Parses creation date from media metadata.
*
* The getID3 library doesn't have a standard method for getting creation dates,
* so the location of this data can vary based on the MIME type.
*
* @since 4.9.0
*
* @link https://github.com/JamesHeinrich/getID3/blob/master/structure.txt
*
* @param array $metadata The metadata returned by getID3::analyze().
* @return int|false A UNIX timestamp for the media's creation date if available
* or a boolean FALSE if a timestamp could not be determined.
*/
function wp_get_media_creation_timestamp( $metadata ) {
$creation_date = false;
if ( empty( $metadata['fileformat'] ) ) {
return $creation_date;
}
switch ( $metadata['fileformat'] ) {
case 'asf':
if ( isset( $metadata['asf']['file_properties_object']['creation_date_unix'] ) ) {
$creation_date = (int) $metadata['asf']['file_properties_object']['creation_date_unix'];
}
break;
case 'matroska':
case 'webm':
if ( isset( $metadata['matroska']['comments']['creation_time'][0] ) ) {
$creation_date = strtotime( $metadata['matroska']['comments']['creation_time'][0] );
} elseif ( isset( $metadata['matroska']['info'][0]['DateUTC_unix'] ) ) {
$creation_date = (int) $metadata['matroska']['info'][0]['DateUTC_unix'];
}
break;
case 'quicktime':
case 'mp4':
if ( isset( $metadata['quicktime']['moov']['subatoms'][0]['creation_time_unix'] ) ) {
$creation_date = (int) $metadata['quicktime']['moov']['subatoms'][0]['creation_time_unix'];
}
break;
}
return $creation_date;
}
/**
* Encapsulates the logic for Attach/Detach actions.
*
* @since 4.2.0
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param int $parent_id Attachment parent ID.
* @param string $action Optional. Attach/detach action. Accepts 'attach' or 'detach'.
* Default 'attach'.
*/
function wp_media_attach_action( $parent_id, $action = 'attach' ) {
global $wpdb;
if ( ! $parent_id ) {
return;
}
if ( ! current_user_can( 'edit_post', $parent_id ) ) {
wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
}
$ids = array();
foreach ( (array) $_REQUEST['media'] as $attachment_id ) {
$attachment_id = (int) $attachment_id;
if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
continue;
}
$ids[] = $attachment_id;
}
if ( ! empty( $ids ) ) {
$ids_string = implode( ',', $ids );
if ( 'attach' === $action ) {
$result = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET post_parent = %d WHERE post_type = 'attachment' AND ID IN ( $ids_string )", $parent_id ) );
} else {
$result = $wpdb->query( "UPDATE $wpdb->posts SET post_parent = 0 WHERE post_type = 'attachment' AND ID IN ( $ids_string )" );
}
}
if ( isset( $result ) ) {
foreach ( $ids as $attachment_id ) {
/**
* Fires when media is attached or detached from a post.
*
* @since 5.5.0
*
* @param string $action Attach/detach action. Accepts 'attach' or 'detach'.
* @param int $attachment_id The attachment ID.
* @param int $parent_id Attachment parent ID.
*/
do_action( 'wp_media_attach_action', $action, $attachment_id, $parent_id );
clean_attachment_cache( $attachment_id );
}
$location = 'upload.php';
$referer = wp_get_referer();
if ( $referer ) {
if ( str_contains( $referer, 'upload.php' ) ) {
$location = remove_query_arg( array( 'attached', 'detach' ), $referer );
}
}
$key = 'attach' === $action ? 'attached' : 'detach';
$location = add_query_arg( array( $key => $result ), $location );
wp_redirect( $location );
exit;
}
}