<?php if (!file_exists(dirname(__FILE__) . '/config.inc.php')) { // site root path define('__TYPECHO_ROOT_DIR__', dirname(__FILE__)); // plugin directory (relative path) define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins'); // theme directory (relative path) define('__TYPECHO_THEME_DIR__', '/usr/themes'); // admin directory (relative path) define('__TYPECHO_ADMIN_DIR__', '/admin/'); // register autoload require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php'; // init \Typecho\Common::init(); } else { require_once dirname(__FILE__) . '/config.inc.php'; $installDb = \Typecho\Db::get(); } /** * get lang * * @return string */ function install_get_lang(): string { $serverLang = \Typecho\Request::getInstance()->getServer('TYPECHO_LANG'); if (!empty($serverLang)) { return $serverLang; } else { $lang = 'zh_CN'; $request = \Typecho\Request::getInstance(); if ($request->is('lang')) { $lang = $request->get('lang'); \Typecho\Cookie::set('lang', $lang); } return \Typecho\Cookie::get('lang', $lang); } } /** * get site url * * @return string */ function install_get_site_url(): string { $request = \Typecho\Request::getInstance(); return install_is_cli() ? $request->getServer('TYPECHO_SITE_URL', 'http://localhost') : $request->getRequestRoot(); } /** * detect cli mode * * @return bool */ function install_is_cli(): bool { return \Typecho\Request::getInstance()->isCli(); } /** * get default router * * @return string[][] */ function install_get_default_routers(): array { return [ 'index' => [ 'url' => '/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive' => [ 'url' => '/blog/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'do' => [ 'url' => '/action/[action:alpha]', 'widget' => '\Widget\Action', 'action' => 'action', ], 'post' => [ 'url' => '/archives/[cid:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'attachment' => [ 'url' => '/attachment/[cid:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'category' => [ 'url' => '/category/[slug]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'tag' => [ 'url' => '/tag/[slug]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'author' => [ 'url' => '/author/[uid:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'search' => [ 'url' => '/search/[keywords]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'index_page' => [ 'url' => '/page/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_page' => [ 'url' => '/blog/page/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'category_page' => [ 'url' => '/category/[slug]/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'tag_page' => [ 'url' => '/tag/[slug]/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'author_page' => [ 'url' => '/author/[uid:digital]/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'search_page' => [ 'url' => '/search/[keywords]/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_year' => [ 'url' => '/[year:digital:4]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_month' => [ 'url' => '/[year:digital:4]/[month:digital:2]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_day' => [ 'url' => '/[year:digital:4]/[month:digital:2]/[day:digital:2]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_year_page' => [ 'url' => '/[year:digital:4]/page/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_month_page' => [ 'url' => '/[year:digital:4]/[month:digital:2]/page/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'archive_day_page' => [ 'url' => '/[year:digital:4]/[month:digital:2]/[day:digital:2]/page/[page:digital]/', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'comment_page' => [ 'url' => '[permalink:string]/comment-page-[commentPage:digital]', 'widget' => '\Widget\Archive', 'action' => 'render', ], 'feed' => [ 'url' => '/feed[feed:string:0]', 'widget' => '\Widget\Archive', 'action' => 'feed', ], 'feedback' => [ 'url' => '[permalink:string]/[type:alpha]', 'widget' => '\Widget\Feedback', 'action' => 'action', ], 'page' => [ 'url' => '/[slug].html', 'widget' => '\Widget\Archive', 'action' => 'render', ], ]; } /** * list all default options * * @return array */ function install_get_default_options(): array { static $options; if (empty($options)) { $options = [ 'theme' => 'default', 'theme:default' => 'a:2:{s:7:"logoUrl";N;s:12:"sidebarBlock";a:5:{i:0;s:15:"ShowRecentPosts";i:1;s:18:"ShowRecentComments";i:2;s:12:"ShowCategory";i:3;s:11:"ShowArchive";i:4;s:9:"ShowOther";}}', 'timezone' => '28800', 'lang' => install_get_lang(), 'charset' => 'UTF-8', 'contentType' => 'text/html', 'gzip' => 0, 'generator' => 'Typecho ' . \Typecho\Common::VERSION, 'title' => 'Hello World', 'description' => 'Your description here.', 'keywords' => 'typecho,php,blog', 'rewrite' => 0, 'frontPage' => 'recent', 'frontArchive' => 0, 'commentsRequireMail' => 1, 'commentsWhitelist' => 0, 'commentsRequireURL' => 0, 'commentsRequireModeration' => 0, 'plugins' => 'a:0:{}', 'commentDateFormat' => 'F jS, Y \a\t h:i a', 'siteUrl' => install_get_site_url(), 'defaultCategory' => 1, 'allowRegister' => 0, 'defaultAllowComment' => 1, 'defaultAllowPing' => 1, 'defaultAllowFeed' => 1, 'pageSize' => 5, 'postsListSize' => 10, 'commentsListSize' => 10, 'commentsHTMLTagAllowed' => null, 'postDateFormat' => 'Y-m-d', 'feedFullText' => 1, 'editorSize' => 350, 'autoSave' => 0, 'markdown' => 1, 'xmlrpcMarkdown' => 0, 'commentsMaxNestingLevels' => 5, 'commentsPostTimeout' => 24 * 3600 * 30, 'commentsUrlNofollow' => 1, 'commentsShowUrl' => 1, 'commentsMarkdown' => 0, 'commentsPageBreak' => 0, 'commentsThreaded' => 1, 'commentsPageSize' => 20, 'commentsPageDisplay' => 'last', 'commentsOrder' => 'ASC', 'commentsCheckReferer' => 1, 'commentsAutoClose' => 0, 'commentsPostIntervalEnable' => 1, 'commentsPostInterval' => 60, 'commentsShowCommentOnly' => 0, 'commentsAvatar' => 1, 'commentsAvatarRating' => 'G', 'commentsAntiSpam' => 1, 'routingTable' => serialize(install_get_default_routers()), 'actionTable' => 'a:0:{}', 'panelTable' => 'a:0:{}', 'attachmentTypes' => '@image@', 'secret' => \Typecho\Common::randString(32, true), 'installed' => 0, 'allowXmlRpc' => 2 ]; } return $options; } /** * get database driver type * * @param string $driver * @return string */ function install_get_db_type(string $driver): string { $parts = explode('_', $driver); return $driver == 'Mysqli' ? 'Mysql' : array_pop($parts); } /** * list all available database drivers * * @return array */ function install_get_db_drivers(): array { $drivers = []; if (\Typecho\Db\Adapter\Pdo\Mysql::isAvailable()) { $drivers['Pdo_Mysql'] = _t('Pdo 驱动 Mysql 适配器'); } if (\Typecho\Db\Adapter\Pdo\SQLite::isAvailable()) { $drivers['Pdo_SQLite'] = _t('Pdo 驱动 SQLite 适配器'); } if (\Typecho\Db\Adapter\Pdo\Pgsql::isAvailable()) { $drivers['Pdo_Pgsql'] = _t('Pdo 驱动 PostgreSql 适配器'); } if (\Typecho\Db\Adapter\Mysqli::isAvailable()) { $drivers['Mysqli'] = _t('Mysql 原生函数适配器'); } if (\Typecho\Db\Adapter\SQLite::isAvailable()) { $drivers['SQLite'] = _t('SQLite 原生函数适配器'); } if (\Typecho\Db\Adapter\Pgsql::isAvailable()) { $drivers['Pgsql'] = _t('Pgsql 原生函数适配器'); } return $drivers; } /** * get current db driver * * @return string */ function install_get_current_db_driver(): string { global $installDb; if (empty($installDb)) { $driver = \Typecho\Request::getInstance()->get('driver'); $drivers = install_get_db_drivers(); if (empty($driver) || !isset($drivers[$driver])) { return key($drivers); } return $driver; } else { return $installDb->getAdapterName(); } } /** * generate config file * * @param string $adapter * @param string $dbPrefix * @param array $dbConfig * @param bool $return * @return string */ function install_config_file(string $adapter, string $dbPrefix, array $dbConfig, bool $return = false): string { global $configWritten; $code = "<" . "?php // site root path define('__TYPECHO_ROOT_DIR__', dirname(__FILE__)); // plugin directory (relative path) define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins'); // theme directory (relative path) define('__TYPECHO_THEME_DIR__', '/usr/themes'); // admin directory (relative path) define('__TYPECHO_ADMIN_DIR__', '/admin/'); // register autoload require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php'; // init \Typecho\Common::init(); // config db \$db = new \Typecho\Db('{$adapter}', '{$dbPrefix}'); \$db->addServer(" . (var_export($dbConfig, true)) . ", \Typecho\Db::READ | \Typecho\Db::WRITE); \Typecho\Db::set(\$db); "; $configWritten = false; if (!$return) { $configWritten = @file_put_contents(__TYPECHO_ROOT_DIR__ . '/config.inc.php', $code) !== false; } return $code; } /** * remove config file if written */ function install_remove_config_file() { global $configWritten; if ($configWritten) { unlink(__TYPECHO_ROOT_DIR__ . '/config.inc.php'); } } /** * check install * * @param string $type * @return bool */ function install_check(string $type): bool { switch ($type) { case 'config': return file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php'); case 'db_structure': case 'db_data': global $installDb; if (empty($installDb)) { return false; } try { // check if table exists $installed = $installDb->fetchRow($installDb->select()->from('table.options') ->where('user = 0 AND name = ?', 'installed')); if ($type == 'db_data' && empty($installed['value'])) { return false; } } catch (\Typecho\Db\Adapter\ConnectionException $e) { return true; } catch (\Typecho\Db\Adapter\SQLException $e) { return false; } return true; default: return false; } } /** * raise install error * * @param mixed $error * @param mixed $config */ function install_raise_error($error, $config = null) { if (install_is_cli()) { if (is_array($error)) { foreach ($error as $key => $value) { echo (is_int($key) ? '' : $key . ': ') . $value . "\n"; } } else { echo $error . "\n"; } exit(1); } else { install_throw_json([ 'success' => 0, 'message' => is_string($error) ? nl2br($error) : $error, 'config' => $config ]); } } /** * @param $step * @param array|null $config */ function install_success($step, ?array $config = null) { global $installDb; if (install_is_cli()) { if ($step == 3) { \Typecho\Db::set($installDb); } if ($step > 0) { $method = 'install_step_' . $step . '_perform'; $method(); } if (!empty($config)) { [$userName, $userPassword] = $config; echo _t('安装成功') . "\n"; echo _t('您的用户名是') . " {$userName}\n"; echo _t('您的密码是') . " {$userPassword}\n"; } exit(0); } else { install_throw_json([ 'success' => 1, 'message' => $step, 'config' => $config ]); } } /** * @param $data */ function install_throw_json($data) { \Typecho\Response::getInstance()->setContentType('application/json') ->addResponder(function () use ($data) { echo json_encode($data); }) ->respond(); } /** * @param string $url */ function install_redirect(string $url) { \Typecho\Response::getInstance()->setStatus(302) ->setHeader('Location', $url) ->respond(); } /** * add common js support */ function install_js_support() { ?> <div id="success" class="row typecho-page-main hidden"> <div class="col-mb-12 col-tb-8 col-tb-offset-2"> <div class="typecho-page-title"> <h2><?php _e('安装成功'); ?></h2> </div> <div id="typecho-welcome"> <p class="keep-word"> <?php _e('您选择了使用原有的数据, 您的用户名和密码和原来的一致'); ?> </p> <p class="fresh-word"> <?php _e('您的用户名是'); ?>: <strong class="warning" id="success-user"></strong><br> <?php _e('您的密码是'); ?>: <strong class="warning" id="success-password"></strong> </p> <ul> <li><a id="login-url" href=""><?php _e('点击这里访问您的控制面板'); ?></a></li> <li><a id="site-url" href=""><?php _e('点击这里查看您的 Blog'); ?></a></li> </ul> <p><?php _e('希望您能尽情享用 Typecho 带来的乐趣!'); ?></p> </div> </div> </div> <script> let form = $('form'), errorBox = $('<div></div>'); errorBox.addClass('message error') .prependTo(form); function showError(error) { if (typeof error == 'string') { $(window).scrollTop(0); errorBox .html(error) .addClass('fade'); } else { for (let k in error) { let input = $('#' + k), msg = error[k], p = $('<p></p>'); p.addClass('message error') .html(msg) .insertAfter(input); input.on('input', function () { p.remove(); }); } } return errorBox; } form.submit(function (e) { e.preventDefault(); errorBox.removeClass('fade'); $('button', form).attr('disabled', 'disabled'); $('.typecho-option .error', form).remove(); $.ajax({ url: form.attr('action'), processData: false, contentType: false, type: 'POST', data: new FormData(this), success: function (data) { $('button', form).removeAttr('disabled'); if (data.success) { if (data.message) { location.href = '?step=' + data.message; } else { let success = $('#success').removeClass('hidden'); form.addClass('hidden'); if (data.config) { success.addClass('fresh'); $('.typecho-page-main:first').addClass('hidden'); $('#success-user').html(data.config[0]); $('#success-password').html(data.config[1]); $('#login-url').attr('href', data.config[2]); $('#site-url').attr('href', data.config[3]); } else { success.addClass('keep'); } } } else { let el = showError(data.message); if (typeof configError == 'function' && data.config) { configError(form, data.config, el); } } }, error: function (xhr, error) { showError(error) } }); }); </script> <?php } /** * @param string[] $extensions * @return string|null */ function install_check_extension(array $extensions): ?string { foreach ($extensions as $extension) { if (extension_loaded($extension)) { return null; } } return _n('缺少PHP扩展', '请在服务器上安装以下PHP扩展中的至少一个', count($extensions)) . ': ' . implode(', ', $extensions); } function install_step_1() { $langs = \Widget\Options\General::getLangs(); $lang = install_get_lang(); ?> <div class="row typecho-page-main"> <div class="col-mb-12 col-tb-8 col-tb-offset-2"> <div class="typecho-page-title"> <h2><?php _e('欢迎使用 Typecho'); ?></h2> </div> <div id="typecho-welcome"> <form autocomplete="off" method="post" action="install.php"> <h3><?php _e('安装说明'); ?></h3> <p class="warning"> <strong><?php _e('本安装程序将自动检测服务器环境是否符合最低配置需求. 如果不符合, 将在上方出现提示信息, 请按照提示信息检查您的主机配置. 如果服务器环境符合要求, 将在下方出现 "开始下一步" 的按钮, 点击此按钮即可一步完成安装.'); ?></strong> </p> <h3><?php _e('许可及协议'); ?></h3> <ul> <li><?php _e('Typecho 基于 <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a> 协议发布, 我们允许用户在 GPL 协议许可的范围内使用, 拷贝, 修改和分发此程序.'); ?> <?php _e('在GPL许可的范围内, 您可以自由地将其用于商业以及非商业用途.'); ?></li> <li><?php _e('Typecho 软件由其社区提供支持, 核心开发团队负责维护程序日常开发工作以及新特性的制定.'); ?> <?php _e('如果您遇到使用上的问题, 程序中的 BUG, 以及期许的新功能, 欢迎您在社区中交流或者直接向我们贡献代码.'); ?> <?php _e('对于贡献突出者, 他的名字将出现在贡献者名单中.'); ?></li> </ul> <p class="submit"> <button class="btn primary" type="submit"><?php _e('我准备好了, 开始下一步 »'); ?></button> <input type="hidden" name="step" value="1"> <?php if (count($langs) > 1) : ?> <select style="float: right" onchange="location.href='?lang=' + this.value"> <?php foreach ($langs as $key => $val) : ?> <option value="<?php echo $key; ?>"<?php if ($lang == $key) : ?> selected<?php endif; ?>><?php echo $val; ?></option> <?php endforeach; ?> </select> <?php endif; ?> </p> </form> </div> </div> </div> <?php install_js_support(); } /** * check dependencies before install */ function install_step_1_perform() { $errors = []; $checks = [ 'mbstring', 'json', 'Reflection', ['mysqli', 'sqlite3', 'pgsql', 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql'] ]; foreach ($checks as $check) { $error = install_check_extension(is_array($check) ? $check : [$check]); if (!empty($error)) { $errors[] = $error; } } $uploadDir = '/usr/uploads'; $realUploadDir = \Typecho\Common::url($uploadDir, __TYPECHO_ROOT_DIR__); $writeable = true; if (is_dir($realUploadDir)) { if (!is_writeable($realUploadDir) || !is_readable($realUploadDir)) { if (!@chmod($realUploadDir, 0755)) { $writeable = false; } } } else { if (!@mkdir($realUploadDir, 0755)) { $writeable = false; } } if (!$writeable) { $errors[] = _t('上传目录无法写入, 请手动将安装目录下的 %s 目录的权限设置为可写然后继续升级', $uploadDir); } if (empty($errors)) { install_success(2); } else { install_raise_error(implode("\n", $errors)); } } /** * display step 2 */ function install_step_2() { global $installDb; $drivers = install_get_db_drivers(); $adapter = install_get_current_db_driver(); $type = install_get_db_type($adapter); if (!empty($installDb)) { $config = $installDb->getConfig(\Typecho\Db::WRITE)->toArray(); $config['prefix'] = $installDb->getPrefix(); $config['adapter'] = $adapter; } ?> <div class="row typecho-page-main"> <div class="col-mb-12 col-tb-8 col-tb-offset-2"> <div class="typecho-page-title"> <h2><?php _e('初始化配置'); ?></h2> </div> <form autocomplete="off" action="install.php" method="post"> <ul class="typecho-option"> <li> <label for="dbAdapter" class="typecho-label"><?php _e('数据库适配器'); ?></label> <select name="dbAdapter" id="dbAdapter" onchange="location.href='?step=2&driver=' + this.value"> <?php foreach ($drivers as $driver => $name) : ?> <option value="<?php echo $driver; ?>"<?php if ($driver == $adapter) : ?> selected="selected"<?php endif; ?>><?php echo $name; ?></option> <?php endforeach; ?> </select> <p class="description"><?php _e('请根据您的数据库类型选择合适的适配器'); ?></p> <input type="hidden" id="dbNext" name="dbNext" value="none"> </li> </ul> <ul class="typecho-option"> <li> <label class="typecho-label" for="dbPrefix"><?php _e('数据库前缀'); ?></label> <input type="text" class="text" name="dbPrefix" id="dbPrefix" value="typecho_" /> <p class="description"><?php _e('默认前缀是 "typecho_"'); ?></p> </li> </ul> <?php require_once './install/' . $type . '.php'; ?> <ul class="typecho-option typecho-option-submit"> <li> <button id="confirm" type="submit" class="btn primary"><?php _e('确认, 开始安装 »'); ?></button> <input type="hidden" name="step" value="2"> </li> </ul> </form> </div> </div> <script> function configError(form, config, errorBox) { let next = $('#dbNext'), line = $('<p></p>'); if (config.code) { let text = $('<textarea></textarea>'), btn = $('<button></button>'); btn.html('<?php _e('创建完毕, 继续安装 »'); ?>') .attr('type', 'button') .addClass('btn btn-s primary'); btn.click(function () { next.val('config'); form.trigger('submit'); }); text.val(config.code) .addClass('mono') .attr('readonly', 'readonly'); errorBox.append(text) .append(btn); return; } errorBox.append(line); for (let key in config) { let word = config[key], btn = $('<button></button>'); btn.html(word) .attr('type', 'button') .addClass('btn btn-s primary') .click(function () { next.val(key); form.trigger('submit'); }); line.append(btn); } } $('#confirm').click(function () { $('#dbNext').val('none'); }); <?php if (!empty($config)) : ?> function fillInput(config) { for (let k in config) { let value = config[k], key = 'db' + k.charAt(0).toUpperCase() + k.slice(1), input = $('#' + key) .attr('readonly', 'readonly') .val(value); $('option:not(:selected)', input).attr('disabled', 'disabled'); } } fillInput(<?php echo json_encode($config); ?>); <?php endif; ?> </script> <?php install_js_support(); } /** * perform install step 2 */ function install_step_2_perform() { global $installDb; $request = \Typecho\Request::getInstance(); $drivers = install_get_db_drivers(); $configMap = [ 'Mysql' => [ 'dbHost' => 'localhost', 'dbPort' => 3306, 'dbUser' => null, 'dbPassword' => null, 'dbCharset' => 'utf8mb4', 'dbDatabase' => null, 'dbEngine' => 'InnoDB', 'dbSslCa' => null, 'dbSslVerify' => 'on', ], 'Pgsql' => [ 'dbHost' => 'localhost', 'dbPort' => 5432, 'dbUser' => null, 'dbPassword' => null, 'dbCharset' => 'utf8', 'dbDatabase' => null, ], 'SQLite' => [ 'dbFile' => __TYPECHO_ROOT_DIR__ . '/usr/' . uniqid() . '.db' ] ]; if (install_is_cli()) { $config = [ 'dbHost' => $request->getServer('TYPECHO_DB_HOST'), 'dbUser' => $request->getServer('TYPECHO_DB_USER'), 'dbPassword' => $request->getServer('TYPECHO_DB_PASSWORD'), 'dbCharset' => $request->getServer('TYPECHO_DB_CHARSET'), 'dbPort' => $request->getServer('TYPECHO_DB_PORT'), 'dbDatabase' => $request->getServer('TYPECHO_DB_DATABASE'), 'dbFile' => $request->getServer('TYPECHO_DB_FILE'), 'dbDsn' => $request->getServer('TYPECHO_DB_DSN'), 'dbEngine' => $request->getServer('TYPECHO_DB_ENGINE'), 'dbPrefix' => $request->getServer('TYPECHO_DB_PREFIX', 'typecho_'), 'dbAdapter' => $request->getServer('TYPECHO_DB_ADAPTER', install_get_current_db_driver()), 'dbNext' => $request->getServer('TYPECHO_DB_NEXT', 'none'), 'dbSslCa' => $request->getServer('TYPECHO_DB_SSL_CA'), 'dbSslVerify' => $request->getServer('TYPECHO_DB_SSL_VERIFY', 'on'), ]; } else { $config = $request->from([ 'dbHost', 'dbUser', 'dbPassword', 'dbCharset', 'dbPort', 'dbDatabase', 'dbFile', 'dbDsn', 'dbEngine', 'dbPrefix', 'dbAdapter', 'dbNext', 'dbSslCa', 'dbSslVerify', ]); } $error = (new \Typecho\Validate()) ->addRule('dbPrefix', 'required', _t('确认您的配置')) ->addRule('dbPrefix', 'minLength', _t('确认您的配置'), 1) ->addRule('dbPrefix', 'maxLength', _t('确认您的配置'), 16) ->addRule('dbPrefix', 'alphaDash', _t('确认您的配置')) ->addRule('dbAdapter', 'required', _t('确认您的配置')) ->addRule('dbAdapter', 'enum', _t('确认您的配置'), array_keys($drivers)) ->addRule('dbNext', 'required', _t('确认您的配置')) ->addRule('dbNext', 'enum', _t('确认您的配置'), ['none', 'delete', 'keep', 'config']) ->run($config); if (!empty($error)) { install_raise_error($error); } $type = install_get_db_type($config['dbAdapter']); $dbConfig = []; foreach ($configMap[$type] as $key => $value) { $config[$key] = !isset($config[$key]) ? (install_is_cli() ? $value : null) : $config[$key]; } switch ($type) { case 'Mysql': $error = (new \Typecho\Validate()) ->addRule('dbHost', 'required', _t('确认您的配置')) ->addRule('dbPort', 'required', _t('确认您的配置')) ->addRule('dbPort', 'isInteger', _t('确认您的配置')) ->addRule('dbUser', 'required', _t('确认您的配置')) ->addRule('dbCharset', 'required', _t('确认您的配置')) ->addRule('dbCharset', 'enum', _t('确认您的配置'), ['utf8', 'utf8mb4']) ->addRule('dbDatabase', 'required', _t('确认您的配置')) ->addRule('dbEngine', 'required', _t('确认您的配置')) ->addRule('dbEngine', 'enum', _t('确认您的配置'), ['InnoDB', 'MyISAM']) ->addRule('dbSslCa', 'file_exists', _t('确认您的配置')) ->addRule('dbSslVerify', 'enum', _t('确认您的配置'), ['on', 'off']) ->run($config); break; case 'Pgsql': $error = (new \Typecho\Validate()) ->addRule('dbHost', 'required', _t('确认您的配置')) ->addRule('dbPort', 'required', _t('确认您的配置')) ->addRule('dbPort', 'isInteger', _t('确认您的配置')) ->addRule('dbUser', 'required', _t('确认您的配置')) ->addRule('dbCharset', 'required', _t('确认您的配置')) ->addRule('dbCharset', 'enum', _t('确认您的配置'), ['utf8']) ->addRule('dbDatabase', 'required', _t('确认您的配置')) ->run($config); break; case 'SQLite': $error = (new \Typecho\Validate()) ->addRule('dbFile', 'required', _t('确认您的配置')) ->addRule('dbFile', function (string $path) { $pattern = "/^(\/[._a-z0-9-]+)*[a-z0-9]+\.[a-z0-9]{2,}$/i"; if (strstr(PHP_OS, 'WIN')) { $pattern = "/(\/[._a-z0-9-]+)*[a-z0-9]+\.[a-z0-9]{2,}$/i"; } return !!preg_match($pattern, $path); }, _t('确认您的配置')) ->run($config); break; default: install_raise_error(_t('确认您的配置')); break; } if (!empty($error)) { install_raise_error($error); } foreach ($configMap[$type] as $key => $value) { $dbConfig[lcfirst(substr($key, 2))] = $config[$key]; } // intval port number if (isset($dbConfig['port'])) { $dbConfig['port'] = intval($dbConfig['port']); } // bool ssl verify if (isset($dbConfig['sslVerify'])) { $dbConfig['sslVerify'] = $dbConfig['sslVerify'] == 'on'; } if (isset($dbConfig['file']) && preg_match("/^[a-z0-9]+\.[a-z0-9]{2,}$/i", $dbConfig['file'])) { $dbConfig['file'] = __DIR__ . '/usr/' . $dbConfig['file']; } // check config file if ($config['dbNext'] == 'config' && !install_check('config')) { $code = install_config_file($config['dbAdapter'], $config['dbPrefix'], $dbConfig, true); install_raise_error(_t('没有检测到您手动创建的配置文件, 请检查后再次创建'), ['code' => $code]); } elseif (empty($installDb)) { // detect db config try { $installDb = new \Typecho\Db($config['dbAdapter'], $config['dbPrefix']); $installDb->addServer($dbConfig, \Typecho\Db::READ | \Typecho\Db::WRITE); $installDb->query('SELECT 1=1'); } catch (\Typecho\Db\Adapter\ConnectionException $e) { $code = $e->getCode(); if (('Mysql' == $type && 1049 == $code) || ('Pgsql' == $type && 7 == $code)) { install_raise_error(_t('数据库: "%s"不存在,请手动创建后重试', $config['dbDatabase'])); } else { install_raise_error(_t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装: "%s"', $e->getMessage())); } } catch (\Typecho\Db\Exception $e) { install_raise_error(_t('安装程序捕捉到以下错误: "%s". 程序被终止, 请检查您的配置信息.', $e->getMessage())); } $code = install_config_file($config['dbAdapter'], $config['dbPrefix'], $dbConfig); if (!install_check('config')) { install_raise_error( _t('安装程序无法自动创建 <strong>config.inc.php</strong> 文件') . "\n" . _t('您可以在网站根目录下手动创建 <strong>config.inc.php</strong> 文件, 并复制如下代码至其中'), [ 'code' => $code ] ); } } // delete exists db if ($config['dbNext'] == 'delete') { $tables = [ $config['dbPrefix'] . 'comments', $config['dbPrefix'] . 'contents', $config['dbPrefix'] . 'fields', $config['dbPrefix'] . 'metas', $config['dbPrefix'] . 'options', $config['dbPrefix'] . 'relationships', $config['dbPrefix'] . 'users' ]; try { foreach ($tables as $table) { switch ($type) { case 'Mysql': $installDb->query("DROP TABLE IF EXISTS `{$table}`"); break; case 'Pgsql': case 'SQLite': $installDb->query("DROP TABLE {$table}"); break; } } } catch (\Typecho\Db\Exception $e) { install_raise_error(_t('安装程序捕捉到以下错误: "%s". 程序被终止, 请检查您的配置信息.', $e->getMessage())); } } // init db structure try { $scripts = file_get_contents(__TYPECHO_ROOT_DIR__ . '/install/' . $type . '.sql'); $scripts = str_replace('typecho_', $config['dbPrefix'], $scripts); if (isset($dbConfig['charset'])) { $scripts = str_replace('%charset%', $dbConfig['charset'], $scripts); } if (isset($dbConfig['engine'])) { $scripts = str_replace('%engine%', $dbConfig['engine'], $scripts); } $scripts = explode(';', $scripts); foreach ($scripts as $script) { $script = trim($script); if ($script) { $installDb->query($script, \Typecho\Db::WRITE); } } } catch (\Typecho\Db\Exception $e) { $code = $e->getCode(); if ( ('Mysql' == $type && (1050 == $code || '42S01' == $code)) || ('SQLite' == $type && ('HY000' == $code || 1 == $code)) || ('Pgsql' == $type && '42P07' == $code) ) { if ($config['dbNext'] == 'keep') { if (install_check('db_data')) { install_success(0); } else { install_success(3); } } elseif ($config['dbNext'] == 'none') { install_remove_config_file(); install_raise_error(_t('安装程序检查到原有数据表已经存在.'), [ 'delete' => _t('删除原有数据'), 'keep' => _t('使用原有数据') ]); } } else { install_remove_config_file(); install_raise_error(_t('安装程序捕捉到以下错误: "%s". 程序被终止, 请检查您的配置信息.', $e->getMessage())); } } install_success(3); } /** * display step 3 */ function install_step_3() { $options = \Widget\Options::alloc(); ?> <div class="row typecho-page-main"> <div class="col-mb-12 col-tb-8 col-tb-offset-2"> <div class="typecho-page-title"> <h2><?php _e('创建您的管理员帐号'); ?></h2> </div> <form autocomplete="off" action="install.php" method="post"> <ul class="typecho-option"> <li> <label class="typecho-label" for="userUrl"><?php _e('网站地址'); ?></label> <input autocomplete="new-password" type="text" name="userUrl" id="userUrl" class="text" value="<?php $options->rootUrl(); ?>" /> <p class="description"><?php _e('这是程序自动匹配的网站路径, 如果不正确请修改它'); ?></p> </li> </ul> <ul class="typecho-option"> <li> <label class="typecho-label" for="userName"><?php _e('用户名'); ?></label> <input autocomplete="new-password" type="text" name="userName" id="userName" class="text" /> <p class="description"><?php _e('请填写您的用户名'); ?></p> </li> </ul> <ul class="typecho-option"> <li> <label class="typecho-label" for="userPassword"><?php _e('登录密码'); ?></label> <input type="password" name="userPassword" id="userPassword" class="text" /> <p class="description"><?php _e('请填写您的登录密码, 如果留空系统将为您随机生成一个'); ?></p> </li> </ul> <ul class="typecho-option"> <li> <label class="typecho-label" for="userMail"><?php _e('邮件地址'); ?></label> <input autocomplete="new-password" type="text" name="userMail" id="userMail" class="text" /> <p class="description"><?php _e('请填写一个您的常用邮箱'); ?></p> </li> </ul> <ul class="typecho-option typecho-option-submit"> <li> <button type="submit" class="btn primary"><?php _e('继续安装 »'); ?></button> <input type="hidden" name="step" value="3"> </li> </ul> </form> </div> </div> <?php install_js_support(); } /** * perform step 3 */ function install_step_3_perform() { global $installDb; $request = \Typecho\Request::getInstance(); $defaultPassword = \Typecho\Common::randString(8); $options = \Widget\Options::alloc(); if (install_is_cli()) { $config = [ 'userUrl' => $request->getServer('TYPECHO_SITE_URL'), 'userName' => $request->getServer('TYPECHO_USER_NAME', 'typecho'), 'userPassword' => $request->getServer('TYPECHO_USER_PASSWORD'), 'userMail' => $request->getServer('TYPECHO_USER_MAIL', 'admin@localhost.local') ]; } else { $config = $request->from([ 'userUrl', 'userName', 'userPassword', 'userMail', ]); } $error = (new \Typecho\Validate()) ->addRule('userUrl', 'required', _t('请填写站点地址')) ->addRule('userUrl', 'url', _t('请填写一个合法的URL地址')) ->addRule('userName', 'required', _t('必须填写用户名称')) ->addRule('userName', 'xssCheck', _t('请不要在用户名中使用特殊字符')) ->addRule('userName', 'maxLength', _t('用户名长度超过限制, 请不要超过 32 个字符'), 32) ->addRule('userMail', 'required', _t('必须填写电子邮箱')) ->addRule('userMail', 'email', _t('电子邮箱格式错误')) ->addRule('userMail', 'maxLength', _t('邮箱长度超过限制, 请不要超过 200 个字符'), 200) ->run($config); if (!empty($error)) { install_raise_error($error); } if (empty($config['userPassword'])) { $config['userPassword'] = $defaultPassword; } try { // write user $hasher = new \Utils\PasswordHash(8, true); $installDb->query( $installDb->insert('table.users')->rows([ 'name' => $config['userName'], 'password' => $hasher->hashPassword($config['userPassword']), 'mail' => $config['userMail'], 'url' => $config['userUrl'], 'screenName' => $config['userName'], 'group' => 'administrator', 'created' => \Typecho\Date::time() ]) ); // write category $installDb->query( $installDb->insert('table.metas') ->rows([ 'name' => _t('默认分类'), 'slug' => 'default', 'type' => 'category', 'description' => _t('只是一个默认分类'), 'count' => 1 ]) ); $installDb->query($installDb->insert('table.relationships')->rows(['cid' => 1, 'mid' => 1])); // write first page and post $installDb->query( $installDb->insert('table.contents')->rows([ 'title' => _t('欢迎使用 Typecho'), 'slug' => 'start', 'created' => \Typecho\Date::time(), 'modified' => \Typecho\Date::time(), 'text' => '<!--markdown-->' . _t('如果您看到这篇文章,表示您的 blog 已经安装成功.'), 'authorId' => 1, 'type' => 'post', 'status' => 'publish', 'commentsNum' => 1, 'allowComment' => 1, 'allowPing' => 1, 'allowFeed' => 1, 'parent' => 0 ]) ); $installDb->query( $installDb->insert('table.contents')->rows([ 'title' => _t('关于'), 'slug' => 'start-page', 'created' => \Typecho\Date::time(), 'modified' => \Typecho\Date::time(), 'text' => '<!--markdown-->' . _t('本页面由 Typecho 创建, 这只是个测试页面.'), 'authorId' => 1, 'order' => 0, 'type' => 'page', 'status' => 'publish', 'commentsNum' => 0, 'allowComment' => 1, 'allowPing' => 1, 'allowFeed' => 1, 'parent' => 0 ]) ); // write comment $installDb->query( $installDb->insert('table.comments')->rows([ 'cid' => 1, 'created' => \Typecho\Date::time(), 'author' => 'Typecho', 'ownerId' => 1, 'url' => 'https://typecho.org', 'ip' => '127.0.0.1', 'agent' => $options->generator, 'text' => '欢迎加入 Typecho 大家族', 'type' => 'comment', 'status' => 'approved', 'parent' => 0 ]) ); // write options foreach (install_get_default_options() as $key => $value) { // mark installing finished if ($key == 'installed') { $value = 1; } $installDb->query( $installDb->insert('table.options')->rows(['name' => $key, 'user' => 0, 'value' => $value]) ); } } catch (\Typecho\Db\Exception $e) { install_raise_error($e->getMessage()); } $parts = parse_url($options->loginAction); $parts['query'] = http_build_query([ 'name' => $config['userName'], 'password' => $config['userPassword'], 'referer' => $options->adminUrl ]); $loginUrl = \Typecho\Common::buildUrl($parts); install_success(0, [ $config['userName'], $config['userPassword'], \Widget\Security::alloc()->getTokenUrl($loginUrl, $request->getReferer()), $config['userUrl'] ]); } /** * dispatch install action * */ function install_dispatch() { // disable root url on cli mode if (install_is_cli()) { define('__TYPECHO_ROOT_URL__', 'http://localhost'); } // init default options $options = \Widget\Options::alloc(install_get_default_options()); \Widget\Init::alloc(); // display version if (install_is_cli()) { echo $options->generator . "\n"; echo 'PHP ' . PHP_VERSION . "\n"; } // install finished yet if ( install_check('config') && install_check('db_structure') && install_check('db_data') ) { // redirect to siteUrl if not cli if (!install_is_cli()) { install_redirect($options->siteUrl); } exit(1); } if (install_is_cli()) { install_step_1_perform(); } else { $request = \Typecho\Request::getInstance(); $step = $request->get('step'); $action = 1; switch (true) { case $step == 2: if (!install_check('db_structure')) { $action = 2; } else { install_redirect('install.php?step=3'); } break; case $step == 3: if (install_check('db_structure')) { $action = 3; } else { install_redirect('install.php?step=2'); } break; default: break; } $method = 'install_step_' . $action; if ($request->isPost()) { $method .= '_perform'; $method(); exit; } ?> <!DOCTYPE HTML> <html> <head> <meta charset="<?php _e('UTF-8'); ?>" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title><?php _e('Typecho 安装程序'); ?></title> <link rel="stylesheet" type="text/css" href="<?php $options->adminStaticUrl('css', 'normalize.css') ?>" /> <link rel="stylesheet" type="text/css" href="<?php $options->adminStaticUrl('css', 'grid.css') ?>" /> <link rel="stylesheet" type="text/css" href="<?php $options->adminStaticUrl('css', 'style.css') ?>" /> <link rel="stylesheet" type="text/css" href="<?php $options->adminStaticUrl('css', 'install.css') ?>" /> <script src="<?php $options->adminStaticUrl('js', 'jquery.js'); ?>"></script> </head> <body> <div class="body container"> <h1><a href="https://typecho.org" target="_blank" class="i-logo">Typecho</a></h1> <?php $method(); ?> </div> </body> </html> <?php } } install_dispatch();