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/位置名}:广告位
{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 区域。
