插件设计

您在开始论坛插件的设计之前,有必要了解一下我们所推荐的插件设计方式,更好的规范性和兼容性,将使得您设计的插件受到更多使用者的欢迎,对于程序员而言,也有助于形成良好的编码习惯,实现自身能力的提升。如果您有意编写 Discuz! 论坛插件,请按照先后顺序仔细阅读本文档。


准备工作

插件实现流程

    开始编写论坛插件,您应当首先对插件实现的流程有一个大致的了解,以下是我们推荐的插件编写流程:

    • 熟练使用 Discuz! 论坛系统后,对希望完善或补充的个性化功能进行评估,进而提出插件的功能需求。
    • 对插件做一个概括性的设计,例如:需要使用什么菜单、什么参数,配置哪些选项、数据结构如何设计、前后台实现哪些功能等等。
    • 阅读本文档并在系统设置中实际体验 Discuz! 插件接口所实现的功用,例如:您的插件应当如何设计才能良好的挂接到论坛系统中来。插件接口能够实现哪些功能、不能实现哪些功能,插件为此而需要做的优化、改造和取舍。
    • 编写相应程序代码和模板语句,实现所需的功能并进行代码测试、兼容性测试和代码改进。
    • 如果需要公开您的插件,可以用插件导出的方式,将插件配置信息导出到一个文本文件中,连同相应的程序和模板文件一同打包。同时,编写一个适合新手的插件的说明书也是必不可少的,其中包括:插件适用的 Discuz! 版本、功能概述、兼容性声明、安装方法、使用方法、卸载方法等等。
    • 将插件提供给他人,或自己使用,根据使用者反馈,对插件进行完善。插件实现流程至此结束。

文件命名规范

    Discuz! 按照如下的规范对程序和模板进行命名,请在设计插件时尽量遵循此命名规范:

    • 可以直接通过浏览器访问的普通程序文件,以 .php 后缀命名。
    • 被普通程序文件引用的程序文件,以 .inc.php 后缀命名。
    • 被普通程序文件,或引用程序文件引用的函数库或类库,以 .func.php(函数库) 或 .class.php(类库) 后缀命名。
    • 模板文件,以 .htm 后缀命名,模板文件只存在于 ./templates 目录中。
    • 模板语言包文件,以 .lang.php 后缀命名,语言包文件只存放于 ./templates 目录中,与模板文件同级目录。
    • 被编译后的模板文件,以 .tpl.php 后缀命名,前面的数字是模板套系的 ID,下划线后面的是模板原名,编译模板文件只存在于 ./forumdata/templates 目录中。
    • 动态缓存文件,存放于 ./forumdata/cache 目录中,依据不同的功用进行独立的命名。
    • 使用后台数据备份功能生成的备份文件,通常以 .sql 为后缀,存放于 ./forumdata/ 目录中。
    • 有些目录中存在内容为空白的 index.htm 文件,此类文件是为了避免 Web 服务器打开 Directory Index 时可能产生的安全问题。

