我们的 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,因为它更加简洁,且易于扩展和维护。如果需要处理更多复杂的网络请求,如跨站点的大文件下载或重定向,代码 1 的 cURL 方式可能更适合。
最后,如果仅仅先备份图片到媒体库,小白建议不要在内链图片地址中包含年份和月份(日期),以方便以后替换链接。只需要在 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