ZX不是纸箱 ZX不是纸箱
ZX不是纸箱

Robes主题修改文章页外链转内链

手工修改:文章页外链改为新窗口打开(兼容 LinkGo 外链转内链)

只改 2 个文件usr/themes/robes/functions.phpusr/themes/robes/post.php
纯 PHP 改动,不用清缓存。改之前先把这两个文件各复制一份 .bak

一、先搞清楚网上的一些方法为什么没生效

装了 LinkGo(外链转内链),它会把文章里的外链改写掉:

地址
正文里原本写的https://www.jb51.net/article/285123.htm
LinkGo 改写后(页面真实源码)https://www.loveq.cc/go/aHR0cHM6Ly93d3cuamI1MS5uZXQvYXJ0aWNsZS8yODUxMjMuaHRt

那串 aHR0cHM6... 就是原地址 base64 编码后的结果(+-/_,还去掉了结尾的 =)。

所以链接的域名变成了自己的站www.loveq.cc),网上教程的逻辑是「域名跟本站不一样才算外链」——一看域名是本站,就当站内链接原样放过了,自然没有 target="_blank"

修正思路:遇到这种 本站/go/xxx 的跳转链接,先解码还原出真实地址,再拿真实域名判断是不是外链。


第 1 步:functions.php 新增两个函数

function themeOption,会看到:

function themeOption($key, $default = '') {
    $val = \Helper::options()->{$key};
    return ($val !== null && $val !== '') ? $val : $default;
}

在它的 } 后面空一行,粘贴这一整段:

// ══════════════════════════════════════
// 外链自动新窗口打开(仅文章页调用)
// ══════════════════════════════════════

/**
 * 识别「外链转内链」插件(如 LinkGo)生成的跳转地址,返回解码后的原始地址
 *
 * LinkGo 会把 https://其它站/xxx 改写成 本站/go/<base64url>,
 * 其中 base64url = base64 编码后把 "+"→"-"、"/"→"_" 并去掉尾部 "="。
 * 这类链接的域名是本站,光看域名会被误判成站内链接,所以要解出真实地址。
 *
 * @param string $href 形如 https://www.loveq.cc/go/aHR0cHM6Ly9...
 * @return string 解码成功且是 http(s) 地址时返回原始地址,否则返回空串
 */
function robesResolveGoTarget($href) {
    if (strpos($href, '/go/') === false) {
        return '';
    }
    $path = (string) parse_url($href, PHP_URL_PATH);
    if ($path === '' || !preg_match('#(?:^|/)go/([A-Za-z0-9_\-=+/]+)$#', $path, $m)) {
        return '';
    }
    $token = strtr($m[1], '-_', '+/');
    $pad = strlen($token) % 4;
    if ($pad !== 0) {
        $token .= str_repeat('=', 4 - $pad);
    }
    $decoded = base64_decode($token, true);
    if ($decoded === false || !preg_match('#^https?://#i', $decoded)) {
        return '';
    }
    return $decoded;
}

/**
 * 给正文中的外部链接自动补上 target="_blank"
 *
 * 规则:链接主机名与本站主机名(含 www / 裸域名变体)不同,即视为外链,
 * 加上 target="_blank" 与 rel="noopener noreferrer";
 * 站内链接、锚点、相对地址以及 mailto: / tel: 等非 http(s) 链接一律保持原样,
 * 以免破坏主题的 PJAX 无刷新跳转。
 *
 * 兼容「外链转内链」插件:形如 本站/go/<base64url> 的跳转地址会先解码成
 * 真实地址,再按真实域名判断是不是外链。
 *
 * 目前只由 post.php(文章详情页)调用,其他页面不受影响。
 *
 * @param string $content 已经过 Markdown 解析的 HTML
 * @return string
 */