common.inc.php 模块功能白皮书

    ./include/common.inc.php 是 Discuz! 的通用初始化模块程序,其几乎被所有的外部代码所引用,在您开始插件设计之前,可以先对该模块的大致功能做一定的了解。common.inc.php 主要完成了以下任务:

    • 对不同 PHP 及操作系统环境做了判断和兼容性处理,使得 Discuz! 可以运行于各种不同配置的服务器环境下。
    • 初始化常量 IN_DISCUZ 为 TRUE,用于 include 或 require 后续程序的判断,避免其他程序被非法引用。
    • 读取论坛所在绝对路径,存放于常量 DISCUZ_ROOT 中。
    • 加载所需的基本函数库 include/global.func.php。
    • 通过 config.inc.php 中提供的数据库账号信息,建立数据库连接。Discuz!支持数据表的前缀,如需获得表的全名,可使用”{$tablepre}tablename” 或 $tablepre.’tablename’的方式。
    • 判断用户是否登录,如登录标记 $discuz_uid 为非 0,同时将 $discuz_user(加了 slash 的用户名,可用于不加修改的插入数据库)、 $discuz_userss(原始的用户名,可用于页面显示)、$discuz_pw(用户密码的MD5串)、$discuz_secques(登录提示问题的加密串)等相应用户信息赋值。
    • 判断用户管理权限,将管理权限标记 $adminid 为 -1~3 中间的值。-1 代表为特殊用户组用户。0 代表普通用户;1 代表论坛管理员;2 代表超级版主;3 代表论坛版主。 将用户权限按照其所在的主用户组 ID 标记为 $groupid,相关权限从该 $groupid 所对应的系统缓存中读出(./forumdata/cache/usergroup_$groupid.php)。 将用户扩展权限按照其扩展用户组 ID 标记为 $extgroupids,中间以 \t(tab) 分隔,格式为“$groupid1\t$groupid2...”,扩展用户组用于确定用户的扩展浏览权限,例如能否访问某些有特殊权限设定的论坛等。
    • 读入系统设置中的各种变量,并根据 Cache 模块的设定,根据当前被调用的程序文件名(如 index.php,forumdisplay.php 等等)读入 相应的缓存代码。缓存代码被存放于 ./forumdata/cache/ 中。除了对应当前程序的缓存,可能还会加载一些通用的缓存数据,例如整个论坛 的设置(./forumdata/cache/cache_settings.php)、界面风格(./forumdata/cache/style_x.php)、当前用户的用户组(./forumdata/cache/usergroup_x.php)、 管理组权限(./forumdata/cache/adminusergroup_x.php)等。
    • 缓存数据的格式,大多是存放在 $_DCACHE['cachename'] 数组中,有些常用的参数,如系统设置中的参数、风格界面等,通常还被进行了 展开操作(extract)或使用常量进行赋值。
    • 用户如果处在登录状态,会自动读出 members 表相关用户的参数值,用户的个性设置参数:如时差、时间格式、界面风格等等,会根据实际 情况覆盖系统默认值,因此在后续程序通常不用再做判断。
    • 如果程序提交的 URL 中包含 tid=x 或 fid=x,common.inc.php 模块会自动读出其所对应的论坛记录及包括 access masks、版主设定等相应 权限,记录在 $forum 变量中。后续程序只要通过URL将tid或fid传递过来,便可通过 $forum 数组的存在性或相关参数来对论坛权限进行判断, 不需要再读 forums 表的资料。


插件接口概述

使用管理员账号登录 Discuz! 系统设置,在左侧菜单将可以看到“插件设置”和“插件管理”两个选项,使用超级版主或版主账号登录,将只出现“插件设置”一个选项。“插件管理”是控制插件打开与否、设计插件模块、菜单、参数和使用权限的地方,插件开发者可以依照设计意图,在此进行插件的初步设置,这里同时也提供插件导入和插件开关的功能,用于导入他人设计的插件和对插件的可用状态进行变更。“插件设置”是对已经安装的插件进行设置的地方,供使用者对插件参数进行调整以实现不同的插件功能。即前者主要面向开发者,后者主要面向使用者。

开始编写一个新插件,请首先在插件管理中,输入新插件的名称和惟一标识符。名称用于表明此插件的用途,例如设置为“虚拟银行插件”。惟一标识符用于在后续的插件模块中调用本插件,不可与现有插件重复,命名规则限制与 PHP 变量命名相同,虽然初次设置后仍可改动,但强烈建议一次性将此配置设置好,否则可能涉及到很多代码方面的变更,增加编码的麻烦。请注意:惟一标识符请不要设置的过短,或使用有可能与其他插件重复的命名,例如制作此插件的公司叫做 Comsenz Inc.,插件名称是“虚拟银行插件”,惟一标识符可设置为“comsenz_virtual_bank”,后面将以“虚拟银行插件”和“comsenz_virtual_bank”为例进行说明。

