使用TGM Plugin Activation带Token验证的自定义插件下载失败求助
自定义WordPress主题TGM插件激活Token验证下载问题排查解决
问题场景
我在自定义WordPress主题中集成了TGM Plugin Activation库,配置了自有服务器上的自定义必需插件,通过source URL传递Token做下载前验证。
TGM插件配置代码
[ 'name' => esc_html__( 'My Theme Toolkit', 'text-domain' ), 'slug' => 'my-theme-toolkit', 'source' => 'https://example.com/tooklkit-plugins/?token=here_is_token', 'required' => true, 'version' => '1.0.0', ]
服务器端Token验证与文件输出代码
$expected_token = 'here_is_token'; // Check if the token is valid. if ( isset( $_GET['token'] ) && $_GET['token'] === $expected_token ) { // Token is valid, allow user to download plugin. $plugin_url = 'https://example.com/plugins/my-theme-toolkit.zip'; header( 'Content-Type: application/octet-stream' ); header( "Content-Transfer-Encoding: Binary" ); header( "Content-disposition: attachment; filename=\"my-theme-toolkit.zip\"" ); readfile( $plugin_url ); } else { echo 'Invalid or missing token. Please contact support for assistance.'; }
执行后返回错误:
The package could not be installed. PCLZIP_ERR_BAD_FORMAT (-10): Unable to find End of Central Dir Record signature
问题原因
- 远程URL读取的额外输出:使用
readfile()读取远程HTTPS URL时,会引入HTTP响应头或服务器额外输出,导致返回内容不是纯ZIP文件,破坏了ZIP的结构。 - 输出缓冲未处理:脚本可能存在未清理的输出(比如PHP文件首尾的空格、BOM头),混入ZIP内容中导致PCLZIP无法识别。
- 文件类型与长度缺失:
application/octet-stream类型不够明确,且未添加Content-Length头,可能导致文件传输不完整。
修复方案
方案1:使用本地文件路径(推荐)
如果插件ZIP在当前服务器本地,直接用本地绝对路径读取,避免远程请求的问题:
$expected_token = 'here_is_token'; // 清空所有输出缓冲 ob_start(); ob_clean(); if ( isset( $_GET['token'] ) && $_GET['token'] === $expected_token ) { // 替换为服务器上插件ZIP的本地绝对路径 $plugin_path = '/home/your-user/public_html/plugins/my-theme-toolkit.zip'; if (!file_exists($plugin_path)) { http_response_code(404); echo 'Plugin file not found.'; exit; } // 设置明确的ZIP类型头 header( 'Content-Type: application/zip' ); header( "Content-Transfer-Encoding: Binary" ); header( "Content-disposition: attachment; filename=\"" . basename($plugin_path) . "\"" ); // 添加文件长度,确保完整传输 header( 'Content-Length: ' . filesize($plugin_path) ); readfile( $plugin_path ); // 终止脚本,防止后续输出 exit; } else { http_response_code(403); echo 'Invalid or missing token. Please contact support for assistance.'; } ob_end_flush();
方案2:用cURL读取远程文件(若必须远程获取)
如果插件文件只能从远程服务器获取,使用cURL替代readfile(),确保只获取纯ZIP内容:
$expected_token = 'here_is_token'; ob_start(); ob_clean(); if ( isset( $_GET['token'] ) && $_GET['token'] === $expected_token ) { $plugin_url = 'https://example.com/plugins/my-theme-toolkit.zip'; $ch = curl_init($plugin_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 生产环境建议开启SSL验证,仅当证书问题时临时关闭 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $zip_content = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200 || empty($zip_content)) { http_response_code(404); echo 'Failed to retrieve plugin file.'; exit; } header( 'Content-Type: application/zip' ); header( "Content-Transfer-Encoding: Binary" ); header( "Content-disposition: attachment; filename=\"my-theme-toolkit.zip\"" ); header( 'Content-Length: ' . strlen($zip_content) ); echo $zip_content; exit; } else { http_response_code(403); echo 'Invalid or missing token. Please contact support for assistance.'; } ob_end_flush();
额外注意事项
- 确保服务器端脚本无任何额外输出(比如PHP文件首尾的空格、多余echo语句),这些都会破坏ZIP结构。
- 优先使用
application/zip作为Content-Type,帮助TGM正确识别文件类型。 - 必须添加
Content-Length头,保证客户端完整接收文件。 - 脚本末尾一定要用
exit或die终止执行,防止后续代码输出混入ZIP内容。
内容的提问来源于stack exchange,提问作者Emon Ahmed
相关产品推荐
相关产品推荐

