纯代码将 WordPress 文章外链图片备份为网站媒体库本地内链图片

文章首发于公众号【小白书签】:https://mp.weixin.qq.com/s/525i8xv7AyNeJtDj1SvXlA

我们的 WordPress 网站在使用了图床,或者复制粘贴了别人网站内容的情况下,文章中会出现一些外部图片(地址)。如果使用的图床停止服务(尤其是白嫖的免费图床),或者别人的网站挂了,就会导致我们网站文章中的外链图片也随之失效,无法显示。

此时,如果提前备份了图片还好,重新上传或者批量替换地址什么的就可以了,无非就是麻烦一些。但如果图片没有备份,那就糟糕了,重新给所有文章配图可不是一件容易的事。

那么, 如何避免这个问题呢?最保险的做法自然是将文章中的外部图片,下载并保存到 WordPress 媒体库,并将文章中的图片链接替换为本地服务器的图片链接。实现也很简单,无需安装插件,直接将下方代码加入到主题 functions.php 文件中,之后更新发布文章就会看到效果了

function ecp_save_post($post_id, $post) {
    global $wpdb;
if ($post->post_status == 'publish') {
$p = '/<img.*[\s]src=[\"|\'](.*)[\"|\'].*>/iU';
$num = preg_match_all($p, $post->post_content, $matches);
if ($num) {
$wp_upload_dir = wp_upload_dir();
            set_time_limit(0);
$ch = curl_init();
            curl_setopt($ch, CURLOPT_HEADER, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);

$ecp_options = $_SERVER['HTTP_HOST'];
            foreach ($matches[1] as $src) {
if (isset($src) && strpos($src, $ecp_options) === false) {
$file_info = wp_check_filetype(basename($src), null);
if ($file_info['ext'] == false) {
                        date_default_timezone_set('PRC');
$file_name = date('YmdHis-') . dechex(mt_rand(100000, 999999)) . '.tmp';
                    } else {
$file_name = dechex(mt_rand(100000, 999999)) . '-' . basename($src);
                    }
                    curl_setopt($ch, CURLOPT_URL, $src);
$file_path = $wp_upload_dir['path'] . '/' . $file_name;
$img = fopen($file_path, 'wb');
                    curl_setopt($ch, CURLOPT_FILE, $img);
$img_data = curl_exec($ch);
                    fclose($img);

if (file_exists($file_path) && filesize($file_path) > 0) {
$t = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$arr = explode('/', $t);
if (pathinfo($file_path, PATHINFO_EXTENSION) == 'tmp') {
$file_path = ecp_handle_ext($file_path, $arr[1], $wp_upload_dir['path'], $file_name, 'tmp');
                        }
$post->post_content = str_replace($src, $wp_upload_dir['url'] . '/' . basename($file_path), $post->post_content);
$attachment = ecp_get_attachment_post(basename($file_path), $wp_upload_dir['url'] . '/' . basename($file_path));
$attach_id = wp_insert_attachment($attachment, ltrim($wp_upload_dir['subdir'] . '/' . basename($file_path), '/'), 0);
$attach_data = wp_generate_attachment_metadata($attach_id, $file_path);
$ss = wp_update_attachment_metadata($attach_id, $attach_data);
                    }
                }
            }
            curl_close($ch);
$wpdb->update($wpdb->posts, array('post_content' => $post->post_content), array('ID' => $post->ID));
        }
    }
}

function ecp_handle_ext($file, $type, $file_dir, $file_name, $ext) {
if ($ext === 'tmp') {
if (rename($file, str_replace('tmp', $type, $file))) {
return $file_dir . '/' . str_replace('tmp', $type, $file_name);
        }
    }
return $file;
}

function ecp_get_attachment_post($filename, $url) {
$file_info = wp_check_filetype($filename, null);
return array(
'guid' => $url,
'post_type' => 'attachement',
'post_mime_type' => $file_info['type'],
'post_title' => preg_replace('/\.[^.]+$/', '', $filename),
'post_content' => '',
'post_status' => 'inherit'
    );
}
add_action('save_post', 'ecp_save_post', 120, 2);

不过,也有这种情况和需求:网站使用了国外服务器,但把图片放在国内服务器上(如利用国内大厂的某些服务当作免费图床)以提升加载速度。因此希望可以暂时使用国内的图片外链,只是先将外链图片提前备份到媒体库。当某天万一外链图片挂了,再替换成内链图片。

如何实现呢?下面提供两段代码,任选其一即可。代码 1 是在上述代码基础上修改而来。

  • 保持原外链图片文件名和格式,不再自动重命名上传的图片
  • 仅上传外链图片至媒体库,不再自动替换文章中的图片链接地址