在插件管理中添加插件后,仅仅是增加了一条插件记录,后面还需要很多相关的设计和设置。在列表中选择插件的“详情”进入插件的详细设置。插件设置分为三个部分:

  • 插件基本设置:
    设置插件的基本参数,配置项目右边括号中的内容,为此设置对应的参数名称,调用方法将在后面的《参数读取与缓存控制》中详细说明。

  • 插件模块和自定义菜单:
    插件接口默认提供五种链接模块和四种程序模块:
    • 链接模块 导航栏项目:可在前台导航栏增加一个菜单项,可自主指派菜单链接的 URL,也可以调用插件的一个模块,模块文件名指派为“./plugins/插件目录/插件模块名.inc.php”。注意:由于引用外部程序,因此即便设置了模块的使用等级,您的程序如需权限判断,仍需要引用 common.inc.php 和插件相关的缓存文件(将在后面的《参数读取与缓存控制》中详细说明),并自行判断使用等级是否合法。
    • 链接模块 插件菜单项目:可在前台导航栏的插件子菜单中增加一个菜单项。
    • 链接模块 个人中心项目(我的):可在前台个人中心侧边上部增加一个菜单项。
    • 链接模块 个人中心项目(工具):可在前台个人中心侧边下部增加一个菜单项。
    • 链接模块 论坛帮助项目:可在前台帮助侧边增加一个菜单项。
    • 链接模块 管理面板项目(基本):可在前台版主管理面板侧边上部增加一个菜单项。
    • 链接模块 管理面板项目(工具):可在前台版主管理面板侧边下部增加一个菜单项。
    • 链接模块 后台菜单项目:可在后台插件设置中为此插件增添一个管理模块。
    • 程序模块 普通脚本:设置一个在“./plugins/插件目录/”目录中可直接调用的脚本。本模块也可以不定义,只要“./plugins/插件目录/插件模块名.inc.php”文件存在即可用“plugin.php?id=目录:插件模块名”方式调用。
    • 程序模块 全局包含:可设置一个在论坛所有页面均包含运行的脚本,此脚本在 ./include/common.inc.php 中加载,脚本文件名指派为“./plugins/插件目录/插件模块名.inc.php”。请注意,为了不导致错误的插件影响论坛运行,在 common.inc.php 加载此模块时,屏蔽了错误信息,因此请务必仔细检查是否存在语法错误,任何微小的语法错误都将不被提示出来,并且导致此模块不被正常加载。如果您配置了不正确的包含脚本而导致论坛系统设置无法使用,删除服务器上相应的脚本文件即可解决。
    • 程序模块 页面嵌入:设置一个包含页面嵌入脚本的模块,模块文件名指派为“./plugins/插件目录/插件模块名.class.php”。(页面嵌入将在后面的《页面嵌入模块开发》中详细说明)
    • 程序模块 特殊主题:设置一个特殊主题脚本的模块,模块文件名指派为“./plugins/插件目录/插件模块名.class.php”。(特殊主题将在后面的《特殊主题模块开发》中详细说明)
    您可以为每个模块设置不同的使用等级,例如设置为“超级版主”,则超级版主及更高的管理者(例如论坛管理员)可以使用此模块。

  • 插件钩子设置:
    插件钩子能够将插件代码埋藏在程序中的任意位置,从而实现更加灵活的插件功能,最大限度的减少对原有程序代码的修改。 论坛版本升级后,只需将相应钩子重新埋入相应程序中,原有插件即可继续使用。 关于插件钩子的具体设计方法请见下面的《插件钩子的设计》。

  • 插件变量配置:
    插件接口中提供了一个通用的插件配置管理程序,在大多数情况下可实现插件的参数配置,省却了插件开发者自行编写后台管理模块(即上面提到的“后台调用(后台菜单)”模块)的麻烦。通常情况下,应优先使用通用插件配置管理程序来实现插件的参数配置,只有在通用程序确实无法实现时,才自行编写后台管理模块。输入配置名称和配置变量名、选择合适的配置类型后,即可为此插件增加一个配置变量,点“详情”可以编辑此配置变量的更多信息。为了方便插件程序调用使用者配置好的参数,配置变量同样被存放在了缓存文件中,读取方法将在后面的《参数读取与缓存控制》中详细说明。

    注意:您只有在插件管理中将插件设置为“可用”,以上设置才能生效。


参数读取与缓存控制

编写插件程序时,可能需要读取一些插件的信息,如果插件需要使用者进行配置,还需要读取使用者设置的参数值。Discuz! 允许插件程序使用数据库读取和缓存读取这两种方法获取插件信息和参数。Discuz! 的插件接口已经对插件信息进行了合理的缓存,使用缓存读取的方式,将比数据库读取速度更快,消耗的资源更是几乎可以忽略不计。缓存读取唯一的局限是需要插件使用插件接口提供的通用后台管理程序。如果使用自定义后台模块的方式,需要后台模块将参数存放到 pluginvars 数据表中,才能被系统正常缓存。我们强烈推荐您通过缓存读取插件信息和配置数据。

