onethink beta1 插件完整文档

浏览:10229 发布日期:2013/09/25 分类:求助交流 关键字: 插件 onethink 文档

插件定义

插件是用于扩展系统的功能的一些独立“组件”。

功能定义

插件的定位是用于实现某些简单的显示及数据处理的功能扩展。所以我们的初衷是插件的开启关闭,不会影响原有数据。

为了管理的方便,我们在后台给插件默认提供了插件列表页,钩子列表,以及配置插件、快速创建插件结构、安装、卸载、启用停用、插件后台页面,方便大家定义管理插件相关的数据。

物理定义

位于站点根目录 wwwroot/Addons 下的一个类库,可以被系统的hooks函数访问到。

目录结构如以下(以自带的Editor插件为示范):|-wwwroot
| |-Addons [所有插件目录]
| | |-Editor [插件目录]
| | |-Controller [控制器目录,有URL访问的时候才需要,可选]
| | | |-UploadController.class.php [插件控制器,名字可以不和插件名一样]
| | |-config.php [配置文件, 有配置项的话可选]
| | |-content.html [插件模板页面,可选]
| | |-EditorAddons.class.php [插件定义和实现的文件,必须有!!]
| | |-[adminlist.html、config.html] [这些模板可选,用于自定义插件的配置页和列表页]
目前仅介绍一下目录结构,在插件开发中将讲解每个文件如何定义。

插件流程

与插件相关的钩子

讲到插件,不得不讲钩子。首先,我们之前说明了插件是一个扩展功能的实现。

既然是扩展的,那么就要很灵活、可复用,并不是像我们之前开发项目,一个功能实现了,就写死在代码里了。

项目其他地方要用了,怎么办,复制一份改个名,改的那个地方能调用实现。这样一次两次可以,次数多了就不行了。

因为后面每次开发的底层架构在不断变化。不断重复的功能版本造成人力的浪费。我们做成插件的目的就是为了方便大家扩展我们这个产品的功能。到时候形成规模,大家自由的搭建自己的站点就方便了。

那么如何让一个扩展的功能在多个地方可随意的使用呢。那就用到了我们的钩子。

为什么叫它钩子呢?因为它的作用就是如此和生活中的钩子类似。

打个比方,我们做的网站比作一个有多个功能的立式衣架。

这个衣架给什么人用就有不同的用途。

假如你专门用来挂大衣的,那就是大衣衣架。如果你专门挂袋子,那就是一个储物衣架。

当你不想要某个挂件、衣服时,取下来即可。并不会破坏原有的袋子或者衣服的功能。

你挂与不挂,钩子就在那里。

为什么能挂那么多东西呢?说明被挂的东西都符合一个标准:能挂的住。

换作你挂一个橡皮泥、或者棉花之类的。挂不了多久就会掉了。因为他们不符合要有部分封闭的可固定的这一个部分的标准。

还有挂一个太重的比如10个背包挂一个钩子上。要么架子毁了,要么钩子断了。总之就是挂不住。

因为任何一个钩子都有其承重上限。你加起来的超过了,肯定不行。

所以我们不能把插件当成万能的使,什么东西都整成插件,不管功能的大小。

任何系统都有瓶颈,你不能把个重量级的东西做成插件后挂上,说不定以后就会影响整个站点。就违背了插件的独立性原则。那些就不应该做成插件而是做成模型扩展或者应用扩展。

完整的插件运行流程

插件安装流程

首先 ,我们打开Editor插件的定义类<?php
// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.onethink.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: yangweijie <[email protected]> <code-tech.diandian.com>
// +----------------------------------------------------------------------

namespace Addons\Editor;
use Common\Controller\Addons;

/**
 * 编辑器插件
 * @author yangweijie <[email protected]>
 */

    class EditorAddons extends Addons{

        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');
        }

    }
整个插件就是一个特殊的继承了 Addons抽象类的子类。必须实现 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'));
        $addons         =   addons($addon_name);
        if(!$addons)
            $this->error('插件不存在');
        $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')传递。

插件卸载流程

hooks函数触发钩子->获取钩子挂载的开启的插件->执行对应插件实现的钩子同名方法
代码上就是如下的过程:hooks('documentEditFormContent');然后hooks函数遍历 该钩子的addons字段,知道有哪些插件可被调用,接下来去读取配置和状态,启用就去执行钩子
hook函数在Common/Common/function.php中,代码如下:/**
 * 处理插件钩子
 * @param string $hook   钩子名称
 * @param mixed $params 传入参数
 * @return mixed
 * @author 麦当苗儿 <[email protected]>
 */
