牛骨文教育服务平台(让学习变的简单)

完整的插件运行流程

插件安装流程

首先 ,我们打开Editor插件的定义类

<?php
// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.onethink.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: yangweijie <yangweijiester@gmail.com> <code-tech.diandian.com>
// +----------------------------------------------------------------------
namespace AddonsEditor;
use CommonControllerAddon;
/**
 * 编辑器插件
 * @author yangweijie <yangweijiester@gmail.com>
 */
class EditorAddon extends Addon{
    public $info = array(
            "name"=>"Editor",
            "title"=>"前台编辑器",
            "description"=>"用于增强整站长文本的输入和显示",
            "status"=>1,
            "author"=>"thinkphp",
            "version"=>"0.1"
        );
    public function install(){
        return true;
    }
    public function uninstall(){
        return true;
    }
    /**
     * 编辑器挂载的文章内容钩子
     * @param array("name"=>"表单name","value"=>"表单对应的值")
     */
    public function documentEditFormContent($data){
        $this->assign("addons_data", $data);
        $this->assign("addons_config", $this->getConfig());
        $this->display("content");
    }
    /**
     * 讨论提交的钩子使用编辑器插件扩展
     * @param array("name"=>"表单name","value"=>"表单对应的值")
     */
    public function topicComment ($data){
        $this->assign("addons_data", $data);
        $this->assign("addons_config", $this->getConfig());
        $this->display("content");
    }
}

整个插件就是一个特殊的继承了 Addon抽象类的子类。必须实现 install和uninstall方法。 然后必须有一个自己的info属性,作为插件自己的信息。name、titile、description、status、author、version这6个是必须的。到时候后台列表里在未安装时会读取插件信息,显示出来。status为1或者0,表示安装插件后是否立即启用。

install和uninstall方法用于后台插件安装和卸载时候调用。返回true或者false用于告诉后台我安装卸载的准备工作是否做好了。比如我安装时候创建了某些表,创建成功可以安装,不成功提示错误。卸载前应该将安装时做的操作恢复到安装前状态。 其次,插件被安装后才能配置插件,卸载后会同时去除钩子处挂载的插件名,安装会添加钩子对应的插件名。

后台AddonsController.class.php

/**
 * 安装插件
 */
public function install(){
    $addon_name     =   trim(I("addon_name"));
    $class          =   get_addon_class($addon_name);
    if(!class_exists($class))
        $this->error("插件不存在");
    $addons  =   new $class;
    $info = $addons->info;
    if(!$info || !$addons->checkInfo())//检测信息的正确性
        $this->error("插件信息缺失");
    session("addons_install_error",null);
    $install_flag   =   $addons->install();
    if(!$install_flag){
        $this->error("执行插件预安装操作失败".session("addons_install_error"));
    }
    $addonsModel    =   D("Addons");
    $data           =   $addonsModel->create($info);
    if(is_array($addons->admin_list) && $addons->admin_list !== array()){
        $data["has_adminlist"] = 1;
    }else{
        $data["has_adminlist"] = 0;
    }
    if(!$data)
        $this->error($addonsModel->getError());
    if($addonsModel->add($data)){
        $config         =   array("config"=>json_encode($addons->getConfig()));
        $addonsModel->where("name="{$addon_name}"")->save($config);
        $hooks_update   =   D("Hooks")->updateHooks($addon_name);
        if($hooks_update){
            S("hooks", null);
            $this->success("安装成功");
        }else{
            $addonsModel->where("name="{$addon_name}"")->delete();
            $this->error("更新钩子处插件失败,请卸载后尝试重新安装");
        }
    }else{
        $this->error("写入插件数据失败");
    }
}

实例化插件类->插件info是否正确->执行install方法,预安装操作->添加插件数据到数据库Addons表和Hooks表

每个步骤出错都会提示,install 方法中错误用 session("addons_install_error", "error")传递。

插件卸载流程
卸载流程和安装相反

后台AddonsController.class.php

/**
 * 卸载插件
 */
public function uninstall(){
    $addonsModel   =   M("Addons");
    $id            =   trim(I("id"));
    $db_addons     =   $addonsModel->find($id);
    $class        =   get_addon_class($db_addons["name"]);
    $this->assign("jumpUrl",U("index"));
    if(!$db_addons || !class_exists($class))
        $this->error("插件不存在");
    session("addons_uninstall_error",null);
    $addons =   new $class;
    $uninstall_flag = $addons->uninstall();
    if(!$uninstall_flag)
        $this->error("执行插件预卸载操作失败".session("addons_uninstall_error"));
    $hooks_update = D("Hooks")->removeHooks($db_addons["name"]);
    if($hooks_update === false){
        $this->error("卸载插件所挂载的钩子数据失败");
    }
    S("hooks", null);
    $delete = $addonsModel->where("name="{$db_addons["name"]}"")->delete();
    if($delete === false){
        $this->error("卸载插件失败");
    }else{
        $this->success("卸载成功");
    }
}

