Typecho1.3版本Cuteen 主题导航栏bug修复

把博客升级到 Typecho 1.3 之后,遇到一个挺隐蔽的问题:顶部导航栏和移动端侧边栏里的「独立页面」链接全部失效了,鼠标放上去能看到链接,点下去却总是回到首页分类菜单、自定义菜单、搜索结果、文章列表全都正常,只有独立页面(page 类型)出问题

问题现象

  • 后台「管理 → 独立页面」里页面一切正常,状态是「公开」;
  • 直接在浏览器里手输 /archives/about.html 能正常打开
  • 但导航栏里的「关于」「留言板」点下去,地址栏变成首页(https://www.azrb.cn/
  • 移动端侧边栏(offcanvas)同样的问题
  • 把主题换成官方 default,独立页面链接又能跳了 —— 说明问题在主题,不在 Typecho 本体

根因

Cuteen 主题的导航栏由 include/navbar.php 渲染,取页面列表的代码长这样:

$this->widget('Widget_Contents_Page_List')->to($pages);

再看一下 Typecho 的 var/Typecho/Widget.php 就会发现问题所在:

// 1.3 中属性是「读的时候才算」的
public function __get(string $name)
{
    $method = '___' . $name;
    $key = '#' . $name;

    if (array_key_exists($key, $this->row)) {
        return $this->row[$key];
    } elseif (method_exists($this, $method)) {
        $this->row[$key] = $this->$method();   // ← permalink 在这里才算出来
        return $this->row[$key];
    }
    // ...
    return null;
}

permalink 不是数据库字段,它由 ___permalink() → ___path() → Router::url() 动态解析路由得到(1.3 里这个方法还带了返回类型声明):

protected function ___permalink(): string
{
    return Common::url($this->path, $this->options->index);
}

也就是说:

  • permalink 只有在当前行通过 $widget->next() 被激活、并且被 __get() 访问过之后,才会被算出来、写进 $this->row;
  • 直接 foreach ($pages->stack as $item) 拿到的是原始行数组,里面只有 cid / title / slug / type 这些数据库字段,根本没有 permalink 这个键;
  • 于是 $item['permalink'] 取到 null,模板输出成 href="";
  • 浏览器对空 href 的处理就是「当前页面」→ 首页,看起来就像「点了没反应」

修复方法

思路:改用官方迭代接口 $pages->next() 安全遍历,在遍历过程中把 slug / permalink / title 这些「解析后」的值转存成普通数组,再基于数组输出。这样既能拿到正确的链接,也不依赖 stack、不依赖版本内部实现

  1. 修改文件
    usr/themes/Cuteen/core/Context.php
  2. 找到 PageEcho(),整体替换为
/**
 * @param $s
 * @param $pages
 * @return void
 * 输出页面
 */
public static function PageEcho($s, $pages)
{
    // [兼容 Typecho 1.3] 1.3 起 permalink / slug 等属性改为动态解析,
    // 直接读组件底层的 $pages->stack 拿不到解析后的链接(href 为空,被浏览器
    // 回退成首页地址)。这里改为用官方 API $pages->next() 安全遍历并转存成数组,
    // 再基于该数组输出,向下兼容 Typecho 1.2 及更早版本。
    $pageItems = array();
    while ($pages->next()) {
        $pageItems[] = array(
            'slug'      => $pages->slug,
            'permalink' => $pages->permalink,
            'title'     => $pages->title,
        );
    }

    if (Helper::options()->MergePages) {
        $str = '<li class="nav-item dropdown">';
        $str .= '<a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">' . Helper::options()->MergePages . '</a>';
        $str .= '<ul class="dropdown-menu">';
        foreach ($pageItems as $child) {
            $str .= ' <li><a class="dropdown-item" href="' . $child['permalink'] . '">' . $child['title'] . '</a></li>';
        }
        $str .= '</ul></li>';
        echo $str;
    } else {
        foreach ($pageItems as $item) {
            echo Context::MenuTag($item['permalink'], $item['title']);
        }
    }
}

改动就两点:两个 foreach ($pages->stack ...) 全部不再出现,数据统一来自 $pageItems。其余输出逻辑、MergePages 折叠菜单的行为都保持原样,且能同时兼容 1.2 和 1.3

评论区
头像