function hooks($hook, $params = array()) {
    $hooks = S('hooks');
    if(!$hooks){
        $hooks = D('Hooks')->getField('name,addons');
        foreach ($hooks as $key => $value) {
            $hooks[$key] = explode(',', $value);
        }
        S('hooks',$hooks);
    }
    $addons = $hooks[$hook];
    if(!empty($addons)) {
        if(APP_DEBUG) {
            G($hook.'Start');
            trace('[ '.$hook.' ] --START--','','INFO');
        }
        // 执行插件
        foreach ($addons as $key => $name) {
            if($name){
                $addons_class = addons($name);
                if($addons_class){
                    $config = $addons_class->getConfig();
                    if(method_exists($addons_class, $hook) && $config['status'] == 1)
                        $addons_class->$hook($params);
                } else {
                    trace("插件 {$name} 入口文件不存在",'ADDONS','ERR');
                }

            }
        }
        if(APP_DEBUG) { // 记录钩子的执行日志
            trace('[ '.$hook.' ] --END-- [ RunTime:'.G($hook.'Start',$hook.'End',6).'s ]','','INFO');
        }
    }else{ // 未注册任何钩子 返回false
        return false;
    }
}
#实例化一个插件类,我们有addons()函数,传入插件名即可,大小写区分
$addons_class = $addons($addon_name);
这个方法就是去执行钩子处挂载的和钩子同名的插件方法。

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

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

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

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

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

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

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

后台中,插件默认会在 插件列表里出现。默认没有安装过的会显示在前面。基本的数据字段都是读取的插件类里的info属性数组。插件未安装的时候是不可以设置的。

插件不一定有设置页面。必须插件目录里有一个config.php并且返回一个非空的数组才会显示。设置页里有显示。

拿Editor插件的配置文件举列子:<?php
// +----------------------------------------------------------------------
// | OneThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.onethink.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: yangweijie <[email protected]> <code-tech.diandian.com>
// +----------------------------------------------------------------------

    return array(
        'editor_type'=>array(
            'title'=>'编辑器类型:',
            'type'=>'select',
            'options'=>array(
                '1'=>'普通文本',
                '2'=>'富文本',
                '3'=>'UBB解析',
                '4'=>'Markdown编辑器'
            ),
            'value'=>'1',
        ),
        'editor_wysiwyg'=>array(
            'title'=>'富文本编辑器:',
            'type'=>'select',
            'options'=>array(
                '1'=>'Kindeditor',
                '2'=>'Ueditor(百度编辑器)',
            ),
            'value'=>1
        ),
        'editor_height'=>array(
            'title'=>'编辑器高度:',
            'type'=>'text',
            'value'=>'300px'
        ),
        'editor_resize_type'=>array(
            'title'=>'是否允许拖拉编辑器',
            'type'=>'radio',
            'options'=>array(
                '0'=>'不允许',
                '1'=>'允许'
            ),
            'value'=>'1',
            'tip'=>'ubb和markdown编辑器不支持此功能'
        ),
    );
数组的每个键都对应一个form表单。键名就是配置里会显示的表单名 并且为了防止冲突加了前缀 ,比如上面的editor_type 会显示为config[editor_type]。title是字段前面的标识字。type是form标准的type,出了一个扩展的group。待会单独讲group。然后有多个选项的会有options键和值是相应选项的数组。值里每个键是选项的value后面的值是显示的label文字。value字段是该表单项的默认值。 tip是表单项里面的灰色提示文字。

具体效果见下图:

实际解析的方法在Admin/View/Addons/config.html里。
Group类型是我为了插件中会去配置多个同类型事物的配置,每次要加前缀导致的混乱。正好用到后台的tab。 每个group里值options就是多个配置分组tab。然后分组显示名是其title。然后options里是每个配置的复合数组。那个写法和之前单独的配置一样。 代码如下:return array(
    'comment_type'=>array(//配置在表单中的键名 ,这个会是config[random]
        'title'=>'使用类型:',     //表单的文字
        'type'=>'select',         //表单的类型:text、textarea、checkbox、radio、select等
        'options'=>array(         //select 和radion、checkbox的子选项
            '1'=>'有言',         //值=>文字
            '2'=>'多说',
        ),
        'value'=>'1',             //表单的默认值
    ),
    'group'=>array(
        'type'=>'group',
        'options'=>array(
            'youyan'=>array(
                'title'=>'友言配置',
                'options'=>array(
                    'comment_uid_youyan'=>array(
                        'title'=>'账号id:',
                        'type'=>'text',
                        'value'=>'90040',
                        'tip'=>'填写自己登录友言后的uid,填写后可进相应官方后台'
                    ),
                )
            ),
            'duoshuo'=>array(
                'title'=>'多说配置',
                'options'=>array(
                    'comment_short_name_duoshuo'=>array(
                        'title'=>'短域名',
                        'type'=>'text',
                        'value'=>'',
                        'tip'=>'每个站点一个域名'
                    ),
                    'comment_form_pos_duoshuo'=>array(
                        'title'=>'表单位置:',
                        'type'=>'radio',
                        'options'=>array(
                            'top'=>'顶部',
                            'buttom'=>'底部'
                        ),
                        'value'=>'buttom'
                    ),
                    'comment_data_list_duoshuo'=>array(
                        'title'=>'单页显示评论数',
                        'type'=>'text',
                        'value'=>'10'
                    ),
                    'comment_data_order_duoshuo'=>array(
                        'title'=>'评论显示顺序',
                        'type'=>'radio',
                        'options'=>array(
                            'asc'=>'从旧到新',
                            'desc'=>'从新到旧'
                        ),
                        'value'=>'asc'
                    )
                )
            )
        )
    )
);
具体效果可以看社会化评论(SocialComment)插件的配置数组。见下图:

