Typecho二开魔改简单指北

简介

Typecho 是由 typeecho 两个词合成的,来自于开发团队的头脑风暴。

Type,有打字的意思,博客这个东西,正是一个让我们通过打字,在网络上表达自己的平台。Echo,意思是回声、反馈、共鸣,也是PHP里最常见、最重要的函数,相信大部分PHP爱好者都是从 echo 'Hello,world!'; 开始自己的PHP编程之路的。

名称就表明 Typecho 是一款博客程序,它在 GPL version 2 许可证下发行,基于 PHP (需要 PHP5 以上版本)构建,可以运行在各种平台上,支持多种数据库(Mysql, PostgreSQL, SQLite)。

准备资料

二开魔改项目

菜单

调整菜单需要从/var/Widget/Menu.php文件里找到下面的代码进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* 执行函数,初始化菜单
*/
public function execute()
{
$parentNodes = [null, _t('控制台'), _t('撰写'), _t('管理'), _t('设置')];

$childNodes = [
[
[_t('登录'), _t('登录到%s', $this->options->title), 'login.php', 'visitor'],
[_t('注册'), _t('注册到%s', $this->options->title), 'register.php', 'visitor']
],
[
[_t('概要'), _t('网站概要'), 'index.php', 'subscriber'],
[_t('个人设置'), _t('个人设置'), 'profile.php', 'subscriber'],
[_t('插件'), _t('插件管理'), 'plugins.php', 'administrator'],
[[Config::class, 'getMenuTitle'], [Config::class, 'getMenuTitle'], 'options-plugin.php?config=', 'administrator', true],
[_t('外观'), _t('网站外观'), 'themes.php', 'administrator'],
[[Files::class, 'getMenuTitle'], [Files::class, 'getMenuTitle'], 'theme-editor.php', 'administrator', true],
[_t('设置外观'), _t('设置外观'), 'options-theme.php', 'administrator', true],
[_t('备份'), _t('备份'), 'backup.php', 'administrator'],
[_t('升级'), _t('升级程序'), 'upgrade.php', 'administrator', true],
[_t('欢迎'), _t('欢迎使用'), 'welcome.php', 'subscriber', true]
],
[
[_t('撰写文章'), _t('撰写新文章'), 'write-post.php', 'contributor'],
[[PostEdit::class, 'getMenuTitle'], [PostEdit::class, 'getMenuTitle'], 'write-post.php?cid=', 'contributor', true],
[_t('创建页面'), _t('创建新页面'), 'write-page.php', 'editor'],
[[PageEdit::class, 'getMenuTitle'], [PageEdit::class, 'getMenuTitle'], 'write-page.php?cid=', 'editor', true],
],
[
[_t('文章'), _t('管理文章'), 'manage-posts.php', 'contributor', false, 'write-post.php'],
[[PostAdmin::class, 'getMenuTitle'], [PostAdmin::class, 'getMenuTitle'], 'manage-posts.php?uid=', 'contributor', true],
[_t('独立页面'), _t('管理独立页面'), 'manage-pages.php', 'editor', false, 'write-page.php'],
[_t('评论'), _t('管理评论'), 'manage-comments.php', 'contributor'],
[[CommentsAdmin::class, 'getMenuTitle'], [CommentsAdmin::class, 'getMenuTitle'], 'manage-comments.php?cid=', 'contributor', true],
[_t('分类'), _t('管理分类'), 'manage-categories.php', 'editor', false, 'category.php'],
[_t('新增分类'), _t('新增分类'), 'category.php', 'editor', true],
[[CategoryAdmin::class, 'getMenuTitle'], [CategoryAdmin::class, 'getMenuTitle'], 'manage-categories.php?parent=', 'editor', true, [CategoryAdmin::class, 'getAddLink']],
[[CategoryEdit::class, 'getMenuTitle'], [CategoryEdit::class, 'getMenuTitle'], 'category.php?mid=', 'editor', true],
[[CategoryEdit::class, 'getMenuTitle'], [CategoryEdit::class, 'getMenuTitle'], 'category.php?parent=', 'editor', true],
[_t('标签'), _t('管理标签'), 'manage-tags.php', 'editor'],
[[TagAdmin::class, 'getMenuTitle'], [TagAdmin::class, 'getMenuTitle'], 'manage-tags.php?mid=', 'editor', true],
[_t('文件'), _t('管理文件'), 'manage-medias.php', 'editor'],
[[AttachmentEdit::class, 'getMenuTitle'], [AttachmentEdit::class, 'getMenuTitle'], 'media.php?cid=', 'contributor', true],
[_t('用户'), _t('管理用户'), 'manage-users.php', 'administrator', false, 'user.php'],
[_t('新增用户'), _t('新增用户'), 'user.php', 'administrator', true],
[[UsersEdit::class, 'getMenuTitle'], [UsersEdit::class, 'getMenuTitle'], 'user.php?uid=', 'administrator', true],
],
[
[_t('基本'), _t('基本设置'), 'options-general.php', 'administrator'],
[_t('评论'), _t('评论设置'), 'options-discussion.php', 'administrator'],
[_t('阅读'), _t('阅读设置'), 'options-reading.php', 'administrator'],
[_t('永久链接'), _t('永久链接设置'), 'options-permalink.php', 'administrator'],
]
];

/** 获取扩展菜单 */
$panelTable = unserialize($this->options->panelTable);
$extendingParentMenu = empty($panelTable['parent']) ? [] : $panelTable['parent'];
$extendingChildMenu = empty($panelTable['child']) ? [] : $panelTable['child'];
$currentUrl = $this->request->getRequestUrl();
$adminUrl = $this->options->adminUrl;
$menu = [];
$defaultChildNode = [null, null, null, 'administrator', false, null];

$currentUrlParts = parse_url($currentUrl);
$currentUrlParams = [];
if (!empty($currentUrlParts['query'])) {
parse_str($currentUrlParts['query'], $currentUrlParams);
}

if ('/' == $currentUrlParts['path'][strlen($currentUrlParts['path']) - 1]) {
$currentUrlParts['path'] .= 'index.php';
}

foreach ($extendingParentMenu as $key => $val) {
$parentNodes[10 + $key] = $val;
}

foreach ($extendingChildMenu as $key => $val) {
$childNodes[$key] = array_merge($childNodes[$key] ?? [], $val);
}

foreach ($parentNodes as $key => $parentNode) {
// this is a simple struct than before
$children = [];
$showedChildrenCount = 0;
$firstUrl = null;

foreach ($childNodes[$key] as $inKey => $childNode) {
// magic merge
$childNode += $defaultChildNode;
[$name, $title, $url, $access] = $childNode;

$hidden = $childNode[4] ?? false;
$addLink = $childNode[5] ?? null;

// 保存最原始的hidden信息
$orgHidden = $hidden;

// parse url
$url = Common::url($url, $adminUrl);

// compare url
$urlParts = parse_url($url);
$urlParams = [];
if (!empty($urlParts['query'])) {
parse_str($urlParts['query'], $urlParams);
}

$validate = true;
if ($urlParts['path'] != $currentUrlParts['path']) {
$validate = false;
} else {
foreach ($urlParams as $paramName => $paramValue) {
if (!isset($currentUrlParams[$paramName])) {
$validate = false;
break;
}
}
}

if (
$validate
&& basename($urlParts['path']) == 'extending.php'
&& !empty($currentUrlParams['panel']) && !empty($urlParams['panel'])
&& $urlParams['panel'] != $currentUrlParams['panel']
) {
$validate = false;
}

if ($hidden && $validate) {
$hidden = false;
}

if (!$hidden && !$this->user->pass($access, true)) {
$hidden = true;
}

if (!$hidden) {
$showedChildrenCount++;

if (empty($firstUrl)) {
$firstUrl = $url;
}

if (is_array($name)) {
[$widget, $method] = $name;
$name = self::widget($widget)->$method();
}

if (is_array($title)) {
[$widget, $method] = $title;
$title = self::widget($widget)->$method();
}

if (is_array($addLink)) {
[$widget, $method] = $addLink;
$addLink = self::widget($widget)->$method();
}
}

if ($validate) {
if ('visitor' != $access) {
$this->user->pass($access);
}

$this->currentParent = $key;
$this->currentChild = $inKey;
$this->title = $title;
$this->addLink = $addLink ? Common::url($addLink, $adminUrl) : null;
}

$children[$inKey] = [
$name,
$title,
$url,
$access,
$hidden,
$addLink,
$orgHidden
];
}

$menu[$key] = [$parentNode, $showedChildrenCount > 0, $firstUrl, $children];
}

$this->menu = $menu;
$this->currentUrl = Common::safeUrl($currentUrl);
}

