1、参考 http://faq.comsenz.com/library/plug/plugin/plugin_pre.htm.
2、由第一点中的官方提供的参考库可以基本可以了解怎么去开发一个插件。这里简述几点重要的
一、配置文件中config_global.php中加上
- $_config['plugindeveloper'] =1 ;
就可以在后台-应用-插件 中设计新插件。将上述变量中的值设置成为2,就可以看DISCUZ每个页面中存在的插件钩子
二、在设置新插件的时候,注意唯一标识符的唯一性,以及其资源文件目录所在是source/plugin/唯一标识符/
三、根据官方提供的的插件形式,制作:
插件模块分为程序链接、扩展项目和程序脚本 3 类:
程序链接 主导航项目:可在主导航栏增加一个菜单项,可自主指派菜单链接的 URL,也可以调用插件的一个模块,模块文件名指派为 source/plugin/插件目录/插件模块名.inc.php”。注意:由于引用外部程序,因此即便设置了模块的使用等级,您的程序仍需进行判断使用等级是否合法。
——当然这里可以根据需要,主导航一般都会重新生成一个入口文件在首页,对于这种重新生成的新单页,要遵守DISCUZ的一些开发的基本要求,其实就是初始化工作内容:
加载基本类class_core.php
调用C::app()->init();
对于需要用到插件的地方,加上执行runhooks()
程序链接 主导航项目 - 插件菜单:可在主导航栏的插件子菜单中增加一个菜单项。
程序链接 顶部导航项目、底部导航项目、快捷导航项目、家园导航项目:可在各个导航中增加一个菜单项。
扩展项目 个人设置:可在个人设置中增加一个菜单项。
扩展项目 个人设置 - 个人资料:可在个人设置的个人资料页上部增加一个菜单项。
扩展项目 个人设置 - 积分:可在个人设置的积分页上部增加一个菜单项。
扩展项目 站点帮助:可在站点帮助中增加一个菜单项。
扩展项目 我的帖子:可在我的帖子中增加一个菜单项。
扩展项目 门户管理:可在门户管理面板上部增加一个菜单项。
扩展项目 论坛管理 - 基本:可在前台论坛管理面板侧边上部增加一个菜单项。
扩展项目 论坛管理 - 工具:可在前台论坛管理面板侧边下部增加一个菜单项。
扩展项目 管理中心:可在后台插件栏目中为此插件增添一个管理模块。
程序脚本 页面嵌入 - 普通版:设置一个包含页面嵌入脚本的模块,该模块用于在普通电脑访问的页面显示。模块文件名指派为 source/plugin/插件目录/插件模块名.class.php”。(页面嵌入将在后面的《页面嵌入模块开发》中详细说明)
程序脚本 页面嵌入 - 手机版:设置一个包含页面嵌入脚本的模块,该模块用于在手机访问的页面显示。
程序脚本 特殊主题:设置一个特殊主题脚本的模块,模块文件名指派为 source/plugin/插件目录/插件模块名.class.php”。(特殊主题将在后面的《特殊主题模块开发》中详细说明)
四、在后台-应用中显示的插件,可以查看每个插件应用的基本信息,简单说就是每个插件不同变量所对应的值。后台数据库中存放的表为common_pluginvar
由于调用系统缓存统一通过“loadcache()”函数调用,并存放于 $_G['cache'] 中,因此“loadcache('plugin')”后插件的变量缓会存放于 $_G['cache']['plugin'] 中。嵌入点插件和以 plugin.php 为主脚本调用的插件无需加载此缓存,系统已自动加载了缓存。变量配置类型为“版块/*”的变量会保存在 $_G['cache']['forums'][fid]['plugin'] 中。变量配置类型为“用户组/*”的变量会保存在 $_G['cache']['usergroup_groupid']['plugin'] 和 $_G['group']['plugin'] 中。所以,在后台启用了的插件的所有变量的内容都会存放在这个全局变量中——当然这里的好处就是进行二次开发的人在获取数据的时候会及其简便,无论是不是这个插件数据,或者其他的内容,但是弊端也有,就是有时候并不需要太多的数据,确在后台进行过多的loadcache的操作,所以,如果对DISCUZ二次开发并做了很大程度改版的话,同时并不打算继续升级DISCUZ版本的话,可以做一些优化操作,对于页面中不会用到的缓存内容,可以省略它的加载过程,而起到加快页面的加载速度。
五、以插件中心免费的插件百度分享为例子
脚本位置:source/plugin/baidu_share/baidu_share.class.php(这个属于第三部分说到的程序脚本-页面嵌入-普通版)。这个也是很常用的。
- <?php
- /**
- * A class of Baidu Share plugin for Discuz! X2.5 RC
- * @author Sun Hongtao
- */
- class plugin_baidu_share {
- function plugin_baidu_share() {
- global $_G;
- $this->script = $_G['cache']['plugin']['baidu_share']['baidu_share_script'];
- $this->position = $_G['cache']['plugin']['baidu_share']['baidu_share_position'];
- $this->content = $_G['cache']['plugin']['baidu_share']['baidu_share_content'];
- }
- function is_ie() {
- $str=preg_match('/MSIE ([0-9]\.[0-9])/',$_SERVER['HTTP_USER_AGENT'],$matches);
- if ($str == 0){
- return 0;
- } else {
- return floatval($matches[1]);
- }
- }
- function is_ff() {
- $str=preg_match('/Firefox/',$_SERVER['HTTP_USER_AGENT'],$matches);
- if ($str == 0){
- return false;
- } else {
- return true;
- }
- }
- }
- class plugin_baidu_share_forum extends plugin_baidu_share {
- function viewthread_postheader() {
- if ($this->position == 1) {
- $v = $this->is_ie();
- if ($v <= 6 && $v != 0) {
- return array('<div style="float:right;position:relative;top:-16px;">' . $this->script . '</div>');
- } else if ($v == 7) {
- return array('<div style="float:right;position:relative;top:-20px;">' . $this->script . '</div>');
- } else if ($this->is_ff()) {
- return array('<div style="float:right;">' . $this->script . '</div>');
- } else {
- return array('<div style="float:right;position:relative;top:-6px;">' . $this->script . '</div>');
- }
- } else {
- return array();
- }
- }
- function viewthread_postfooter(){
- if($this->position == 2) {
- $v = $this->is_ie();
- if ($v <= 6 && $v != 0) {
- return array('<div style="margin-top:6px;line-height:14px;float:left;">' . $this->script . '</div>');
- } else if ($v == 7) {
- return array('<div style="margin-top:6px;line-height:14px;float:left;">' . $this->script . '</div>');
- } else if ($v == 8) {
- return array('<div style="margin-top:6px;line-height:14px;float:right;margin-top:6px;">' . $this->script . '</div>');
- } else if ($this->is_ff()) {
- return array('<div style="margin-top:6px;line-height:14px;float:right;margin-top:6px;">' . $this->script . '</div>');
- } else {
- return array('<div style="margin-top:6px;line-height:14px;float:right;position:relative;">' . $this->script . '</div>');
- }
- }else {
- return array();
- }
- }
- function viewthread_posttop() {
- if ($this->position == 3) {
- return array('<div style="float:left;padding-top:5px;margin-bottom:25px;width:100%">' . $this->script . '</div>');
- } else {
- return array();
- }
- }
- function viewthread_postbottom() {
- if ($this->position == 4) {
- return array('<div style="margin-bottom:-10px; overflow:hidden;">' . $this->script . '</div>');
- } else {
- return array('');
- }
- }
- function viewthread_endline(){
- if($this->content == 2){
- $v = "<script type='text/javascript'>var arrAll=document.getElementsByTagName('*');for (var i=0;i<arrAll.length;i++){if(arrAll[i].id.substring(0,11)=='postmessage'){var data=arrAll[i].innerHTML.replace(/<[^>].*?>/g,\"\").replace(/\\n|\\r|\\t/g,\" \");var baidushare=document.getElementById('bdshare');baidushare.setAttribute('data','{\"text\":data}');break;}}</script>";
- return array('<div>' . $v . '</div>');
- }
- else{
- return array();
- }
- }
- function viewthread_useraction() {
- return "";
- }
- }
- ?>
重点内容在类plugin_baidu_share_forum
plugin_ 普通版脚本中的类名以 plugin_ 开头。手机版脚本中的类名以 mobileplugin_ 开头。
identifier(baidu_share) 插件的唯一标识符,在插件设置中设置。
CURSCRIPT (forum) 嵌入点位于的脚本名,如 forum.php 为 forum。
按照第一点设置之后,就可以发现该类中的函数都对应了论坛主题页面中,每一个帖子上对应的位置,function_core.php中的hookscript()就会分析当前脚本CURSCRIPT中相符合的钩子是否存在于插件中,并执行其。
上述中的position就是为了区别于将百度分享的代码插入页面中的哪个位置而用的。如图
3、最后再了解该插件的执行过程究竟是怎样的,那就需要了解下运行钩子函数runhooks()和执行插件脚本的函数hookscript()。
- function runhooks($scriptextra = '') {
- if(!defined('HOOKTYPE')) {
- //定义钩子的类型,主要是为了区别究竟是手机端用户,还算PC端用户的访问,不同类型又不同的执行过程
- define('HOOKTYPE', !defined('IN_MOBILE') ? 'hookscript' : 'hookscriptmobile');
- }
- if(defined('CURMODULE')) {
- global $_G;
- if($_G['setting']['plugins']['func'][HOOKTYPE]['common']) {
- hookscript('common', 'global', 'funcs', array(), 'common');
- }
- hookscript(CURMODULE, $_G['basescript'], 'funcs', array(), '', $scriptextra);
- }
- }
详情就看注释中的说明吧
- function hookscript($script, $hscript, $type = 'funcs', $param = array(), $func = '', $scriptextra = '') {
- global $_G;//加入全局变量,其中$_G['setting'][HOOKTYPE]存放了插件所需要的全部数据,当然这个获取需要通过loadcache加载插件缓存得到
- static $pluginclasses;//静态变量用于存放实例化后的插件
- if($hscript == 'home') {//特别针对当$hscirpt=home时,做些默认变量值的修改
- if($script == 'space') {
- $scriptextra = !$scriptextra ? $_GET['do'] : $scriptextra;
- $script = 'space'.(!empty($scriptextra) ? '_'.$scriptextra : '');
- } elseif($script == 'spacecp') {
- $scriptextra = !$scriptextra ? $_GET['ac'] : $scriptextra;
- $script .= !empty($scriptextra) ? '_'.$scriptextra : '';
- }
- }
- if(!isset($_G['setting'][HOOKTYPE][$hscript][$script][$type])) {//判断当前脚本所对应的插件函数是否存在,如果没有的话则退出
- return;
- }
- if(!isset($_G['cache']['plugin'])) {//如果插件缓存信息已经加载,就不需要重复执行了。
- loadcache('plugin');//加载插件缓存,并将后台中开启的所有插件的数据存放在$_G['cache']['plugin']['插件唯一标识符']中
- }//循环module数组,数组内部存放的是已经开启了的插件的文件目录,然后通过include_once加载进来。
- foreach((array)$_G['setting'][HOOKTYPE][$hscript][$script]['module'] as $identifier => $include) {
- $hooksadminid[$identifier] = !$_G['setting'][HOOKTYPE][$hscript][$script]['adminid'][$identifier] || ($_G['setting'][HOOKTYPE][$hscript][$script]['adminid'][$identifier] && $_G['adminid'] > 0 && $_G['setting']['hookscript'][$hscript][$script]['adminid'][$identifier] >= $_G['adminid']);
- if($hooksadminid[$identifier]) {
- @include_once DISCUZ_ROOT.'./source/plugin/'.$include.'.class.php';
- }
- }
- if(@is_array($_G['setting'][HOOKTYPE][$hscript][$script][$type])) {//判断插件的函数集合是否为数组,当然这个在存储缓存的时候就已经完成了,如果是的话就继续。
- $_G['inhookscript'] = true;//感觉像个开关
- //获得所有插件中需要进行处理的所有函数信息,每个元素对应一个钩子位置,每个位置在不同插件有可能有不同的执行内容,通过下面的循环遍历完成
- $funcs = !$func ? $_G['setting'][HOOKTYPE][$hscript][$script][$type] : array($func => $_G['setting'][HOOKTYPE][$hscript][$script][$type][$func]);
- foreach($funcs as $hookkey => $hookfuncs) {//遍历每个钩子
- foreach($hookfuncs as $hookfunc) {//遍历相同钩子下不同插件对应的函数
- if($hooksadminid[$hookfunc[0]]) {
- //因为已经定义好了插件的类名规则,所以这里就可以轻松拼接插件的类名了
- $classkey = (HOOKTYPE != 'hookscriptmobile' ? '' : 'mobile').'plugin_'.($hookfunc[0].($hscript != 'global' ? '_'.$hscript : ''));
- if(!class_exists($classkey, false)) {//判断被加载的所有脚本中是否存在该类,没有的话则跳出当前循环
- continue;
- }
- if(!isset($pluginclasses[$classkey])) {
- $pluginclasses[$classkey] = new $classkey;//生成某个插件的类的对象,并存放在静态变量中,当然这里就可以完成一些初始化插件数据的关系了。
- }
- if(!method_exists($pluginclasses[$classkey], $hookfunc[1])) {//如果钩子函数不存在实例化后的类中,则退出当前循环
- continue;
- }
- $return = $pluginclasses[$classkey]->$hookfunc[1]($param);//获取钩子函数返回的内容
- if(is_array($return)) {
- if(!isset($_G['setting']['pluginhooks'][$hookkey]) || is_array($_G['setting']['pluginhooks'][$hookkey])) {
- foreach($return as $k => $v) {
- $_G['setting']['pluginhooks'][$hookkey][$k] .= $v;
- }
- }
- } else {
- if(!is_array($_G['setting']['pluginhooks'][$hookkey])) {
- $_G['setting']['pluginhooks'][$hookkey] .= $return;
- } else {
- foreach($_G['setting']['pluginhooks'][$hookkey] as $k => $v) {
- $_G['setting']['pluginhooks'][$hookkey][$k] .= $return;
- }
- }
- }
- }
- }
- }
- }
- $_G['inhookscript'] = false;
- }
总上可以看出插件中获取到的数据都存放在全局变量$_G[setting][pluginhooks]中了。
而模板htm文件中的钩子的解析工作理所当然的要在template()来完成了,举个简单例子,在具体主题帖子页面,用户发帖后的楼层中会存在如下内容,当然这句代码可以在data/template/数字_diy_forum_viewthread.tpl.php缓存文件中找到,该文件是经过了模板解析得到的:
- if(!empty($_G['setting']['pluginhooks']['viewthread_posttop'][$postcount])) echo $_G['setting']['pluginhooks']['viewthread_posttop'][$postcount];
依旧以百度分享插件为例子,假如站长在设置插件信息的时候,将百度分享设置成在“主贴正文上方”的话,就会通过上面代码显示在对应的位置上了。