代码 1

function ecp_save_post($post_id, $post) {
    global $wpdb;

    if ($post->post_status == 'publish') {
        $p = '/<img.*[\s]src=[\"|\'](.*)[\"|\'].*>/iU';
        $num = preg_match_all($p, $post->post_content, $matches);

        if ($num) {
            $wp_upload_dir = wp_upload_dir();
            set_time_limit(0);
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_HEADER, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_MAXREDIRS, 20);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);

            $ecp_options = $_SERVER['HTTP_HOST'];

            foreach ($matches[1] as $src) {
                if (isset($src) && strpos($src, $ecp_options) === false) {
                    $attachment_id = attachment_url_to_postid($src);
                    if ($attachment_id) {
                        continue;
                    }

                    $file_name = basename($src);
                    $file_path = $wp_upload_dir['path'] . '/' . $file_name;

                    if (file_exists($file_path)) {
                        continue;
                    }

                    curl_setopt($ch, CURLOPT_URL, $src);
                    $img = fopen($file_path, 'wb');
                    curl_setopt($ch, CURLOPT_FILE, $img);
                    $img_data = curl_exec($ch);
                    fclose($img);

                    if (file_exists($file_path) && filesize($file_path) > 0) {
                        $attachment = ecp_get_attachment_post($file_name, $wp_upload_dir['url'] . '/' . $file_name);
                        $attach_id = wp_insert_attachment($attachment, $file_path, 0);
                        $attach_data = wp_generate_attachment_metadata($attach_id, $file_path);
                        wp_update_attachment_metadata($attach_id, $attach_data);
                    }
                }
            }

            curl_close($ch);
        }
    }
}

function ecp_get_attachment_post($filename, $url) {
    $file_info = wp_check_filetype($filename, null);
    return array(
        'guid' => $url,
        'post_type' => 'attachment',
        'post_mime_type' => $file_info['type'],
        'post_title' => preg_replace('/\.[^.]+$/', '', $filename),
        'post_content' => '',
        'post_status' => 'inherit'
    );
}

add_action('save_post', 'ecp_save_post', 120, 2);

代码 2

function backup_external_images_to_media_library( $post_id ) {
    $post = get_post( $post_id );
    $content = $post->post_content;

    // 使用正则匹配图片
    preg_match_all( '/<img[^>]+src="([^">]+)"/i', $content, $matches );

    if ( isset( $matches[1] ) && !empty( $matches[1] ) ) {
        foreach ( $matches[1] as $image_url ) {
            // 跳过本站内图片
            if ( strpos( $image_url, home_url() ) === false ) {
                $attachment_id = attachment_url_to_postid( $image_url );
                if ( !$attachment_id ) {
                    // 上传图片到媒体库
                    $upload = upload_image_to_media_library_without_replacement( $image_url );

                    if ( $upload ) {
                        // 可根据需要处理成功上传的逻辑
                    }
                }
            }
        }
    }
}

function upload_image_to_media_library_without_replacement( $image_url ) {
    // 下载图片
    $image_data = file_get_contents( $image_url );
    if ( !$image_data ) {
        return false;
    }

    // 生成唯一文件名
    $filename = basename( parse_url( $image_url, PHP_URL_PATH ) );
    $upload_dir = wp_upload_dir();
    $upload_path = $upload_dir['path'] . '/' . $filename;

    // 检查文件是否已经存在
    if ( file_exists( $upload_path ) ) {
        return false;
    }

    // 保存图片到上传目录
    file_put_contents( $upload_path, $image_data );

    // 创建附件
    $file_type = wp_check_filetype( $filename, null );
    $attachment = array(
        'guid'           => $upload_dir['url'] . '/' . basename( $filename ),
        'post_mime_type' => $file_type['type'],
        'post_title'     => sanitize_file_name( $filename ),
        'post_content'   => '',
        'post_status'    => 'inherit',
    );

    // 将附件插入到媒体库
    $attachment_id = wp_insert_attachment( $attachment, $upload_path );

    // 生成并更新元数据
    require_once( ABSPATH . 'wp-admin/includes/image.php' );
    $attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload_path );
    wp_update_attachment_metadata( $attachment_id, $attachment_data );

    return array(
        'id'  => $attachment_id,
        'url' => wp_get_attachment_url( $attachment_id ),
    );
}

// 注册文章保存钩子
add_action( 'save_post', 'backup_external_images_to_media_library' );

哪段代码更好?