插件数据结构

    插件数据使用两个数据表存放,分别是 plugins 和 pluginvars。前者用于存放插件信息:安装了多少个插件,就有多少条记录;后者用于存放插件的配置参数和配置值:所有已安装的插件总共有多少个配置项目,就有多少条记录。下面的表格列出了这两个表的主要字段及其用途说明。

    plugins 表:
    
    	pluginid	插件的惟一 ID,自动递增
    	available	插件是否可用,1=是,0=否
    	adminid		使用系统设置中插件接口自带的插件参数设置程序所需的最低权限等级要求,1=管理员,2=超级版主,3=版主
    	name		插件名称
    	identifier	插件惟一标识符
    	description	插件简介
    	datatables	插件数据表,不包含前缀,多个表使用半角逗号“,”分隔
    	directory	插件所在目录,例如设置为 comsenz_bank,则对应论坛目录的位置为 ./plugins/comsenz_bank/
    	copyright	插件版权信息
    	modules		插件模块信息,数组格式,使用 serialize() 序列化后存放
    

    pluginvars 表:
    
    	pluginvarid	插件配置的惟一 ID,自动地增
    	pluginid	本项配置所隶属的插件 ID
    	displayorder	本项配置的显示顺序,数值低的排在前面
    	title		插件配置的名称
    	description	插件配置的简介
    	variable	插件配置的变量名
    	type		插件配置的类型
    	value		插件配置的值
    	extra		当本项配置为“选择(select)”时,可选的取值范围
    

    如果您使用自行编写的插件后台管理模块进行插件参数配置,请尽量将配置项目按照 pluginid 的对应关系,将参数存储于 pluginvars 表中,这样系统就可以自动将您增加的配置参数缓存起来,以供插件程序进行调用。

插件参数读取

    了解了 Discuz! 插件存储的数据结构后,您可以在插件程序中根据需要选择合适的数据读取方式。由于数据库读取方式可以由数据结构推断而来,因此这里只介绍缓存读取的方式,这种方式是我们强烈推荐的插件数据读取方式。

    在管理者配置好插件信息,或用户进行插件的参数设置之后,系统将根据插件设置的惟一标识符,自动生成一个插件数据的缓存文件,例如惟一标识符为 comsenz_virtual_bank,则缓存文件位于 ./forumdata/cache/plugin_comsenz_virtual_bank.php,您可以打开此文件查看其中的数据内容和格式。缓存采用数组的方式进行存储,引用此文件即可将所需的插件参数一次性赋值。

    其中,$_DPLUGIN['comsenz_virtual_bank'] 这个数组下标,为插件的惟一标识符,所有插件缓存数据,一经被引用,就会赋值到 $_DPLUGIN 这个多维数组中。modules 描述了这个插件的模块信息;vars 描述了这个插件的配置变量,前面为变量名,后面为使用者赋予这个变量的值。


