解决数据库替换 url 导致的错误。
警告(禁止) unserialize(): Error at offset 4351 of 4374 bytes
wp-includes/functions.php:655
unserialize()
wp-includes/functions.php:655
maybe_unserialize()
wp-includes/functions.php:655
array_map()
wp-includes/meta.php:692
get_metadata_raw()
wp-includes/meta.php:602
get_metadata()
wp-includes/post.php:2712
get_post_meta()
wp-admin/includes/class-wp-privacy-policy-content.php:69
WP_Privacy_Policy_Content::text_change_check()
wp-includes/class-wp-hook.php:341
do_action('admin_init')
wp-admin/admin.php:180
修复代码:
<?php
/**
* WordPress 序列化数据修复脚本
* 修复因直接 SQL REPLACE 导致的序列化数据损坏问题
*
* 使用方法:
* 1. 修改下面的数据库连接信息
* 2. 在命令行运行: php fix-unserialize-error.php
* 3. 或者通过浏览器访问(需要配置 web 服务器)
*/
// ========== 配置区域 ==========
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'zxj');
define('DB_USER', 'root');
define('DB_PASS', 'zxj');
define('DB_PREFIX', 'wp_'); // 表前缀
// 旧 URL 和新 URL(用于修复)
define('OLD_URL', 'http://test.h4ck.org.cn:18888');
define('NEW_URL', 'https://zhongxiaojie.com');
// ========== 主程序 ==========
// 连接数据库
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage() . "\n");
}
echo "==========================================\n";
echo "WordPress 序列化数据修复工具\n";
echo "==========================================\n\n";
/**
* 修复序列化字符串中的 URL
* 正确更新序列化数据中的长度信息
*/
function fix_serialized_url($serialized, $old_url, $new_url) {
// 如果不是序列化数据,直接替换
if (!is_serialized($serialized)) {
return str_replace($old_url, $new_url, $serialized);
}
// 尝试反序列化
$data = @unserialize($serialized);
// 如果反序列化失败,尝试修复
if ($data === false && $serialized !== serialize(false)) {
// 尝试修复常见的序列化问题
$fixed = fix_broken_serialize($serialized, $old_url, $new_url);
$data = @unserialize($fixed);
if ($data === false && $fixed !== serialize(false)) {
return false; // 无法修复
}
$serialized = $fixed;
$data = @unserialize($serialized);
}
// 递归替换数组或对象中的 URL
if (is_array($data)) {
$data = array_map(function($value) use ($old_url, $new_url) {
if (is_string($value)) {
return str_replace($old_url, $new_url, $value);
} elseif (is_array($value)) {
return array_map(function($v) use ($old_url, $new_url) {
return is_string($v) ? str_replace($old_url, $new_url, $v) : $v;
}, $value);
}
return $value;
}, $data);
} elseif (is_object($data)) {
foreach ($data as $key => $value) {
if (is_string($value)) {
$data->$key = str_replace($old_url, $new_url, $value);
}
}
} elseif (is_string($data)) {
$data = str_replace($old_url, $new_url, $data);
}
// 重新序列化
return serialize($data);
}
/**
* 检查字符串是否是序列化数据
*/
function is_serialized($data) {
if (!is_string($data)) {
return false;
}
$data = trim($data);
if ('N;' == $data) {
return true;
}
if (strlen($data) < 4) {
return false;
}
if (':' !== $data[1]) {
return false;
}
$semi = strpos($data, ';');
if (false === $semi) {
return false;
}
$token = $data[0];
switch ($token) {
case 's':
if ('"' !== $data[$semi - 1]) {
return false;
}
case 'a':
case 'O':
return (bool) preg_match("/^{$token}:[0-9]+:/s", $data);
case 'b':
case 'i':
case 'd':
return (bool) preg_match("/^{$token}:[0-9.E-]+;/", $data);
}
return false;
}
/**
* 尝试修复损坏的序列化数据
*/
function fix_broken_serialize($serialized, $old_url = null, $new_url = null) {
// 如果包含旧 URL,先替换
if ($old_url && $new_url && strpos($serialized, $old_url) !== false) {
$serialized = str_replace($old_url, $new_url, $serialized);
}
// 尝试修复长度信息
// 匹配字符串长度模式: s:数字:"内容"
// 注意:需要处理转义字符和嵌套引号
$pattern = '/s:(\d+):"((?:[^"\\\\]|\\\\.)*)"/';
$serialized = preg_replace_callback($pattern, function($matches) {
// 计算实际字符串长度(考虑转义字符)
$str = $matches[2];
$length = strlen($str);
return 's:' . $length . ':"' . $str . '"';
}, $serialized);
return $serialized;
}
/**
* 检测并修复损坏的序列化数据
*/
function detect_and_fix_broken_serialize($serialized) {
// 如果不是序列化数据,直接返回
if (!is_serialized($serialized)) {
return ['fixed' => false, 'data' => $serialized, 'error' => null];
}
// 清除之前的错误
@error_clear_last();
// 尝试反序列化
$data = @unserialize($serialized);
// 检查是否有错误
$error = error_get_last();
// 如果反序列化成功且没有错误,返回原数据
if ($data !== false && ($error === null || strpos($error['message'], 'unserialize') === false)) {
// 特殊处理:false 的序列化结果是 'b:0;'
if ($serialized === 'b:0;' || $serialized === serialize(false)) {
return ['fixed' => false, 'data' => $serialized, 'error' => null];
}
return ['fixed' => false, 'data' => $serialized, 'error' => null];
}
// 反序列化失败,记录错误
$original_error = $error ? $error['message'] : 'Unserialize failed';
// 尝试修复长度信息
$fixed = fix_broken_serialize($serialized);
@error_clear_last();
$test_data = @unserialize($fixed);
$test_error = error_get_last();
if ($test_data !== false && ($test_error === null || strpos($test_error['message'], 'unserialize') === false)) {
return ['fixed' => true, 'data' => $fixed, 'error' => $original_error];
}
// 如果还是失败,尝试更激进的修复
// 修复所有字符串长度(更精确的匹配)
$fixed2 = preg_replace_callback('/s:(\d+):"((?:[^"\\\\]|\\\\.)*)"/', function($m) {
$str = $m[2];
$len = strlen($str);
return 's:' . $len . ':"' . $str . '"';
}, $serialized);
@error_clear_last();
$test_data2 = @unserialize($fixed2);
$test_error2 = error_get_last();
if ($test_data2 !== false && ($test_error2 === null || strpos($test_error2['message'], 'unserialize') === false)) {
return ['fixed' => true, 'data' => $fixed2, 'error' => $original_error];
}
return ['fixed' => false, 'data' => $serialized, 'error' => $original_error];
}
$fixed_count = 0;
$error_count = 0;
$broken_count = 0;
// 修复 wp_postmeta 表
echo "[1/4] 检查 wp_postmeta 表中的序列化数据...\n";
// 首先查找所有序列化数据
$stmt = $pdo->prepare("SELECT meta_id, post_id, meta_key, meta_value FROM " . DB_PREFIX . "postmeta WHERE meta_value LIKE 'a:%' OR meta_value LIKE 'O:%' OR meta_value LIKE 's:%' OR meta_value LIKE 'i:%' OR meta_value LIKE 'b:%' OR meta_value LIKE 'd:%'");
$stmt->execute();
$all_serialized = $stmt->fetchAll();
echo "找到 " . count($all_serialized) . " 条可能的序列化数据记录\n";
echo "开始检测损坏的数据...\n\n";
$update_stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "postmeta SET meta_value = ? WHERE meta_id = ?");
$broken_records = [];
foreach ($all_serialized as $row) {
$result = detect_and_fix_broken_serialize($row['meta_value']);
if ($result['fixed']) {
$broken_count++;
$broken_records[] = [
'meta_id' => $row['meta_id'],
'post_id' => $row['post_id'],
'meta_key' => $row['meta_key'],
'error' => $result['error']
];
try {
$update_stmt->execute([$result['data'], $row['meta_id']]);
$fixed_count++;
if ($fixed_count % 10 == 0) {
echo " 已修复 {$fixed_count} 条损坏的记录...\n";
}
} catch (PDOException $e) {
echo " ✗ 更新失败 meta_id={$row['meta_id']}: " . $e->getMessage() . "\n";
$error_count++;
}
}
}
if ($broken_count > 0) {
echo "\n发现并修复了 {$broken_count} 条损坏的序列化数据\n";
if (count($broken_records) <= 20) {
echo "\n损坏的记录详情:\n";
foreach ($broken_records as $rec) {
echo " - meta_id={$rec['meta_id']}, post_id={$rec['post_id']}, meta_key={$rec['meta_key']}\n";
}
}
} else {
echo "未发现损坏的序列化数据\n";
}
// 也检查包含旧 URL 的记录(即使已经修复了长度问题,URL 可能还需要更新)
echo "\n检查是否还有包含旧 URL 的记录...\n";
$stmt = $pdo->prepare("SELECT meta_id, post_id, meta_key, meta_value FROM " . DB_PREFIX . "postmeta WHERE meta_value LIKE ?");
$stmt->execute(['%' . OLD_URL . '%']);
$url_rows = $stmt->fetchAll();
if (count($url_rows) > 0) {
echo "找到 " . count($url_rows) . " 条包含旧 URL 的记录,正在修复...\n";
foreach ($url_rows as $row) {
$original = $row['meta_value'];
$fixed = fix_serialized_url($original, OLD_URL, NEW_URL);
if ($fixed !== false && $fixed !== $original) {
try {
$update_stmt->execute([$fixed, $row['meta_id']]);
$fixed_count++;
} catch (PDOException $e) {
$error_count++;
}
}
}
}
echo "\n[2/4] 检查 wp_options 表中的序列化数据...\n";
// 检查所有序列化数据
$stmt = $pdo->prepare("SELECT option_id, option_name, option_value FROM " . DB_PREFIX . "options WHERE option_value LIKE 'a:%' OR option_value LIKE 'O:%' OR option_value LIKE 's:%' OR option_value LIKE 'i:%' OR option_value LIKE 'b:%' OR option_value LIKE 'd:%'");
$stmt->execute();
$all_options = $stmt->fetchAll();
echo "找到 " . count($all_options) . " 条可能的序列化选项\n";
echo "开始检测损坏的数据...\n\n";
$update_stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "options SET option_value = ? WHERE option_id = ?");
$broken_options = [];
foreach ($all_options as $row) {
// 跳过 siteurl 和 home
if (in_array($row['option_name'], ['siteurl', 'home'])) {
continue;
}
$result = detect_and_fix_broken_serialize($row['option_value']);
if ($result['fixed']) {
$broken_count++;
$broken_options[] = [
'option_id' => $row['option_id'],
'option_name' => $row['option_name'],
'error' => $result['error']
];
try {
$update_stmt->execute([$result['data'], $row['option_id']]);
$fixed_count++;
} catch (PDOException $e) {
$error_count++;
}
}
}
if (count($broken_options) > 0) {
echo "发现并修复了 " . count($broken_options) . " 条损坏的选项\n";
if (count($broken_options) <= 20) {
echo "\n损坏的选项详情:\n";
foreach ($broken_options as $opt) {
echo " - option_id={$opt['option_id']}, option_name={$opt['option_name']}\n";
}
}
}
// 检查包含旧 URL 的选项
echo "\n检查是否还有包含旧 URL 的选项...\n";
$stmt = $pdo->prepare("SELECT option_id, option_name, option_value FROM " . DB_PREFIX . "options WHERE option_value LIKE ?");
$stmt->execute(['%' . OLD_URL . '%']);
$url_options = $stmt->fetchAll();
if (count($url_options) > 0) {
echo "找到 " . count($url_options) . " 条包含旧 URL 的选项,正在修复...\n";
foreach ($url_options as $row) {
if (in_array($row['option_name'], ['siteurl', 'home'])) {
continue;
}
$original = $row['option_value'];
$fixed = fix_serialized_url($original, OLD_URL, NEW_URL);
if ($fixed !== false && $fixed !== $original) {
try {
$update_stmt->execute([$fixed, $row['option_id']]);
$fixed_count++;
} catch (PDOException $e) {
$error_count++;
}
}
}
}
echo "\n[3/4] 检查其他元数据表...\n";
$tables = [
DB_PREFIX . 'usermeta' => ['umeta_id', 'user_id', 'meta_key', 'meta_value'],
DB_PREFIX . 'commentmeta' => ['meta_id', 'comment_id', 'meta_key', 'meta_value'],
DB_PREFIX . 'termmeta' => ['meta_id', 'term_id', 'meta_key', 'meta_value']
];
foreach ($tables as $table => $columns) {
if (!table_exists($pdo, $table)) {
continue;
}
echo "检查 {$table}...\n";
// 检查所有序列化数据
$stmt = $pdo->prepare("SELECT {$columns[0]}, {$columns[1]}, {$columns[2]}, {$columns[3]} FROM {$table} WHERE {$columns[3]} LIKE 'a:%' OR {$columns[3]} LIKE 'O:%' OR {$columns[3]} LIKE 's:%' OR {$columns[3]} LIKE 'i:%' OR {$columns[3]} LIKE 'b:%' OR {$columns[3]} LIKE 'd:%'");
$stmt->execute();
$all_rows = $stmt->fetchAll();
if (count($all_rows) > 0) {
echo " 找到 " . count($all_rows) . " 条序列化数据,检测中...\n";
$update_stmt = $pdo->prepare("UPDATE {$table} SET {$columns[3]} = ? WHERE {$columns[0]} = ?");
foreach ($all_rows as $row) {
$result = detect_and_fix_broken_serialize($row[$columns[3]]);
if ($result['fixed']) {
$broken_count++;
try {
$update_stmt->execute([$result['data'], $row[$columns[0]]]);
$fixed_count++;
} catch (PDOException $e) {
$error_count++;
}
}
}
}
// 也检查包含旧 URL 的记录
$stmt = $pdo->prepare("SELECT {$columns[0]}, {$columns[1]}, {$columns[2]}, {$columns[3]} FROM {$table} WHERE {$columns[3]} LIKE ?");
$stmt->execute(['%' . OLD_URL . '%']);
$url_rows = $stmt->fetchAll();
if (count($url_rows) > 0) {
echo " 找到 " . count($url_rows) . " 条包含旧 URL 的记录,正在修复...\n";
$update_stmt = $pdo->prepare("UPDATE {$table} SET {$columns[3]} = ? WHERE {$columns[0]} = ?");
foreach ($url_rows as $row) {
$original = $row[$columns[3]];
$fixed = fix_serialized_url($original, OLD_URL, NEW_URL);
if ($fixed !== false && $fixed !== $original) {
try {
$update_stmt->execute([$fixed, $row[$columns[0]]]);
$fixed_count++;
} catch (PDOException $e) {
$error_count++;
}
}
}
}
}
echo "\n[4/4] 查找可能导致错误的特定记录...\n";
echo "根据错误信息 'Error at offset 4351 of 4374 bytes',查找长度接近的记录...\n";
// 查找长度在 4300-4400 字节之间的序列化数据
$stmt = $pdo->prepare("SELECT meta_id, post_id, meta_key, meta_value, LENGTH(meta_value) as value_length FROM " . DB_PREFIX . "postmeta WHERE (meta_value LIKE 'a:%' OR meta_value LIKE 'O:%' OR meta_value LIKE 's:%') AND LENGTH(meta_value) BETWEEN 4300 AND 4400 ORDER BY value_length");
$stmt->execute();
$suspicious = $stmt->fetchAll();
if (count($suspicious) > 0) {
echo "找到 " . count($suspicious) . " 条可疑记录(长度在 4300-4400 字节之间)\n";
echo "这些记录可能是导致错误的原因:\n\n";
$update_stmt = $pdo->prepare("UPDATE " . DB_PREFIX . "postmeta SET meta_value = ? WHERE meta_id = ?");
foreach ($suspicious as $sus) {
echo " - meta_id={$sus['meta_id']}, post_id={$sus['post_id']}, meta_key={$sus['meta_key']}, length={$sus['value_length']}\n";
// 直接尝试反序列化完整数据
@error_clear_last();
$test_data = @unserialize($sus['meta_value']);
$test_error = error_get_last();
// 检查是否有 unserialize 错误
if ($test_error && strpos($test_error['message'], 'unserialize') !== false) {
echo " ✗ 检测到损坏: {$test_error['message']}\n";
echo " 正在修复...\n";
// 尝试修复
$fix_result = detect_and_fix_broken_serialize($sus['meta_value']);
if ($fix_result['fixed']) {
try {
$update_stmt->execute([$fix_result['data'], $sus['meta_id']]);
echo " ✓ 已修复并更新数据库\n";
$fixed_count++;
$broken_count++;
} catch (PDOException $e) {
echo " ✗ 更新失败: " . $e->getMessage() . "\n";
$error_count++;
}
} else {
// 如果自动修复失败,尝试手动修复字符串长度
echo " 尝试手动修复字符串长度...\n";
$manual_fixed = preg_replace_callback('/s:(\d+):"((?:[^"\\\\]|\\\\.)*)"/', function($m) {
$str = $m[2];
$len = strlen($str);
if ($len != $m[1]) {
return 's:' . $len . ':"' . $str . '"';
}
return $m[0];
}, $sus['meta_value']);
@error_clear_last();
$manual_test = @unserialize($manual_fixed);
$manual_error = error_get_last();
if ($manual_test !== false && ($manual_error === null || strpos($manual_error['message'], 'unserialize') === false)) {
try {
$update_stmt->execute([$manual_fixed, $sus['meta_id']]);
echo " ✓ 手动修复成功并更新数据库\n";
$fixed_count++;
$broken_count++;
} catch (PDOException $e) {
echo " ✗ 更新失败: " . $e->getMessage() . "\n";
$error_count++;
}
} else {
echo " ✗ 无法修复此记录\n";
echo " 建议:如果这个 meta_key 不重要,可以考虑删除\n";
echo " DELETE FROM " . DB_PREFIX . "postmeta WHERE meta_id = {$sus['meta_id']};\n";
$error_count++;
}
}
} else {
echo " ✓ 数据正常(未检测到错误)\n";
}
echo "\n";
}
}
function table_exists($pdo, $table) {
try {
$stmt = $pdo->query("SHOW TABLES LIKE '{$table}'");
return $stmt->rowCount() > 0;
} catch (PDOException $e) {
return false;
}
}
echo "\n==========================================\n";
echo "修复完成!\n";
echo "==========================================\n";
echo "成功修复: {$fixed_count} 条记录\n";
echo "发现损坏: {$broken_count} 条记录\n";
echo "修复失败: {$error_count} 条记录\n";
echo "\n";
if ($fixed_count > 0) {
echo "✓ 已修复 {$fixed_count} 条损坏的序列化数据\n";
echo "\n建议:\n";
echo "1. 清除 WordPress 缓存(对象缓存、页面缓存)\n";
echo "2. 刷新后台页面,检查错误是否消失\n";
echo "3. 如果仍有问题,请提供新的错误信息\n";
} else {
echo "⚠ 未发现损坏的序列化数据\n";
echo "\n可能的原因:\n";
echo "1. 错误来自其他表或数据\n";
echo "2. 错误是动态生成的(不是数据库中的数据)\n";
echo "3. 需要查看完整的错误堆栈信息\n";
echo "\n建议:\n";
echo "1. 启用 WordPress 调试模式(wp-config.php 中设置 WP_DEBUG = true)\n";
echo "2. 查看错误日志,找到具体的 meta_id 或 option_id\n";
echo "3. 检查 wp-includes/functions.php:655 附近的代码\n";
}
echo "\n";
