实例讲解
在开始插件开发前, 请您务必详细阅读插件开发手册。
马甲插件是一款能够让用户在论坛中以多个帐号身份进行使用、交流的插件。论坛开启马甲插件后, 用户无需进行退出操作即可迅速切换到其他帐号。下面以马甲插件为例进行插件开发说明。
前台实现帐号切换、马甲设置等功能, 如图:
后台实现管理、锁定、查找等管理功能, 如图:
插件开发流程介绍说明:
- 1.首先在 Discuz! 配置文件 config/config_global.php 底部加入代码:$_config['plugindeveloper'] = 1;开启开发者模式。( 论坛后台插件设置项会开启开发者模式。 新增加的插件也会有设计模式的设置选项。)
- 2.管理员进入后台应用 -> 插件 -> 插件设计, 开始进行新插件的设计, 添加初始化插件的基本信息。
添加完基本信息后, 在插件文件夹目录 source/plugin/ 下新建对应的文件夹(这里我们新建 myrepeats/ 文件夹)。 如果插件开发过程中需要语言包, 则后台开启设置后在 data/plugindata/ 下添加语言包文件, myrepeats.lang.php (myrepeats为插件初始化中添加的唯一标识符)来存储我们插件开发过程中用到的语言。
添加的语言包文件,初始化状态如下:$scriptlang数组中存储脚本文件的语言包,$templatelang 数组中存储模版文件的语言包,$installlang 数组中存储安装、升级、卸载脚本用的语言包。<?php $scriptlang['myrepeats'] = array( 'login_strike' => "密码错误次数过多,请重新设置马甲账号信息并在 15 分钟后再尝试切换。", /* 含有变量值的语言包一般用在脚本文件中调用, 其中变量可以在showmessage(), lang()等函数中某个参数以数组 键值对的形式指定替换值。*/
例如:showmessage('myrepeats:adduser_succeed', 'home.php?mod=spacecp&ac=plugin&id=myrepeats: memcp', array('usernamenew' => stripslashes($usernamenew))); */ 'adduser_succeed' => "马甲账号 {usernamenew} 已成功添加。", ); $templatelang['myrepeats'] = array( 'myrepeats' => "我的马甲", 'adduser' => "添加马甲账号", ); $installlang['myrepeats'] = array( ); ?> - 3.接下来我们需要添加我们开发过程中需要用到的程序模块文件。明确需要使用 Discuz! 插件模块中的以下几项:
- 扩展项目 个人面板:可在个人面板上部增加一个菜单项。(实现个人设置面板部分)
- 程序脚本 页面嵌入:设置一个包含页面嵌入脚本的模块,模块文件名指派为 source/plugin/插件目录/插件模块名.class.php”。(通过嵌入点来实现头部用户信息中, 快捷切换马甲的效果。)
- 扩展项目 管理中心:可在后台 -> 插件栏目中为此插件增添一个管理模块。(在后台为我们的马甲插件添加一个管理项目。)
在刚才新建好的插件中添加以上三个插件模块, 如图: - 扩展项目 个人面板:可在个人面板上部增加一个菜单项。(实现个人设置面板部分)
- 以上指定好模块文件后, 我们需要在对应的插件文件夹 source/plugin/myrepeats/ 里新建我们刚才添加的模块脚本文件。
- 接下来添加插件中需要用到的变量。(马甲插件只需要一个用户组是否开启的状态。)如图:
- 根据需要开发的插件功能, 设计数据表结构。在自己的开发环境下建好数据表, 以便在后面的开发过程中使用。当你新建完数据库之后, 需要根据你构建的数据库, 在 source/plugin/插件目录/table/ 下构建你的数据库类对象, 以便在后续的开发中使用。(每一个数据表, 都需要构建一个单独的数据库类对象。)
- 现在前期工作基本完成了, 接下来开始编写脚本文件,开发需要的功能了。以马甲插件为例, 现在开始在页面头部用户资料栏添加一个马甲切换的功能。此功能需要用页面嵌入的模块来开发。 前期准备工作中已经新建了这个模块文件, 即 myrepeats.class.php 脚本文件。下面我们来看一看这个脚本文件的代码实现:
<?php /* 所有与插件有关的程序,包括全部的前后台程序,因全部使用外壳调用, 请务必在第一行加入以下三行代码, 以免其被 URL 直接请求调用,产生安全问题。 */ if(!defined('IN_DISCUZ')) { exit('Access Denied'); } /* 全局嵌入点类(必须存在)*/ class plugin_myrepeats { var $value = array(); //初始化返回值变量。 /* 嵌入点对象初始化函数, 属于php面向对象机制特性。这里的函数名和类名是一致的, 在初始化类的时候以便执行这 个函数,对$value进行赋值,以便下面的global_usernav_extra1()函数调用。*/ function plugin_myrepeats() { global $_G; if(!$_G['uid']) { return; } /* 读取可以使用马甲的用户组 usergroups 变量值。需要注意参数的读取方式,详情见插件手册-参数读取 。 */ $myrepeatsusergroups = (array)dunserialize($_G['cache']['plugin']['myrepeats']['use rgroups']); if(in_array('', $myrepeatsusergroups)) { $myrepeatsusergroups = array(); } $userlist = array(); /* 对当前登录用户进行马甲验证, 即当前用户组不再权限许可范围内, 但其他帐号所在用户组有权限, 则当 前用户也有使用权限。*/ if(!in_array($_G['groupid'], $myrepeatsusergroups)) { if(!isset($_G['cookie']['myrepeat_rr'])) { /* 这里需要注意一下你所建的数据表对象的构建, 即 source/plugin/myrepeats/t able/下的 table_新建表名.php */ $users = count(C::t('#myrepeats#myrepeats')->fetch_all_by_username( $_G['username'])); dsetcookie('myrepeat_rr', 'R'.$users, 86400); } else { $users = substr($_G['cookie']['myrepeat_rr'], 1); } if(!$users) { return ''; } } /* 前台显示代码 */ $this->value['global_usernav_extra1'] = '<script>'. 'function showmyrepeats() {if(!$(\'myrepeats_menu\')) {'. 'menu=document.createElement(\'div\');menu.id=\'myrepeats_menu\';menu.style .display=\'none\';menu.className=\'p_pop\';'. '$(\'append_parent\').appendChild(menu);'. 'ajaxget(\'plugin.php?id=myrepeats:switch&list=yes\',\'myrepeats_menu\',\'a jaxwaitid\');}'. 'showMenu({\'ctrlid\':\'myrepeats\',\'duration\':2});}'. '</script>'. /* 此处是对个人前台设置管理马甲程序模块的连接,需要注意下格式是固定的。 */ '<span class="pipe">|</span><a id="myrepeats" href="home.php?mod=spacecp&ac=plugin& id=myrepeats:memcp" class="showmenu cur1" onmouseover="delayShow(this, showmyrepeat s)">'.lang('plugin/myrepeats', 'switch').'</a>'."\n"; } /* 这里使用了嵌入点函数 global_usernav_extra1() 返回到它对应输的显示位置, 所有嵌入点函数及对应位置见 手册。 */ function global_usernav_extra1() { return $this->value['global_usernav_extra1']; } } ?>
- 上面的脚本在前台界面头部增加了马甲入口,如图:
它的连接地址指向前台个人设置页面。程序由 home.php 进入, 然后进入默认的个人设置流程里面。
上图所示的模板显示是直接调用的 source/plugin/myrepeats/template/ 文件夹下的 memcp.htm 模版文件, memcp.inc.php 是与之对应的脚本处理代码。入口处的马甲切换列表, 是ajax调用插件中的 switch.inc.php 扩展脚本处理返回的。以上个人设置和帐号切换流程都是正常的 php 逻辑代码处理流程,这里就不复述了。 - 以上前台的功能我们基本已经开发完成, 现在需要开始开发后台管理的功能, 即 admincp.inc.php, 此文件在前面已经添加。这个文件主要功能是对后台插件数据进行处理。开发者可以根据自己的需求, 设计此文件的代码结构。插件开发时需要注意, 后台提供了很多Discuz! 内置的函数来显示界面, 例如: showtableheader(), showformheader(), showsubmit() 等函数, 方便开发使用。具体用法请参照开发手册-后台页面开发。
<?php if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } /* 语言包文件已经引入, 这里直接读取语言包,赋值给变量 $Plang。 */ $Plang = $scriptlang['myrepeats']; /* 锁定、删除处理流程 */ if($_GET['op'] == 'lock') { /* 插件数据库表对象方法的调用和使用形式。 */ $myrepeat = C::t('#myrepeats#myrepeats')->fetch_all_by_uid_username($_GET['uid'], $_GET['us ername']); $lock = $myrepeat['lock']; $locknew = $lock ? 0 : 1; C::t('#myrepeats#myrepeats')->update_locked_by_uid_username($_GET['uid'], $_GET['username'] , $locknew); ajaxshowheader(); echo $lock ? $Plang['normal'] : $Plang['lock']; ajaxshowfooter(); } elseif($_GET['op'] == 'delete') { C::t('#myrepeats#myrepeats')->delete_by_uid_usernames($_GET['uid'], $_GET['username']); ajaxshowheader(); echo $Plang['deleted']; ajaxshowfooter(); } $ppp = 100; $resultempty = FALSE; $srchadd = $searchtext = $extra = $srchuid = ''; $page = max(1, intval($_GET['page'])); if(!empty($_GET['srchuid'])) { $srchuid = intval($_GET['srchuid']); $srchadd = "AND uid='$srchuid'"; } elseif(!empty($_GET['srchusername'])) { $srchuid = C::t('common_member')->fetch_uid_by_username($_GET['srchusername']); if($srchuid) { $srchadd = "AND uid='$srchuid'"; } else { $resultempty = TRUE; } } elseif(!empty($_GET['srchrepeat'])) { $extra = '&srchrepeat='.rawurlencode($_GET['srchrepeat']); $srchadd = "AND username='".addslashes($_GET['srchrepeat'])."'"; $searchtext = $Plang['search'].' "'.$_GET['srchrepeat'].'" '.$Plang['repeats'].' '; } if($srchuid) { $extra = '&srchuid='.$srchuid; $member = getuserbyuid($srchuid); $searchtext = $Plang['search'].' "'.$member['username'].'" '.$Plang['repeatusers'].' '; } $statary = array(-1 => $Plang['status'], 0 => $Plang['normal'], 1 => $Plang['lock']); $status = isset($_GET['status']) ? intval($_GET['status']) : -1; if(isset($status) && $status >= 0) { $srchadd .= " AND locked='$status'"; $searchtext .= $Plang['search'].$statary[$status].$Plang['statuss']; } if($searchtext) { $searchtext = '<a href="'.ADMINSCRIPT.'?action=plugins&operation=config&do='.$pluginid.'&id entifier=myrepeats&pmod=admincp">'.$Plang['viewall'].'</a> '.$searchtext; } /* 加载用户组缓存信息。 */ loadcache('usergroups'); /* 这里输出表格头部和表单 html 到当前位置。Discuz! 后台输出 html 界面函数, 可在后台函数库文件source/function/function_admincp.php 中查看具体输出内容。*/ showtableheader(); /* 本页面的地址连接,其中 do = $pluginid 为当前插件标识id, 此id为自动生成的id, 在书写本页面地址时需要注意此参数。*/ showformheader('plugins&operation=config&do='.$pluginid.'&identifier=myrepeats&pmod=admincp', 'repeatsubmit'); showsubmit('repeatsubmit', $Plang['search'], $lang['username'].': <input name="srchusername" value="'.htmlspecialchars($_GET['srchusername']).'" class="txt" /> '.$Plang['repeat'].': <input name="srchrepeat" value="'.htmlspecialchars($_GET['srchrepeat']).'" class="txt" />', $searchtext); showformfooter(); $statselect = '<select onchange="location.href=\''.ADMINSCRIPT.'?action=plugins&operation=config&do='.$pluginid.'&identifier=myrepeats&pmod=admincp'.$extra.'&status=\' + this.value">'; foreach($statary as $k => $v) { $statselect .= '<option value="'.$k.'"'.($k == $status ? ' selected' : '').'>'.$v.'</option>'; } $statselect .= '</select>'; /* 界面具体内容显示输出。*/ echo '<tr class="header"><th>'.$Plang['username'].'</th><th>'.$lang['usergroup'].'</th><th>'.$Plang['repeat'].'</th><th>'.$Plang['lastswitch'].'</th><th>'.$statselect.'</th><th></th></tr>'; if(!$resultempty) { $count = C::t('#myrepeats#myrepeats')->count_by_search($srchadd); $myrepeats = C::t('#myrepeats#myrepeats')->fetch_all_by_search($srchadd, ($page - 1) * $ppp , $ppp); $uids = array(); foreach($myrepeats as $myrepeat) { $uids[] = $myrepeat['uid']; } $users = C::t('common_member')->fetch_all($uids); $i = 0; foreach($myrepeats as $myrepeat) { $myrepeat['lastswitch'] = $myrepeat['lastswitch'] ? dgmdate($myrepeat['lastswitch'] ) : ''; $myrepeat['usernameenc'] = rawurlencode($myrepeat['username']); $opstr = !$myrepeat['locked'] ? $Plang['normal'] : $Plang['lock']; $i++; echo '<tr><td><a href="'.ADMINSCRIPT.'?action=plugins&operation=config&do='.$plugin id.'&identifier=myrepeats&pmod=admincp&srchuid='.$myrepeat['uid'].'">'.$users[$myre peat['uid']]['username'].'</a></td>'.'<td>'.$_G['cache']['usergroups'][$users[$myre peat['uid']]['groupid']]['grouptitle'].'</td>'.'<td><a href="'.ADMINSCRIPT.'?action =plugins&operation=config&do='.$pluginid.'&identifier=myrepeats&pmod=admincp&srchre peat='.rawurlencode($myrepeat['username']).'" title="'.htmlspecialchars($myrepeat[' comment']).'">'.$myrepeat['username'].'</a>'.'</td>'.'<td>'.($myrepeat['lastswitch' ] ? $myrepeat['lastswitch'] : '').'</td>'.'<td><a id="d'.$i.'" onclick="ajaxget(thi s.href, this.id, \'\');return false" href="'.ADMINSCRIPT.'?action=plugins&operation =config&do='.$pluginid.'&identifier=myrepeats&pmod=admincp&uid='.$myrepeat['uid'].' &username='.$myrepeat['usernameenc'].'&op=lock">'.$opstr.'</a></td>'.'<td><a id="p' .$i.'" onclick="ajaxget(this.href, this.id, \'\');return false" href="'.ADMINSCRIPT .'?action=plugins&operation=config&do='.$pluginid.'&identifier=myrepeats&pmod=admin cp&uid='.$myrepeat['uid'].'&username='.$myrepeat['usernameenc'].'&op=delete">['.$la ng['delete'].']</a></td></tr>'; } } showtablefooter(); /* 分页输出 */ echo multi($count, $ppp, $page, ADMINSCRIPT."?action=plugins&operation=config&do=$pluginid&identifier=myrepeats&pmod=admincp$extra"); ?>
- 这样整个插件的功能开发已经完成, 下面我们需要将我们制作好的插件导出即可。此时我们导出的是xml配置文件, 里面主要是插件的一些基本信息配置参数以及语言包内容。另外, 插件作者可以设计 2 个脚本文件用于插件的安装和卸载,文件名任意。脚本中可用 runquery() 函数执行 SQL 语句,表名可以直接写“cdb_”。插件作者只需在导出的 XML 文件结尾加上安装、卸载脚本的文件名即可
<item id="installfile"><![CDATA[install.php]]></item> <item id="uninstallfile"><![CDATA[uninstall.php]]></item> </item> </root>