Discuz! 官方站

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 41294|回复: 56

[展示] 深入分析discuz的管理后台技术.

[复制链接]
发表于 2009-6-7 21:06:16 | 显示全部楼层 |阅读模式
本帖最后由 元首 于 2009-6-7 21:07 编辑

discuz的后台拥有着非常变态的实现过程, 即没有html文件, 全部的实现都靠几个函数来完成,当然这种结构下就促使了它的后台单一化,及极其难修改. 整体来说过程是非常清晰的, 仅仅用了几个通用函数就解决了这一页页的后台管理.

   现在我们开始分析.

  admincp.php文件:
  1. <?php

  2. /*
  3.         [Discuz!] (C)2001-2009 Comsenz Inc.
  4.         This is NOT a freeware, use is subject to license terms

  5.         $Id: admincp.php 16688 2008-11-14 06:41:07Z cnteacher $
  6. */

  7. define('IN_ADMINCP', TRUE);
  8. define('NOROBOT', TRUE);
  9. require_once './include/common.inc.php';
  10. //加载后台函数
  11. require_once DISCUZ_ROOT.'./admin/global.func.php';
  12. //加载管理员类库
  13. require_once DISCUZ_ROOT.'./admin/cpanel.share.php';
  14. //加载缓存系统.
  15. require_once DISCUZ_ROOT.'./include/cache.func.php';
  16. //加载语言模块
  17. include language('admincp');

  18. //设置当前动作
  19. $discuz_action = 211;
  20. //取得当前IP
  21. $admincp['checkip'] && $onlineip = empty($_SERVER['REMOTE_ADDR']) ? getenv('REMOTE_ADDR') : $_SERVER['REMOTE_ADDR'];
  22. //开始更新管理员记录
  23. $adminsession = new AdminSession($discuz_uid, $groupid, $adminid, $onlineip);
  24. //取得管理员记录.
  25. $dactionarray = $adminsession->get('dactionarray');
  26. //判断.
  27. if($dactionarray ===  null) {
  28.         $dactionarray = array();
  29.         if($radminid != $groupid) {
  30.                 $tmp = unserialize($db->result_first("SELECT disabledactions FROM {$tablepre}adminactions WHERE admingid='$groupid'"));
  31.                 $dactionarray = $tmp ? $tmp : array();
  32.         }
  33.         $adminsession->set('dactionarray', $dactionarray, true);
  34. }
  35. //如果会员ID不是管理员,就为0, 如果是管理员, 就为1, 如果已经登录,就为2
  36. $cpaccess = $adminsession->cpaccess;
  37. //如果标识为0 即前台会员未登录,或者管理员ID不正确, 就进入登录页.
  38. if($cpaccess == 0 || (!$discuz_secques && $admincp['forcesecques'])) {
  39.         require_once DISCUZ_ROOT.'./admin/login.inc.php';
  40. } elseif($cpaccess == 1) {
  41.         //当$admin_password不为空, 就登录.
  42.         if($admin_password != '') {
  43.                 require_once DISCUZ_ROOT.'./uc_client/client.php';
  44.                 $ucresult = uc_user_login($discuz_uid, $admin_password, 1, 1, $admin_questionid, $admin_answer);  //uc的接口.
  45.                 if($ucresult[0] > 0) {
  46.                         $adminsession->errorcount = -1;
  47.                         $adminsession->update();
  48.                         dheader('Location: '.$BASESCRIPT.'?'.cpurl('url', array('sid')));  //直接用跳转.
  49.                 } else {
  50.                         $adminsession->errorcount ++;
  51.                         $adminsession->update();
  52.                         writelog('cplog', dhtmlspecialchars("$timestamp\t$discuz_userss\t$adminid\t$onlineip\t$action\tAUTHENTIFICATION(PASSWORD)"));
  53.                 }
  54.         }
  55.         //如果上而代码没有跳转, 那又回到登录页.
  56.         require_once DISCUZ_ROOT.'./admin/login.inc.php';
  57. } else {
  58.         //$cpaccess为2了.
  59.         //处理管理员用户名
  60.         $username = !empty($username) ? dhtmlspecialchars($username) : '';
  61.         //处理GET
  62.         $action = !empty($action) && is_string($action) ? trim($action) : '';
  63.         $operation = !empty($operation) && is_string($operation) ? trim($operation) : '';
  64.         $page = isset($page) ? intval((max(1, $page))) : 0;
  65.         //处理两个动作.
  66.         if(!empty($action) && !in_array($action, array('main', 'logs'))) {
  67.                 switch($cpaccess) {
  68.                         case 1:
  69.                                 $extralog = 'AUTHENTIFICATION(ERROR #'.intval($adminsession['errorcount']).')';
  70.                                 break;
  71.                         case 3:
  72.                                 $extralog = implodearray(array('GET' => $_GET, 'POST' => $_POST), array('formhash', 'submit', 'addsubmit', 'admin_password', 'sid', 'action'));
  73.                                 break;
  74.                         default:
  75.                                 $extralog = '';
  76.                 }
  77.                 $extralog = trim(str_replace(array('GET={};', 'POST={};'), '', $extralog));
  78.                 $extralog = (($action == 'home' && isset($securyservice)) || ($action == 'insenz' && in_array($operation, array('register', 'binding')))) ? '' : $extralog;
  79.                 writelog('cplog', implode("\t", clearlogstring(array($timestamp,$discuz_userss,$adminid,$onlineip,$action,$extralog))));
  80.                 unset($extralog);
  81.         }

  82.         $isfounder = $adminsession->isfounder = isfounder();
  83.         if(empty($action) || isset($frames)) {
  84.                 //管理页的首页.
  85.                 $extra = cpurl('url');
  86.                 $extra = $extra && $action ? $extra : (!empty($runwizard) ? 'action=runwizard' : 'action=home');
  87.                 require_once DISCUZ_ROOT.'./admin/main.inc.php';
  88.         } elseif($action == 'logout') {
  89.                 //退出动作.
  90.                 $adminsession ->destroy();
  91.                 dheader("Location: $indexname");
  92.         } else {
  93.                 //更新当前动作.
  94.                 checkacpaction($action, $operation);
  95.                 //这句有意思了, action来引入某个执行文件. 关键点.
  96.                 if(in_array($action, array('home', 'settings', 'members', 'profilefields', 'admingroups', 'usergroups', 'ranks', 'forums', 'threadtypes', 'threads', 'moderate', 'attach', 'smilies', 'recyclebin', 'prune', 'styles', 'plugins', 'tasks', 'magics', 'medals', 'google', 'qihoo', 'video', 'announce', 'faq', 'ec', 'tradelog', 'creditwizard', 'jswizard', 'project', 'counter', 'misc', 'adv', 'insenz', 'logs', 'tools', 'checktools', 'search', 'upgrade')) || ($isfounder && in_array($action, array('runwizard', 'templates', 'db')))) {
  97.                         require_once DISCUZ_ROOT.'./admin/'.$action.'.inc.php';  // 引入.
  98.                         $title = 'cplog_'.$action.($operation ? '_'.$operation : '');
  99.                         if(!in_array($action, array('home', 'custommenu')) && lang($title, false)) {
  100.                                 (strtolower($_SERVER['REQUEST_METHOD']) == 'get') && admincustom($title, cpurl('url'));
  101.                         }
  102.                 } else {
  103.                         cpheader();
  104.                         cpmsg('noaccess');
  105.                 }
  106.                 //结束函数.
  107.                 cpfooter();
  108.         }
  109. }

  110. ?>
