在您的WordPress主题的functions.php文件中添加以下代码,以启用远程图片下载并保存到本地的功能:
/**
* 保存文章时,自动将外部图片下载到媒体库并替换链接。
* 跳过 v.fenxiangzu.com 域名,其他外部图片均会下载。
*/
add_action('save_post', 'custom_download_external_images', 10, 3);
function custom_download_external_images($post_id, $post, $update) {
// 避免自动保存、修订版本等
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
if (wp_is_post_autosave($post_id)) return;
if (!current_user_can('edit_post', $post_id)) return;
$content = $post->post_content;
if (empty($content)) return;
// 确保必要的 WordPress 文件处理函数可用
if (!function_exists('download_url')) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
if (!function_exists('media_handle_sideload')) {
require_once ABSPATH . 'wp-admin/includes/media.php';
}
if (!function_exists('wp_read_image_metadata')) {
require_once ABSPATH . 'wp-admin/includes/image.php';
}
// 使用 DOMDocument 安全解析 HTML
$dom = new DOMDocument();
libxml_use_internal_errors(true);
// 确保 UTF-8 内容正确处理
$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));
libxml_clear_errors();
$images = $dom->getElementsByTagName('img');
$changed = false;
$site_host = parse_url(home_url(), PHP_URL_HOST);
foreach ($images as $img) {
$src = $img->getAttribute('src');
if (empty($src)) continue;
// 解析 URL 各部分
$url_parts = parse_url($src);
if (!isset($url_parts['host'])) continue; // 无 host,可能是 data URI 等,跳过
$host = $url_parts['host'];
// 跳过本站图片
if ($host === $site_host) continue;
// 黑名单域名:不进行下载
$skip_hosts = ['v.fenxiangzu.com'];
if (in_array($host, $skip_hosts)) {
error_log('跳过黑名单域名图片: ' . $src);
continue;
}
// 仅允许 http 和 https 协议
$scheme = isset($url_parts['scheme']) ? strtolower($url_parts['scheme']) : '';
if (!in_array($scheme, ['http', 'https'])) {
error_log('不允许的协议: ' . $src);
continue;
}
// 防止 SSRF:解析域名对应 IP,禁止内网地址
$ip = gethostbyname($host);
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
error_log('禁止内网/保留 IP 地址: ' . $host . ' (' . $ip . ')');
continue;
}
// 下载图片(15秒超时)
$tmp_file = download_url($src, 15);
if (is_wp_error($tmp_file)) {
error_log('下载失败: ' . $src . ' 错误: ' . $tmp_file->get_error_message());
continue;
}
// 从 URL 路径中提取原始文件名,并去除查询参数
$path = isset($url_parts['path']) ? $url_parts['path'] : '';
$filename = basename($path);
// 如果文件名无扩展名,尝试根据 MIME 补充
if (!preg_match('/\.[a-zA-Z0-9]+$/', $filename)) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$real_mime = finfo_file($finfo, $tmp_file);
finfo_close($finfo);
$ext = '';
if ($real_mime) {
$ext = explode('/', $real_mime)[1] ?? '';
}
if ($ext) {
$filename .= '.' . $ext;
} else {
// 无法判断,跳过该文件
@unlink($tmp_file);
error_log('无法识别文件类型: ' . $src);
continue;
}
}
// 清理文件名
$filename = sanitize_file_name($filename);
// 准备 sideload 参数
$file_array = [
'name' => $filename,
'tmp_name' => $tmp_file
];
// 导入媒体库,设置替代文本为原图片 alt 属性
$attachment_id = media_handle_sideload($file_array, $post_id, null, [
'post_title' => $img->getAttribute('alt') ?: ''
]);
if (is_wp_error($attachment_id)) {
@unlink($tmp_file);
error_log('媒体库导入失败: ' . $attachment_id->get_error_message());
continue;
}
// 获取新图片 URL
$new_url = wp_get_attachment_url($attachment_id);
if ($new_url) {
$img->setAttribute('src', $new_url);
$changed = true;
}
}
// 如果内容有改动,则更新文章(移除钩子避免死循环)
if ($changed) {
remove_action('save_post', 'custom_download_external_images');
wp_update_post([
'ID' => $post_id,
'post_content' => $dom->saveHTML()
]);
add_action('save_post', 'custom_download_external_images', 10, 3);
}
}