
本文详解如何通过自定义 action 控制器或 action 验证钩子(hook)来安全、可靠地覆盖 elgg 默认的用户头像上传逻辑,避免因钩子注册错误导致拦截失效。
本文详解如何通过自定义 action 控制器或 action 验证钩子(hook)来安全、可靠地覆盖 elgg 默认的用户头像上传逻辑,避免因钩子注册错误导致拦截失效。
在 Elgg 框架中,用户头像上传由内置的 avatar/upload Action 处理(对应路由 /action/avatar/upload),其核心逻辑封装在 \Elgg\Action\Avatar\Upload 类中。许多开发者尝试通过 entity:avatar:prepare 或 entity:icon:prepare 等钩子拦截上传流程,但这类钩子属于实体图标准备阶段(通常用于生成缩略图或设置元数据),并不参与请求验证与文件处理主流程,因此无法阻止上传动作本身执行——这正是提问中钩子“未被调用”或“无效”的根本原因。
✅ 正确覆盖方式有两种,推荐按优先级使用:
1. 完全替换 Action:注册自定义控制器(最彻底)
在插件的 elgg-plugin.php 中,通过 'actions' 键显式重映射 avatar/upload 到你自己的控制器类:
// my_plugin/elgg-plugin.php
return [
'plugin' => [
'name' => 'My Plugin',
'activate_on_install' => true,
],
'actions' => [
'avatar/upload' => [
'controller' => \MyPlugin\Actions\AvatarUpload::class,
],
],
];然后实现你的控制器(需继承 \Elgg\Action\Response 或直接返回响应):
// my_plugin/src/Actions/AvatarUpload.php
<?php
namespace MyPlugin\Actions;
use Elgg\Action\Response;
use Elgg\Http\Request;
use Elgg\Values;
use Elgg\Exceptions\Http\EntityPermissionsException;
use Elgg\Exceptions\Http\BadRequestException;
class AvatarUpload extends Response {
public function __invoke(Request $request): \Elgg\Http\Response {
$user = elgg_get_logged_in_user_entity();
if (!$user) {
throw new BadRequestException(elgg_echo('error:missing_data'));
}
// ✅ 自定义业务逻辑:例如权限校验、格式限制、水印添加等
$file = $request->files->get('avatar');
if (empty($file['tmp_name'])) {
throw new BadRequestException(elgg_echo('avatar:upload:fail:no_file'));
}
// 示例:禁止上传大于 2MB 的文件
if ($file['size'] > 2 * 1024 * 1024) {
throw new BadRequestException(elgg_echo('avatar:upload:fail:too_large'));
}
// ✅ 调用原生逻辑(可选)或完全自定义存储逻辑
// $result = elgg_save_user_icon_from_uploaded_file($user->guid);
// 返回成功响应(兼容前端)
return elgg_ok_response([
'icon_urls' => $user->getIconURLs(), // 返回各尺寸 URL
], elgg_echo('avatar:upload:success'));
}
}⚠️ 注意事项:
- 命名空间必须为插件专属(如 \MyPlugin\),避免与 Elgg 核心 \Elgg\ 冲突;
- 控制器类需可被自动加载(建议遵循 PSR-4,如 src/ 目录映射到 \MyPlugin\);
- 若需复用 Elgg 原有逻辑,可调用 elgg_save_user_icon_from_uploaded_file(),但务必先完成所有前置校验。
2. 预验证拦截:使用 action:validate 钩子(轻量级方案)
若仅需做权限或参数校验,无需重写整个流程,推荐使用 action:validate 钩子:
// my_plugin/elgg-plugin.php
return [
// ... 其他配置
'hooks' => [
'validate' => [
'action' => [
\MyPlugin\Hooks\ValidateAvatarUpload::class => [],
],
],
],
];// my_plugin/src/Hooks/ValidateAvatarUpload.php
<?php
namespace MyPlugin\Hooks;
use Elgg\Hook;
class ValidateAvatarUpload {
public function __invoke(Hook $hook) {
$action = $hook->getParam('action');
if ($action !== 'avatar/upload') {
return;
}
$user = elgg_get_logged_in_user_entity();
if (!$user || !$user->canEdit()) {
return elgg_error_response(elgg_echo('no_permission'));
}
// 拦截非法 MIME 类型
$file = $_FILES['avatar'] ?? null;
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
if ($file && !in_array($file['type'], $allowed_types, true)) {
return elgg_error_response(elgg_echo('avatar:upload:fail:bad_type'));
}
}
}✅ 优势:无需修改路由,不影响原有 Action 结构;
❗ 限制:仅适用于验证阶段,不能修改上传后处理逻辑(如缩略图生成、CDN 同步等)。
总结
- ❌ entity:avatar:prepare 钩子不适用于阻止上传,它发生在上传之后的图标处理环节;
- ✅ 优先使用 'actions' => ['avatar/upload' => [...]] 替换控制器,掌控全流程;
- ✅ 次选 action:validate 钩子,适合快速增加前置校验;
- ? 所有自定义类应使用插件专属命名空间(如 \MyPlugin\),避免与 Elgg 核心冲突;
- ? 开发时启用 Elgg 调试模式(elgg_set_config('debug', true)),并检查 System Log 中的 hook 触发记录,快速定位注册失败问题。
通过以上任一方式,即可稳定、可维护地覆盖 Elgg 用户头像上传行为。










