Discuz!官方免费开源建站系统

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索

Discuz! X系列多个版本用户组体系模块逻辑不一致,存在多种异常情况

[复制链接]
reada 发表于 2014-10-24 17:04:06 | 显示全部楼层 |阅读模式
Discuz! X系列多个版本用户组体系模块逻辑不一致,存在多种异常情况。根据问题表现,阅读调试相关代码后,个人推测相关模块的设计思路,在扩展用户组过期后,需要用户自行选择续费或退出。
这种处理方法有别于以往的Discuz!系列,相比之下,从网站运营和用户体验上更为人性化,充分尊重用户的知情权,不再悄无声息对用户过期的扩展用户组去除而不提醒用户。

程序版本:Discuz! X3.2 Release 20140618
案例简介:主用户组“新人报到”,购买公共用户组“网站VIP”,某版块赋予“网站VIP”相关权限,购买的用户组到期后,不续期,不退出,权限同样存在。
影响范围:已开放购买公共用户组的网站
原因分析:discuz_application.php 初始化用户、购买公共用户组、切换主用户组、网站后台编辑用户组、版块权限判断之间存在逻辑不一致的问题。

请看 source/class/discuz/discuz_application.php 的第466-479行代码片段
  1. if($user && $user['groupexpiry'] > 0 && $user['groupexpiry'] < TIMESTAMP) {
  2.         $memberfieldforum = C::t('common_member_field_forum')->fetch($discuz_uid);
  3.         $groupterms = dunserialize($memberfieldforum['groupterms']);
  4.         if(!empty($groupterms['main'])) {
  5.                 C::t("common_member")->update($user['uid'], array('groupexpiry'=> 0, 'groupid' => $groupterms['main']['groupid'], 'adminid' => $groupterms['main']['adminid']));
  6.                 $user['groupid'] = $groupterms['main']['groupid'];
  7.                 $user['adminid'] = $groupterms['main']['adminid'];
  8.                 unset($groupterms['main'], $groupterms['ext'][$this->var['member']['groupid']]);
  9.                 $this->var['member'] = $user;
  10.                 C::t('common_member_field_forum')->update($discuz_uid, array('groupterms' => serialize($groupterms)));
  11.         } elseif((getgpc('mod') != 'spacecp' || CURSCRIPT != 'home') && CURSCRIPT != 'member') {
  12.                 dheader('location: home.php?mod=spacecp&ac=usergroup&do=expiry');
  13.         }
  14. }
复制代码


初始化用户符合判断条件
用户是否存在
并且用户组设置有过期时间
并且用户组有过期时间小于当前时间
后,将处理扩展用户组。

问题一,这个判断条件可以跳过,不用进入此流程。方法如下:


A、网站现有用户->购买公共用户组,此时程序处理写入主用户表的过期时间。

请看 source/include/spacecp/spacecp_usergroup.php 的第95-97行代码片段
  1. $groupexpirynew = groupexpiry($groupterms);


  2. C::t('common_member')->update($_G['uid'], array('groupexpiry' => $groupexpirynew, 'extgroupids' => $extgroupidsnew));
复制代码



此处用户组过期时间调用方法 groupexpiry ,该方法通过 source/include/spacecp/spacecp_usergroup.php 的第75行代码片段
  1. require_once libfile('function/forum');
复制代码

加载调用,在 source/function/function_forum.php 的第274-285行代码片段
  1. function groupexpiry($terms) {
  2.         $terms = is_array($terms) ? $terms : dunserialize($terms);
  3.         $groupexpiry = isset($terms['main']['time']) ? intval($terms['main']['time']) : 0;
  4.         if(is_array($terms['ext'])) {
  5.                 foreach($terms['ext'] as $expiry) {
  6.                         if((!$groupexpiry && $expiry) || $expiry < $groupexpiry) {
  7.                                 $groupexpiry = $expiry;
  8.                         }
  9.                 }
  10.         }
  11.         return $groupexpiry;
  12. }
复制代码


