WooCommerce多语言PDF导出问题:优惠券下载为空的解决与调试
2025-03-14 00:43:18
WooCommerce 多语言 PDF 导出问题:优惠券下载为空
我开发了一个 WooCommerce 商店的小扩展程序来管理优惠券,其中包含 PDF 导出功能。法语版本可以正常工作,但英语版本会损坏:返回的 PDF 是空的。我的目标是在用户仪表盘添加一个"Vouchers" 的菜单,展示客户购买的优惠券并且允许重新下载,为什么会出现这样的状况,下面一起来看看。
问题原因分析
这个问题,初看像是语言切换导致了某种错误,但根据你提供的信息,问题更可能出在以下几个方面:
-
输出缓冲(Output Buffering): 最有可能的是,在 PDF 内容发送到浏览器之前,其他地方已经输出了内容(比如空格、换行、错误消息等),这会导致 PDF 文件损坏。 FPDI 生成 PDF 依赖于一个干净的输出环境。
-
文件路径问题: 尽管您提到
$pdf
转储似乎是正确的,但我们需要仔细的排查路径是否有微妙的不同(尤其是跨操作系统,Windows 与 类Unix 系统)。 -
TranslatePress 插件干扰: 翻译插件有时会通过钩子(hook)修改输出或请求流程, 需要测试一下在禁用插件的情况下 PDF 下载行为是否发生变化。
-
字体问题 (可能性较低): 如果使用的模板中某些字体只支持法语,或者英文字体在服务器上缺失,那么可能导致虽然 FPDI 对象内部有数据,但是最终输出时候缺少必要字体渲染。
-
FPDI 配置差异 : 排查法语版本和英文版本的 FPDI 模板的配置差异
解决方案
针对上述可能的原因,逐一排查并解决。下面我提供几个逐步排查的方法,每一个方法都尽可能包含原理和代码示例:
1. 清理输出缓冲
原理: PHP 的输出缓冲机制允许你在脚本执行过程中捕获所有输出(echo、print 等产生的),并在最后一次性发送给浏览器,或进行其他操作(比如丢弃)。如果我们在 generate_pdf
函数 之前 就已经有内容输出,那么 PDF 的头部信息就会被破坏。
操作步骤:
-
在
generate_pdf
函数开头添加ob_clean()
: 确保没有任何意外输出。function generate_pdf($order_id, $item_id) { ob_clean(); // 关键:清除之前的输出缓冲区 $order = wc_get_order($order_id); // ... 其余代码 ... }
-
检查插件和主题的其他文件: 仔细检查你的插件文件,包括其他函数文件、甚至是主题的
functions.php
,查找任何可能的echo
、print
语句,以及可能在 PHP 标签之前或之后出现的空格或空行. 尤其留意可能存在bom头。<?php // 之前不要有任何空行或空格! // ... 代码 ... ?> // 之后也不要有任何空行或空格!
-
在最后return前增加
ob_end_flush();
return $pdf->Output("voucher_".$reference.".pdf", "D"); ob_end_flush(); }
- 刷新输出缓冲
2. 检查和规范化文件路径
原理: 使用绝对路径,减少因为相对路径在不同环境下解释不同带来的错误。plugin_dir_path(__FILE__)
确保我们得到插件目录的正确路径。 但为保证完全一致,对这个路径进行规范化处理。
操作步骤:
-
使用
realpath()
规范化路径:realpath()
可以将相对路径转换为绝对路径,并解析其中的符号链接,确保不同环境下路径的一致性。if ($language === "en_GB") { $template_path = realpath(plugin_dir_path(__FILE__) . 'template_voucher_en.pdf'); $pdf->setSourceFile($template_path); // ... } else { $template_path = realpath(plugin_dir_path(__FILE__) . 'template_voucher_fr.pdf'); $pdf->setSourceFile($template_path); // ... }
-
文件权限: 确认 PHP 进程(通常是
www-data
用户)有权限读取模板文件。 可以用命令行查看:ls -l /path/to/your/plugin/
3. 临时禁用 TranslatePress 插件
原理: 排除法。 如果禁用翻译插件后问题消失,说明问题与该插件相关。
操作步骤:
-
在 WordPress 后台,进入“插件” -> “已安装插件”。
-
找到 "TranslatePress",点击“停用”。
-
测试英文版 PDF 下载。
-
如果问题解决, 单独处理TranslatePress。
-
可以进一步调试,通过在
functions.php
文件逐步注释掉有关TranslatePress的代码。 -
向TranslatePress 的支持团队求助
-
4. 确保字体可用(及使用通用字体)
原理: PDF渲染依赖于特定字体,确认这些字体已安装并且 FPDI 可以访问它们。 为了避免这类问题, 使用标准 PDF 字体, 例如 Helvetica。
操作步骤:
-
确认字体已安装: (通常不需要,除非你用了自定义字体)。FPDI 默认情况下使用 Helvetica。 如果你用了自定义字体,则需要保证这个字体在系统中已安装,并使用类似如下代码:
$pdf->AddFont('YourCustomFont','','yourcustomfont.php'); //包含字体文件.php $pdf->SetFont('YourCustomFont','',14);
-
使用
Helvetica
: 本身使用了Helvetica
这里不用修改
5. 模板文件的检查
检查你的两个模板文件(template_voucher_en.pdf
和 template_voucher_fr.pdf
),确保它们的内部结构没有大的区别(页数、基本设置等)。可以尝试都用最简单的、仅包含少量文本的 PDF 模板文件做测试,缩小问题范围.
进阶调试技巧
-
错误日志: 打开详细的 PHP 错误日志,检查有没有错误被忽略掉了:
在
wp-config.php
中, 添加以下行:define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); // 不要在生产环境显示错误!
错误日志文件一般在
wp-content/debug.log
-
Xdebug + IDE: 如果有条件,配置好 Xdebug 和一个支持调试的 IDE(例如 PhpStorm),单步执行
generate_pdf
函数,这会让你看到每一行代码的执行状态和变量的值,定位输出差异,快速查错。 -
缩小问题范围:
如果上述基本步骤都排查过了,试着大幅简化
generate_pdf
函数。例如,暂时去掉所有跟订单数据相关的部分,直接输出一个最简单的、只包含固定文本的 PDF, 先确保 PDF 生成的基础流程没有问题。再逐步添加回原来的代码, 看在哪一步开始出错。
代码修改示例 (综合)
function generate_pdf($order_id, $item_id)
{
ob_clean(); // 清除缓冲区
$order = wc_get_order($order_id);
$current_user = wp_get_current_user();
$language = get_locale();
if ($order->get_user_id() != $current_user->ID) {
wp_die('You are not authorized to generate vouchers for this order');
}
$item = $order->get_item($item_id);
$product_id = $item->get_product_id();
$pdf = new Fpdi();
error_log('Current language: ' . $language);
if ($language === "en_GB") {
$template_path = realpath(plugin_dir_path(__FILE__) . 'template_voucher_en.pdf');
error_log('Template Path En: ' . $template_path);
if (!file_exists($template_path)) {
error_log("template en file not found ! ");
}
$pdf->setSourceFile($template_path);
$tplIdx = $pdf->importPage(1);
if (has_term('bons-cadeaux', 'product_cat', $product_id)) {
$recipient_name = $item->get_meta('_gift_card_recipient', true);
$reference = $item->get_meta('_voucher_reference', true);
$sender_name = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
$pdf->AddPage('L');
$pdf->useTemplate($tplIdx);
$pdf->SetFont('Helvetica', '', 14); //
$pdf->SetXY(52.5, 13.5);
$pdf->Write(0, $reference);
$pdf->SetXY(93, 136.5);
$pdf->Write(0, $item->get_total() . '€');
$pdf->SetXY(118, 147);
$pdf->Write(0, $recipient_name);
$pdf->SetXY(87.5, 157.5);
$pdf->Write(0, $sender_name);
$pdf->SetXY(103, 168);
$pdf->Write(0, date('d/m/Y', strtotime('+1 year', strtotime($order->get_date_created()))));
}
} else {
$template_path = realpath(plugin_dir_path(__FILE__) . 'template_voucher_fr.pdf');
error_log('Template Path Fr: ' . $template_path);
if (!file_exists($template_path)) {
error_log("template fr file not found ! ");
}
$pdf->setSourceFile($template_path);
$tplIdx = $pdf->importPage(1);
if (has_term('bons-cadeaux', 'product_cat', $product_id)) {
$recipient_name = $item->get_meta('_gift_card_recipient', true);
$reference = $item->get_meta('_voucher_reference', true);
$sender_name = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
$pdf->AddPage('L');
$pdf->useTemplate($tplIdx);
$pdf->SetFont('Helvetica', '', 14); //
$pdf->SetXY(53, 13.5);
$pdf->Write(0, $reference);
$pdf->SetXY(94.5, 136.5);
$pdf->Write(0, $item->get_total() . '€');
$pdf->SetXY(109, 147);
$pdf->Write(0, $recipient_name);
$pdf->SetXY(104, 157.5);
$pdf->Write(0, $sender_name);
$pdf->SetXY(109.5, 168);
$pdf->Write(0, date('d/m/Y', strtotime('+1 year', strtotime($order->get_date_created()))));
}
}
return $pdf->Output("voucher_".$reference.".pdf", "D");
ob_end_flush();
}
通过以上修改, 在测试下载。
记住,每修改一处,都做一次测试. 细致的排查,问题总可以找到的。