Discuz! X2源码分析-插件机制

以下是我的阅读笔记.方便日后记忆.

插件事件勾子默认入口:runhooks函数.主要运行插件初始函数和全局方法,如:common(),HookId().

先看源码,
source/function/function_core.php 约1191行开始:

function runhooks() {
	if(!defined('HOOKTYPE')) {
		define('HOOKTYPE', !defined('IN_MOBILE') ? 'hookscript' : 'hookscriptmobile');
	}
	if(defined('CURMODULE')) {
		global $_G;
		if($_G['setting']['plugins'][HOOKTYPE.'_common']) {
			hookscript('common', 'global', 'funcs', array(), 'common');
		}
		hookscript(CURMODULE, $_G['basescript']);
	}
}

其中,HOOKTYPE当电脑浏览时是指hookscript,手机浏览时hookscriptmobile.

CURMODULE其实就是url中的$mod=XXX,其实就是常见的$action,也不是动作.以下代码是插件中的commom()方法.

if($_G['setting']['plugins'][HOOKTYPE.'_common']) {
	hookscript('common', 'global', 'funcs', array(), 'common');
}

接着,$_G['basescript']是当然的主程序名称,比如,forum.php?mod=XXX&do=XX,那么$_G['basescript']就是forum.

hookscript运行在当前主程序下注册的相关函数.

接着是hookscript函数,这个是插件机制的重心.内容很多,我就直接在里面写注释好了.

/**
 * @param String $script 事件勾子名称
 * @param String $hscript 事件勾子所在的区域,常的有global,forum,home,member等,就是某个大块
 * @param String $type 事件类型,一般都是函数形式,funcs
 * @param Array $param 给给被调用的函数的参数.这是一个特殊的数组, 一般地,也会传递调用hookscript函数的函数的所有参数,读起来有点别扭:)有时候还会添加一些特殊的内容,如step等.
 * @param Array $func 被调用的函数.虽然使用了=''的方式正义,但看过代码后,我觉得还是使用Array来正义它的类型会好些.
 */
function hookscript($script, $hscript, $type = 'funcs', $param = array(), $func = '') {
    global $_G;
    static $pluginclasses;

    #对家园这类勾子做特别处理.
    if($hscript == 'home') {
        if($script != 'spacecp') {
            $script = 'space_'.(!empty($_G['gp_do']) ? $_G['gp_do'] : (!empty($_GET['do']) ? $_GET['do'] : ''));
        } else {
            $script .= !empty($_G['gp_ac']) ? '_'.$_G['gp_ac'] : (!empty($_GET['ac']) ? '_'.$_GET['ac'] : '');
        }
    }
    #没有注册勾子,直接返回.
    if(!isset($_G['setting'][HOOKTYPE][$hscript][$script][$type])) {
        return;
    }
    #加载插件缓存
    if(!isset($_G['cache']['plugin'])) {
        loadcache('plugin');
    }

    #引用插件主类所在文件
    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;
        /**
         * 自动校正第五个参数,以取得正确的调用函数.
         * OK,$_G['setting'][HOOKTYPE][$hscript][$script][$type] = $_G['setting']['hookscript']['forum']['viewthread']['funcs']
         * print_r($_G['setting'][HOOKTYPE][$hscript][$script][$type]);
         * 得到类似下面的结果:
         * Array
         * (
         * [viewthread_posttop] => Array
         * (
         * [0] => Array
         *      (
         *          [0] => tbackup
         *          [1] => viewthread_posttop
         *      )
         *
         * )
         * )
        */
        $funcs = !$func ? $_G['setting'][HOOKTYPE][$hscript][$script][$type] : array($func => $_G['setting'][HOOKTYPE][$hscript][$script][$type][$func]);

        #展开$funcs数组第一层:[viewthread_posttop] => Array
        foreach($funcs as $hookkey => $hookfuncs) {
            #再展开viewthread_posttop数组下的每个项:0~N
            foreach($hookfuncs as $hookfunc) {
                #$hookfunc[0]是插件类的identifier(例中的是:tbackup),而$hookfunc[1]则是类下面注册的一个方法(例中的是:viewthread_posttop)
                if($hooksadminid[$hookfunc[0]]) {
                    $classkey = (HOOKTYPE != 'hookscriptmobile' ? '' : 'mobile').'plugin_'.($hookfunc[0].($hscript != 'global' ? '_'.$hscript : ''));
                    #无此类,跳过.
                    if(!class_exists($classkey)) {
                        continue;
                    }
                    #此类未在插件中注册,也跳过
                    if(!isset($pluginclasses[$classkey])) {
                        $pluginclasses[$classkey] = new $classkey;
                    }
                    #此类中无此方法,例中的是:viewthread_posttop,也跳过.
                    if(!method_exists($pluginclasses[$classkey], $hookfunc[1])) {
                        continue;
                    }
                    #执行类中的方法,例中的是:$pluginclasses['tbackup']->viewthread_posttop($param)
                    $return = $pluginclasses[$classkey]->$hookfunc[1]($param);
                    #print_r($return);
                    #Array ( [0] => HI )
                    #如果返回的是数组,那么将$retgurn的$key和$value插入到$_G['setting']['pluginhooks'][$hookkey]中,在例中是$_G['setting']['pluginhooks']['tbackup']
                    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 {
                        /**
                         * $return非数组的情况下,
                         * 如果$_G['setting']['pluginhooks'][$hookkey]本身不是数组(一般说,它就是要求字符串的),那么直接把返回值$return追加到$_G['setting']['pluginhooks'][$hookkey]后在面
                         * */
                        if(!is_array($_G['setting']['pluginhooks'][$hookkey])) {
                            $_G['setting']['pluginhooks'][$hookkey] .= $return;
                        } else {
                            /**
                             * 如果$_G['setting']['pluginhooks'][$hookkey]本身就是数组(一般说,它就是要求返回数组的),那么把$return的每个KEY追加到
                             * $_G['setting']['pluginhooks'][$hookkey]对应的KEY后面.从而达到当$return为数组的情况一样的效果.
                             * */
                            foreach($_G['setting']['pluginhooks'][$hookkey] as $k => $v) {
                                $_G['setting']['pluginhooks'][$hookkey][$k] .= $return;
                            }
                        }
                    }
                }
            }
        }
    }
    #更新标记,说明我已经跳出插件勾子啦!
    $_G['inhookscript'] = false;
}