修改上面代码的childNodes部分,你可以得到如下效果:

  • 删除你不需要的菜单
  • 或者加上自己需要的菜单(菜单指向的文件需要准备好)

内容管理

内容管理页面即:/admin/manage-posts.php文件,在这里你可以对文章管理功能进行修改。但是修改也会牵扯到下面文件:

  • 基础类:/var/Widget/Base/Contents.php
  • 文章管理列表组件:/var/Widget/Contents/Post/Admin.php
  • 按日期归档列表组件:/var/Widget/Contents/Post/Date.php
  • 编辑文章组件:/var/Widget/Contents/Post/Edit.php
  • 最新评论组件:/var/Widget/Contents/Post/Recent.php

如有需要,可以拷贝复制这些代码已增加自己需要的其它管理功能模块。

如果需要增加其它管理模块,例如:资讯管理、新闻管理等,强烈建议拷贝复制一份并更名为你需要模块名,例如:ArticlesNews

其它模块的增加最好也同样如此,例如用户管理等。

统计项

后台网站概要页面中统计项的代码,需要修改文件:/var/Widget/Stat.php,可在此文件种修改或增加自己需要的统计项,代码可参考原文件中的统计代码

插件

注入激活

插件存放目录:/usr/plugins

插件需要在适当的时机激活加载,才能达到好的效果,请参考官方文档