页面嵌入模块开发

  • 页面嵌入类型脚本格式
    <?php
    
    class plugin_identifier {
    
    	function HookId_1() {
    		......
    		return ...;
    	}
    
    	function HookId_2() {
    		......
    		return ...;
    	}
    
    	......
    
    }
    
    ?>
    
    identifier
    插件的唯一标识符,在插件设置中设置。
    HookId
    可用函数名以及被调用的脚本请看参照以下列表。这些函数将在 Discuz! 执行到 common.inc.php 的时候调用。如果要在模板输出前调用,需在函数名结尾加上“_output”。全局调用的函数(“global_”开头的)在模板输出前调用。
    如:viewthread_imicons() 在 common.inc.php 时调用,viewthread_imicons_output() 在模板输出前调用。
  • 预定义嵌入点列表
    函数名/嵌入点名/HookId对应脚本返回值类型
    index_headerindex.phpstring
    index_hotindex.phpstring
    index_navbarindex.phpstring
    index_topindex.phpstring
    index_middleindex.phpstring
    index_bottomindex.phpstring
    forumdisplay_headerforumdisplay.phpstring
    forumdisplay_forumactionforumdisplay.phpstring
    forumdisplay_modlinkforumdisplay.phpstring
    forumdisplay_topforumdisplay.phpstring
    forumdisplay_middleforumdisplay.phpstring
    forumdisplay_threadforumdisplay.phparray
    forumdisplay_bottomforumdisplay.phpstring
    memcp_sidememcp.phpstring
    profile_baseinfo_topprofile.phpstring
    profile_baseinfo_bottomprofile.phpstring
    profile_extrainfoprofile.phpstring
    profile_side_topprofile.phpstring
    profile_side_bottomprofile.phpstring
    viewthread_topviewthread.phpstring
    viewthread_fastpost_sideviewthread.phpstring
    viewthread_fastpost_contentviewthread.phpstring
    viewthread_profilesideviewthread.phparray
    viewthread_imiconsviewthread.phparray
    viewthread_sidetopviewthread.phparray
    viewthread_sidebottomviewthread.phparray
    viewthread_postheaderviewthread.phparray
    viewthread_posttopviewthread.phparray
    viewthread_postbottomviewthread.phparray
    viewthread_useractionviewthread.phpstring
    viewthread_postfooterviewthread.phparray
    viewthread_endlineviewthread.phparray
    viewthread_middleviewthread.phpstring
    viewthread_bottomviewthread.phpstring
    post_toppost.phpstring
    post_middlepost.phpstring
    post_bottompost.phpstring
    my_navextramy.phpstring
    global_header全局string
    global_footer全局string
    global_footerlink全局string

    以上预定义的嵌入点会在页面预置好的位置输出函数返回的内容。函数返回值类型如果是 array 且是空值的,必须输出一个空数组,如:
    return array();
    函数名并不限于以上列表,您可以自定义,只要符合以下规则,函数就会在适当的地方被调用。
    function CURSCRIPT_USERDEFINE[_output]()
    CURSCRIPT 指明了此函数是哪个脚本执行,USERDEFINE 可自定义,如果函数名以“_output”结尾则会在模板输出前调用,否则会在 common.inc.php 的时候调用。
    如:attachment_test() 函数会在下载附件的时候执行。
    “_output”结尾的函数的第一个参数为数组,含义为 array('template' => 要输出的模板名, 'message' => showmessage 的文字)
    如:以下函数将在登录的时候输出调试文字
    function logging_test_output($a) {
    	print_r($a);
    	print_r($_POST);
    }
    
    plugin_identifier 类中的其它函数为了便于阅读建议以“_”开头,如:
    <?php
    
    class plugin_sample {
    
    	function _updatecache() {
    		......
    		return ...;
    	}
    
    	function viewthread_posttop() {
    		......
    		return ...;
    	}
    
    	......
    
    }
    
    ?>
    


特殊主题模块开发

  • 特殊主题模块用于创建一个特殊主题,特殊主题类型脚本格式
    <?php
    
    class threadplugin_identifier {
    
    	var $name = 'XX主题';			//主题类型名称
    	var $iconfile = 'icon.gif';		//images/icons/ 目录下新增的主题类型图片文件名
    	var $buttontext = '发布xx主题';		//发帖时按钮文字
    
    	function newthread($fid) {
    		return ...;
    	}
    
    	function newthread_submit($fid) {
    
    	}
    
    	function newthread_submit_end($fid) {
    
    	}
    
    	function editpost() {
    		return ...;
    	}
    
    	function editpost_submit() {
    
    	}
    
    	function editpost_submit_end() {
    
    	}
    
    	function newreply_submit_end() {
    
    	}
    
    	function viewthread() {
    		return ...;
    	}
    }
    
    ?>
    
    identifier
    插件的唯一标识符,在插件设置中设置。
  • 函数名以及含义

    函数名含义
    newthread发主题时页面新增的表单项目,通过 return 返回即可输出到发帖页面中
    newthread_submit主题发布后的数据判断
    newthread_submit_end主题发布后的数据处理
    editpost编辑主题时页面新增的表单项目,通过 return 返回即可输出到编辑主题页面中
    editpost_submit主题编辑后的数据判断
    editpost_submit_end主题编辑后的数据处理
    newreply_submit_end回帖后的数据处理
    viewthread查看主题时页面新增的内容,通过 return 返回即可输出到主题首贴页面中