当然如果你觉的这样解析不够你的效果。你可以在Addons定义类里加个custom_config属性。其值是配置页会包含的模板。然后你可以使用各种js特效样式了。这个可以看返回顶部的配置页(wwwroot/Addons/ReturnTop/config.html)。

同理我们为方便显示插件后台列表,也做了一系列规定,插件类里加了个adminlist属性。然后定制模板的类属性是custom_adminlist 这个你们可以将Attachement 附件 插件的那个属性去了看有显示,加上也有显示。 具体实现可以看AddonsController里的adminlist方法和对应模板。

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

插件开发

首先清楚插件就是符合一定规范拥有一定目录结构的类文件。

然后清楚自己要开发的插件的要求,在什么位置显示、是否有配置文件、是否需要外部url访问、是否需要后台显示。

接下来我们就用后台快速开发一个Example插件示范。

首先,进入后台-》扩展-》插件列表-》快速创建。会出现一个如下的页面。


由于仅作简单的示范,我们不需要配置文件和外部控制器,都用默认的。钩子我们选‘documentDetailAfter’

可以点“预览”按钮看下将要生成的类文件,如下图:


点确定后,出现“创建成功”,并且列表里出现“示列”这个插件表示插件已经创建成功了。

然后我们可以看见生成的类文件 documentDetailAfter方法里什么都没有。我们在里面输出点文字,见下图:


然后后台安装下,进入前台文章



其实和我们开发widget方法没啥太大区别。只不过这里前台用hooks函数调用类里的钩子方法。

因为本产品使用了命名空间,所以插件类的引入需要通过命名空间的写法,才能正确自动加载类。而我们的快速创建就是为你们指定好插件定义信息后,自动创建一些规范的目录文件。所以你们只需要考虑插件的一些信息即可。

如果需要外部url 访问插件里的控制器。需要用addons_url('插件名://控制器名/方法') 这样访问控制器。控制器里必须继承Home/AddonsController类。不能继承后台的。那个里面很多方法插件用不到,是为后台所定制的。要使用,只需在创建时候勾选'是否需要外部访问',就会自动创建控制器目录和文件,还有Model目录。然后自己按需修改吧。

如果插件需要后台显示,可以勾上'是否需要后台列表',然后配置下列表显示需要的数组参数,如果需要制定模板,可以写下custom_adminlist文本域,指定列表页的模板。然后可以在那里二次开发,比如弄个弹窗更新部分数据什么的。

插件注意点

1. 插件的基础是物理文件,所以插件类的结构一定要和官方一致。开发阶段不要随便删除文件,导致系统运行不正常。要删除插件请先后台卸载插件后再删除物理文件,否则钩子表有脏数据或者叫未挂载的插件。
2. 插件的资源文件的存放,首先插件本身应当保存原始静态资源,然后 安装的时候,通过install 方法 移动到wwwroot/Public/Home/Addons/插件目录下,然后用__PUBLIC__常量拼好资源路径去获取。
3. 整个产品的上传目录在/Uploads下,编辑器统一上传到下面的Editor目录里。并且配置好上传后缀,比如只允许图片的后缀,发布时没定义后缀,被人上传了phg文件篡改了系统文件,是个严重的漏洞。安全由二次开发者自己把握。
4. 钩子里挂载的插件是可以排序和删除的。如后台首页钩子的编辑页,见下图:

所以当一个插件安装失败了,钩子上写入了该插件,没事直接去钩子配置里删除整个插件挂载就行了。
而且同一个展示的钩子上挂载了多个插件,直接后台排个序更新下就生效执行的顺序了。不用去数据库改了,方便吧。

5. 后台首页的钩子,上样式是公用的,如果想扩展这样的插件,直接复制SystemInfo插件改改类名和显示内容就可以了,改好安装就行了。显示宽度配置里可以设置1、2、4格子。以后会提供刷新接口和最小化js函数。等正式版发布了,同步对比下那个插件就好了。

6. 扩展这块属于开发人员负责,所以钩子的编辑更新,只有超级管理员可以管理,其他人没有权限编辑的。乱加、乱删都会影响系统稳定性和效率。

7. 注意插件类里assign复制的变量在真个程序模板中有和相应页面控制器的模板的一样的作用域,因此会存在变量被污染的情况,就是我在插件里赋值一个data,在插件前的控制器里赋值data变量就被插件污染了,会造成开发上很大的困惑,因此我们强烈建议大家给插件向模板赋值的时候加上addons_前缀,这样被污染的机率小很多了。
最佳答案
评论( 相关
后面还有条评论,点击查看>>