代码 1 的优势在于它使用了 cURL,可以更好地处理复杂的网络请求,如大图片下载、重定向等场景。如果需要处理更多不确定性因素(如外部服务器响应时间较长、复杂的重定向情况),第一段代码可能更适合,但也需要更复杂的维护和优化。

代码 2 更加简洁、易读、模块化,且错误处理更清晰,适合大多数常见场景。它的实现更符合现代编程习惯,代码维护性较好。如果没有特殊的超时或重定向处理需求,它的实现方式较优。

因此,如果目标是简单的下载和备份外部图片,推荐使用 代码 2,因为它更加简洁,且易于扩展和维护。如果需要处理更多复杂的网络请求,如跨站点的大文件下载或重定向,代码 1cURL 方式可能更适合。

最后,如果仅仅先备份图片到媒体库,小白建议不要在内链图片地址中包含年份和月份(日期),以方便以后替换链接。只需要在 WordPress 网站后台 - 设置 - 媒体 - 文件上传,取消勾选”以年—月目录形式组织上传内容“就可以了。

代码 3

在代码 2 基础上进一步优化

展开更多内容

function backup_external_images_to_media_library( $post_id ) {
// 获取文章内容
$post = get_post( $post_id );
if ( empty( $post ) || wp_is_post_revision( $post_id ) ) {
return;
}

$content = $post->post_content;

// 提前检查 <img> 标签以减少不必要的正则匹配
if ( strpos( $content, '<img' ) === false ) {
return;
}

// 使用 DOMDocument 来解析图片
libxml_use_internal_errors( true ); // 忽略 HTML 解析中的警告
$dom = new DOMDocument();
$dom->loadHTML( $content );
$images = $dom->getElementsByTagName( 'img' );

// 缓存已经处理的 URL
$processed_urls = array();

foreach ( $images as $img ) {
$image_url = $img->getAttribute( 'src' );

// 跳过本站内的图片
if ( strpos( $image_url, home_url() ) !== false || in_array( $image_url, $processed_urls ) ) {
continue;
}

// 检查缓存,避免重复下载相同的图片
$cached_attachment_id = get_transient( 'external_image_' . md5( $image_url ) );
if ( $cached_attachment_id ) {
$processed_urls[] = $image_url;
continue;
}

// 异步上传图片
wp_schedule_single_event( time(), 'process_external_image', array( $image_url ) );
}
}

// 异步处理外部图片下载和上传
function process_external_image( $image_url ) {
if ( empty( $image_url ) ) {
return;
}

// 下载并上传图片
$upload = upload_image_to_media_library_without_replacement( $image_url );

if ( !empty( $upload['id'] ) ) {
// 设置 transient 缓存,防止重复处理
set_transient( 'external_image_' . md5( $image_url ), $upload['id'], DAY_IN_SECONDS );
}
}

// 上传图片的函数保持不变,只做轻微调整
function upload_image_to_media_library_without_replacement( $image_url ) {
// 下载图片数据
$response = wp_remote_get( $image_url, array( 'timeout' => 15 ) );
$image_data = wp_remote_retrieve_body( $response );

if ( empty( $image_data ) ) {
return false;
}

// 获取唯一文件名
$filename = wp_unique_filename( wp_upload_dir()['path'], basename( parse_url( $image_url, PHP_URL_PATH ) ) );
$upload_dir = wp_upload_dir();
$upload_path = $upload_dir['path'] . '/' . $filename;

// 保存图片到上传目录
file_put_contents( $upload_path, $image_data );

// 创建附件
$file_type = wp_check_filetype( $filename, null );
$attachment = array(
'guid' => $upload_dir['url'] . '/' . basename( $filename ),
'post_mime_type' => $file_type['type'],
'post_title' => sanitize_file_name( $filename ),
'post_content' => '',
'post_status' => 'inherit',
);

$attachment_id = wp_insert_attachment( $attachment, $upload_path );

// 生成元数据并更新
require_once( ABSPATH . 'wp-admin/includes/image.php' );
$attachment_data = wp_generate_attachment_metadata( $attachment_id, $upload_path );
wp_update_attachment_metadata( $attachment_id, $attachment_data );

return array(
'id' => $attachment_id,
'url' => wp_get_attachment_url( $attachment_id ),
);
}

// 注册异步任务的事件
add_action( 'process_external_image', 'process_external_image' );

// 注册文章保存钩子
add_action( 'save_post', 'backup_external_images_to_media_library' );

0

评论0

没有账号?注册  忘记密码?

社交账号快速登录

微信扫一扫关注
如已关注,请回复“登录”二字获取验证码