举个例子,实现在查看主题页面的下面添加"HI".

class plugin_tbackup_forum extends plugin_tbackup {

	function viewthread_bottom() {
		return array("HI");
	}
}

但是,有时候DZ定义的勾子不够多也不够细化,那么就只能请求全局勾子来帮下忙了.
全局勾子是hookscriptoutput,来自官方WIKI的说明:"如果函数名以“_output”结尾则会在模板输出前调用,否则会在模块执行前调用".

/**
 * hookscript的一个特例,当在某个模板准备输出时调用.
 * @param String $tplfile 模板名,如viewthread
 *
 * */
function hookscriptoutput($tplfile) {
	global $_G;
	if(!empty($_G['hookscriptoutput'])) {
		return;
	}

	/**
	 * 这块是手机的
	 * */
	if(!empty($_G['gp_mobiledata'])) {
		require_once libfile('class/mobiledata');
		$mobiledata = new mobiledata();
		if($mobiledata->validator()) {
			$mobiledata->outputvariables();
		}
	}
	hookscript('global', 'global');
	if(defined('CURMODULE')) {
		$param = array('template' => $tplfile, 'message' => $_G['hookscriptmessage'], 'values' => $_G['hookscriptvalues']);
		hookscript(CURMODULE, $_G['basescript'], 'outputfuncs', $param);
	}
	$_G['hookscriptoutput'] = true;
}

hookscriptoutput本身可以做的事不多,但是,如果你对DZ代码很了解的话,你可以修改任何GLOBAL中的内容,下面来个例子,用于修改每个帖子中的签名(DZ本身没有修改签名的勾子).

class plugin_tbackup_forum extends plugin_tbackup {

	function viewthread_output($param) {
		global $postlist;
		foreach ((array) $postlist as $k=>$post) {
			$postlist[$k]['signature'] = "HI,Tim";
		}

	}
}

例中,通过global $postlist;来引入全局程序中的$postlist也就是帖子列表.然后一个foreaech把$post里的signature修改掉.有心的朋友可以扩展下,$postlist[$k]['signature'] = "HI,Tim";可以改成一个过滤函数,那么不就可以过滤掉一些你不喜欢的签名了吗?

发表评论?

0 条评论。

发表评论


注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>