XIUNOX 更新日志 2026-08-06 [复制链接]

管理员组

死模板清理

  • [核心] 删除 4 个无任何加载入口的死模板:view/htm/my.htm、my.template.htm、my_password.htm、my.common.template.htm(grep 比对确认路由 _include()<template include> 均无引用,个人中心相关页面实际使用 my_security.htm / my.layout.inc.htm / my_profile.htm 等)

xnx_oauth(OAuth 登录后强制补全邮箱+密码)

  • [xnx_oauth] OAuthService 新增 isOAuthUser()(uid 是否有任一第三方绑定记录)与 needsProfileCompletion()(缺密码/缺邮箱判断)两个静态方法,仅 OAuth 用户触发补全,普通注册用户不受影响

  • [xnx_oauth] route/oauth.php callback 成功分支:登录/绑定/合并成功后检查补全状态,缺密码或邮箱时设置 session 标记并跳转 oauth-perfect 补全页

  • [xnx_oauth] 新增 route/oauth.php case ‘perfect’:渲染补全页(GET)/ 保存密码+邮箱(POST,Csrf 校验)/ 子路由 oauth-perfect-sendcode 发送邮箱验证码(域名白名单 + 唯一性 + 频率限制 + email_change_code 邮件模板)

  • [xnx_oauth] 新增 hook/index_inc_route_before.php 全局强制拦截:已登录且 OAuth 账号缺密码/邮箱时,除 oauth 全部子路由、user 登录/注册/退出、captcha/lang/attach/api 外统一 302 跳转补全页,防止无密码账号被会话劫持后直接改绑邮箱、再走"忘记密码"接管账号

  • [xnx_oauth] 新增 view/htm/oauth_perfect.htm 补全页:按需渲染密码/邮箱字段(条件式),邮箱需通过验证码验证;表单防重 xnx_form_guard + 发送验证码 60s 倒计时;页面底部保留退出登录入口

  • [xnx_oauth] 三语语言包新增 oauth_perfect_title/desc/submit/success/email_exists 5 个键;conf.json 版本 1.1.0 → 1.1.1

个人资料页修改(route/my.php profile 分支)

  • [核心] 未做任何修改提交时不再提示"更新成功",改为 message(1) 返回红色错误提示(HTMX 模式渲染 alert 片段)

  • [核心] GET 分支计算 $need_profile_audit 并传入模板,供前端确认弹窗区分"需审核/直接生效"

  • [核心] my_profile.htm 次数用尽(limit>0 且 remaining<=0)时禁用对应输入框并显示红色提示;昵称与签名均不可编辑时禁用提交按钮(limit=0 表示不限次数,不启用限制)

  • [核心] my_profile.htm 新增提交前确认:htmx:confirm 拦截,未修改任何内容时阻止提交并 toast 提示;有修改时弹出 XN.confirm 展示变更字段列表 + 修改次数消耗/审核提示(_htmxConfirmAsync 异步模式,异常时放行请求兜底)

  • [核心] 三语言包补齐 zh-tw/en-us 缺失的 nickname_remaining_tip/signature_remaining_tip,并新增 8 个键(nickname_exhausted、signature_exhausted、profile_no_change、profile_confirm_title/message/audit/consumed_nickname/consumed_signature)

  • [安全] 上传 MIME 严格校验(security_upload_strict_mime)默认改为关闭(0),避免未安装 fileinfo 扩展时前台只提示无法上传,用户误以为程序故障

敏感词库删除/清空 CSRF 修复

  • [核心] admin/view/htm/security_words.htm:ajaxPost 请求头补充 X-CSRF-Token(token 从 $header[csrf_token] 注入 JS),修复点 X 删除、清空全部被 admin/index.inc.php 中央化 CSRF 校验拦截报 “CSRF token verification failed” 的问题