function robesExternalBlank($content) {
    if (!is_string($content) || $content === '' || stripos($content, '<a') === false) {
        return $content;
    }

    // 本站所有可能的主机名(本站域名 + 当前访问域名 + www 变体)
    static $hosts = null;
    if ($hosts === null) {
        $hosts = array();
        $urls = array();
        try {
            if (class_exists('\Helper') && method_exists('\Helper', 'options')) {
                $urls[] = (string) \Helper::options()->siteUrl;
            }
        } catch (\Throwable $e) {
            // 忽略:下面的 HTTP_HOST 兜底
        }
        if (!empty($_SERVER['HTTP_HOST'])) {
            $urls[] = 'http://' . $_SERVER['HTTP_HOST'];
        }
        foreach ($urls as $url) {
            $host = strtolower((string) parse_url($url, PHP_URL_HOST));
            if ($host === '') {
                continue;
            }
            $bare = preg_replace('/^www\./', '', $host);
            $hosts[] = $host;
            $hosts[] = $bare;
            $hosts[] = 'www.' . $bare;
        }
        $hosts = array_values(array_unique($hosts));
    }

    return preg_replace_callback('/<a\s[^>]*>/i', function ($match) use ($hosts) {
        $tag = $match[0];

        if (!preg_match('/\bhref\s*=\s*(["\'])(.*?)\1/is', $tag, $hm)) {
            return $tag;
        }

        $href = trim(html_entity_decode($hm[2], ENT_QUOTES, 'UTF-8'));

        // 只处理 http(s) 绝对地址;
        // 空链接、锚点、相对地址、协议相对地址,以及 mailto:/tel:/javascript: 等一律跳过
        if ($href === '' || !preg_match('#^https?://#i', $href)) {
            return $tag;
        }

        $linkHost = strtolower((string) parse_url($href, PHP_URL_HOST));
        // 解析不出主机名,跳过
        if ($linkHost === '') {
            return $tag;
        }
        if (in_array($linkHost, $hosts, true)) {
            // 域名是本站:可能是「外链转内链」插件的跳转地址,解出真实地址再判断
            $realUrl = robesResolveGoTarget($href);
            if ($realUrl === '') {
                return $tag; // 真正的站内链接,原样放过(PJAX 无刷新靠这个)
            }
            $realHost = strtolower((string) parse_url($realUrl, PHP_URL_HOST));
            if ($realHost === '' || in_array($realHost, $hosts, true)) {
                return $tag; // 跳转地址指向本站,同样放过
            }
            // 跳转地址指向外站 → 继续往下,按外链处理
        }

        // 外链:先剥掉原有的 target / rel,再统一补上,保证重复执行结果一致
        $tag = preg_replace('/\s+target\s*=\s*(["\']).*?\1/is', '', $tag);
        $rel = 'noopener noreferrer';
        if (preg_match('/\s+rel\s*=\s*(["\'])(.*?)\1/is', $tag, $rm)) {
            $old = trim($rm[2]);
            if ($old !== '') {
                $rel = $old . ' ' . $rel;
            }
            $tag = preg_replace('/\s+rel\s*=\s*(["\']).*?\1/is', '', $tag, 1);
        }
        $rel = implode(' ', array_unique(preg_split('/\s+/', trim($rel))));

        return rtrim(substr($tag, 0, -1)) . ' target="_blank" rel="' . $rel . '">';
    }, $content);
}

第 2 步:post.php 换一行

themePhotoFilter::processShortcodes(这个文件里只有 1 处)。

找到:

                        <?php ob_start(); $this->content(); $c = ob_get_clean(); echo themePhotoFilter::processShortcodes($c); ?>

换成:

                        <?php /* 正文输出:外链自动新窗口打开 */ ?>
                        <?php ob_start(); $this->content(); $c = ob_get_clean(); echo robesExternalBlank(themePhotoFilter::processShortcodes($c)); ?>

(第二行前面保留原来的 24 个空格缩进。page.php 里有一模一样的一行,别改错文件。)

跳到「四、改完怎么验证」。


二、改完怎么验证

  1. 打开一篇带外链的文章,Ctrl+F5 刷新;
  2. 右键查看网页源代码,搜 target="_blank" —— 外链那个 <a> 上应该有了;
  3. 点外链:新标签页打开 LinkGo 的中间跳转页,中间页再自动跳到真实外链;原文章页不动
  4. 点站内链接(分类、上下篇、文章里的站内链接):仍然是本页无刷新跳转,不会开新窗口。

排错

现象原因
白屏 / 500粘贴时漏了字符(最常见是少一个 }),用 .bak 还原重来
外链还是没有 targetfunctions.php 里搜 robesResolveGoTarget:搜不到说明补丁 A 没生效;搜得到就清一下 CDN/缓存再试(宝塔若有「静态缓存」插件,清一次缓存)

三、这段代码在干什么(一句话)

正文输出前逐个检查 <a>

  • 本站/go/xxx → 先按 base64 解码还原出真实地址,再判断:真实域名是外站就加 target="_blank" rel="noopener noreferrer",指向本站就放过;
  • 域名本来就不是本站(比如没被 LinkGo 接管的裸外链)→ 同样加 target="_blank"
  • 站内链接、锚点、相对地址、mailto:tel: → 一个字都不动,所以站内跳转和 PJAX 无刷新完全不受影响。
上一篇 Robes 主题 · 电影 / 书籍 / 好物 收藏集页面
下一篇 已经是最后一篇了

暂无评论

Ctrl + Enter 发送

还没有评论,来说点什么吧

ZX的研发日志
Theme Robes