插件安装、卸载、升级脚本的设计

  • 安装、卸载
    插件作者可以设计 2 个脚本文件用于插件的安装和卸载,文件名任意。脚本中可用 runquery() 函数执行 SQL 语句,表名可以直接写“cdb_”。插件作者只需在导出的 XML 文件结尾加上安装、卸载脚本的文件名即可
    		<item id="installfile"><![CDATA[install.php]]></item>
    		<item id="uninstallfile"><![CDATA[uninstall.php]]></item>
    	</item>
    </root>
    
    安装、卸载程序中可随意设计页面的跳转,只要在插件安装、卸载结束时候输出添加以下代码即可。
    $finish = TRUE;
  • 升级
    插件作者可以设计一个脚本文件用于插件的升级,文件名任意。脚本中可用 runquery() 函数执行 SQL 语句,表名可以直接写“cdb_”。插件作者只需在导出的 XML 文件结尾加上升级脚本的文件名即可
    		<item id="upgradefile"><![CDATA[upgrade.php]]></item>
    	</item>
    </root>
    
    升级程序中可通过 $fromversion 和 $toversion 变量判断升级的具体版本号,并随意设计页面的跳转,只要在插件升级结束时候输出添加以下代码即可。
    $finish = TRUE;
    插件的当前版本号位于 XML 文件的以下分支中,可自行更改。
    	<item id="plugin">
    		......
    		<item id="version"><![CDATA[当前版本]]></item>
    		......
    	</item>
    
  • 授权协议、插件介绍
    插件在安装的时候您可以自定义授权信息文本,文本支持 Discuz 代码,站长同意后才能安装插件。如果插件存在后台管理界面或者变量配置,那么插件介绍文本会显示在插件后台页面中。插件作者只需在导出的 XML 文件结尾加上以下内容即可
    		<item id="license"><![CDATA[授权协议文本]]></item>
    		<item id="intro"><![CDATA[插件介绍文本]]></item>
    	</item>
    </root>
    
  • 模板、语言包
    插件中的语言包可以写到导出的 XML 文件结尾,这样在插件安装的时候会自动把语言包生成文件名为 forumdata/plugins/identifier.lang.php 的文件。插件开发时,可直接在后台开启语言包选项后编辑此语言包文件。
    		<item id="language">
    			<item id="scriptlang">
    				<item id="text"><![CDATA[脚本语言文字]]></item>
    			</item>
    			<item id="templatelang">
    				<item id="text"><![CDATA[模版语言文字]]></item>
    			</item>
    			<item id="installlang">
    				<item id="text"><![CDATA[安装语言文字]]></item>
    			</item>
    		</item>
    	</item>
    </root>
    
    scriptlang 为脚本文件的语言包,templatelang 为模版文件的语言包,installlang 为安装、升级、卸载脚本用的语言包。
    插件的模板文件可以放置在 plugins 目录下,如 plugins/hooktest/templates 子目录下。页面嵌入模块的脚本中可用以下方式调用此目录下的模板。
    include template('index_top', 'hooktest', './plugins/hooktest/templates');
    通过 plugin.php 调用的插件可直接用以下函数。
    include plugintemplate('index_top');
    插件模版文件中的语言包中通过 {lang identifier:langvar} 方式调用,例如:
    <!--{block return}-->
    {lang hooktest:text}
    <!--{/block}-->
    
    上例中对应的变量为语言包文件 forumdata/plugins/identifier.lang.php 中 $templatelang['hooktest']['text'] 的值。脚本中引用语言包无需包含语言包文件,可直接使用变量。如插件脚本中可用变量 $scriptlang['hooktest']['text'],安装脚本中可用变量 $installlang['hooktest']['text']。
  • 其他论坛数据导入
    插件安装时可以直接导入一个或多个论坛数据,这些论坛数据包括数据调用(request)、论坛方案(project)、表情(smilies)、风格(styles)的数据。在导出的 XML 文件结尾加上需要导入数据的类型和数据文件名即可,多个文件名用逗号(",")分隔。
    		<item id="importfile">
    			<item id="request"><![CDATA[discuz_request_test.xml,discuz_request_test2.xml]]></item>
    			<item id="project"><![CDATA[discuz_project_test.xml]]></item>
    			<item id="smilies"><![CDATA[discuz_smilies_test.xml]]></item>
    			<item id="styles"><![CDATA[discuz_styles_test.xml]]></item>
    		</item>
    	</item>
    </root>
    
  • 小提示
    在新插件内核中,通过 plugin.php 方式访问的插件可直接通过 plugin.php?id=xxx:yyy 方式调用而无需再在后台定义为普通脚本模块,只要 plugins/xxx/yyy.inc.php 文件存在即可。如果 xxxyyy 同名,可直接通过 plugin.php?id=xxx 方式访问。如果导出的 XML 文件名以 SC_GBK、SC_UTF8、TC_BIG5、TC_UTF8 结尾,显示的时候将直接显示为“简体”、“繁体”、“UTF8”等字样。