审核模式次数消耗与页面刷新修复(审计问题 B / D)

  • [核心] route/my.php 审核分支:昵称/签名提交审核时同步写 nickname_change_log / signature_change_log,每次提交消耗 1 次修改次数(审核被拒同样占用),防止无限刷审核队列;与直接更新分支逻辑一致

  • [核心] my_profile.htm 确认弹窗:审核模式下同时显示次数消耗提示 + 审核提示(原逻辑审核模式不显示消耗提示,与 B 修复后的实际行为不符)

  • [核心] my_profile.htm 新增 htmxSuccess 监听(仅审核模式):提交成功进入审核队列后延迟 1.5s 刷新页面,展示待审禁用态(message(0) 的 htmx 响应为 HX-Trigger 头,无 JSON,原 JSON.parse 成功分支不可达)

  • [核心] lib/security/SensitiveWordFilter.php:delete_word/add_word/batch_import/clear_words 检查 file_put_contents 返回值,写入失败时记录 xn_log 并返回 false/0(此前静默返回成功,config 目录不可写时“提示成功但未生效”),审计加固

  • [核心] 修复语言包占位符被 JS 拼接绕过:security_words.htm 删除敏感词确认文案改为 .replace(“{word}”, word)(原拼接导致提示显示"确定删除敏感词「{word}」?文革?");同步修复 security_blacklist.htm 三处同类问题(ip_blacklist/ip_whitelist 的 {ip}、email 的 {domain}),三语言包无需改动

  • [抽奖] 侧边栏抽奖每日次数耗尽时,右侧栏抽奖按钮与弹窗"再抽一次"按钮置灰禁用;新增"今日剩余 N 次"显示与 JS 状态刷新

  • [抽奖] 修复侧边栏中奖通知奖品显示为"神秘奖品":sendWinNotify 改传归一化后的 prize 结构

  • [核心] 修复"昵称修改被签名次数用完拦截":route/my.php 与 api/v1/my.php 的签名变更判定改为净化归一化比较(旧值同过一次 xn_signature_purify),净化规范化差异(实体编码/属性重排/空白压缩/历史非法标签剥离)不再把"未实际修改的签名"误判为变更,未修改签名不再消耗 signature_change_log 次数、不触发限流;同时清理 tmp/route_my.php 编译缓存

帖子 2 级评论 HTML 源码不解析修复

  • [核心] model/post.func.php:post_message_fmt() 的 doctype==1 分支新增结构化 HTML 容错检测(post_message_looks_html:成对闭合常用标签或
    视为富文本),命中则改走 xn_html_purify 解析,避免快速回复/手机 postbar 等纯文本入口存入的 HTML 内容被 xn_txt_to_html 转义后页面显示

    源码

  • [核心] view/htm/thread_js.inc.htm:快速回复表单 htmx:config:request 拦截,message 含结构化 HTML 时把 doctype 改为 0;手机端 postbar fetch 提交同步动态判断 doctype(postMessageLooksHtml 与后端规则一致)

  • [核心] 个人资料页昵称/签名拆分为两个独立表单(my_profile.htm):各自保存按钮/确认弹窗/loading/结果处理(bindProfileForm 通用绑定),后端 route/my.php 与 api/v1/my.php 按字段存在性(array_key_exists)独立校验与消耗次数,签名次数用完只拦截签名提交、昵称修改不受影响;剩余次数文案改为"剩余 {remaining} 次"(三语言包),新增 save_nickname/save_signature 键;my.layout.inc.htm 签名展示补 user-signature-display class(修复保存后左侧卡片签名不刷新);清理 route_my/view_htm_my_profile/view_htm_my.layout/lang_*_bbs 编译缓存

xnx_api_tester 设置页内嵌测试工具

  • [xnx_api_tester] 新增标准插件设置页(setting.php)内嵌 API 测试工具,入口统一为插件列表「设置」按钮;删除独立后台路由(admin.php + hook/admin_index_route_case_end.php),conf.json hooks_rank 移除对应 hook;conf.json version 1.0.0 → 1.0.1

  • [核心] 个人资料页保存成功后即时更新剩余次数(remaining-tip .replace 占位符)与禁用态:次数用完即时禁用输入区+按钮并切换 exhausted 文案,按钮恢复逻辑加"已耗尽保持禁用"守卫,成功同步 initialValue 避免重复点击重复消耗次数;清理 tmp/view_htm_my_profile.htm

