开放平台
首页
我的应用
我的分销
开发文档
技术社区
魔方加密
WitFrame
注册
登录
资料设置
消息提醒
财务管理
密码安全
身份认证
退出
新增插件接口
<div class="tbmu bbda mbm"><a href="?ac=document&page=dev_x5_index">结构概述</a><span class="pipe">|</span><a href="?ac=document&page=dev_x5_app">App 扩展</a><span class="pipe">|</span><a href="?ac=document&page=dev_x5_plugin" class="a">新增插件接口</a><span class="pipe">|</span><a href="?ac=document&page=dev_x5_template">新增模板接口</a><span class="pipe">|</span><a href="?ac=document&page=dev_x5_witframe">云插件</a></div> [TOCM] ## 新增系统函数 ### DISCUZ_PLUGIN() X5.0 中新增了 DISCUZ_PLUGIN() 函数,代替类似“/source/plugin/xxx”目录的写法,例如: ```php require_once DISCUZ_PLUGIN('xxx').'/index.php'; ``` 提示:如果您的插件要同时兼容 Discuz!ᵂ 您必须使用此函数,但是,涉及前端调用的路径写法是 URI 的一部分,而非后台目录文件调用,无需修改。 ### DISCUZ_TEMPLATE() X5.0 中新增了 DISCUZ_TEMPLATE() 函数,代替类似“/template/xxx”目录的写法,例如: ```php $f = DISCUZ_TEMPLATE('xxx').'/data.htm'; ``` 提示:如果您的插件要同时兼容 Discuz!ᵂ 您必须使用此函数,但是,涉及前端调用的路径写法是 URI 的一部分,而非后台目录文件调用,无需修改。 ## 新增系统常量 ### DISCUZ_DATA X5.0 中新增了 DISCUZ_DATA 常量,代替类似“/data”目录的写法,例如: ```php $lock = DISCUZ_DATA.'cache/data.php'; ``` 提示:如果您的插件要同时兼容 Discuz!ᵂ 您必须使用此函数。由于 Discuz!ᵂ 中所有永久性文件都会存入对象存储,因此 DISCUZ_DATA 目录禁止写入永久文件,否则审核员有权将应用打回。 ### DISCUZ_ROOT_STATIC 对于 Discuz!ᵂ,由于 /data 目录禁止写入,您可以把永久文件写入到此目录中,它有以下特性: - 此目录与 DISCUZ_ROOT 意义相同,但只有存放的静态文件(md、txt、js、css、json、xml、图片等)可对外展示; - 如保存的文件和实际产品文件及路径相同,不会产生覆盖效果,也不会对外展示; - 此目录占用的空间将计算到 Discuz!ᵂ 用户的平台存储中; 提示:此常量只针对 Discuz!ᵂ 有实际作用,X5.0 开源版此常量与 DISCUZ_ROOT 相等。 ## 多语言 Discuz!ᵂ 原生支持多语言内核,您只需自定义多语言 key 并设置此 key 对应的语言包文件路径即可 ### 设置自定义语言 ```php i18n('set', 'mylang', DISCUZ_ROOT.'./i18n/en'); ``` 设置多语言 mylang 的语言包文件路径为 DISCUZ_ROOT.'/i18n/en' 目录,支持插件路径 ### 读取设置的自定义语言列表 ```php i18n('get'); ``` 把设置的语言 key 赋值给 $_G['cookie']['i18n'] 即可实时切换语言 其他第三方插件若要兼容 i18n 对应的语言 key,参考此范例 https://gitee.com/Discuz/DiscuzXPluginSample/tree/master/sample/i18n/mylang ### 语言包文件 X5.0 的插件文件中可以直接写语言包文件,而不必写在 XML 中,之前写在 XML 中的语言包在更新缓存后会自动写入到语言包文件中,语言包文件位于为 /source/plugin/myplugin/i18n/SC_UTF8/lang_plugin.php ```php $scriptlang['myplugin'] = [ 'nice' => '不错!', ]; $templatelang['myplugin'] = [ 'nice' => '不错!', ]; $systemlang['myplugin'] = [ 'nice' => '不错!', ]; $installlang['myplugin'] = [ 'name' => '插件名称', ] ``` XML 中的 name、license、description、intro、copyright、version、var.title、var.description、var.extra、modules.menu 可以写进 $installlang 中 同时,语言包也兼容 i18n,可以针对相应的 i18n 语言定制额外语种的语言包,兼容 i18n 的语言包文件位为 /source/plugin/myplugin/i18n/en/lang_plugin.php,我们统一约定用2个字母的缩写代表各个语言,例如 en、de、fr 等等 ### JS 语言包 JS 语言包为 X5.0 新增的语言包类型,文件名为 lang_js.php,默认语言包位于 /source/plugin/myplugin/i18n/SC_UTF8/ 目录下,语言包内容参考以下格式: ``` $lang = [ 'jslang' => 'JS 语言包', ]; ``` 所有调用了 common.js 的页面中,在您的 JS 文件中通过 $L('jslang') 方式调用 JS 语言包: ``` function sample_notice() { alert($L('jslang')) } ``` ## 自定义后台菜单 这是 Discuz!ᵂ 中的新增内容,插件无需添加 menu 扩展,即可实现菜单的添加与删除 **您所添加的菜单请务必在卸载插件后清理完毕** ### 准备工作 如果在非安装、卸载脚本中使用,最好是判断下方法是否存在 示例: ```php if(!function_exists('set_admin_menu')){ include_once libfile('function/plugin'); } ``` ### 添加菜单 完整示例: ```php if(!function_exists('set_admin_menu')){ include_once libfile('function/plugin'); } $menus = array( array('侧边菜单1','plugins?id=1'), array('侧边菜单2','plugins?id=2'), ); set_admin_menu('顶部菜单名称', $menus); ``` ### 删除菜单 完整示例: ```php if(!function_exists('remove_admin_menu')){ include_once libfile('function/plugin'); } remove_admin_menu('顶部菜单名称');//单独删除 remove_admin_menu(array('顶部菜单名称1','顶部菜单名称2'));//批量删除 ``` ### 自定义新平台 这是 Discuz!ᵂ 中的新增内容,开发者可直接添加一个平台管理中心,在这里,您可以完全设计整个后台的菜单以及界面。同时在“站长”->“多平台管理” 站长还能自行调整。 ### XML 格式规范 这是平台配置 XML 文件的模板: ```xml <?xml version="1.0" encoding="ISO-8859-1"?> <root> <name><![CDATA[新平台]]></name> <title><![CDATA[新平台]]></title> <framecss><![CDATA[xxx.css]]></framecss> <pagecss><![CDATA[ccc.css]]></pagecss> <logo><![CDATA[<a class="logo"><img src="static/image/admincp/logo.svg"></a>]]></logo> <navbar><![CDATA[<form></form>]]></navbar> <defaultId><![CDATA[默认首页的menuId]]></defaultId> <menu> <menuId>主菜单1</menuId> <sub> <subId>action_operation_do1</subId> <title>子菜单1</title> </sub> <sub> <subId>action_operation_do2</subId> <title>子菜单2</title> </sub> </menu> <menu> <menuId>主菜单2</menuId> <sub> <subId>plugin_id:pmod1</subId> <title>子菜单3</title> </sub> <sub> <subId>plugin_id:pmod2</subId> <title>子菜单4</title> </sub> </menu> <userdef><![CDATA[1]]></userdef> </root> ``` - name:平台名称,在多平台列表中展示的名称; - title:平台网页标题,显示在浏览器标题栏中的名称; - location: 跳转 URL,如果只想在多平台菜单中添加一个跳转链接,而不使用 X5 自带的后台时可使用此参数 - logo:左上角 LOGO 区域 HTML; - navbar:右侧搜索区域 HTML; - framecss:框架页 CSS,可直接写 CSS 代码,也可以写文件 URL (.css 结尾); - pagecss:内容页 CSS,规则同 framecss; - menu:主菜单节点,多个主菜单按照顺序书写; - menuId:菜单 ID、外显名称,可写语言包名或中文; - sub 子菜单节点; - subId:子菜单 ID,格式“action_operation_do”,action、operation、do 为相应页面 GET 参数拼接的内容,opeartion、do 可省略;如果是跳转到插件页面,格式为 “plugin_id:pmod”,id 为插件唯一标识符 ID,pmod 为后台模块的参数,pmod 可省略; - title:外显名称,可写语言包名或中文; - type:1=区域开始,2=区域结束; - showMethod:显示此菜单项的条件,外调方法,系统值不建议修改。插件用 id::method 方式调用具体方法,系统会调用 plugin/id/ 中 class platform_id 的静态方法 method(); - listMethod:菜单显示内容由具体方法返回,格式同 showMethod; - subPerms:当前菜单项关联的其他子菜单 ID,多个用逗号“,”分割, ID 格式见 subId; ### 添加平台 ```php $xml = '<?xml version="1.0" encoding="ISO-8859-1"?> ... '; menu::platform_add('test', $xml); // test 为平台标识,$xml 为配置数据 ``` ### 删除平台 ```php menu::platform_del('test');// test 为平台标识 ``` ### platform.class.php 详解 开发者需书写一个 platform.class.php 脚本 plugin/myplugin/platform.class.php 完整示例: ```php class platform_myplugin { // myplugin:showMethodSample public static function showMethodSample() { return true; } // myplugin:listMethodSample public static function listMethodSample() { return array( array('menu_members_edit', 'members_search', 0, '', '', array('members_clean', 'members_repeat')), array(cplang('nav_home'), '', 1, 'homestatus'), array('menu_maint_doing', 'doing', 0, 'doingstatus'), array('menu_maint_share', 'share', 0, 'sharestatus'), array(cplang('nav_home'), '', 2, 'homestatus'), ); } } ``` showMethodSample 为 showMethod 的范例,返回 true/false 即可,true 时菜单项显示 listMethodSample 为 listMethod 的范例,返回的内容会替换掉相应位置中的数据。其中返回的数组与 XML 中 sub 下节点的对应关系为: 0:title 1:subId 2:type 3:showMethod 4:listMethod 5:subPerms ## 第三方登录 由于 X5.0 增加了全新的账号管理系统,通过此全新的账号平台,开发者可很方便的开发出自己的登录系统 第三方登录类固定的文件为 /acccount.class.php,例如插件目录为 sample,那么文件为 /source/plugin/sample/account.class.php ```php class account_sample extends account_base { // 不自动生成头像 public bool $interface_noAutoAvatar = true; // 不支持绑定 public bool $interface_noBind = false; public function __construct() { $this->conf = parent::getConfig('plugin_sample'); } public static function name() { return '测试登录'; } public static function icon() { return '<svg t="1715821283732" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28056" width="20" height="20"><path d="M840.7 626.2c-33.3 33.3-144 23.5-144 23.5s-9.7-110.7 23.5-144c33.3-33.3 77.3-0.5 77.3 43.2 44.3 0 76.5 44 43.2 77.3zM611.2 560.7c-29.7 0-53.9-24.3-53.9-53.9V245.3c0-29.7 24.3-53.9 53.9-53.9 29.7 0 53.9 24.3 53.9 53.9v261.5c0 29.7-24.3 53.9-53.9 53.9zM226.3 614.7c-29.7 0-53.9-24.3-53.9-53.9V338.5c0-29.7 24.3-53.9 53.9-53.9 29.7 0 53.9 24.3 53.9 53.9v222.2c0.1 29.7-24.2 54-53.9 54zM482.9 551.5c-29.7 0-53.9-24.3-53.9-53.9V121.2c0-29.7 24.3-53.9 53.9-53.9 29.7 0 53.9 24.3 53.9 53.9v376.3c0 29.7-24.2 54-53.9 54zM354.6 571.5c-29.7 0-53.9-24.3-53.9-53.9v-337c0-29.7 24.3-53.9 53.9-53.9 29.7 0 53.9 24.3 53.9 53.9v337c0.1 29.7-24.2 53.9-53.9 53.9zM445.7 960c-67.4 0-130.8-26.2-178.4-73.9-47.7-47.7-73.9-111-73.9-178.4 0-18.2 14.8-33 33-33s33 14.8 33 33c0 102.8 83.6 186.4 186.4 186.4 102.8 0 186.4-83.6 186.4-186.4 0-18.2 14.8-33 33-33s33 14.8 33 33c0 67.4-26.2 130.8-73.9 178.4S513.1 960 445.7 960z" fill="#E95431" p-id="28057"></path></svg>'; } // 通知发送 public function notificationAdd($touid, $note, $notestring) { } // 用于自动登录,判断是否在环境中 public function inEnv() { return isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'xxx'); } // 登录(绑定)跳转 public function login($referer = '', $op = 0) { if(empty($_POST)) { echo <<<EOF <form method="post" action="$_SERVER[PHP_SELF]?method=plugin_sample"> User:<input name="user"><br /> Pass:<input name="pass" type="password"><br /> <input type="submit"> </form> EOF; } else { $data = array( 'test1' => array('pass' => '1', 'uid' => 'sampleUid1'), 'test2' => array('pass' => '1', 'uid' => 'sampleUid2'), ); if(!isset($data[$_POST['user']])) { echo 'User not found'; exit; } if($data[$_POST['user']]['pass'] != $_POST['pass']) { echo 'Password error'; exit; } $callback = $this->conf['callbackUrl'].'&uid='.$data[$_POST['user']]['uid']; echo '<a href="'.$callback.'">callback</a>'; } exit; } // 登录回调,/api/account/callback.php?id=sample 会调用此方法 public function getLoginUser() { global $_G; $account = new account(); $param = array( 'type' => 'plugin_sample', 'atype' => parent::getAccountType('sample'), 'account' => $_GET['uid'], 'bindname' => $_GET['uid'], ); if(!$account->checkUser($param)) { if($_G['uid']) { $account->userBind($_G['uid'], $param); } else { $param += array( 'username' => $_GET['uid'], 'password' => '', ); $msg = $account->userRegister($param); if($msg) { if($msg == 'profile_username_duplicate' || $msg == 'profile_email_duplicate') { dheader('Location: '.$_G['siteurl'].'?index=member&mod=register'); } showmessage($msg); } } } else { $account->userLogin(); } dheader('Location: '.$_G['siteurl']); } // 管理后台 public function admincp() { global $_G; $conf = $_G['setting']['account_plugin_confs']['sample']; if(!submitcheck('submit')) { showformheader('account&method=plugin_sample'); ... showformfooter(); } else { ... cpmsg('setting_update_succeed', 'action=account&method=plugin_sample', 'succeed'); } } } ``` 第三方登录插件需要在插件安装的时候通过以下方法注册: ``` account_base::registerAccount('sample'); ``` 卸载的时候取消注册: ``` account_base::unregisterAccount('sample'); ``` ## DIY 模块 X5.0 允许通过插件拓展 DIY 的模块,您需要把 DIY 模块文件写到 /source/plugin/myplugin/block 目录下即可,目录中文件的内容同系统 /source/class/block。 ## 子文件 由于 X5.0 用子文件 /source/child 代替了原先的 /source/include 目录,因此您可以通过注册接管的方式完全代替任何一个系统子文件的原始调用,您需要把子文件写到 /source/plugin/myplugin/child 目录下。 ```php <?php //childfile:forum/viewthread/postarr if(!defined('IN_DISCUZ')) { exit('Access Denied'); } ... ``` 文件中添加 //childfile:...... 格式的注释,注册需要接管子文件。您可以在自己接管的子文件中,完全代替系统原有的逻辑,也可以继续调用系统原有逻辑,并在其之前、之后调用自己的逻辑。 forum/viewthread/postarr 表示接管 /source/app/forum/child/viewthread/postarr.php 文件。 global/core/ip 表示接管 /source/child/core/ip.php 文件。 友情提示:由于子文件接管的特殊性,同一个子文件有且只有一个插件接管。admin/* 禁止接管。 秘籍提示:更多全局 childfile 请搜索 'global/core',都可以用插件接管哦! ### 后台二次校验脚本 后台二次校验脚本为一个特殊的 childfile,插件可通过接管 global/adminvalidate/[name] 实现后台的二次校验,加强后台的安全 在 /source/plugin/myplugin/child/ 目录下添加一个脚本 ``` <?php //childfile:global/adminvalidate/xxxx //二次登录逻辑 ``` 详细范例见 sample 插件。 后台二次校验模式开启方法: 删除 “admin.php” 文件,然后在 config_global.php 中设置 ``` $_config['admincp']['validate']['method'] = 'sample'; ``` 通过 https://website/?app=admin 访问后台管理中心 针对登录还有配套的退出 ``` $_config['admincp']['logout']['method'] = 'sample'; ``` 对应下面的 childfile //childfile:global/adminlogout/xxxx ## 日志系统 X5.0 对系统的日志进行了全面的优化和开放,您可以把插件的日志接入到此处,站长可以在“操作日志”处统一查看。 添加日志的方法通过以下系统函数 ```php logger('myplugin:text', $_G['member'], $_G['member']['uid'], array('p' => $text, 't' => $time)); ``` myplugin:text 中,myplugin 为插件ID,text 为自定义日志类型。后续参数可酌情添加。 日志名称的语言包为 ``` $scriptlang['sample']['log_text'] = 'text日志'; ``` 日志后台文件写到 /source/plugin/myplugin/log 目录下,文件名固定为 log_日志类型.php,例如上例中的 log_text.php ```php <?php if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } showtableheader('', 'fixpadding'); showtablerow('class="header"', array('class="td23"', 'class="td24"', 'class="td23"', 'class="td23"', 'class="td23"'), array( cplang('time'), 'text', cplang('logs_device'), )); foreach($logs as $k => $logrow) { $data = json_decode($logrow['data'], true); $device = json_decode($logrow['device'], true); showtablerow('', array('class="smallefont"', 'class="smallefont"', 'class="bold"', 'class="smallefont"', 'class="smallefont"'), array( dgmdate($logrow['dateline']), is_array($data['p']) ? print_r($data['p'], 1) : $data['p'], $_G['group']['allowviewip'] ? 'ClientIP: '.$device['client_ip'].' <a href="javascript:;" onclick="togglelog('.$logrow['id'].')">'.cplang('more').'</a>' : '-', )); echo showdevice($logrow['id'], $device, 6); } ``` ## 管理中心小组件 X5.0 的管理中心首页可通过小组件的方式进行拓展,要增加小组件您需要将脚本写到 /source/plugin/myplugin/admin/admin_widget.php 文件中,文件结构为: ```php <?php namespace myplugin; if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } class admin_widget { public static function widget_a() { } public static function widget_b_left() { } ... } ``` admin_widget 类中可以写多个 “widget_” 开头的小组件方法,默认小组件位于右侧,如果想写左侧的小组件,请让方法名以 “_left” 结尾。 ## 设置项组件 X5.0 中可以定义自己的设置项,要增加设置项组件您需要将脚本写到 /source/plugin/myplugin/admin/component 目录中,一个文件一个组件,每个文件都以 component_ 开头命名,文件结构为: component_a.php ```php <?php namespace myplugin\admin; if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } class component_a { var $name = '组件名称'; // 编辑时显示的内容 function show(&$var, &$extra) { ... } // 数据提交入口后进行打包 function serialize($params, &$value) { ... } // 数据重新编辑前进行解包 function unserialize($params, &$value) { ... } } ``` 定义后,插件代码中可用下面的函数进行调用: ```php showcomponent('测试1', 'abc1', 'yellow', 'myplugin:component_a'); ``` 表单提交后可通过下面的函数对组件的数据进行序列化封装,通常系统会自动调用 ```php serializecomponent(); ``` ## 插件变量的优化 - X5.0 中,上面提到的设置项组件可直接用于插件变量、模板变量中。 - “显示顺序”设置为负数的变量将隐藏,隐藏的变量用户无法设置,可用于应用内部使用。 - 配置在“版块”、“用户组”面板且变量名为“fields\_”开头的变量,将会把变量存储在 X5.0 新增的 fields(JSON类型) 字段中。 ## 自定义多媒体代码解析脚本 X5.0 中可以自定义 [media] 标签中视频解析的脚步,要增加新的解析脚本您需要将脚本写到 /source/plugin/myplugin/media 目录中,一个文件一个脚本,每个文件都以 media_ 开头命名,文件结构为: media_video.php ```php <?php namespace myplugin; class media_video { public static $version = '1.0'; public static $name = 'name'; public static $checkurl = ['video.com/']; public static function parse($url, $width, $height) { if(preg_match('/^https?:\/\/video.com\/(\d+)/i', $url, $matches)) { $iframe = 'https://www.video.com/iframe/'.$matches[1]; $flv = $imgurl = ''; } return [$flv, $iframe, $url, $imgurl]; } } ``` ## 分类信息类型 X5.0 中可以定义自己的分类信息类型,要增加新的类型您需要将脚本写到 /source/plugin/myplugin/threadtype 目录中,一个文件一个类型,每个文件都以 threadtype_ 开头命名,文件结构为: threadtype_a.php ```php <?php namespace myplugin; if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) { exit('Access Denied'); } class threadtype_a { var $name = '类型名称'; // 编辑时显示的内容 function show($option, $params) { ... } // 结果输出时显示的内容 function view($viewtype, $option, $params, $value) { ... } // 数据提交入口后进行打包 function serialize($params, &$value) { ... } // 数据重新编辑前进行解包 function unserialize($params, &$value) { ... } } ``` ## JSON编辑器区块 X5.0 引入了全新的JSON编辑器,基于 Editor.js 内核,是区块风格(Block-Styled)编辑器,具有干净JSON输出的块样式,同时,让编辑器有更多的自由性和可扩展性。块是构成条目的结构单元,例如,Paragraph,Heading,Image,Video,List都是块。每个块由插件表示,以热插拔的形式灵活为编辑器扩展功能。 可以通过插件方式为JSON编辑器扩展功能区块,您需要把区块文件写到 ```/source/plugin/myplugin/editorblock``` 目录下。 区块类固定以 ```editorblock_自定义区块英文标识.php``` 命名, 如 ```editorblock_myBlock.php``` ,放在 ```editorblock``` 根目录,区块 ```js、css``` 静态资源放入 ```editorblock/tools/myBlock``` 目录下。 ### 目录结构 ``` editorblock // 固定命名 ├── tools // 固定命名 ├── myBlock // 自定义区块英文标识 ├── myBlock.js // 自定义区块核心js ├── myBlock.css // 自定义区块核心css ├── editorblock_myBlock.php // 自定义区块核心类 ``` ### 区块核心类书写范例 以下是区块核心类书写范例,其中 ```getConfig()``` 、 ```getStyle()``` 、 ```getParser()``` 将支持后台在线编辑,即,用户可以自由定义区块展示结构与样式。 ``` class editorblock_myBlock { var $version = '1.0.5'; // 版本 var $name = '自定义区块名称'; // 区块名称 var $available = 1; // 默认启用状态 0:不启用 1:启用 var $columns = 1; // 默认是否支持多列 0:不支持 1:支持 var $identifier = 'myBlock'; // 自定义区块标识 var $description = '插入联系方式信息区块,可用于插入电话、微信号、QQ号等,添加内容格式为:类型标识(mobile、wechat、qq)/联系人名称/联系方式(电话、微信等)'; // 区块描述 var $filename = 'myBlock'; // 区块文件名,与自定义区块标识一致即可 var $copyright = '云诺'; // 版权,显示在后台JSON区块列表中 var $type = '0'; // 0:数据类型 1:图片类型 2:附件类型 function __construct() { } // 区块 数据结构 示例,参照 Editor.js 区块数据结构 function getParameter() { return <<<EOF { "data": { "channelType": "mobile", // mobile、wechat、qq "channelName": "云诺", "messageId": 123456 }, "id": "ZT8S70Q34G", // 区块id "type": "myBlock" // 区块类型 } EOF; } /* * 区块 config 配置,参照 Editor.js 区块配置 * * 结构(左顶头): * { * tools_自定义区块英文标识: { * 自定义区块英文标识: { * ... * } * } * } */ function getConfig() { return <<<EOF { tools_myBlock: { myBlock: { class: myBlock, tunes: ['anchorTune'] }, } } EOF; } // 尚未启用 function getI18n() { return <<<EOF EOF; } // 区块在编辑器中的 显示样式 配置 // CSS样式代码部分必须严格书写在<style type="text/css">......</style>内部。 function getStyle() { return <<<EOF <style type="text/css"> .ce-block { /* margin-bottom: 20px; */ } .ce-block__content,.ce-toolbar__content { /* max-width:calc(100% - 50px) */ margin-left: auto; margin-right: auto; } .ce-myBlock { position: relative; float: left; width: 280px; height: auto; padding: 15px 10px 15px 20px; box-sizing: border-box; border: 1px solid #3f9dffa3; border-radius: 50px; margin: 10px 15px; } .ce-myBlock dl { margin: 0 } .ce-myBlock dl dt { width: 50px; height: 50px; float: left; position: relative; margin-right: 12px; } .ce-myBlock dl dt svg { width: 100%; height: 100% } .ce-myBlock dl dd { float: left; position: relative; } .ce-myBlock dl dd h3 { height: 20px; line-height: 20px; font-size: 14px; margin-top: 5px; } .ce-myBlock dl dd h3 em { font-style: normal; margin-left: 5px; color: #fa5555; font-size: 12px; } .ce-myBlock dl dd p.p1 { color: #999; } .ce-myBlock dl dd p { width: 185px; height: 20px; line-height: 20px; margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } </style> EOF; } // 区块 解析模板 配置,解析模板开发方式见下文 function getParser($block = array()) { global $_G; return <<<EOF <div class="ce-block ce-block--focused" data-id="{id}" [if tunes.anchorTune.anchor=notnull]id="{tunes.anchorTune.anchor}"[/if]> <div class="ce-block__content"> <div class="ce-myBlock "> <dl> <dt> [if data.channelType=mobile] <svg t="1705559455679" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="69723" width="80" height="80"><path d="M511.484254 0.066515C227.987504 0.066515-0.001023 227.399103-0.001023 510.114047c0 282.71392 227.987504 512.917906 511.485277 512.917906 283.461957 0 511.48016-230.203986 511.48016-512.917906C1022.964414 227.399103 794.94621 0.066515 511.484254 0.066515zM762.853281 772.349563l-57.003272 43.768853c-54.072523 16.044418-187.064466 42.271756-352.206644-190.872189-187.063443-252.120135-112.512517-394.946051-84.758406-419.698804l59.904345-42.270732 24.854061 7.306406 100.833523 145.69629-2.901073 23.320125-58.471716 40.803311c-26.28669 21.851681-8.772804 49.541323 10.237155 101.988836 17.543561 30.625508 57.003272 91.816149 83.290985 115.137297 45.297672 37.896098 65.777099 62.684668 96.468099 45.201481l55.505152-39.333844 21.977547 2.871397 105.142665 141.357472L762.853281 772.349563z" fill="#18CC73" p-id="69724"></path></svg> [/if] [if data.channelType=wechat] <svg t="1705558999833" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="47545" width="80" height="80"><path d="M512 1.896C229.452 1.896 0 229.452 0 512s229.452 510.104 512 510.104S1022.104 794.548 1022.104 512 794.548 1.896 512 1.896z m-91.022 629.57c-26.548 0-49.304-5.688-75.852-11.377l-75.852 37.926 22.756-66.37c-54.993-37.926-87.23-87.23-87.23-147.912 0-104.296 98.607-185.837 218.074-185.837 108.089 0 201.007 64.474 219.97 153.6-7.585 0-13.274-1.896-20.859-1.896-104.296 0-185.837 77.748-185.837 172.563 0 15.17 1.896 30.34 7.585 45.511-7.585 3.793-15.17 3.793-22.755 3.793z m322.37 77.749l17.067 54.992-58.785-34.133c-22.756 5.689-43.615 11.378-66.37 11.378-104.297 0-185.838-70.163-185.838-157.393S530.963 424.77 635.26 424.77c98.608 0 185.837 70.163 185.837 159.29 0 47.407-32.237 91.021-77.748 125.155z" fill="#46BB36" p-id="47546"></path><path d="M318.578 379.26c0 17.066 13.274 30.34 30.34 30.34s30.341-13.274 30.341-30.34-13.274-30.341-30.34-30.341-30.341 13.274-30.341 30.34z m235.14 159.288c0 13.274 11.378 24.652 24.652 24.652 13.274 0 24.652-11.378 24.652-24.652 0-13.274-11.378-24.652-24.652-24.652-13.274-1.896-24.651 9.482-24.651 24.652z m-81.54-159.289c0 17.067 13.274 30.341 30.34 30.341 17.067 0 30.341-13.274 30.341-30.34 0-17.067-13.274-30.341-30.34-30.341-17.067 0-30.341 13.274-30.341 30.34zM675.08 538.55c0 13.273 11.378 24.651 24.652 24.651 13.274 0 24.652-11.378 24.652-24.652 0-13.274-11.378-24.652-24.652-24.652-13.274-1.896-24.652 9.482-24.652 24.652z" fill="#46BB36" p-id="47547"></path></svg> [/if] [if data.channelType=qq] <svg t="1705559232025" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="57063" width="80" height="80"><path d="M511.500488 512.499512m-511.500488 0a511.500488 511.500488 0 1 0 1023.000976 0 511.500488 511.500488 0 1 0-1023.000976 0Z" fill="#1BC1FA" p-id="57064"></path><path d="M784.234146 572.440976c8.178014 0 16.273108 0.253752 24.282287 0.728288-16.961436-38.434466-42.247742-69.886751-58.977405-90.331785 2.822244-8.482716 11.30496-56.536788-25.443153-90.453666v-2.827239c0-132.850263-96.103149-231.785647-214.822213-231.785647-118.717065 0-214.82521 96.107145-214.825209 231.785647v2.827239c-33.915879 33.915879-25.437159 81.969951-19.782681 90.453666-25.444152 28.265397-73.49223 87.62343-76.320469 155.461182 0 16.961436 2.827239 45.226833 11.305959 56.531794 11.305959 14.133198 39.570357-2.827239 62.186272-48.054073 5.650482 19.788675 19.78368 53.709549 50.876316 93.281905-50.876316 11.302962-65.009514 62.186271-48.049077 90.450669 11.305959 19.788675 39.570357 33.920874 87.624429 33.920874 78.496343 0 115.815899-19.378076 134.378771-35.711126C492.644901 814.680414 490.520976 800.136617 490.520976 785.233171c0-117.522232 131.500581-212.792195 293.71317-212.792195z" fill="#FFFFFF" p-id="57065"></path><path d="M514.925143 819.204995c5.654478 0 11.309955 2.82624 14.132199 5.649483 16.960437 16.960437 53.709549 39.575352 138.50674 39.575352 48.054072 0 76.320468-16.960437 87.625428-33.920874 16.960437-28.264398 2.827239-79.147707-48.054072-90.450669 31.092636-39.572355 45.225834-73.493229 50.881311-93.281905 19.787676 45.226833 50.881311 62.18727 62.186271 48.054073 2.827239-11.30496 5.650482-39.570357 5.650482-56.531794-1.93511-23.223321-8.508691-45.121936-17.337069-65.128398-8.009179-0.475536-16.104273-0.729288-24.282287-0.729287-162.212589 0-293.713171 95.269963-293.71317 212.792195 0 14.903446 2.123926 29.447243 6.147996 43.485533a88.18688 88.18688 0 0 0 4.122973-3.864226c2.827239-2.823243 8.481717-5.649483 14.133198-5.649483z" fill="#FFFFFF" opacity=".4" p-id="57066"></path></svg> [/if] </dt> <dd> <h3 class="comm" imagentlist="">{data.channelName}<em></em></h3> <p class="p1"><em>{data.messageId}</em></p> </dd> </dl> </div> </div> </div> EOF; } } ``` ### 区块核心JS开发方式 以 Editor.js 编辑器区块开发方式开放即可。 ### 区块解析模板开发 变量使用方式:```{字段名}``` , 多层级使用 ```.``` 进行连接, 如:```{字段名1.字段名2.字段名3}``` ; 变量使用方式示例如:```{id}``` 、 ```{type}``` 、 ```{data.alignment}``` 、 ```{data.items.text}``` ; 支持 ```[loop]``` 循环,示例区块代码如: ``` { "data": { ... "items": [ { "text" : "abc" }, { "text" : "def" }, ... ] } } ``` 循环使用方式: ``` <ul> [loop data.items] <li>{text}</li> [/loop] </ul> ``` 循环索引(放到循环体内部,自动计数): ``` [loopindex] ``` 多列渲染(放到循环体内部,指定子数据块,仅用于多列渲染): ``` [column blocks] ``` IF判断方式: ``` [if data.items=1] {text}块数据变量 或者 普通字符串 [/if] ``` URL转换绝对网址: ``` [url data.file.url] ``` ### 区块解析模板样式CSS开发 如区块前端解析需要css样式支持,可编写独立的 ```myBlock.css``` 文件,并使用 ```viewthread_postbottom``` 嵌入点往页面中植入该css文件。 ``` class plugin_myplugin_forum extends plugin_myplugin { function viewthread_postbottom() { $postbottom[0] = '<link rel="stylesheet" type="text/css" href="source/plugin/myplugin/editorblock/tools/myBlock/myBlock.css?'.getglobal('style/verhash').'" />'; return $postbottom; } } ``` ## 支付 通过拓展支付,可以让应用无需做额外过多的开发即可之间接入 Discuz! 系统的支付体系。甚至自定义一个新的支付通道。 ### 自定义支付入口 ``` payment::enable:是否启用支付服务 payment::channels:获取所有支付渠道(微信支付,支付宝,...),包含未启用的支付渠道,适用于定制支付页面 payment::channels_switch:开启和关闭 payment::get:根据支付渠道名称,创建一个支付实例对象,适用于接入新的支付方式 payment::create_order:创建一个支付订单 payment::query_order:支付订单状态查询 payment::finish_order:完成一个支付订单,如:补单,重试 payment::retry_callback_order:业务回调重试 payment::refund:退款 payment::refund_status:退款状态查询 payment::transfer:转账 payment::transfer_status:转账状态查询 ``` ### 自定义支付通道 ``` payment::channels_add:添加支付通道 payment::channels_delete:删除支付通道 ``` 在 pay/pay_xxx.php 定义支付发起和回调方法 ```php namespace myplugin; // sample 仅实现了 pay 方法,更多参见 /class/pay class pay_test extends \pay_base { public function callback($data, $order) { ... } public function pay($order) { ... } } ``` 在 admin/payment/payment_xxx.php 中可以添加后台设置脚本 ```php namespace myplugin\admin; class payment_test { var $name = 'xxx支付'; public function admincp() { if(!submitcheck('submit')) { showformheader('ec&operation=method&id=myplugin:test'); ...... showsubmit('submit'); showtablefooter(); showformfooter(); } else { ...... } } } ``` ## 安全验证组件 从 X5.0 开始,验证问答升级为安全验证,兼容全新方法,可拓展出更加复杂的验证系统 要增加新的安全验证组件,您需要将脚本写到 /source/plugin/myplugin/secqaa/ 目录下,脚本命名为 secqaa_xxx.php ```php class secqaa_name { var $version = '1.0';//脚本版本号 var $name = 'name';//安全验证名称 (可填写语言包项目) var $description = 'desc';//安全验证说明 (可填写语言包项目) var $copyright = 'Discuz! Team';//版权 (可填写语言包项目) var $settingurl = 'action=plugins&operation=config&identifier=sample&pmod=op1';//安全验证设置页URL function create(&$question) {//返回安全验证的答案和问题 ($question 为问题,函数返回值为答案) $question = '<div>输入 answer</div>'; return 'answer';//可返回答案也可以返回校验ID } function check($value, $answer) {//通过返回的 $value 和 create 返回的 $answer 校验是否正确 return $value == $answer; } } ``` 此功能需要用户开启 Memory 缓存,如 Redis ## 安全验证场景 X5.0 增加了安全验证场景,可自定义新的安全验证场景,并配套相应设置页面和相应脚本实现一套完整的安全验证场景流程。 要增加新的安全验证场景,您需要将脚本写到 /source/plugin/myplugin/seccheck/ 目录下,脚本命名为 seccheck_xxx.php ``` class seccheck_name { var $version = '1.0';//脚本版本号 var $name = 'seccheck_name';//安全验证场景名称 (可填写语言包项目) var $copyright = 'Discuz! Team';//版权 (可填写语言包项目) var $settingurl = 'action=plugins&operation=config&identifier=sample&pmod=op4';//安全验证场景设置页URL function rule($param = []) {//安全验证显示规则,返回 true 表示此处显示安全验证 return true; } } ``` PHP 脚本通过以下方式判断是否需要显示验证码及安全验证 ``` list($seccodecheck, $secqaacheck) = seccheck('sample:name', [$id]); ``` 模板代码中通过以下代码调用输出 ``` <!--{if $seccodecheck || $secqaacheck}--> <!--{block sectpl}--><div class="rfm"><table><tr><th><sec>: </th><td class="login_sec"><sec><sec></td></tr></table></div><!--{/block}--> <!--{subtemplate common/seccheck}--> <!--{/if}--> ``` 提交后通过以下代码验证正确性 ``` submitcheck('submit', 0, $seccodecheck, $secqaacheck) ``` ## RESTFul API 接口 X5.0 增加了基于 oAuth2 协议的开放 API 接口,开发者可以通过设计 XML 文件,即可增加 API 接口, [详见 API Gitee 文档](https://gitee.com/Discuz/discuz-restful-api "详见 API Gitee 文档") ## 权限组件 从 X5.0 开始,通过插件可以扩展版块的权限机制,插件可以用新的维度控制用户是否可以进入版块、发帖、上传附件等操作。要增加权限组件您需要将脚本写到 /source/plugin/myplugin/perm 目录中,一个文件一个组件,每个文件都以 perm_ 开头命名,文件结构为: ``` class perm_a { var $name = 'perm_a'; public function fetch_perm($uid) { ...... return true; } } ``` $name 为权限组件名称 fetch_perm 方法返回指定用户是否有权限 同时,X5.0 支持扩展权限类型,只需使用如下文件结构定义类型名称即可: ``` class perm_type { var $typename = 'perm_type'; } ``` 扩展的权限类型可用如下系统函数判断是否拥有权限: ``` forumperm($_G['forum']['extra']['perms']['perm_type']); ``` ## 对象存储组件 X5.0 支持从上传底层接入对象存储,您需要将脚本写到 /source/plugin/myplugin/oss 目录中,创建一个 init.php 文件 ``` class oss_plugin_sample { const Name = 'oss_name';//OSS名称 const Desc = 'oss_desc';//OSS配置说明 public static function load($param) { return new oss_class($param['oss_id'], $param['oss_key'], $param['oss_bucket'], $param['oss_endpoint'], $param['oss_url'], $param['oss_bucket_url']); } } ``` 如需支持多个 OSS,可用下面的写法 ``` class oss_plugin_sample { const SubType = [ 'oss1' => ['name' => 'oss_name1', 'desc' => 'oss_desc1'], 'oss2' => ['name' => 'oss_name2', 'desc' => 'oss_desc2'], ]; ... } ``` 详细范例请参考 sample 插件 ## 邮件发送组件 X5.0 可以用插件方式接管系统原有的邮件发送,您需要将脚本写到 /source/plugin/myplugin/mailsend/mailsend_xxx.php 中,例如: ``` class mailsend_xxx { public function sendmail($toemail, $subject, $message = '', $from = '') { // 发送邮件 runlog('SMTP', "--", 0); return true; } } ``` 使用时告知站长在 “邮件设置”>“邮件发送方式” 中选择 “通过插件发送”,“插件标识及脚本标识” 填写 “myplugin:xxx” 即可 ## 超级分类信息 X5.0 中通过全新的形式加强了分类信息,不仅可以通过安装、卸载脚本直接安装分类信息字段,还可以为分类信息添加独立的列表页和内容页模板。 ### 安装 ``` require_once libfile('function/plugin'); threadtype_install( name: 'sample', // 名称 fieldPrefix: 'sample', // 字段前缀 typeData: [ 'name' => 'sample 分类信息', 'description' => 'sample 超级分类信息范例', 'special' => 1, 'modelid' => 0, 'expiration' => 0, 'template' => '内容页模板', 'stemplate' => '列表页模板', 'ptemplate' => '发帖模板', 'btemplate' => 'DIY模板', 'super' => serialize([ // 调用的独立模板 'forumdisplay' => 'sample:super_forumdisplay', 'viewthread' => 'sample:super_viewthread', ]), ], fieldData: [ // 字段列表 [ 'title' => '字段1', 'identifier' => 'sample_field1', 'type' => 'text', ], [ 'title' => '字段2', 'identifier' => 'sample_field2', 'type' => 'textarea', ], ], ); ``` ### 卸载 ``` require_once libfile('function/plugin'); threadtype_uninstall('sample'); ``` ## 管理平台新增组件 ### 权限选择器组件 为了达到权限机制的统一,X5 中内置了权限选择器的系统组件,开发者可通过此组件直接显示出一个与版块权限设置同等功能系统组件,用于设置自己应用的权限,保存的设置项可直接通过 forumperm() 函数检测是否拥有权限。 ``` showcomponent('setname', 'varname', $value, 'component_perm', 'comment', [ 'permtype' => ['group', 'verify', 'account', 'tag', 'plugin'], //显示的权限类型,留空为显示全部权限类型 'formula' => true //按照权限公式表达式方式显示 ]); ``` ### 容量选择器组件 容量选择器是一个直接可以同时选择 KB、MB、GB 单位的组件,通过此组件,站长不必再了解容量单位的换算,直接选择相关的容量单位即可。 ``` showcomponent('setname', 'varname', value, 'component_size', '', $conf); ``` $conf 参数默认为 “KB,MB,GB”,如单独显示2个单位可单独设置 ## 插件范例 X5.0 开发者预览版新增插件接口完整范例见 [sample 插件](https://gitee.com/Discuz/DiscuzX/tree/MitFrame/upload/source/plugin/sample "sample 插件")
开发文档
平台介绍
技术文档