PrestaShop中Hook、Tpl与前端控制器间文件传递问题
PrestaShop 模块CSV文件上传功能实现方案
现有代码核心问题
当前代码有3处硬伤会直接导致文件上传失效:
- 表单未添加
enctype="multipart/form-data"属性,浏览器不会携带文件数据提交请求 - 表单未配置提交目标地址,请求不会路由到你编写的
files前端控制器 - 后端错误使用
Tools::getValue()获取上传文件,该方法仅能读取普通GET/POST参数,无法读取文件流数据
分步实现代码
1. 修正模块主文件Hook逻辑(mymodule.php)
在Hook中生成前端控制器提交地址、CSRF校验令牌传给模板,不需要提前读取文件参数,上传逻辑统一在控制器处理:
public function hookDisplayLeftColumnProduct($params) { // 生成指向模块前端控制器的提交链接 $formAction = $this->context->link->getModuleLink( 'mymodule', 'files', [], true ); // 生成CSRF校验令牌,防止跨站伪造请求 $csrfToken = Tools::getToken(false); $this->context->smarty->assign([ 'form_action' => $formAction, 'csrf_token' => $csrfToken ]); return $this->display(__FILE__, 'views/templates/hook/mymodule.tpl'); }
2. 修正上传表单模板(views/templates/hook/mymodule.tpl)
补充表单必填属性,增加文件类型限制、提交校验:
<div id="mymodule_block_home"> <!-- 必须配置enctype和action属性才能正常提交文件 --> <form method="POST" action="{$form_action}" enctype="multipart/form-data"> <!-- 隐藏域传递CSRF令牌 --> <input type="hidden" name="token" value="{$csrf_token}"> <label for="files">上传CSV文件</label> <!-- 限制文件选择器仅显示CSV格式文件 --> <input type="file" name="files" id="files" accept=".csv" required> <button type="submit" name="submit_csv" value="1">提交</button> </form> </div>
3. 修正前端控制器逻辑(controller/front/files.php)
控制器中完成请求校验、文件合法性校验、文件存储、数据库写入全流程:
class MyModuleFilesFrontController extends ModuleFrontController { public function initContent() { parent::initContent(); $uploadResult = [ 'success' => false, 'msg' => '' ]; // 仅处理POST方式的上传请求 if (Tools::isSubmit('submit_csv')) { // 1. CSRF令牌校验 $submittedToken = Tools::getValue('token'); if (Tools::getToken(false) !== $submittedToken) { $uploadResult['msg'] = '非法请求,请刷新页面重试'; $this->context->smarty->assign('result', $uploadResult); $this->setTemplate('module:mymodule/views/templates/front/files.tpl'); return; } // 2. 获取上传文件信息,必须用文件读取方法,不能用Tools::getValue $uploadedFile = Tools::fileAttachment('files'); if (!$uploadedFile || $uploadedFile['error'] != UPLOAD_ERR_OK) { $uploadResult['msg'] = '文件上传失败,请检查服务器文件大小限制后重试'; $this->context->smarty->assign('result', $uploadResult); $this->setTemplate('module:mymodule/views/templates/front/files.tpl'); return; } // 3. 校验文件格式,仅允许CSV $fileExt = strtolower(pathinfo($uploadedFile['name'], PATHINFO_EXTENSION)); if ($fileExt != 'csv') { $uploadResult['msg'] = '仅支持上传CSV格式文件'; $this->context->smarty->assign('result', $uploadResult); $this->setTemplate('module:mymodule/views/templates/front/files.tpl'); return; } // 4. 创建上传目录,移动临时文件到正式存储位置 $uploadDir = _PS_MODULE_DIR_ . 'mymodule/uploads/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } // 生成唯一文件名避免重名覆盖 $saveFileName = date('YmdHis') . '_' . uniqid() . '.csv'; $savePath = $uploadDir . $saveFileName; if (!move_uploaded_file($uploadedFile['tmp_name'], $savePath)) { $uploadResult['msg'] = '文件保存失败,请检查uploads目录读写权限'; $this->context->smarty->assign('result', $uploadResult); $this->setTemplate('module:mymodule/views/templates/front/files.tpl'); return; } // 5. 写入数据库,字段根据自身业务表结构调整 Db::getInstance()->insert( 'mymodule_csv_records', [ 'original_name' => pSQL($uploadedFile['name']), 'storage_path' => pSQL($savePath), 'file_size' => (int)$uploadedFile['size'], 'id_customer' => (int)$this->context->customer->id, 'date_add' => date('Y-m-d H:i:s') ] ); $uploadResult['success'] = true; $uploadResult['msg'] = 'CSV文件上传成功'; } $this->context->smarty->assign('result', $uploadResult); $this->setTemplate('module:mymodule/views/templates/front/files.tpl'); } }
4. 补充结果展示模板(views/templates/front/files.tpl)
简单实现上传结果反馈即可:
<div class="csv-upload-result"> {if $result.success} <p style="color: green">{$result.msg}</p> {else} <p style="color: red">{$result.msg}</p> {/if} <p><a href="{$link->getPageLink('index')}">返回首页</a></p> </div>
上线前注意事项
- 模块安装方法中提前创建对应数据库表,Db类会自动适配PrestaShop表前缀规则
- 模块下的
uploads目录需配置0755读写权限,避免文件写入失败 - 可在控制器中增加文件大小判断逻辑,匹配业务允许的上传阈值
- 正式环境建议增加特殊文件名过滤逻辑,规避文件上传安全风险
内容的提问来源于stack exchange,提问作者GKS
相关产品推荐
相关产品推荐