xnx_api_tester 审计问题修复

  • [xnx_api_tester] 清空 conf.json 中 3 个无效 hooks_rank 死配置(lang_*_bbs_admin.php 指向的 hook 文件不存在,语言包由 admin/index.php 统一兜底加载无需 hook)

  • [xnx_api_tester] install.php return; 补 ponytail 注释说明语义安全(install 非 hook,return 仅终止 include 返回宿主)

  • [xnx_api_tester] api_tester.htm 第309/313行 XN.toast 统一加空值守卫,与同文件复制按钮风格一致

  • [xnx_api_tester] conf.json version 1.0.1 → 1.0.2

帖子详情页复制链接优化

  • [核心] 帖子详情页右侧扫码区复制按钮与主标题新增「[复制链接]」按钮,复制内容由原纯网址改为「标题 + 换行 + 网址 + 换行 +(出处:网站名称 版块名称)」三行格式(出处行全角括号冒号、含版块名),两个按钮共用 copyThreadShare 逻辑,复制成功复用 XN.toast 提示

  • [核心] thread_main.inc.htm 标题 h1 末尾新增 [复制链接] 辅助按钮(text-body-secondary 浅色、12px、vertical-align 对齐)

  • [核心] 三语言包新增 copy_link 键(zh-cn 复制链接 / zh-tw 複製連結 / en-us Copy Link);清理 tmp/view_htm_thread*、tmp/lang_*_bbs 编译缓存

xnx_lottery 插件审计修复

  • [抽奖] 修复发帖抽奖 lottery_data 永不上传(根因):post_special_topic_tab_pane.htm 隐藏字段带 disabled 属性,htmx 序列化跳过 disabled 控件(getInputValues 的 :disabled 过滤),后端恒收不到抽奖配置导致活动不创建;删除 disabled 属性

  • [抽奖] 修复回帖后抽奖卡片不刷新:lottery.js htmx:after:swap 用 evt.target(发起请求的快速回复表单)判断 #postlist 恒不匹配,改 evt.detail.target

  • [抽奖] 清理 37 处 [bug-lottery] 调试残留:thread_create_thread_end.php 26 处 xn_log(日志文件名不含 error,生产环境也写入)+ lottery_post.js 11 处 console.log

  • [抽奖] thread_create_thread_end.php 后端范围校验(中奖人数/积分范围/积分类型白名单)+ 抽奖创建失败时退还已扣发起费用(CreditsService add lottery_create_refund)

  • [抽奖] lottery_post.js:htmx:config:request 改直接写 evt.detail.parameters(原改 DOM input 值在序列化后无效);编辑页未改倒计时沿用原开奖时间(data-original-draw-* 属性),修复重算导致 draw_time 漂移;上传/扣费提示文案改 LOTTERY_LANG 语言包

  • [抽奖] LotteryService:isMedalPluginEnabled 改 plugin_paths_enabled()(前台不用 global $plugins);getRecentWinners 缓存走 CacheHelper::remember + registerKeys(p_xnx_lottery_ 前缀 + __v 哨兵),grantPrizes/grantSidebarPrize/regrantPrize 中奖写入后 pluginDeletePrefix 清中奖榜缓存;两处 // hook xnx_lottery_grant_end.php 注释改 // hook: 格式(避免编译器误匹配重复拼接)

  • [抽奖] lottery_card.htm 中奖名单两处原生 头像改 avatar_component_from_data(含 gid/group 信息);setting.htm 图标预览 innerHTML 改 DOM API + ti- 白名单正则校验;危险操作确认按钮 okText 硬编码"删除"改 lang(delete)

  • [抽奖] lottery.js refreshPrizeStock 删除无对应 DOM 的死代码分支(.fw-bold.text-primary 模板不存在),库存 0 与次数耗尽统一禁用按钮

  • [抽奖] 三语语言包(hook/lang__bbs.php + lang/.php)新增 confirm_deduct_title/ok、upload_uploading/success/fail 5 个键

  • [抽奖] conf.json version 1.3.5 → 1.3.6;清 tmp 编译缓存;php -l 与 node --check 全部通过

