4.1 广告类、道具类、任务类扩展

三类挂载式扩展的位置约定,以及广告类的完整类骨架。

4.1.1 什么是挂载式扩展

前面几篇的插件模块本质是「我提供一个新页面」。广告类、道具类、任务类不是这样:它们把自己挂到系统已有的功能位上,系统负责页面、后台设置页和调用时机,你只提供一个类。省掉的是整套界面——广告的投放表单是系统画的,道具的使用按钮是系统给的。代价是控制权少,模板怎么排版不由你定。

生活里整理工具箱有条朴素经验:刀、钳子、螺丝刀各有各的抽屉,抽屉上贴标签。文件名和语言包名的对应关系就是那个标签——写错一个字符,等于工具放错抽屉,用户找不着。

4.1.2 三类扩展对照表

从 X3.0 起位置约定就固定了,X5.0 沿用未改:

类型 脚本位置(X3.0 起) 语言包 附件
广告类 source/plugin/{标识}/adv/adv_{name}.php source/language/adv/lang_{name}.php 同名 .gif 缩略图
道具类 source/plugin/{标识}/magic/magic_{name}.php source/language/magic/lang_{name}.php .small.gif 与 .gif
任务类 source/plugin/{标识}/task/task_{name}.php source/language/task/lang_{name}.php .gif

脚本文件名的 {name} 段,就是语言包文件名里的 {name} 段。写 adv_promo.php,语言包就得叫 lang_promo.php。缩略图跟语言包同级:source/language/adv/promo.gif;道具类要放两个,xxx.small.gif 是小图标、xxx.gif 是大图。

4.1.3 广告类:完整类骨架

广告类结构最完整,先吃透它,另两类就是减法。文件放 source/plugin/{标识}/adv/adv_promo.php:

<?php
// source/plugin/你的标识/adv/adv_promo.php
// 语言包 source/language/adv/lang_promo.php,缩略图同目录 promo.gif
class adv_promo {
    var $version = '1.0';                 // 这个类自己的版本
    var $name = 'lang_promo_name';        // 名称,填语言包 key
    var $description = 'lang_promo_desc'; // 说明,填语言包 key
    var $copyright = '你的团队';
    var $targets = array('portal','home','member','forum','group','userapp','plugin','custom');
    var $imagesizes = array('120x60','120x240');

    function getsetting() {}                        // 返回后台设置项目定义
    function setsetting(&$advnew, &$parameters) {} // 保存设置,两个都是引用
    function evalcode() {}                          // 返回 check / create 两段代码
}

$targets 决定广告出现在哪些入口的设置页,取值是 portal 门户、home 空间、member 个人中心、forum 论坛、group 群组、userapp 应用、plugin 插件页、custom 自定义位置。想不清楚就全写上,让站长自己勾。$imagesizes 的 x 是小写字母,仅作提示。$version 与插件 XML 清单里的 version 是两回事,后者是兼容性声明,会写成 X3,X3.5,X5.0 这种形式。

4.1.4 getsetting 与 setsetting

getsetting() 返回设置项目定义,键是设置项标识,值描述类型、标题、说明。字段拼法随类型有差别,照抄系统自带广告脚本的结构最保险:

function getsetting() {
    return array(
        'promoid' => array(
            'type' => 'string',
            'label' => '推广活动标识',
            'description' => '由活动方提供,用于拼接推广链接',
        ),
    );
}

function setsetting(&$advnew, &$parameters) {
    $advnew['promoid'] = isset($parameters['promoid']) ? trim($parameters['promoid']) : '';
    return $advnew;
}

$parameters 是站长刚提交的内容,$advnew 是引用传进来的当前记录,取参数一律 isset 兜底。

4.1.5 evalcode:两段代码的分工

段 何时执行 作用
check 广告位渲染之前 判断此刻该不该出现,不成立返回 false 或空串
create 决定投放之后 生成实际输出的 HTML,即广告位显示的内容
function evalcode() {
    return array(
        'check' => 'return !empty($parameters[\'promoid\']);',
        'create' => 'return \'&lt;a href="\' . $parameters[\'promourl\'] . \'">\' . $parameters[\'promoid\'] . \'&lt;/a>\';',
    );
}

check 做前置校验:参数没填全、活动过期、投放次数用完,都在这里返回 false,广告位自动留空。create 拿到的参数已通过校验,直接拼 HTML 返回;系统不替你转义,用户可控内容自己动手 htmlspecialchars。

4.1.6 道具类:多两个方法

道具类的类名是 magic_{name},成员与广告类大体相同,额外多两个方法:

方法 何时调用 作用
usesubmit($magic, $parameters) 用户点「使用」时 执行道具效果,返回处理结果
show($magic) 渲染道具条目时 返回这一条的显示内容
<?php
// source/plugin/你的标识/magic/magic_card.php
class magic_card {
    var $icon = 'card';   // 图标名,对应 .small.gif / .gif

    function usesubmit($magic, $parameters) {
        global $_G, $discuz;
        if (empty($parameters['toid'])) {
            return '请选择要使用的对象';   // 返回值直接显示给用户
        }
        // 真实业务在这里,用 C::t('xxx')->... 读写
        return 'ok';
    }

    function show($magic) {
        return '把卡片交给对方';
    }
}

道具不可退,usesubmit 开头先校验参数,别让用户白扣道具。

4.1.7 任务类:规则写在类里

任务类(task_{name})的思路不同:任务的完成条件本身就是一类规则,按约定填数组即可。

成员 作用
$icon 任务图标名,对应同名 .gif
$period 周期数值,如每天 1 次填 1
$periodtype 周期类型:0 按天,1 按周,2 按月
$conditions 完成条件数组,任务类的主角
class task_sign {
    var $icon = 'sign';
    var $period = 1;
    var $periodtype = 0;
    var $conditions = array(
        'posts' => array(
            'title' => '发表主题数',
            'required' => 5,   // 达成阈值
            'selected' => 1,   // 是否计入任务进度
        ),
    );
}

可用的条件维度随版本变化,写之前搜一遍系统自带的任务脚本看字段名。

4.1.8 上线前检查清单

  • 脚本、语言包、缩略图三处的 {name} 完全一致。
  • $name、$description 填的是语言包 key,不是中文。
  • $targets 包含所有要投放的位置。
  • setsetting() 取值全部 isset 兜底,create 里自己转义用户内容。

这几类扩展控制权有限,但省掉的界面代码极多。下一节处理插件页面上的文字,尤其是要发到海外的那些。