用于获取主用户组和扩展用户组中最小过期时间,最终目的用作 source/class/discuz/discuz_application.php 初始化用户时的判断条件。


B、拥有扩展用户组后,可以把扩展用户组切换为主用户组,而问题就在切换用户组过程,

请看 source/include/spacecp/spacecp_usergroup.php 的第151行代码片段
  1. $groupexpirynew = $groupterms['ext'][$groupid];
复制代码

当顺利切换后,用户组过期时间更换成对应的扩展用户组过期时间,
仅一个主用户组和一个扩展用户组且第(奇数)次切换的情况下,结果是预期正确结果。
此外,将产生一个非设计预期结果,有可能是其他扩展用户组的过期时间,有可能是原来的主用户组的过期时间(空或不存在),从而改变原来需要的最小时间。

至此,当扩展用户组过期后,初始化用户时的判断条件存在不成立:
用户组过期时间不存在(0)
用户组过期时间存在,但大于当前时间

最终结果用户购买的扩展用户组已过期,但没有强制用户处理,扩展用户组权限继续生效。
两个临时修正此问题方法:
1、请将 source/include/spacecp/spacecp_usergroup.php 的第151行代码片段
  1. $groupexpirynew = $groupterms['ext'][$groupid];
复制代码

删除,第165行代码片段
  1. C::t('common_member')->update($_G['uid'], array('groupid' => $groupid, 'adminid' => $newadminid, 'groupexpiry' => $groupexpirynew, 'extgroupids' => $extgroupidsnew));
复制代码

替换为
  1. C::t('common_member')->update($_G['uid'], array('groupid' => $groupid, 'adminid' => $newadminid, 'extgroupids' => $extgroupidsnew));
复制代码

目的取消更新用户组过期时间。
2、请将 source/include/spacecp/spacecp_usergroup.php 的第151行代码片段
  1. $groupexpirynew = $groupterms['ext'][$groupid];
复制代码

替换为
  1. require_once libfile('function/forum');
  2. $groupexpirynew = groupexpiry($groupterms);
复制代码

目的使用户组过期时间获得预期正确结果。(感觉是过定义,双保险)
当然,如果不想修改官方程序,还可以通过插件系统的showmessage钩子二次处理,好处就是更新或升级新版本变得更容易,这里就细说了。
其他Discuz! X各系列版本自行修改测试。

问题二、网站后台编辑扩展用户组过期的用户时,无修改直接提交只会去除过期时间,还保留扩展用户组。
这个问题是上一问题的延伸,请看 source/admincp/admincp_members.php 的第1160-1166行
  1. if(is_array($_GET['extgroupexpirynew'])) {
  2.         foreach($_GET['extgroupexpirynew'] as $extgroupid => $expiry) {
  3.                 if(is_array($_GET['extgroupidsnew']) && in_array($extgroupid, $_GET['extgroupidsnew']) && !isset($groupterms['ext'][$extgroupid]) && $expiry && ($expiry = strtotime($expiry)) > TIMESTAMP) {
  4.                         $groupterms['ext'][$extgroupid] = $expiry;
  5.                 }
  6.         }
  7. }
复制代码

目的是把扩展用户组过期时间存在且大于当前时间的数据保存。
个人猜测,此处的对扩展用户组存在过期时间且小于当前时间的情况因考虑不全面被疏忽。
可以将上述代码替换成
  1. if(is_array($_GET['extgroupexpirynew'])) {
  2.         foreach($_GET['extgroupexpirynew'] as $extgroupid => $expiry) {
  3.                 if(is_array($_GET['extgroupidsnew']) && in_array($extgroupid, $_GET['extgroupidsnew']) && !isset($groupterms['ext'][$extgroupid]) && $expiry) {
  4.                         if (($expiry = strtotime($expiry)) > TIMESTAMP) {
  5.                           $groupterms['ext'][$extgroupid] = $expiry;
  6.                         } else {
  7.                           $_GET['extgroupidsnew'] = array_flip($_GET['extgroupidsnew']);
  8.                           unset($_GET['extgroupidsnew'][$extgroupid]);
  9.                           $_GET['extgroupidsnew'] = array_keys($_GET['extgroupidsnew']);
  10.                         }
  11.                 }
  12.         }
  13. }