Restful接口

如需为typecho提供对外api接口,推荐安装使用Typecho Restful 插件

更多api需求,可参照插件代码自行魔改

部署

服务器/虚拟主机

很常规的部署方式,此处不在多说。

环境变量

如果使用面板部署php网站时需要使用环境变量方式,可参考如下配置,以1Panel为例:

  • 修改config.inc.php文件,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config db
$db = new \Typecho\Db($_SERVER["TYPECHO_ADAPTER_NAME"], $_SERVER["TYPECHO_PREFIX"]);
$db->addServer(array (
'host' => $_SERVER["TYPECHO_HOST"],
'port' => $_SERVER["TYPECHO_PORT"],
'user' => $_SERVER["TYPECHO_USERNAME"],
'password' => $_SERVER["TYPECHO_PASSWORD"],
'charset' => $_SERVER["TYPECHO_CHARSET"],
'database' => $_SERVER["TYPECHO_NAME"],
'engine' => $_SERVER["TYPECHO_ENGINE"],
'sslCa' => $_SERVER["TYPECHO_SSL_CA"],
'sslVerify' => true,
), \Typecho\Db::READ| \Typecho\Db::WRITE);
\Typecho\Db::set($db);
  • 访问1Panel面板打开你的网站设置
  • 打开OpenResty配置文件
  • 在相应位置,添加如下代码:
1
2
3
4
5
6
7
8
9
fastcgi_param TYPECHO_ADAPTER_NAME Pdo_Mysql;
fastcgi_param TYPECHO_PREFIX xxx;
fastcgi_param TYPECHO_HOST xxx;
fastcgi_param TYPECHO_PORT 3306;
fastcgi_param TYPECHO_USERNAME xxx;
fastcgi_param TYPECHO_PASSWORD xxx;
fastcgi_param TYPECHO_NAME xxx;
fastcgi_param TYPECHO_ENGINE InnoDB;
fastcgi_param TYPECHO_CHARSET utf8mb4;

QQ%E6%88%AA%E5%9B%BE20240416155614.png

Vercel

步骤

  • 需要准备数据库,或者nosql也可以
  • 创建vercel.json文件,内容如下:
1
2
3
4
5
6
7
8
9
10
{
"functions": {
"api/*.php": {
"runtime": "vercel-php@0.7.0"
}
},
"routes": [
{ "src": "/(.*)", "dest": "/api/index.php" }
]
}
  • 创建/api/index.php文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
<?php
$file=__DIR__. '/..'.$_SERVER["PHP_SELF"];

if(file_exists($file))
{
return false;
}
else
{
require_once__DIR__. '/../index.php';
}
#echo $_SERVER["PHP_SELF"];
  • 设置环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config db
$db = new \Typecho\Db($_ENV["TYPECHO_ADAPTER_NAME"], $_ENV["TYPECHO_PREFIX"]);
$db->addServer(array (
'host' => $_ENV["TYPECHO_HOST"],
'port' => $_ENV["TYPECHO_PORT"],
'user' => $_ENV["TYPECHO_USERNAME"],
'password' => $_ENV["TYPECHO_PASSWORD"],
'charset' => $_ENV["TYPECHO_CHARSET"],
'database' => $_ENV["TYPECHO_NAME"],
'engine' => $_ENV["TYPECHO_ENGINE"],
'sslCa' => $_ENV["TYPECHO_SSL_CA"],
'sslVerify' => true,
), \Typecho\Db::READ| \Typecho\Db::WRITE);
\Typecho\Db::set($db);
  • Vercel部署你的Typecho,部署时用默认配置就好
  • 设置好你的环境变量
  • 享受去吧

参考文档:https://github.com/vercel-community/php

问题解决

  • The Runtime “vercel-php@0.5.2“ is using “nodejs14.x”, which is discontinued. Please upgrade your Runtime to a more recent version or consult the author for more details.

这个错误需要在vercel.json文件里指定新的vercel-php版本,可用版本参考

  • The following Serverless Functions contain an invalid “runtime”: - api/index (nodejs18.x).

这个错误需要在你的vercel项目,设置中修改Node.js Version为需要的node版本号,例如:18.x

其它

此文可能会不定期更新