xnx_quest 任务系统联动签到/邀请/勋章插件

  • [xnx_quest] 新增 3 个跨插件事件监听 hook,复用现有 onUserEvent 计数达标发奖机制,零侵入依赖插件:

    • hook/xnx_checkin_do_checkin_end.php:签到成功事件(事件 checkin),监听 xnx_checkin 签到完成,$uid 可用

    • hook/xnx_invite_reward_end.php:邀请奖励成功事件(事件 invite),监听 xnx_invite 奖励入账,奖励邀请人 $inviterUid

    • hook/xnx_medal_grant_end.php:勋章授予事件(事件 medal_grant),监听 xnx_medal 新勋章授予,任意勋章均计数

  • [xnx_quest] conf.json hooks_rank 注册 3 个新 hook;version 1.0.2 → 1.1.0

  • [xnx_quest] 三语言包新增 3 个事件标签(checkin/invite/medal_grant),后台任务编辑下拉框与前台任务卡片"去完成"跳转同步适配(签到→xnx_checkin、邀请→xnx_invite-center、勋章→medals)

  • 依赖插件未启用时 hook 不编译加载,对应任务永远不会达标但不报错(静默忽略策略)

xnx_appcenter 下载前提示失效修复

  • [xnx_appcenter] 修复详情页下载/购买/外站跳转三场景确认提示全部失效:v1.14.12 的拦截脚本在模板内联输出时立即执行,而 bootstrap.bundle.min.js 在 footer.inc.htm 才加载,typeof bootstrap === "undefined" 守卫直接 return 导致三个 Modal(免费下载免责/付费购买 3 秒倒计时/外站责任切割)从未弹出。改为轮询等待 bootstrap 就绪后再绑定;同时修复付费购买拦截机制:htmx 4 的 submit 处理器不检查 evt.defaultPrevented,原 submit 事件 preventDefault 拦不住 htmx 请求,改挂 htmx:config:request 上 preventDefault 才能取消请求;外站跳转选择器从全站 a[target="_blank"] 收窄到 #appcenter-buy-result 容器,避免误伤页脚等无关外链;删除 appcenter_detail.js 旧 XN.confirm 购买拦截避免双弹窗。conf.json 版本 1.15.2 → 1.15.3,static_version 1.4.113 → 1.4.114,清理 tmp/plugin_xnx_appcenter_view_htm_appcenter_detail.htm 编译缓存

  • [xnx_appcenter] 修复付费购买确认仍不弹出(v1.15.3 修复不完整):htmx 4 的 htmx:config:request 事件 detail 结构为 {ctx: requestConfig},触发元素字段是 detail.ctx.sourceElement 而非 detail.elt,原判断 !detail.elt 恒真导致拦截逻辑直接 return 从未执行;同时购买失败后 htmx 重新渲染 #appcenter-buy-result 产生新表单,闭包捕获的旧 buyForm 引用失效,改为事件触发时动态 getElementById 取最新表单。conf.json 版本 1.15.3 → 1.15.4,清理 tmp 编译缓存

日志报错修复(13:20:15 / 18:02:47 / 22:02:38)

  • (xnx_red/xnx_hidden/xnx_feeds/xnx_site_images/xnx_avatar_frame/xnx_theme_moments/xnx_landing),均正确闭合,无同类问题;conf.json version 1.1.0 → 1.1.1

  • [xnx_oauth] 修复 Call to undefined method OAuthService::isOAuthUser() 致插件被自动禁用:hook/index_inc_route_before.php 原仅有 class_exists('OAuthService', false) 守卫,第二参数 false 表示不自动加载,当 OPcache 缓存了旧版 OAuthService.php(无 isOAuthUser 方法,1.1.1 后才加入)时,class_exists 返回 true 但方法不存在,触发 fatal。补 method_exists('OAuthService', 'isOAuthUser') 二次守卫;conf.json version 1.1.1 → 1.1.2

  • 清理 tmp/view_htm_header.inc.htm、tmp/index.inc.php、tmp/route_.php、tmp/model_.func.php、tmp/view_htm_.htm、tmp/lang__bbs.php 编译缓存触发重新编译;php -l 全部通过

  • 提示:xnx_oauth 已被 autoDisableCrashedPlugin 自动禁用,修复后需后台手动启用;若 PHP-FPM 启用 OPcache 且 validate_timestamps=0,启用后仍可能命中旧版字节码,需重启 PHP-FPM 或等 OPcache 自动刷新

最新回复

请先登录后再回复 登录