编写插件的原则与注意事项

请在您动手编写插件之前,还需要仔细的阅读以下原则,遵循这些原则,将有效的避免可能发生的问题:

  • 所有与插件的程序,包括其全部的前后台程序,请全部放入 ./plugins 目录中,同时在插件的安装说明中指出,插件的文件需要复制到哪些目录。为了避免与其他插件冲突,请尽量建立 ./plugins 下的子目录,并将插件程序放置于子目录下,这样您编写的插件将获得更好的兼容性。
  • 如果您的插件包含“前台调用(前台菜单)”模块,该模块将统一用 plugin.php?identifier=xxx&module=yyy 的方式调用,请在相应链接、表单中使用此方式。其中 xxx 为插件的惟一标识符,yyy 为模块名称。前台插件外壳程序 plugin.php 已经加载了通用初始化模块(./include/common.inc.php),不需再次引用。
  • 如果您的插件包含“后台调用(后台菜单)”模块,该模块将统一用 admincp.php?action=plugins&identifier=xxx&mod=yyy 的方式调用,请在相应链接、表单中使用此方式。其中 xxx 和 yyy 的定义与“前台调用(前台菜单)”模块中的相同。系统还允许用 admincp.php?action=plugins&edit=$edit&mod=$mod 的方式来生成链接和表单地址,$edit 和 $mod 变量已经被插件后台管理接口赋值,因此将这两个变量值带入 URL 中也是被支持的。由于后台模块是被 admincp.php 调用,因此已加载了通用初始化模块(./include/common.inc.php)并进行了后台管理人员权限验证,因此模块程序中可直接写功能代码,不需再进行验证。
  • 请勿绕过插件的前后台外壳(plugin.php 和 admincp.php)而以直接调用某程序的方式编写插件,因为这样既导致了用户使用不便,代码冗余和不规范,同时又产生了因验证程序考虑不周到而带来的安全隐患。您可以在任何地方,包括链接、表单等处方便的使用上述 URL 地址对插件模块进行调用。
  • 所有与插件有关的程序,包括全部的前后台程序,因全部使用外壳调用,请务必在第一行加入
    
    	if(!defined('IN_DISCUZ')) {
    		exit('Access Denied');
    	}
    
    以免其被 URL 直接请求调用,产生安全问题。
  • 一般情况下,您发布插件请使用插件导出的功能,以方便使用者一次性导入插件的配置数据,极特殊的情况下,也可以分步骤告知使用者如何进行插件配置管理和安装此插件。
  • 如果功能独立,请尽量使用单独程序的方式编写插件(即外挂型插件),而尽量少的对论坛本身代码进行修改,这将为使用者今后的升级带来很大方便。
  • 您可以修改 Discuz! 本身的数据结构,但更推荐在不很影响效率的前提下将插件数据用另外的数据表存储,因为不能排除您增加的字段或索引和今后版本 Discuz! 核心数据字段重名的可能。在任何情况下,请不要删除 Discuz! 标准版本数据结构中已有的字段或索引
  • 请在插件说明书中对插件做以详尽的描述,例如增加了哪些字段、哪些表,修改了或新增了哪些程序,版本兼容性,后续支持的提供方式(例如不提供支持,或以什么样的方式提供)。如果方便,请尽可能提供插件的卸载方法,例如去除哪些字段、删除哪些新增的程序、将哪些被插件修改的程序恢复原状等等,使用者会感激您为此付出的辛勤劳动,甚至愿意支付相应的费用支持您未来的发展。
  • 如果插件使用另外的数据表存储,请在插件管理中准确的设置插件所使用的数据表名称(不包含前缀),这样用户在备份数据的时候,能够把插件数据一同备份。
  • Discuz! 自 4.0.0 版本起,内置了 8 种自定义积分,存储于 members 表中的 extcredits1 至 extcredits8 字段中,类型为有符号整数,您可以在引用 common.inc.php 后,在 $extcredits 或 $_DCACHE['settings']['extcredits'] 中读取 8 种积分的启用信息(详情请参考 ./forumdata/cache/cache_settings.php)。插件程序中如需更新用户积分,可直接 UPDATE 相应的积分字段,无需其他操作。

插件钩子的设计

