前情提要
上一篇我们分析到了 install.php
文件的 set_include_path
。今天我们继续。
进入安装流程
来到了引入 Typecho/Common.php
这样,也许你会很奇怪,找不到这个目录呢?不要忘了我们上面设置了好几个引入路径,所以我们要一个个的查找,最后我们会在 var
目录下,找到 Typecho/Common.php
。你看用这种方法引入,找文件都不好弄。所以命名空间什么的才会愈发的重要,set_include_path
这种引入的方案也会逐渐减少使用。
紧接着执行 Typecho_Common::init();
这行代码的方法就在刚才我们引入的文件中,我们进入这个方法。
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
|
/**
* 自动载入类
*
* @param $className
*/
public static function __autoLoad($className)
{
@include_once str_replace(array('\\', '_'), '/', $className) . '.php';
}
/**
* 程序初始化方法
*
* @access public
* @return void
*/
public static function init()
{
/** 设置自动载入函数 */
spl_autoload_register(array('Typecho_Common', '__autoLoad'));
/** 兼容php6 */
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
$_GET = self::stripslashesDeep($_GET);
$_POST = self::stripslashesDeep($_POST);
$_COOKIE = self::stripslashesDeep($_COOKIE);
reset($_GET);
reset($_POST);
reset($_COOKIE);
}
/** 设置异常截获函数 */
set_exception_handler(array('Typecho_Common', 'exceptionHandle'));
}
|
先看 if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
这段,这块在php5.4 以后永远都返回 false 了,所以这段在5.4 以后不会执行。如果是老版本呢,这块的处理就是把一些转义带有反斜线的字符给恢复过来,变成原始的内容。
然后剩下的部分就是注册自动加载,和异常处理函数了。自动加载可以看我上一篇文章,然后自己理解一下。
异常处理部分,就是根据是否是 debug
两种输出模式。
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
|
/**
* 异常截获函数
*
* @access public
* @param $exception 截获的异常
* @return void
*/
public static function exceptionHandle($exception)
{
if (defined('__TYPECHO_DEBUG__')) {
echo '<pre><code>';
echo '<h1>' . htmlspecialchars($exception->getMessage()) . '</h1>';
echo htmlspecialchars($exception->__toString());
echo '</code></pre>';
} else {
@ob_end_clean();
if (404 == $exception->getCode() && !empty(self::
$exceptionHandle)) {
$handleClass = self::$exceptionHandle;
new $handleClass($exception);
} else {
self::error($exception);
}
}
exit;
}
|
self::$exceptionHandle
这个会在初始化的时候我们再说。
如果没有 exceptionHandle 的时候会调用 error
方法,进行错误输出,并且记录 error_log
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
|
/**
* 输出错误页面
*
* @access public
* @param mixed $exception 错误信息
* @return void
*/
public static function error($exception)
{
$isException = is_object($exception);
$message = '';
if ($isException) {
$code = $exception->getCode();
$message = $exception->getMessage();
} else {
$code = $exception;
}
$charset = self::$charset;
if ($isException && $exception instanceof Typecho_Db_Exception) {
$code = 500;
@error_log($message);
//覆盖原始错误信息
$message = 'Database Server Error';
if ($exception instanceof Typecho_Db_Adapter_Exception) {
$code = 503;
$message = 'Error establishing a database connection';
} else if ($exception instanceof Typecho_Db_Query_Exception) {
$message = 'Database Query Error';
}
} else {
switch ($code) {
case 500:
$message = 'Server Error';
break;
case 404:
$message = 'Page Not Found';
break;
default:
$code = 'Error';
break;
}
}
/** 设置http code */
if (is_numeric($code) && $code > 200) {
Typecho_Response::setStatus($code);
}
$message = nl2br($message);
if (defined('__TYPECHO_EXCEPTION_FILE__')) {
require_once __TYPECHO_EXCEPTION_FILE__;
} else {
echo
<<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="{$charset}">
<title>{$code}</title>
<style>
html {
padding: 50px 10px;
font-size: 16px;
line-height: 1.4;
color: #666;
background: #F6F6F3;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
html,
input { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
body {
max-width: 500px;
_width: 500px;
padding: 30px 20px;
margin: 0 auto;
background: #FFF;
}
ul {
padding: 0 0 0 40px;
}
.container {
max-width: 380px;
_width: 380px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
{$message}
</div>
</body>
</html>
EOF;
}
exit;
}
|
到这里 init
方法已经执行完了,我们回到 install.php
文件继续往下看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 挡掉可能的跨站请求
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}
$parts = parse_url($_SERVER['HTTP_REFERER']);
if (!empty($parts['port'])) {
$parts['host'] = "{$parts['host']}:{$parts['port']}";
}
if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
exit;
}
}
|
这块就是挡掉跨域攻击,比如 iframe 的嵌套页面,为了安全,我们会判断 referer
如果跟请求的 host
不一致就阻挡掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$options = new stdClass();
$options->generator = 'Typecho ' . Typecho_Common::VERSION;
list($soft, $currentVersion) = explode(' ', $options->generator);
$options->software = $soft;
$options->version = $currentVersion;
list($prefixVersion, $suffixVersion) = explode('/', $currentVersion);
/** 获取语言 */
$lang = _r('lang', Typecho_Cookie::get('__typecho_lang'));
$langs = Widget_Options_General::getLangs();
if (empty($lang) || (!empty($langs) && !isset($langs[$lang]))) {
$lang = 'zh_CN';
}
if ('zh_CN' != $lang) {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
Typecho_I18n::setLang($dir . '/' . $lang . '.mo');
}
Typecho_Cookie::set('__typecho_lang', $lang);
|
设置版本,设置语言,顺便把语言写入到 cookie
中。
剩下的部分就都是安装流程了,我们慢慢拆分来看。
先说 安装文件 最后的部分
1
2
3
4
|
<?php
include 'admin/copyright.php';
include 'admin/footer.php';
?>
|
因为这两个文件都因为开头
1
|
<?php if(!defined('__TYPECHO_ADMIN__')) exit; ?>
|
由于没有定义那个常量而退出了,所以这两个部分都在我们用到的时候再说。
1
2
3
4
|
<li<?php if (!isset($_GET['finish']) && !isset($_GET['config'])) : ?> class="current"<?php endif; ?>><span>1</span><?php _e('欢迎使用'); ?></li>
<li<?php if (isset($_GET['config'])) : ?> class="current"<?php endif; ?>><span>2</span><?php _e('初始化配置'); ?></li>
<li<?php if (isset($_GET['start'])) : ?> class="current"<?php endif; ?>><span>3</span><?php _e('开始安装'); ?></li>
<li<?php if (isset($_GET['finish'])) : ?> class="current"<?php endif; ?>><span>4</span><?php _e('安装成功'); ?></li>
|
这块就是根据 url
的参数状态决定显示的问题 _e
和 _t
都是 一个是翻译并 echo
另一个是翻译。
注意哦,start
这个状态,在正常状态时看不到的哦,只有失败才会看得到。
安装第一步
显示一些说明文件,如果语言配置有多个,那么就出现语言选择列表框,不过默认就只有一个简体中文。然后点击下一步以后会跳转到当前 url
,增加config
参数。
进入配置
点击下一步以后我们就进入到了配置的步骤,当我们输入完相关数据参数,以及管理员信息以后点击下一步,会 post
方法跳转到当前 config
网址。这里有个主要注意的地方是,当我们改变数据库的适配器以后,会跳转到切换相应的数据库适配器配置页面。而且会在页面加载的时候判定支持什么数据。这两段在下面的代码中
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
$adapters = array('Mysql', 'Mysqli', 'Pdo_Mysql', 'SQLite', 'Pdo_SQLite', 'Pgsql', 'Pdo_Pgsql');
foreach ($adapters as $firstAdapter) {
if (_p($firstAdapter)) {
break;
}
}
$adapter = _r('dbAdapter', $firstAdapter);
$parts = explode('_', $adapter);
$type = $adapter == 'Mysqli' ? 'Mysql' : array_pop($parts);
?>
<?php require_once './install/' . $type . '.php'; ?>
|
1
2
3
4
5
6
|
<script>
var _select = document.config.dbAdapter;
_select.onchange = function() {
setTimeout("window.location.href = 'install.php?config&dbAdapter=" + this.value + "'; ",0);
}
</script>
|
不同的适配器会加载不同的数据库配置页面,我们这边用的是 mysql
,我们进入 install/mysql.php
页面,里面有很多环境的判断 sae、gae、bae 什么的判定。这些我们都跳过,直接看下面。
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
|
<?php else: ?>
<li>
<label class="typecho-label" for="dbHost"><?php _e('数据库地址'); ?></label>
<input type="text" class="text" name="dbHost" id="dbHost" value="<?php _v('dbHost', 'localhost'); ?>"/>
<p class="description"><?php _e('您可能会使用 "%s"', 'localhost'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbPort"><?php _e('数据库端口'); ?></label>
<input type="text" class="text" name="dbPort" id="dbPort" value="<?php _v('dbPort', '3306'); ?>"/>
<p class="description"><?php _e('如果您不知道此选项的意义, 请保留默认设置'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbUser"><?php _e('数据库用户名'); ?></label>
<input type="text" class="text" name="dbUser" id="dbUser" value="<?php _v('dbUser', 'root'); ?>" />
<p class="description"><?php _e('您可能会使用 "%s"', 'root'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbPassword"><?php _e('数据库密码'); ?></label>
<input type="password" class="text" name="dbPassword" id="dbPassword" value="<?php _v('dbPassword'); ?>" />
</li>
<li>
<label class="typecho-label" for="dbDatabase"><?php _e('数据库名'); ?></label>
<input type="text" class="text" name="dbDatabase" id="dbDatabase" value="<?php _v('dbDatabase', 'typecho'); ?>" />
<p class="description"><?php _e('请您指定数据库名称'); ?></p>
</li>
<?php endif; ?>
<input type="hidden" name="dbCharset" value="<?php _e('utf8'); ?>" />
<li>
<label class="typecho-label" for="dbCharset"><?php _e('数据库编码'); ?></label>
<select name="dbCharset" id="dbCharset">
<option value="utf8"<?php if (_r('dbCharset') == 'utf8'): ?> selected<?php endif; ?>>utf8</option>
<option value="utf8mb4"<?php if (_r('dbCharset') == 'utf8mb4'): ?> selected<?php endif; ?>>utf8mb4</option>
</select>
<p class="description"><?php _e('选择 utf8mb4 编码至少需要 MySQL 5.5.3 版本'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbEngine"><?php _e('数据库引擎'); ?></label>
<select name="dbEngine" id="dbEngine">
<option value="MyISAM"<?php if (_r('dbEngine') == 'MyISAM'): ?> selected<?php endif; ?>>MyISAM</option>
<option value="InnoDB"<?php if (_r('dbEngine') == 'InnoDB'): ?> selected<?php endif; ?>>InnoDB</option>
</select>
</li>
|
这块就是我们显示配置的位置哟。
我们填写完配置信息以后,post
提交当前页面,进入各种判定的部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
if (_r('created') && !file_exists('./config.inc.php')) {
echo '<p class="message error">' . _t('没有检测到您手动创建的配置文件, 请检查后再次创建') . '</p>';
$success = false;
} else {
if (NULL == _r('userUrl')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的网站地址') . '</p>';
} else if (NULL == _r('userName')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的用户名') . '</p>';
} else if (NULL == _r('userMail')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的邮箱地址') . '</p>';
} else if (32 < strlen(_r('userName'))) {
$success = false;
echo '<p class="message error">' . _t('用户名长度超过限制, 请不要超过 32 个字符') . '</p>';
} else if (200 < strlen(_r('userMail'))) {
$success = false;
echo '<p class="message error">' . _t('邮箱长度超过限制, 请不要超过 200 个字符') . '</p>';
}
}
|
这部分会进行一些判定相关的东西。不符合规范的会进行报错 if (_r('created') && !file_exists('./config.inc.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
|
$_dbConfig = _rFrom('dbHost', 'dbUser', 'dbPassword', 'dbCharset', 'dbPort', 'dbDatabase', 'dbFile', 'dbDsn', 'dbEngine');
$_dbConfig = array_filter($_dbConfig);
$dbConfig = array();
foreach ($_dbConfig as $key => $val) {
$dbConfig[strtolower(substr($key, 2))] = $val;
}
// 在特殊服务器上的特殊安装过程处理
if (_r('config')) {
$replace = array_keys($dbConfig);
foreach ($replace as &$key) {
$key = '{' . $key . '}';
}
if (!empty($_dbConfig['dbDsn'])) {
$dbConfig['dsn'] = str_replace($replace, array_values($dbConfig), $dbConfig['dsn']);
}
$config = str_replace($replace, array_values($dbConfig), _r('config'));
}
if (!isset($config) && $success && !_r('created')) {
$installDb = new Typecho_Db($adapter, _r('dbPrefix'));
$installDb->addServer($dbConfig, Typecho_Db::READ | Typecho_Db::WRITE);
/** 检测数据库配置 */
try {
$installDb->query('SELECT 1=1');
} catch (Typecho_Db_Adapter_Exception $e) {
$success = false;
echo '<p class="message error">'
. _t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装') . '</p>';
} catch (Typecho_Db_Exception $e) {
$success = false;
echo '<p class="message error">'
. _t('安装程序捕捉到以下错误: " %s ". 程序被终止, 请检查您的配置信息.',$e->getMessage()) . '</p>';
}
}
|
这块是获取数据库连接配置,然后对数据库进行连接,数据库相关的代码是 typecho
自己的封装的,大家可以自己看一下,很厉害。如果连接失败,会进行报错。
如果成功了,就重置数据库相关信息,这块应该是应对重复安装的。然后 cookie
写入数据库配置信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 重置原有数据库状态
if (isset($installDb)) {
try {
$installDb->query($installDb->update('table.options')
->rows(array('value' => 0))->where('name = ?', 'installed'));
} catch (Exception $e) {
// do nothing
}
}
Typecho_Cookie::set('__typecho_config', base64_encode(serialize(array_merge(array(
'prefix' => _r('dbPrefix'),
'userName' => _r('userName'),
'userPassword' => _r('userPassword'),
'userMail' => _r('userMail'),
'adapter' => $adapter,
'siteUrl' => _r('userUrl')
), $dbConfig))));
|
注意下面这段
1
2
3
4
|
if (_r('created')) {
header('Location: ./install.php?start');
exit;
}
|
这段什么意思?我们后面再说
1
2
3
4
5
6
7
8
9
10
11
12
|
/** 初始化配置文件 */
$lines = array_slice(file(__FILE__), 1, 31);
$lines[] = "
/** 定义数据库参数 */
\$db = new Typecho_Db('{$adapter}', '" . _r('dbPrefix') . "');
\$db->addServer(" . (empty($config) ? var_export($dbConfig, true) : $config) . ", Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(\$db);
";
$contents = implode('', $lines);
if (!Typecho_Common::isAppEngine()) {
@file_put_contents('./config.inc.php', $contents);
}
|
这段就写入配置文件了。
1
2
3
4
5
6
7
8
9
10
11
|
if (!file_exists('./config.inc.php')) {
?>
<div class="message notice"><p><?php _e('安装程序无法自动创建 <strong>config.inc.php</strong> 文件'); ?><br />
<?php _e('您可以在网站根目录下手动创建 <strong>config.inc.php</strong> 文件, 并复制如下代码至其中'); ?></p>
<p><textarea rows="5" onmouseover="this.select();" class="w-100 mono" readonly><?php echo htmlspecialchars($contents); ?></textarea></p>
<p><button name="created" value="1" type="submit" class="btn primary">创建完毕, 继续安装 »</button></p></div>
<?php
} else {
header('Location: ./install.php?start');
exit;
}
|
如果写入文件失败了,就会跳转到当前页面了并且携带 created
参数,就应对上一步的判定了。如果写入成功了,就跳转到 start
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 安装不成功删除配置文件
if(!$success && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php')) {
@unlink(__TYPECHO_ROOT_DIR__ . '/config.inc.php');
}
安装失败了,就删除文件。
### 来到 `start`
这一步就到了比较关键的一步了。
```php
require_once dirname(__FILE__) . '/config.inc.php';
//判断是否已经安装
$db = Typecho_Db::get();
try {
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));
if (empty($installed) || $installed['value'] == 1) {
Typecho_Response::setStatus(404);
exit;
}
} catch (Exception $e) {
// do nothing
}
|
这块就应对上前面说得了,如果包含了配置文件,就会链接数据库,并且查询是否已安装了,如果已安装了就报错 404
。
1
2
3
4
5
6
7
8
|
<?php if (!isset($db)) : ?>
<h1 class="typecho-install-title"><?php _e('安装失败!'); ?></h1>
<div class="typecho-install-body">
<form method="post" action="?config" name="config">
<p class="message error"><?php _e('您没有上传 config.inc.php 文件, 请您重新安装!'); ?> <button class="btn primary" type="submit"><?php _e('重新安装 »'); ?></button></p>
</form>
</div>
<?php else : ?>
|
如果没有连接 db
就报错。
如果一切顺利就执行创建数据库,初始化配置文件。如果失败了就报错安装失败,如果成功了,就跳转到,安装成功。
上面说的看不到 安装过程 页面就是因为太快了,所以就一闪而过了,上面表述的不清楚,这里在说明一下。
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
|
if(('Mysql' == $type && (1050 == $code || '42S01' == $code)) ||
('SQLite' == $type && ('HY000' == $code || 1 == $code)) ||
('Pgsql' == $type && '42P07' == $code)) {
if(_r('delete')) {
//删除原有数据
$dbPrefix = $config['prefix'];
$tableArray = array($dbPrefix . 'comments', $dbPrefix . 'contents', $dbPrefix . 'fields', $dbPrefix . 'metas', $dbPrefix . 'options', $dbPrefix . 'relationships', $dbPrefix . 'users',);
foreach($tableArray as $table) {
if($type == 'Mysql') {
$installDb->query("DROP TABLE IF EXISTS `{$table}`");
} elseif($type == 'Pgsql') {
$installDb->query("DROP TABLE {$table}");
} elseif($type == 'SQLite') {
$installDb->query("DROP TABLE {$table}");
}
}
echo '<p class="message success">' . _t('已经删除完原有数据') . '<br /><br /><button class="btn primary" type="submit" class="primary">'
. _t('继续安装 »') . '</button></p>';
} elseif (_r('goahead')) {
//使用原有数据
//但是要更新用户网站
$installDb->query($installDb->update('table.options')->rows(array('value' => $config['siteUrl']))->where('name = ?', 'siteUrl'));
unset($_SESSION['typecho']);
header('Location: ./install.php?finish&use_old');
exit;
} else {
echo '<p class="message error">' . _t('安装程序检查到原有数据表已经存在.')
. '<br /><br />' . '<button type="submit" name="delete" value="1" class="btn btn-warn">' . _t('删除原有数据') . '</button> '
. _t('或者') . ' <button type="submit" name="goahead" value="1" class="btn primary">' . _t('使用原有数据') . '</button></p>';
}
|
这块就是在异常的时候如果数据库存在,的判断过程。删库或者使用原有数据库,然后等我们决策后,在决定安装流程。
结语
至此,安装全部搞定了,我们分析完了一个安装模块,接下来说些什么呢,先说前台,部分,然后在说明上面没说明的 db
部分,最后说后台。我们下次再见