Save External Images in Media Library When Save Post/Page
This i built to assist me on my main site. Sometimes, there might be external images when you copy a bulk of text from a website or document. This will help pull the external images into the media library, and update your post body. You can use WPCodeBox or FluentSnippets to Run this easily without installing any plugin for this specific purpose.
<?php
/**
* Plugin Name: RR — Import External Images on Save
* Description: On save (draft/publish), sideload external <img> to Media Library, rewrite src, and set alt/title/description from filename.
* Version: 1.1.0
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
add_action( 'save_post', 'rr_import_external_images_on_save', 10, 3 );
function rr_import_external_images_on_save( $post_id, $post, $update ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) return;
if ( empty( $post ) || empty( $post->post_content ) ) return;
rr_require_media_libs();
$ptype = get_post_type( $post );
if ( ! post_type_supports( $ptype, 'editor' ) ) return;
$content = $post->post_content;
$attrs = rr_extract_img_srcs( $content ); // [ raw ' src=".."' => url ]
if ( empty( $attrs ) ) return;
$site_host = wp_parse_url( home_url(), PHP_URL_HOST );
$map = get_post_meta( $post_id, '_rr_extimg_map', true );
if ( ! is_array( $map ) ) $map = [];
$changed = false;
foreach ( $attrs as $raw => $src ) {
if ( ! wp_http_validate_url( $src ) ) continue;
$host = wp_parse_url( $src, PHP_URL_HOST );
if ( ! $host || strcasecmp( $host, $site_host ) === 0 ) continue; // already local
$new_url = $map[ $src ] ?? '';
if ( empty( $new_url ) ) {
$existing_id = attachment_url_to_postid( $src );
if ( $existing_id ) {
rr_fill_attachment_texts_if_empty( $existing_id, $src );
$new_url = wp_get_attachment_url( $existing_id );
} else {
$attach_id = rr_sideload_image_as_attachment( $src, $post_id );
if ( is_wp_error( $attach_id ) || ! $attach_id ) continue;
$new_url = wp_get_attachment_url( $attach_id );
}
if ( $new_url ) $map[ $src ] = $new_url;
}
if ( $new_url && $new_url !== $src ) {
// Replace ONLY the src attribute string. Keep the preceding space.
$content = str_replace( $raw, ' src="' . esc_url( $new_url ) . '"', $content );
$changed = true;
}
}
if ( $changed ) {
remove_action( 'save_post', 'rr_import_external_images_on_save', 10 );
wp_update_post( [ 'ID' => $post_id, 'post_content' => $content ] );
update_post_meta( $post_id, '_rr_extimg_map', $map );
add_action( 'save_post', 'rr_import_external_images_on_save', 10, 3 );
}
}
/**
* Match ONLY the src="..." attribute inside <img>, keeping the leading space for safe replacement.
* Returns [ raw_attr => url ] where raw_attr looks like: ' src="https://..."'
*/
function rr_extract_img_srcs( string $html ) : array {
$results = [];
if ( preg_match_all( '#<img\b[^>]*\K\s+src=("|\')([^"\']+)\1#i', $html, $m, PREG_SET_ORDER ) ) {
foreach ( $m as $match ) {
$results[ $match[0] ] = trim( html_entity_decode( $match[2] ) );
}
}
return $results;
}
/**
* Sideload image and set texts from filename.
*/
function rr_sideload_image_as_attachment( string $url, int $post_id ) {
$id = media_sideload_image( $url, $post_id, null, 'id' );
if ( is_wp_error( $id ) ) return $id;
// Generate sizes.
$path = get_attached_file( $id );
if ( $path ) {
$meta = wp_generate_attachment_metadata( $id, $path );
if ( $meta ) wp_update_attachment_metadata( $id, $meta );
}
update_post_meta( $id, '_rr_source_url', esc_url_raw( $url ) );
// Set title, alt, caption, description from filename.
rr_fill_attachment_texts_if_empty( $id, $url );
return (int) $id;
}
/**
* If attachment text fields are empty, fill from a humanized filename.
*/
function rr_fill_attachment_texts_if_empty( int $attach_id, string $fallback_url = '' ) : void {
$path = get_attached_file( $attach_id );
$base = '';
if ( $path && file_exists( $path ) ) {
$pi = pathinfo( $path );
$base = $pi['filename'] ?? '';
}
if ( ! $base && $fallback_url ) {
$pi = pathinfo( wp_parse_url( $fallback_url, PHP_URL_PATH ) ?? '' );
$base = $pi['filename'] ?? '';
}
$label = rr_humanize_filename( $base );
if ( $label === '' ) return;
// Title.
$att = get_post( $attach_id );
if ( $att && $att->post_title === '' ) {
wp_update_post( [
'ID' => $attach_id,
'post_title' => $label,
] );
}
// Alt text.
$alt = get_post_meta( $attach_id, '_wp_attachment_image_alt', true );
if ( $alt === '' ) {
update_post_meta( $attach_id, '_wp_attachment_image_alt', $label );
}
// Caption (excerpt) and Description (content).
$cur_caption = get_post_field( 'post_excerpt', $attach_id );
if ( $cur_caption === '' ) {
wp_update_post( [
'ID' => $attach_id,
'post_excerpt' => $label,
] );
}
$cur_desc = get_post_field( 'post_content', $attach_id );
if ( $cur_desc === '' ) {
wp_update_post( [
'ID' => $attach_id,
'post_content' => $label,
] );
}
}
/**
* Turn "my-photo_2025-08-29" into "My Photo 2025 08 29".
*/
function rr_humanize_filename( string $name ) : string {
$name = urldecode( $name );
$name = preg_replace( '/[_\-]+/u', ' ', $name );
$name = preg_replace( '/\s+/u', ' ', $name );
$name = trim( $name );
if ( $name === '' ) return '';
// Title case safely.
if ( function_exists( 'mb_convert_case' ) ) {
$name = mb_convert_case( $name, MB_CASE_TITLE, 'UTF-8' );
} else {
$name = ucwords( strtolower( $name ) );
}
return sanitize_text_field( $name );
}
/**
* Ensure media functions exist in snippet context.
*/
function rr_require_media_libs() : void {
if ( ! function_exists( 'download_url' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
require_once ABSPATH . 'wp-admin/includes/image.php';
}
if ( ! function_exists( 'media_sideload_image' ) ) {
require_once ABSPATH . 'wp-admin/includes/media.php';
}
}