实例化插件类->执行预卸载->卸载插件所挂载过的钩子处信息->删除插件文件。 每个步骤出错都会提示,install 方法中错误用 session("addons_uninstall_error", "error")传递。

插件运行流程

hook函数调用Hook类的listen静态方法触发钩子->获取钩子挂载的开启的插件->执行对应插件实现的钩子同名方法(这个在init_hooks函数会初始化)

代码上就是如下的过程:
>hook("documentEditFormContent");

然后hook函数遍历 Hook类里的$tag属性,知道有哪些插件可被调用,接下来去读取配置和状态,启用就去执行钩子

/**
 * 处理插件钩子
 * @param string $hook   钩子名称
 * @param mixed $params 传入参数
 * @return void
 */
function hook($hook,$params=array()){
    ThinkHook::listen($hook,$params);
}
而Hook::listen方法里面
    /**
 * 监听标签的插件
 * @param string $tag 标签名称
 * @param mixed $params 传入参数
 * @return void
 */
static public function listen($tag, &$params=NULL) {
    if(isset(self::$tags[$tag])) {
        if(APP_DEBUG) {
            G($tag."Start");
            trace("[ ".$tag." ] --START--","","INFO");
        }
        foreach (self::$tags[$tag] as $name) {
            APP_DEBUG && G($name."_start");
            $result =   self::exec($name, $tag,$params);
            if(APP_DEBUG){
                G($name."_end");
                trace("Run ".$name." [ RunTime:".G($name."_start",$name."_end",6)."s ]","","INFO");
            }
            if(false === $result) {
                // 如果返回false 则中断插件执行
                return ;
            }
        }
        if(APP_DEBUG) { // 记录行为的执行日志
            trace("[ ".$tag." ] --END-- [ RunTime:".G($tag."Start",$tag."End",6)."s ]","","INFO");
        }
    }
    return;
}
/**
 * 执行某个插件
 * @param string $name 插件名称
 * @param Mixed $params 传入的参数
 * @return void
 */
static public function exec($name, $tag,&$params=NULL) {
    if(false === strpos($name,"")) {
        // 插件(多个入口)
        $class   =  "Addons{$name}{$name}Addon";
    }else{
        // 行为扩展(只有一个run入口方法)
        $class   =  $name."Behavior";
        $tag    =   "run";
    }
    $addon   = new $class();
    return $addon->$tag($params);
}
#实例化一个插件类,我们有get_addon_classs()函数,传入插件名即可或得插件类名,然后直接new 就行了,区分大小写 
$addons_class = new get_addon_classs($addon_name);

$addon->$tag() 这个方法就是去执行钩子处挂载的和钩子同名的插件方法。

这个方法里可以display渲染模板,默认插件的模板就在插件目录下,比如Editor插件类里用的$this->display("content");。

如果你想有目录层可以传入目录/模板这样的参数,这样是不支持主题的,如果要支持主题,也可以传入具体的模板路径,使用T函数如T("Addons://Attachment@Article/edit")这样,到时候想切换主题了,T函数定位模板之前C("DEFAULT_THEME","default")这样就行了。

当然这个方法支持传参,只允许一个,为了能实现引用。所以多个参数,请封装成数组,传入hook函数的第二个参数。

执行完毕,这个钩子的某个插件前台功能方法就运行完了。

插件被禁用,钩子处的插件不会被执行该同名方法,并且插件后台列表里不会出现该插件的列表。

插件不光前台能用 ,后台有钩子,并且插件里实现了该钩子,也可以用。为了前后台编辑器插件可以配置不同的编辑器,我们复制了一份Editor插件,改名为EditroForAdmin了。

还有后台首页,其实是用AdminIndex 钩子挂载了几个插件。

后台中,插件默认会在 插件列表里出现。默认没有安装过的会显示在前面。

基本的数据字段都是读取的插件类里的info属性数组。插件未安装的时候是不可以设置的。

假如插件需要url访问,就必须插件里有Controller目录和tp结构一样。 只不过生成这个方法的用addons_url("插件名://控制器名/操作方法"),生成访问url。并且访问权限由插件去做。具体写法,参照附件Attachment插件 里的控制器写法。和模板里url调用。