复制代码

完善处理条件,将编辑时扩展用户组已过期,或新添加扩展用户组且过期时间错误的数据去除,以达到预期结果。
当然,如果不想修改也没关系,只是得习惯在编辑用户时,留意扩展用户组是否已过期,从而去掉勾选来达到预期结果。


问题三,当主用户组存在过期时间,且迟于扩展用户组过期时间,会提前使得主用户组失效。
这是特例,同样也是第一个问题的延伸。请看 source/class/discuz/discuz_application.php 的第466-479行代码片段
  1. if($user && $user['groupexpiry'] > 0 && $user['groupexpiry'] < TIMESTAMP) {
  2.         $memberfieldforum = C::t('common_member_field_forum')->fetch($discuz_uid);
  3.         $groupterms = dunserialize($memberfieldforum['groupterms']);
  4.         if(!empty($groupterms['main'])) {
  5.                 C::t("common_member")->update($user['uid'], array('groupexpiry'=> 0, 'groupid' => $groupterms['main']['groupid'], 'adminid' => $groupterms['main']['adminid']));
  6.                 $user['groupid'] = $groupterms['main']['groupid'];
  7.                 $user['adminid'] = $groupterms['main']['adminid'];
  8.                 unset($groupterms['main'], $groupterms['ext'][$this->var['member']['groupid']]);
  9.                 $this->var['member'] = $user;
  10.                 C::t('common_member_field_forum')->update($discuz_uid, array('groupterms' => serialize($groupterms)));
  11.         } elseif((getgpc('mod') != 'spacecp' || CURSCRIPT != 'home') && CURSCRIPT != 'member') {
  12.                 dheader('location: home.php?mod=spacecp&ac=usergroup&do=expiry');
  13.         }
  14. }
复制代码

第一个错误,只判断用户附加表的 groupterms 数据是否存在,没有判断是否已经过期;
第二个错误,没有严格检验是否存在扩展用户组的过期时间,直接设置主用户表的过期时间为 0 ;
建议完善处理流程,避免出现错误,可以将上述代码片段替换为
  1. if($user && $user['groupexpiry'] > 0 && $user['groupexpiry'] < TIMESTAMP) {
  2.         $memberfieldforum = C::t('common_member_field_forum')->fetch($discuz_uid);
  3.         $groupterms = dunserialize($memberfieldforum['groupterms']);
  4.         if(!empty($groupterms['main']) && $groupterms['main']['time'] < TIMESTAMP) {
  5.                 require_once libfile('function/forum');
  6.                 $user['groupid'] = $groupterms['main']['groupid'];
  7.                 $user['adminid'] = $groupterms['main']['adminid'];
  8.                 unset($groupterms['main'], $groupterms['ext'][$this->var['member']['groupid']]);
  9.                 $user['groupexpiry'] = groupexpiry($groupterms);
  10.                 $this->var['member'] = $user;
  11.                 C::t("common_member")->update($user['uid'], array('groupexpiry'=> $user['groupexpiry'], 'groupid' => $user['groupid'], 'adminid' => $user['adminid']));
  12.                 C::t('common_member_field_forum')->update($discuz_uid, array('groupterms' => serialize($groupterms)));
  13.         } elseif((getgpc('mod') != 'spacecp' || CURSCRIPT != 'home') && CURSCRIPT != 'member') {
  14.                 dheader('location: home.php?mod=spacecp&ac=usergroup&do=expiry');
  15.         }
  16. }
复制代码




