让 typecho 存在多重后台

今天,权那他来教大家,如何让 typecho 存在多重后台主题。
众所周知,typecho 可以自定义后台路径,但是能,只能存在一个,另外一个的后台路径会报错。
比如默认后台路径是https://domain.com/admin,你自定义后为https://domain.com/apex,你访问后一个是正常的,但是,但是当你访问第一个就会报错。
然后有个朋友找我,让我帮他实现后台主题插件化。
期初,我准备是在typecho注册路由,导向后台主题目录,但是不能实现。下面是我摸索的过程。

顺藤摸瓜

是的,顺藤摸瓜。我首先打开admin/index.php 文件。首先可以看见

<?php
include 'common.php';
include 'header.php';
include 'menu.php';

我们肯定再去看common.php,好,继续更近。

打开common.php

<?php
if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

define('__TYPECHO_ADMIN__', true);

/** 载入配置文件 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once __DIR__ . '/../config.inc.php') {
    file_exists(__DIR__ . '/../install.php') ? header('Location: ../install.php') : print('Missing Config File');
    exit;
}

/** 初始化组件 */
Typecho_Widget::widget('Widget_Init');

当我看见的时候,是跟进Typecho_Widget::widget('Widget_Init');的。

打开var/Widget/Init.php,我们发现没有提及到定义后台路径的代码。所以我们后退。

然后我瞄向了

/** 载入配置文件 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once __DIR__ . '/../config.inc.php') {
    file_exists(__DIR__ . '/../install.php') ? header('Location: ../install.php') : print('Missing Config File');
    exit;
}

找到config.inc.php,打开config.inc.php,发现

/** 后台路径(相对路径) */
define('__TYPECHO_ADMIN_DIR__', '/admin/');

这里定义了后台路径。

然后我在 https://github.com/typecho/typecho 搜索 __TYPECHO_ADMIN_DIR__,发现整个typecho程序只有var/Widget/Options.phpinstall.php 出现了__TYPECHO_ADMIN_DIR__

显然我们只能再去var/Widget/Options.php探索,打开查询。

protected function ___adminUrl()
{
    return Typecho_Common::url(defined('__TYPECHO_ADMIN_DIR__') ?
    __TYPECHO_ADMIN_DIR__ : '/admin/', $this->rootUrl);
}

if (defined('__TYPECHO_ADMIN__')) {
    /** 识别在admin目录中的情况 */
    $adminDir = '/' . trim(defined('__TYPECHO_ADMIN_DIR__') ? __TYPECHO_ADMIN_DIR__ : '/admin/', '/');
    $this->rootUrl = substr($this->rootUrl, 0, - strlen($adminDir));
}

我们发现,不可以再次定义后台路径 ,也没有单实例getInstance。恼火。

我就再次后退。

重新看着config.inc.php。发现

/** 程序初始化 */
Typecho_Common::init();

var/Typecho/Common.php是typecho程序初始化,所有的都在这里注册。

所以,我们要在这个初始化之前,定义路径。但是,改了后,还是只能留一个。

对象明确

经过上述的跟踪,我们明确了目标,就是config.inc.php。要在Typecho_Common::init();初始化之前变动路径。
要知道的是,typecho后台是不走插件里注册的路径,是绝对路径。所以无法插件化后台主题。
然后,我想到的是,重写config.inc.php,然而里面包含了数据库等等一些配置,所以无法继承重写。

要放弃了吗?

难道要放弃了吗?不,我们继续。
既然目标是config.inc.php,那么我就必须对它千刀万剐。

继续

我记得php有一个eval函数,eval() 函数把字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。

我萌生了一个想法。用file_get_contents 读取config.inc.php的内容,然后替换define('__TYPECHO_ADMIN_DIR__', '/admin/'); 中定义的路径为自己另外的一个路径。

然后用eval运行,就达到我们的目标了。

实现

经过上面我摸索,最后我写了以下实现的代码

$file_path = '../config.inc.php';
if (file_exists($file_path)) {
    $str = file_get_contents($file_path);//将整个文件内容读入到一个字符串中
    $str = str_replace("dirname(__FILE__)", "dirname(dirname(__FILE__))", $str);
    $str = str_replace("/admin/", "/apex/", $str);
    eval("?>" . $str);
}

这里,假如,主后台是https://doamin.com/admin/ ,副后台是https://doamin.com/apex/

那么,我们要在apex/common.php 中的

/** 载入配置文件 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once __DIR__ . '/../config.inc.php') {
    file_exists(__DIR__ . '/../install.php') ? header('Location: ../install.php') : print('Missing Config File');
    exit;
}

替换为

$file_path = '../config.inc.php';
if (file_exists($file_path)) {
    $str = file_get_contents($file_path);//将整个文件内容读入到一个字符串中
    $str = str_replace("dirname(__FILE__)", "dirname(dirname(__FILE__))", $str);
    $str = str_replace("/admin/", "/apex/", $str);
    eval("?>" . $str);
}

然后,我们发现,主后台https://doamin.com/admin/ ,副后台https://doamin.com/apex/,都可以同时打开运行。那么我们的目标实现了。

如果你需要你的后台主题是在插件目录,比如/usr/plugins/Apex/apex/,那么上述的代码不能直接用,因为dirname(dirname(__FILE__)) 是定义apex的父级目录。

那么要/usr/plugins/Apex/apex/common.php 要父级父级父级。。。。。。。

最后对于https://domain.php/usr/plugins/Apex/apex/的实现的代码是

$file_path = '../../../../config.inc.php';
if (file_exists($file_path)) {
    $str = file_get_contents($file_path);//将整个文件内容读入到一个字符串中
    $str = str_replace("dirname(__FILE__)", "dirname(dirname(dirname(dirname(dirname(__FILE__)))))", $str);
    $str = str_replace("/admin/", "/usr/plugins/Apex/apex/", $str);
    eval("?>" . $str);
}

好嘞,可以根据自己的目录,另行变动

终极

为了小伙伴们,自定义一个很硬币的地方。权那他,简单的写了一个,只需定义你需要的路径即可,不需要改代码。

// 只需改动 $admin_path 的 /krait/nabo/admin/为你的后台路径,即可,其他的不要改动。
//$admin_path_before 为config.inc.php中的路径,一般是默认。
$admin_path = "/apex/";
$admin_path_before = "/admin/";

//路径实现来源 https://krait.cn/major/2021.html
$config_path = 'config.inc.php';
$array_path = array_filter(explode("/", $admin_path));
foreach ($array_path as $value) {
    $config_path = str_replace("config", "../config", $config_path);
}
if (file_exists($config_path)) {
    $str = file_get_contents($config_path);
    $str = str_replace($admin_path_before, $admin_path, $str);
    foreach ($array_path as $value) {
        $str = str_replace("__FILE__", "dirname(__FILE__)", $str);
    }
    eval("?>" . $str);
}

注意

这不是重写路径,是真实路径。
可以自己nginx规则重写路径

另外发现

当我在翻看typecho的源码时。发现
判断插件是否启用,可以是这样

//判断插件是否存在
if (array_search("插件名", Typecho_Plugin::export())) {
  echo "启用存在";
} else {
  echo "不存在";
}

声明

引用上述实现多重后台的代码,集成插件等等

需要源码中声明备注来源。
需要源码中声明备注来源。
需要源码中声明备注来源。
因为我发现最近,好像流行写后台主题。。。。不多说了。。。。,都明白