插件钩子的设计,需要您具有一定编程基础,比较了解 Discuz! 论坛程序的结构,并能够使用 PHP 语言撰写代码。对于普通用户,可以略过以下内容。 插件钩子(以下简称“钩子”)属于插件的一部分,因此在设计钩子之前应当首先进入后台——插件管理,新增插件或者编辑一个现有的插件,即可看到相关设置。

钩子的添加

  • 钩子名称:在一个插件内,钩子的名称是唯一的,不可重复。名称可以由英文字母、数字和“_”组成,不支持中文,最长255个字符。为了便于理解和记忆钩子的作用, 名称应当尽量简洁清晰,能够表述一定的含义。注意:钩子名称对字母大小敏感,例如:Index_start 和 index_start 将视为两个不同的钩子。
  • 钩子描述:对钩子的详细说明,如功能介绍、调用方法、使用方法等。
  • PHP代码:这里是钩子的核心内容,也是一段PHP代码,需要您自行设计,完成钩子需要处理的数据或者需要执行的操作。
  • 可用:每个插件允许有多个钩子,您可以自由选择关闭或者开启某个钩子。

钩子的删除

    在钩子管理界面,您可以随时删除某一个钩子。 钩子删除以后,您应当及时修正放置钩子的程序文件,清除钩子标记,以免影响您论坛的正常使用。

钩子的编辑与升级

  • 编辑:插件设计阶段,您可能需要随时编辑钩子,每次更改,系统会自动更新缓存文件,您可以立即看到更改的效果。如果是更改钩子名称,那么您可能需要调整钩子放置的程序,修改钩子调用的名称。
  • 升级:论坛程序进行升级之前,您应当使用插件的导出功能,导出插件备份。论坛升级并正常运行后,再导入插件备份,修改相关程序,重新安放钩子。

钩子的放置与调用

    钩子设计完成以后,您需要在相应的程序中安放钩子,不同钩子由于作用的不同,放置的位置也是不同的。 安放钩子,您仅仅需要将钩子的调用代码放入即可。调用代码格式如下:
    
    	 eval($hooks['插件唯一标识符(identifier)_钩子名称']);	  

    例如:调用 插件demo 的钩子 testhook, 我们需要在程序中适当的地方加入下面的代码
    
    	eval($hooks['demo_testhook']); 

设计范例

    此处我们为您提供一个使用钩子技术的插件范例,完成功能十分简单,旨在使您直观的了解钩子的使用。此范例实现了当游客访问论坛首页时,强制用户登录的功能。

    • 进入系统设置中的插件管理,添加一个名称和惟一标识符均为 demo 的插件。
    • 编辑插件 demo 详情,找到“插件钩子设置”,添加一个名为 index_force_loggedin 的钩子,然后提交。
    • 编辑钩子 index_force_loggedin,按如下内容填写设置:
      • 钩子描述:当游客访问论坛首页的时候,程序会弹出提示框,并自动跳转到登陆页面。
      • PHP代码:
        
        	if(!$discuz_uid) {
        		showmessage('请您登陆后访问本站,现在将转入登录页面。', 'logging.php?action=login');
        	}
        
    • 提交后,回到此插件的设置页面。将钩子 index_force_loggedin 设置为可用。
    • 点击左侧导航,进入插件管理,将插件 demo 设置为可用。
    • 编辑论坛程序 index.php 安放钩子,方法如下:
      • 查找代码:
        
        	$discuz_action = 1;
        

      • 在上述代码下放添加代码:
        
        	eval($hooks['demo_index_alter']);
        
    • 保存文件后将文件上传到服务器。
    • 至此,一个简单的使用钩子实现的插件已经彻底完成。您可以退出登陆并访问论坛首页,测试一下插件的效果。

Discuz! 插件的钩子技术,为广大的插件开发者提供了一个更加灵活的插件设计机制。当 Discuz! 升级后,用户只需重新将钩子调用代码安放到程序中原来的位置,就几乎可以继续使用原来已安装的插件,降低了对于程序修改的幅度和插件安装的难度,更加有利于插件程序的规范、管理、维护、相互交流。因此我们强烈建议插件开发者能够深入研究并应用这一机制,创作出越来越多的优秀插件。

意见反馈

插件接口是 Discuz! 开发组为了方便插件设计、安装和使用而专门开发,虽然经过长期的优化和改进,可能仍然会有不够合理或不够完善的地方,欢迎各位插件程序员在使用此接口的过程中,为我们提出意见和建议,感谢您的支持。