复制代码
我们依后台管理的全局为例子, 来新建立一个管理页面..
  1. if(in_array($action, array('home', 'settings', 'members', 'profilefields', 'admingroups', 'usergroups', 'ranks', 'forums', 'threadtypes', 'threads', 'moderate', 'attach', 'smilies', 'recyclebin', 'prune', 'styles', 'plugins', 'tasks', 'magics', 'medals', 'google', 'qihoo', 'video', 'announce', 'faq', 'ec', 'tradelog', 'creditwizard', 'jswizard', 'project', 'counter', 'misc', 'adv', 'insenz', 'logs', 'tools', 'checktools', 'search', 'upgrade')) || ($isfounder && in_array($action, array('runwizard', 'templates', 'db')))) {
  2.                         require_once DISCUZ_ROOT.'./admin/'.$action.'.inc.php';  // 引入.
  3. //全局的标识是: settings 所以我们会引入settings.inc.php文件. 此文件在admin目录中.
复制代码
标识在哪里修改呢??  在admin目录中menu.inc.php文件中.
似乎里面写的全是代码,没有中文, 能够问这个问题, 你可以先看看templates/default/admincp.lang.php语言文件.

首先我们打开:menu.inc.php文件
查找:
  1. showmenu('global', array(
复制代码
在它的下一行增加:
  1.         array('menu_settings_function, 'settings&operation=function'),
复制代码
接着,我们需要修改一下语言文件admincp.menu.lang.php让它显示出我们想要的管理页名称.
查找:
  1. 'menu_settings_basic' => '站点信息',
复制代码
在它的上一行增加:
  1. 'menu_settings_function' => '分享工作室',
复制代码
现在刷新一下, 左边是不是显示了一个新的页链接? 分享工作室?

接着, 我们需要定义一下链接显示页的标题:打开admincp.lang.php文件
查找:
  1. 'settings_basic' => '站点信息',
复制代码
在它的上一行增加:
  1. 'settings_function'=>'新增加管理页',
复制代码
似乎现在可以点击,但内容显示为未定义操作。, 那么我们要怎么让程序来运行新的动作呢? 接着我们打开 settings.inc.php,查找:
  1. cpmsg('undefined_action');
复制代码
这个函数位于动作判断的最后一个默认输出, 即前面没有符全项目, 就进入这个函数输出.为此,我们增加一个条件判断:写在else前面..
  1. elseif ($operation === 'function'){
  2.                 echo '我是新符合条件的管理页';
  3. }
复制代码
再点击一下链接,完美了, 结果就是这样:

评分

7

查看全部评分

 楼主| 发表于 2009-6-7 21:08:04 | 显示全部楼层
接着分析页面的构造过程..

  通过上面的图片, 大家发现,文字是我们定义的, 可是我们没有定义提交那个点击哈. 为什么呢? 主要是在判断体中没有中断输出, 程序继续往下时, 会统一有一个提交标志. 我们修改一下:
  1.                 echo '我是新符合条件的管理页';
  2.                 exit();
复制代码
好了, 现在就纯洁得多了..

接着函数分析: 我们把代码修改成这样:
  1.                 showtableheader('我是新符合条件的管理页');
  2.                 exit();
复制代码
一个典型的标题产生了.html代码产生了:
  1. <table class="tb tb2 ">
  2. <tr><th colspan="15" class="partition">我是新符合条件的管理页</th></tr>
复制代码
函数解释:
  1. showtableheader(文字, 类名(加在 tb2后面的), id名, $titlespan = 15) //最后一个参数有意思, 默认跨15栏.
复制代码
至于为什么没有</table>结束, 因为过程还没有结束.

接着, 我们把代码修改成这样:
  1.                 showtips('<li>我是管理页的提示信息</li>');
  2.                 showtableheader('我是新符合条件的管理页');
  3.                 exit();
复制代码
产生html为:
  1. <table class="tb tb2 " id="tips">
  2. <tr><th  class="partition">技巧提示</th></tr>
  3. <tr><td class="tipsblock"><ul id="tipslis"><li>我是管理页的提示信息</li></ul></td></tr></table>
复制代码
技巧提示是默认增加的, 可见,表格包围ul  所以用li来书写内容是正确的.

函数解释:
  1. showtips(文字, CSS id, $display = TRUE){ //最后一个参数是设置是否隐藏.
复制代码
discuz的横切点击就是使用了改变$display方式来实现的.

接着,我们把内容改成:
  1.                 showtips('<li>我是管理页的提示信息</li>');
  2.                 showtableheader('我是新符合条件的管理页');
  3.                 showtitle('新功能项目');
  4.                 exit();
复制代码
showtitle跟showtableheader有着基本相同同的的功能, 不过showtitle生成的是一行 <tr><td>XXX</td></tr> 没有table的开头. 也许有人会问, 那这功能如何利用呢?  其实你可以设置showtableheader()为这样, 就知道showtitle的作用了, 可以设置一下功能的组别.

接着,我们把内容改成:
  1.                 showtips('<li>我是管理页的提示信息</li>');
  2.                 showtableheader('我是新符合条件的管理页');
  3.                 showtitle('新功能项目');
  4.                 $names = '于安';
  5.                 showsetting('请输入用户名', 'names',$names, 'text');
  6.                 exit();
复制代码
新函数产生了如下html:
  1. <tr class="noborder"><td class="vtop rowform">
  2. <input name="names" value="于安" type="text" class="txt"   /></td><td class="vtop tips2"></td></tr>
复制代码
函数解释:
showsetting(说明文字,input的name,默认值, 类型,$disabled = '' (radio有用), $hidden = 0(隐藏),后追加文字, css id);  
这儿会有人问, 假如我要在input后面跟些文字呢? discuz就这么干过, 如果你要快速的, 就设置后追加文字参数, 如果你的说明文字是从语言模块来: 就有规律可寻:
  1.         'settings_seo_sitemap_baidu_open' => '启用百度SiteMap:', //提示信息
  2.         'settings_seo_sitemap_baidu_open_comment' => '启用百度SiteMap会增加或者加快百度搜索对您网站的收录。', //后追加文字
复制代码
再接着我们把代码修改为:
  1.                 showtips('<li>我是管理页的提示信息</li>');
  2.                 showtableheader('我是新符合条件的管理页');
  3.                 showtitle('新功能项目');
  4.                 $names = '于安';
  5.                 showsetting('请输入用户名', 'names',$names, 'text','',0,'老板,你的名字是什么?');
  6.                 showsubmit('settingsubmit', '报名');
  7.                 exit();
复制代码
函数解释:
  1. showsubmit(name名, 按扭的文字, 提示信息, 后追加内容, 传说的css浮动)
复制代码
再解释一个函数:
  1. showtablerow($trstyle = '', $tdstyle = array(), $tdtext = array(), $return = FALSE)
  2. showtablerow(css 样式, td样式, td中的文字,是否返回值)   //简单理解就是生成一个新的tr行.
复制代码
其它函数: 基本上一目了然.

  1. function showtagfooter($tagname) {
  2.         echo '</'.$tagname.'>';
  3. }

  4. function showtablefooter() {
  5.         echo '</table>'."\n";
  6. }

  7. function showformfooter() {
  8.         echo '</form>'."\n";
  9. }
复制代码
回复

使用道具 举报

 楼主| 发表于 2009-6-7 21:08:35 | 显示全部楼层
最终的样式..
回复

使用道具 举报

发表于 2009-6-7 21:18:29 | 显示全部楼层
支持这种帖子
回复

使用道具 举报

头像被屏蔽
发表于 2009-6-7 21:22:39 | 显示全部楼层
很有技术含量,得收藏起来慢慢看
回复

使用道具 举报

发表于 2009-6-7 21:26:17 | 显示全部楼层
我完全不懂DZ不懂PHP的人,居然可以看懂,写的非常通俗易懂
回复

使用道具 举报

发表于 2009-6-7 22:01:23 | 显示全部楼层
这个帖子技术含量相当高
值得收藏
回复

使用道具 举报

发表于 2009-6-7 22:02:57 | 显示全部楼层
希望楼主把每个都分析下
回复

使用道具 举报

发表于 2009-6-7 22:07:02 | 显示全部楼层
:lol我说谁敢写这种帖子 !
原来是于安!
咋又换个ID?
回复

使用道具 举报

头像被屏蔽
发表于 2009-6-7 22:42:16 | 显示全部楼层
ppc的于安?
回复

使用道具 举报

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

本版积分规则

小黑屋|手机版|Archiver|Comsenz ( 粤B2-20090059-165 )star

GMT+8, 2019-9-22 22:07

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

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