此外,还有可能出现第三个错误,没有 $groupterms['main']['groupid'] 的定义及赋值,主用户组为 0;
请看 source/admincp/admincp_members.php 的第1147-1156行
  1. if($_GET['expgroupidnew'] == $_GET['groupidnew']) {
  2.         cpmsg('members_edit_groups_illegal', '', 'error');
  3. } elseif($maingroupexpirynew > TIMESTAMP) {
  4.         if($_GET['expgroupidnew'] || $_GET['expadminidnew']) {
  5.                 $groupterms['main'] = array('time' => $maingroupexpirynew, 'adminid' => $_GET['expadminidnew'], 'groupid' => $_GET['expgroupidnew']);
  6.         } else {
  7.                 $groupterms['main'] = array('time' => $maingroupexpirynew);
  8.         }
  9.         $groupterms['ext'][$_GET['groupidnew']] = $maingroupexpirynew;
  10. }
复制代码

可能出现无用户组的赋值,而用户初始化符合条件时却直接使用 $groupterms['main']['groupid'] 而无判断及处理。
建议当主用户组设置有过期时间,必须强制设置主用户组过期后的候选用户组,替换为
  1. if($_GET['expgroupidnew'] == $_GET['groupidnew']) {
  2.         cpmsg('members_edit_groups_illegal', '', 'error');
  3. } elseif($maingroupexpirynew > TIMESTAMP) {
  4.   if(empty($_GET['expgroupidnew'])) {
  5.     cpmsg('undefined_action', '', 'error');
  6.   }
  7.         $groupterms['main'] = array('time' => $maingroupexpirynew, 'groupid' => $_GET['expgroupidnew']);
  8.         if($_GET['expadminidnew']) {
  9.                 $groupterms['main']['adminid'] = $_GET['expadminidnew'];
  10.         }
  11.         $groupterms['ext'][$_GET['groupidnew']] = $maingroupexpirynew;
  12. }
复制代码


另外还有提示信息需要修正“您当前的用户组已经到期,请选择继续续费还是要切换到其他用户组”。
购买扩展用户组得跟网站后台的用户编辑功能保持一致,购买扩展用户组数量多时,否则用户花费积分买了扩展用户组却没有写入主用户表。
请看 source/admincp/admincp_members.php 的第1098-1100行
  1. if(strlen(is_array($_GET['extgroupidsnew']) ? implode("\t", $_GET['extgroupidsnew']) : '') > 30) {
  2.         cpmsg('members_edit_groups_toomany', '', 'error');
  3. }
复制代码


说实话,DZX功能越来越强大,模块也越来越多,历史余留的问题也越来越多。
每一个大小版本的发布都是瞎忙乎的阶段,努力修改程序模板,慢慢的感觉不是办法,放弃一下非常用功能的问题修正。
同时开始转向使用插件系统来达到修正问题,调整界面风格什么的,简化版本更新带来的工作量。

最后,再重复提一个相关的历史遗留问题,其实也很多用户在反馈得不到解决。
请看 source/include/space/space_profile.php 的第38行代码片段
  1. $e_ids = explode(',', $space['extgroupids']);
复制代码

替换为
  1. $e_ids = explode("\t", $space['extgroupids']);
复制代码

目的使用户资料页面正确显示超过二个及以上扩展用户组。

 楼主| reada 发表于 2014-10-24 17:40:18 | 显示全部楼层
本帖最后由 reada 于 2014-10-24 17:54 编辑

另外补充说明一下,上面特例还有个漏洞:
主用户组有过期时间,而切换过扩展用户组为主用户组,则程序还是不完善,必须把主用户组替换前的编号记录到 $groupterms['main'] ,以便 unset 使用,因为初始化时,unset($groupterms['main'], $groupterms['ext'][$this->var['member']['groupid']]); 中的 $this->var['member']['groupid'] 有可能不是对应的原主用户组。最后得校验主用户表的 extgroupids ,去除有可能包含的已过期原主用户组,更新主用户表。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|Discuz! 官方站 ( 皖ICP备16010102号 )star

GMT+8, 2025-2-25 15:49 , Processed in 0.026106 second(s), 5 queries , Gzip On, Redis On.

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表