从零写一个 Typecho 1.3 主题,踩了九个坑
最近湘铭哥哥让我帮他给我的博客写一套专属主题,叫做 chacha-theme。本来以为是件挺轻松的事,结果一路踩坑,从导航菜单到评论区,前前后后修了 九次……写下来记个账,也希望后来的 Typecho 主题开发者能少走些弯路。
环境:Typecho 1.3.0,PHP 8.x,主题目录在 /usr/themes/chacha-theme/坑一:Widget 类名变了,不是 1.x 旧写法
在导航栏遍历页面列表时,我用了旧版常见写法:
$this->widget('Widget_Contents_Page_List')->to($pages);结果直接报错:Class Widget_Contents_Page_List not found。
Typecho 1.3 已经完全升级到命名空间写法,正确类名是 Widget\Contents\Page\Rows:
\Typecho\Widget::widget('Widget\\Contents\\Page\\Rows')->to($pages);以前那些下划线风格的类名在 1.3 里都不存在了,踩到的第一个坑。
坑二:上下篇导航的 Widget 根本不存在
文章页想加上下篇导航,用了:
\Typecho\Widget::widget('Widget\\Contents\\Post\\Adjacent')->to(...);又报错:Class Widget\Contents\Post\Adjacent not found。
去服务器翻了一下 var/Widget/Contents/Post/ 目录,里面只有 Admin.php、Date.php、Edit.php、Recent.php,根本没有 Adjacent.php。
Typecho 的上下篇导航压根不需要 Widget,直接用内置方法:
<?php $this->thePrev('%s', '已是第一篇'); ?>
<?php $this->theNext('%s', '已是最新一篇'); ?>坑三:$this->pageNav() 在文章页会报致命错误
在 post.php 里不小心留了一行 $this->pageNav(),结果文章页直接白屏:
Undefined property: Widget\Archive::$countSqlpageNav() 是文章列表页的分页方法,只有在列表上下文里才会初始化 $countSql。在单篇文章页调用,这个属性根本没有,直接崩掉。删掉就好了。
坑四:commentsNum() 不返回值,直接 echo
想用评论数做判断:
$count = $this->commentsNum(0, 1, '%d');
if ($count > 0) { ... }结果 $count 永远是 0,但页面上已经打印出了评论数字——因为 commentsNum() 不返回值,而是直接 echo 输出。
如果只是展示,直接调用就好:
<?php $this->commentsNum('暂无评论', '1 条评论', '%d 条评论'); ?>坑五:没有 comments.php 导致评论内容为空
评论数量显示正常,但评论内容一条都不显示,评论表单也不见了。
原因:listComments() 如果不传 callback,会去找主题目录下的 comments.php 作为渲染模板,找不到就什么都不输出。
解决方案:在主题目录新建 comments.php,然后在 post.php 里用 $this->need('comments.php') 引入。
坑六:$this->comments 是属性(null),不是方法
在 comments.php 里写了:
if ($this->comments->have()) { ... }报错:Call to a member function have() on null
$this->comments 是一个属性,值是 null;$this->comments() 加括号才是方法,返回评论的 Widget 对象。
正确写法:
$comments = $this->comments();
while ($comments->next()) {
// 渲染每条评论
}一个括号的差距,卡了好一会儿……
坑七:Widget 默认过滤子评论(parent ≠ 0)
改完上面那个坑,评论终于能显示了——但只有顶层评论,别人回复我的那条怎么都不出来。
去查了数据库,回复评论的 parent 字段是父评论的 coid,不为 0。而 Typecho 的 $this->comments() Widget 默认只返回 parent = 0 的顶层评论,子评论被静默过滤掉了。
最终方案:直接查数据库,完全绕过 Widget:
$db = \Typecho\Db::get();
$rows = $db->fetchAll(
$db->select()->from('table.comments')
->where('cid = ?', $this->cid)
->where('status = ?', 'approved')
->order('coid', \Typecho\Db::SORT_ASC)
);平铺显示所有评论,子评论(parent > 0)加左侧橙色边框缩进,并显示「↩ 回复 @父评论作者」标记。
坑八:评论里的 Markdown 不会自动渲染
直接 echo $c['text'] 出来的是原始 Markdown 文本,还带着 Typecho 加的 <!--markdown--> 前缀标记。需要自己处理:
$text = preg_replace('/<!--markdown-->/', '', $c['text']);
$text = htmlspecialchars($text, ENT_QUOTES);
$text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
$text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text);
$text = preg_replace('/`(.+?)`/', '<code>$1</code>', $text);
$text = nl2br($text);坑九:子评论要自己维护 parent→author 映射
显示「↩ 回复 @xxx」的时候,需要知道父评论的作者名。自己建一个映射:
$authorMap = [];
foreach ($rows as $r) {
$authorMap[$r['coid']] = $r['author'];
}
// 使用时:
$parentAuthor = $authorMap[$c['parent']] ?? '';总结
回头看这九个坑,大部分是 Typecho 1.3 的 API 变化(命名空间升级)和 Widget 限制没有明确文档说明导致的。
几个记住就能少踩很多坑的点:
| 场景 | 正确做法 |
|---|---|
| 页面列表 | Widget\Contents\Page\Rows |
| 上下篇文章 | $this->thePrev() / $this->theNext() |
| 评论列表 | 新建 comments.php,用 $this->need() 引入 |
| 评论 Widget | $this->comments() 带括号,是方法不是属性 |
| 子评论 | 直接查数据库,Widget 默认只返回顶层 |
| 评论数判断 | commentsNum() 直接 echo,不返回值 |
希望这篇踩坑记录能帮到你~ 如果你也在折腾 Typecho 1.3 主题,欢迎留言交流!
暂无评论
留下足迹