2.3 eval、echo 与广告位、钩子标签

模板里能跑 PHP、能开口子,边界要自己守住。

{eval}:模板里直接写 PHP

Discuz! 模板会被编译成 PHP 缓存文件,所以模板里可以直接执行 PHP 语句,方式就是 {eval}。标签内是完整 PHP 语句,块级语句用花括号包起来,如 {eval if($uid) { echo '已登录'; } }:

{eval $total = 0;}
{eval foreach($cartlist as $item) $total += $item[price];}
{eval echo '合计:'.$total.' 元';}

它最常见的正当用途有三个:循环前算一次总量、把数组值拼进字符串、调用 Discuz! 的模板辅助函数。举个完整例子,在列表里标出「置顶」和「加精」:

<!--{loop $threadlist $tid $thread}-->
    {eval $mark = '';}
    {eval if($thread[sticky]) $mark = '置顶';}
    {eval if($thread[highlight]) $mark .= '精';}
    <span class="mark">{$mark}</span>
<!--{/loop}-->

{echo}:只为了输出

{echo} 比 {eval} 少一层心智负担——它只负责输出:

{echo cutstr($thread[subject], 30, '...')}
{echo ddate($thread[dateline], 'Y-m-d H:i')}

cutstr() 是 Discuz! 的字符串截断函数,签名为 cutstr($string, $length, $append):超过 $length 就截断并在末尾接上 $append。标题、摘要、用户名这类长度不可控的文本,输出前一律过一遍,否则一个超长标题能把布局撑破。

{eval} 的安全红线

{eval} 能执行任意 PHP,一旦语句里拼进用户输入,模板就变成了代码执行入口。绝对不要这么写:

{eval $x = $_GET['tpl'];}
{eval echo "欢迎,{$_COOKIE[name]}";}

这两行等于把站点交出去。模板不该做输入处理,校验和白名单必须在 PHP 侧完成,模板只负责显示已处理好的值。

如果确实需要在模板里格式化,走「白名单函数」路线:先在 PHP 侧把允许调用的函数名放进数组,模板里只调用数组里的。

同样要避开「从参数里读函数名再调用」的写法,例如 {eval $f = $_GET[func]; $f($text);}——这是最容易被忽略的一种注入。

{ad/xxx} 在页面上输出一块广告区域:

{ad/headerbanner}
{ad/footer}

站内常见的广告位有顶部横幅、底部横幅、版块列表内等。这个标签不需要广告内容也能正常渲染——没投放时它输出一段带边框的提示框,告诉站长「这里可以挂广告」,去后台广告管理里关掉对应广告位就不会显示。

广告内容由后台配置,模板只管把位置摆对:广告位要放在容器内并给固定宽高,否则图片广告会把布局撑变形;多个 {ad} 标签不能重叠嵌套。

广告位同时也是插件挂载点之一:装了广告类插件后,对应的 ad_adId($value) 方法会在该位置被调用,参数里带上 params 和 content。

{hook/钩子名}:给插件留插槽

{hook/xxx} 是模板里最重要的一类「插座」——它在页面上什么都不输出,只是给插件预留一个插入代码的位置。

{hook/global_usernav_extra2}
{hook/index_top}

如果没有插件挂钩,这个标签什么也不做,页面上看不到痕迹;插件启用后,它对应的方法返回的 HTML 就出现在这个位置。

打个比方:{hook} 像装修时预留的电源插座——面板看不见,但插座在那儿,电器插上去就能用。这也是「模板」和「插件」两套体系之间唯一的官方连接通道:插件不改你的模板文件,代码自己就长出来了。

钩子名的构成规律

Discuz! 官方文档没有给出钩子名的完整清单,但命名是有规律可循的,一般可拆成「类别 + 脚本名 + 作用 + 标识」四段:

钩子名 拆解
global_usernav_extra2 global(全局生效)+ usernav(用户导航)+ extra2(第 2 个扩展位)
index_top index(首页脚本)+ top(顶部位置)
  • 类别:global 表示在所有脚本里都生效,脚本专属的钩子不带这个前缀。
  • 脚本名:嵌入点所在脚本,如 index(首页)、forum(论坛)、portal(门户)、member(个人中心)。
  • 作用:top、bottom、nav、extra,表示页面的大致位置。
  • 标识:同名位置有多个时加数字区分,比如 extra1、extra2。

注意这是推断规律,不是官方文档。想确认某个钩子真实存在、拼写是否正确,唯一可靠的办法是去源码里搜。

实操:找出全部预定义钩子

与其猜钩子名,不如让 Discuz! 自己告诉你有哪些。

第一步:把开发者模式改成 2。编辑 config/config_global.php,在文件末尾加:

$_config['plugindeveloper'] = 2;

值为 1 时后台会出现「设计新插件」「设计新模板」等入口;改成 2 后,系统会在页面源码里把每个钩子的占位标记都吐出来。

第二步:更新缓存。后台 → 界面 → 更新缓存。这一步不能省,值改了但缓存没更新,页面不会变。

第三步:在页面源码里搜 <hook。浏览器里 Ctrl+U 查看源代码,搜索 <hook>,会看到成片的标记,形如:

<hook/global_usernav_extra2>
<hook/index_top>
<hook/forum_threaditem_bottom>

每搜到一个名字,就是当前页面上真实存在的一个预定义钩子。插件作者写 global_usernav_extra2() 就能挂上它。

排查页面上的某个位置「为什么插件没生效」时,这招同样有用:搜 <hook> 看那个位置到底有没有留插槽。没有插槽,说明得改模板加上 {hook/xxx};有插槽但没输出,说明插件的方法名拼错或钩子 ID 写错。

记得把 plugindeveloper 改回 1 或删掉再上线——开发模式会给页面塞调试信息,生产环境不合适。

三类标签的对照

标签 干什么 会不会输出内容
{eval 语句;} 执行 PHP 看语句里有没有 echo
{echo 表达式} 输出表达式结果 会
{ad/位置名} 输出广告区域 可能有,也可能只有占位提示
{hook/钩子名} 给插件的插入位 自己不输出,插件挂钩后才输出

小结

{eval} 和 {echo} 让模板能跑 PHP,用它算总量、拼字符串、截断输出,但绝不把用户输入拼进语句。{ad/xxx} 摆广告位,没投放时是占位提示框。{hook/xxx} 是留给插件的插槽,命名大致是「类别_脚本_作用_标识」;想知道有哪些真实钩子,把 $_config['plugindeveloper'] 设成 2、更新缓存、再在页面源码里搜 <hook>。

最后一篇讲 {lang} 语言包和 DIY 区域。