add i18n support
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
|
||||||
|
This is a PHP Smart HTTP Git server with no dependency manager or generated build output.
|
||||||
|
|
||||||
|
- `index.php` is the public entry point and request router.
|
||||||
|
- `install.php` creates the initial configuration and database schema.
|
||||||
|
- `manage.php` provides administrator and managed-repository operations.
|
||||||
|
- `lib/` contains shared HTTP, authentication, repository, routing, Git protocol, and i18n code.
|
||||||
|
- `operations/` handles clone, pull, push, branch, and tag requests.
|
||||||
|
- `schema.mysql.sql` and `migration.repository-ownership.mysql.sql` define database changes.
|
||||||
|
- `config.php.sample` documents settings; local `config.php` must never be committed.
|
||||||
|
- `repos/` contains runtime bare repositories. `README.md` and `usage.md` document deployment.
|
||||||
|
|
||||||
|
There is no dedicated automated test directory.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
|
PHP is interpreted directly, so there is no build step. Run syntax checks from the repository root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
php -l index.php
|
||||||
|
php -l install.php
|
||||||
|
php -l manage.php
|
||||||
|
for file in lib/*.php operations/*.php; do php -l "$file" || exit 1; done
|
||||||
|
```
|
||||||
|
|
||||||
|
Check required runtime support with `php -m | grep pdo_mysql`. For a local smoke test, use a disposable `config.php` and PHP-capable web server, then exercise the UI and Git endpoints with `git ls-remote` or `git clone`.
|
||||||
|
|
||||||
|
## Coding Style & Naming Conventions
|
||||||
|
|
||||||
|
Use four-space indentation, same-line opening braces, uppercase PHP constants (`TRUE`, `FALSE`, `NULL`), and explicit `<?php` files. Use `snake_case` functions and variables, page-specific helper prefixes such as `home_`, `install_`, and `manage_`, and lowercase filenames. Escape HTML values with the local `*_escape()` helper. Keep security headers, CSRF checks, path validation, and protocol responses intact. Put user-facing web text in `lib/i18n.php` and access it through `t()`.
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
No PHPUnit or coverage requirement is configured. Every change should pass all `php -l` checks and include a focused manual smoke test for affected HTTP or Git behavior. For authentication, repository, or push changes, test both success and rejection paths.
|
||||||
|
|
||||||
|
## Commit & Pull Request Guidelines
|
||||||
|
|
||||||
|
Existing commits use short, imperative, lowercase summaries such as `add management page` and `fix permission errors`. Keep commits focused and concise. Pull requests should explain behavior and security impact, list validation commands, identify configuration or schema changes, and include screenshots for UI changes. Never include credentials, production `config.php`, or real repository data.
|
||||||
|
|
||||||
|
## Security & Configuration Tips
|
||||||
|
|
||||||
|
Use a disposable database locally. Review `usage.md` before changing routing, permissions, authentication, or ownership. Keep `config.php` restricted to the application, prevent direct web exposure of `repos/`, and update SQL migration files when persistent metadata changes.
|
||||||
@@ -11,6 +11,9 @@ $url_base = '';
|
|||||||
|
|
||||||
$git_executable = 'git';
|
$git_executable = 'git';
|
||||||
|
|
||||||
|
/* Interface language: 'en', 'zh', or empty to follow the browser. */
|
||||||
|
$language = '';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Application accounts and access tokens. Import schema.mysql.sql before
|
* Application accounts and access tokens. Import schema.mysql.sql before
|
||||||
* enabling this section. Use a dedicated MySQL user with SELECT, INSERT,
|
* enabling this section. Use a dedicated MySQL user with SELECT, INSERT,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ if (!file_exists(__DIR__.'/config.php')) {
|
|||||||
|
|
||||||
require(__DIR__.'/config.php');
|
require(__DIR__.'/config.php');
|
||||||
require(__DIR__.'/lib/http.php');
|
require(__DIR__.'/lib/http.php');
|
||||||
|
require(__DIR__.'/lib/i18n.php');
|
||||||
require(__DIR__.'/lib/auth.php');
|
require(__DIR__.'/lib/auth.php');
|
||||||
require(__DIR__.'/lib/repository.php');
|
require(__DIR__.'/lib/repository.php');
|
||||||
require(__DIR__.'/lib/router.php');
|
require(__DIR__.'/lib/router.php');
|
||||||
@@ -141,34 +142,20 @@ function home_require_csrf($url_base, $configuration) {
|
|||||||
|
|
||||||
function home_auth_result_notice($result) {
|
function home_auth_result_notice($result) {
|
||||||
switch ($result['status']) {
|
switch ($result['status']) {
|
||||||
case 'registered':
|
case 'registered': return array('success', t('notice.registered'));
|
||||||
return array('success', '账号已注册并登录。');
|
case 'logged_in': return array('success', t('notice.logged_in'));
|
||||||
case 'logged_in':
|
case 'registration_disabled': return array('error', t('notice.registration_disabled'));
|
||||||
return array('success', '登录成功。');
|
case 'invalid_username': return array('error', t('notice.invalid_username'));
|
||||||
case 'registration_disabled':
|
case 'invalid_password': return array('error', t('notice.invalid_password'));
|
||||||
return array('error', '当前不允许注册新账号。');
|
case 'password_mismatch': return array('error', t('notice.password_mismatch'));
|
||||||
case 'invalid_username':
|
case 'username_exists': return array('error', t('notice.username_exists'));
|
||||||
return array('error', '用户名须为 3 至 64 个字母、数字、点、短横线或下划线。');
|
case 'invalid_credentials': return array('error', t('notice.invalid_credentials'));
|
||||||
case 'invalid_password':
|
case 'invalid_token_name': return array('error', t('notice.invalid_token_name'));
|
||||||
return array('error', '密码长度须为 8 至 72 个字符,且不能超过 72 字节。');
|
case 'token_created': return array('success', t('notice.token_created'));
|
||||||
case 'password_mismatch':
|
case 'token_revoked': return array('success', t('notice.token_revoked'));
|
||||||
return array('error', '两次输入的密码不一致。');
|
case 'invalid_token': return array('error', t('notice.invalid_token'));
|
||||||
case 'username_exists':
|
case 'session_unavailable': return array('error', t('notice.session_unavailable'));
|
||||||
return array('error', '该用户名已存在或不可用。');
|
default: return array('error', t('notice.auth_database_unavailable'));
|
||||||
case 'invalid_credentials':
|
|
||||||
return array('error', '用户名或密码错误。');
|
|
||||||
case 'invalid_token_name':
|
|
||||||
return array('error', 'Token 名称不能为空,且最多 80 个字符。');
|
|
||||||
case 'token_created':
|
|
||||||
return array('success', 'Access token 已创建。请立即保存,关闭页面后无法再次查看。');
|
|
||||||
case 'token_revoked':
|
|
||||||
return array('success', 'Access token 已撤销。');
|
|
||||||
case 'invalid_token':
|
|
||||||
return array('error', 'Access token 不存在或已撤销。');
|
|
||||||
case 'session_unavailable':
|
|
||||||
return array('error', '当前无法建立安全会话。');
|
|
||||||
default:
|
|
||||||
return array('error', '认证数据库当前不可用。');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +187,7 @@ function home_handle_auth_action($url_base, $configuration, $action) {
|
|||||||
if (!auth_logout()) {
|
if (!auth_logout()) {
|
||||||
send_error(500, 'Internal Server Error', 'Unable to close the login session.');
|
send_error(500, 'Internal Server Error', 'Unable to close the login session.');
|
||||||
}
|
}
|
||||||
home_set_notice($url_base, $configuration, 'success', '已退出登录。');
|
home_set_notice($url_base, $configuration, 'success', t('notice.logged_out'));
|
||||||
home_redirect($url_base);
|
home_redirect($url_base);
|
||||||
} else if ($action === 'create_token') {
|
} else if ($action === 'create_token') {
|
||||||
if ($session_user === NULL) {
|
if ($session_user === NULL) {
|
||||||
@@ -251,50 +238,31 @@ function home_repository_url_conflicts($url_base, $definitions, $configuration,
|
|||||||
function home_creation_result_notice($result) {
|
function home_creation_result_notice($result) {
|
||||||
$name = isset($result['name']) ? $result['name'] : '';
|
$name = isset($result['name']) ? $result['name'] : '';
|
||||||
switch ($result['status']) {
|
switch ($result['status']) {
|
||||||
case 'invalid_name':
|
case 'invalid_name': return array(422, 'error', t('notice.repository_invalid_name'));
|
||||||
return array(422, 'error', '仓库名称格式无效。');
|
case 'already_exists': return array(409, 'error', t('notice.repository_exists', array('name' => $name)));
|
||||||
case 'already_exists':
|
case 'create_busy': return array(409, 'error', t('notice.repository_create_busy'));
|
||||||
return array(409, 'error', '仓库 '.$name.' 已存在。');
|
case 'root_unavailable': return array(503, 'error', t('notice.repository_root_unavailable'));
|
||||||
case 'create_busy':
|
case 'git_unavailable': return array(503, 'error', t('notice.repository_git_unavailable'));
|
||||||
return array(409, 'error', '另一个仓库正在创建,请稍后重试。');
|
case 'metadata_unavailable': return array(503, 'error', t('notice.repository_metadata_unsaved'));
|
||||||
case 'root_unavailable':
|
default: return array(500, 'error', t('notice.repository_create_failed'));
|
||||||
return array(503, 'error', '仓库存放目录不可用或不可写。');
|
|
||||||
case 'git_unavailable':
|
|
||||||
return array(503, 'error', 'Git 初始化服务当前不可用。');
|
|
||||||
case 'metadata_unavailable':
|
|
||||||
return array(503, 'error', '仓库所有权信息无法保存,请稍后重试。');
|
|
||||||
default:
|
|
||||||
return array(500, 'error', '仓库创建失败,请检查服务器日志。');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function home_deletion_result_notice($result) {
|
function home_deletion_result_notice($result) {
|
||||||
$name = isset($result['name']) ? $result['name'] : '';
|
$name = isset($result['name']) ? $result['name'] : '';
|
||||||
switch ($result['status']) {
|
switch ($result['status']) {
|
||||||
case 'deleted':
|
case 'deleted': return array(303, 'success', t('notice.repository_deleted', array('name' => $name)));
|
||||||
return array(303, 'success', '仓库 '.$name.' 已删除。');
|
case 'record_deleted': return array(303, 'success', t('notice.repository_record_deleted', array('name' => $name)));
|
||||||
case 'record_deleted':
|
case 'invalid_repository': return array(422, 'error', t('notice.repository_invalid_name'));
|
||||||
return array(303, 'success', '仓库记录 '.$name.' 已删除,非 bare 路径未作改动。');
|
case 'forbidden': return array(403, 'error', t('notice.repository_delete_forbidden'));
|
||||||
case 'invalid_repository':
|
case 'configured_repository': return array(403, 'error', t('notice.repository_configured_home'));
|
||||||
return array(422, 'error', '仓库名称格式无效。');
|
case 'not_found': return array(404, 'error', t('notice.repository_not_found'));
|
||||||
case 'forbidden':
|
case 'repository_busy': return array(409, 'error', t('notice.repository_busy'));
|
||||||
return array(403, 'error', '只有仓库所有者可以删除该仓库。');
|
case 'root_unavailable': return array(503, 'error', t('notice.repository_root_unavailable'));
|
||||||
case 'configured_repository':
|
case 'metadata_unavailable': return array(503, 'error', t('notice.repository_metadata_unavailable'));
|
||||||
return array(403, 'error', '该路径由 config.php 静态配置,不能从网页删除。');
|
case 'cleanup_failed': return array(500, 'error', t('notice.repository_cleanup_failed'));
|
||||||
case 'not_found':
|
case 'restore_failed': return array(500, 'error', t('notice.repository_restore_failed'));
|
||||||
return array(404, 'error', '托管仓库不存在或尚未创建完成。');
|
default: return array(500, 'error', t('notice.repository_delete_failed'));
|
||||||
case 'repository_busy':
|
|
||||||
return array(409, 'error', '仓库正在执行其他操作,请稍后重试。');
|
|
||||||
case 'root_unavailable':
|
|
||||||
return array(503, 'error', '仓库存放目录不可用或不可写。');
|
|
||||||
case 'metadata_unavailable':
|
|
||||||
return array(503, 'error', '仓库所有权信息当前不可用。');
|
|
||||||
case 'cleanup_failed':
|
|
||||||
return array(500, 'error', '仓库记录已删除,但残留目录清理失败,请检查服务器日志。');
|
|
||||||
case 'restore_failed':
|
|
||||||
return array(500, 'error', '删除失败且仓库目录无法恢复,请立即检查服务器日志。');
|
|
||||||
default:
|
|
||||||
return array(500, 'error', '仓库删除失败,请检查服务器日志。');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +306,7 @@ function home_create_repository(
|
|||||||
|
|
||||||
if ($result['status'] === 'created') {
|
if ($result['status'] === 'created') {
|
||||||
home_set_notice(
|
home_set_notice(
|
||||||
$url_base, $configuration, 'success', '仓库 '.$result['name'].' 已创建。');
|
$url_base, $configuration, 'success', t('notice.repository_created', array('name' => $result['name'])));
|
||||||
home_redirect($url_base);
|
home_redirect($url_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,14 +444,16 @@ function home_clone_url_prefix() {
|
|||||||
return $scheme.'://'.$host;
|
return $scheme.'://'.$host;
|
||||||
}
|
}
|
||||||
|
|
||||||
function home_send_head() {
|
function home_send_head($url_base='') {
|
||||||
|
$lang = home_escape(i18n_html_lang());
|
||||||
|
$title = home_escape(t('home.title'));
|
||||||
|
echo '<!DOCTYPE html>'."\n"
|
||||||
|
.'<html lang="'.$lang.'">'."\n"
|
||||||
|
.'<head>'."\n"
|
||||||
|
.'<meta charset="utf-8">'."\n"
|
||||||
|
.'<meta name="viewport" content="width=device-width, initial-scale=1">'."\n"
|
||||||
|
.'<title>'.$title.'</title>'."\n";
|
||||||
echo <<<'HTML'
|
echo <<<'HTML'
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>PHP Git 服务器</title>
|
|
||||||
<style>
|
<style>
|
||||||
:root { color-scheme: light dark; }
|
:root { color-scheme: light dark; }
|
||||||
body { max-width: 62rem; margin: 0 auto; padding: 2.5rem 1.25rem; line-height: 1.6;
|
body { max-width: 62rem; margin: 0 auto; padding: 2.5rem 1.25rem; line-height: 1.6;
|
||||||
@@ -578,12 +548,15 @@ footer { margin-top: 2.5rem; color: #5b6472; font-size: .9rem; }
|
|||||||
.notice-success { background: #152b20; color: #95dab1; }
|
.notice-success { background: #152b20; color: #95dab1; }
|
||||||
.notice-error { background: #331c1c; color: #f0abab; }
|
.notice-error { background: #331c1c; color: #f0abab; }
|
||||||
}
|
}
|
||||||
|
.language-switcher { font-size: .85rem; }
|
||||||
|
.language-switcher a { color: #075d8f; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>PHP Git 服务器</h1>
|
|
||||||
<p class="lead">通过 HTTP 发布下列 Git 仓库,支持 clone、fetch、pull,并可按仓库启用 push。</p>
|
|
||||||
HTML;
|
HTML;
|
||||||
|
echo '<h1>'.home_escape(t('home.title')).'</h1>'."\n";
|
||||||
|
echo '<p class="lead">'.home_escape(t('home.lead')).'</p>'."\n";
|
||||||
|
echo i18n_language_switcher(home_page_url($url_base))."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
function home_send_authentication($url_base, $configuration, $notice) {
|
function home_send_authentication($url_base, $configuration, $notice) {
|
||||||
@@ -592,14 +565,14 @@ function home_send_authentication($url_base, $configuration, $notice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo '<section class="account" aria-labelledby="account-title">' ."\n";
|
echo '<section class="account" aria-labelledby="account-title">' ."\n";
|
||||||
echo '<h2 id="account-title">账号与 Access Token</h2>' ."\n";
|
echo '<h2 id="account-title">'.home_escape(t('home.account_title')).'</h2>' ."\n";
|
||||||
if ($notice !== NULL && isset($notice['token']) && is_string($notice['token'])) {
|
if ($notice !== NULL && isset($notice['token']) && is_string($notice['token'])) {
|
||||||
echo '<pre class="token-result"><code>'.home_escape($notice['token']).'</code></pre>' ."\n";
|
echo '<pre class="token-result"><code>'.home_escape($notice['token']).'</code></pre>' ."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$csrf_token = home_csrf_token($url_base, $configuration);
|
$csrf_token = home_csrf_token($url_base, $configuration);
|
||||||
if ($csrf_token === FALSE) {
|
if ($csrf_token === FALSE) {
|
||||||
echo '<p class="notice notice-error" role="status">当前无法初始化安全表单。</p>' ."\n";
|
echo '<p class="notice notice-error" role="status">'.home_escape(t('home.form_unavailable')).'</p>' ."\n";
|
||||||
echo '</section>' ."\n";
|
echo '</section>' ."\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -612,56 +585,62 @@ function home_send_authentication($url_base, $configuration, $notice) {
|
|||||||
echo '<div class="account-grid">' ."\n";
|
echo '<div class="account-grid">' ."\n";
|
||||||
echo '<form method="post" action="'.$action.'">' ."\n".$csrf_field;
|
echo '<form method="post" action="'.$action.'">' ."\n".$csrf_field;
|
||||||
echo '<input type="hidden" name="action" value="login">' ."\n";
|
echo '<input type="hidden" name="action" value="login">' ."\n";
|
||||||
echo '<div class="credentials"><div><label for="login-username">用户名</label>' ."\n";
|
echo '<div class="credentials"><div><label for="login-username">'.home_escape(t('home.username')).'</label>' ."\n";
|
||||||
echo '<input id="login-username" name="username" maxlength="64" autocomplete="username" required></div>' ."\n";
|
echo '<input id="login-username" name="username" maxlength="64" autocomplete="username" required></div>' ."\n";
|
||||||
echo '<div><label for="login-password">密码</label>' ."\n";
|
echo '<div><label for="login-password">'.home_escape(t('home.password')).'</label>' ."\n";
|
||||||
echo '<input id="login-password" name="password" type="password" minlength="8" maxlength="72" autocomplete="current-password" required></div></div>' ."\n";
|
echo '<input id="login-password" name="password" type="password" minlength="8" maxlength="72" autocomplete="current-password" required></div></div>' ."\n";
|
||||||
echo '<button type="submit">登录</button></form>' ."\n";
|
echo '<button type="submit">'.home_escape(t('home.login')).'</button></form>' ."\n";
|
||||||
|
|
||||||
if (auth_registration_is_enabled()) {
|
if (auth_registration_is_enabled()) {
|
||||||
echo '<form method="post" action="'.$action.'">' ."\n".$csrf_field;
|
echo '<form method="post" action="'.$action.'">' ."\n".$csrf_field;
|
||||||
echo '<input type="hidden" name="action" value="register">' ."\n";
|
echo '<input type="hidden" name="action" value="register">' ."\n";
|
||||||
echo '<div class="credentials"><div><label for="register-username">注册用户名</label>' ."\n";
|
echo '<div class="credentials"><div><label for="register-username">'.home_escape(t('home.register_username')).'</label>' ."\n";
|
||||||
echo '<input id="register-username" name="username" minlength="3" maxlength="64" pattern="[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9_-]" autocomplete="username" required></div>' ."\n";
|
echo '<input id="register-username" name="username" minlength="3" maxlength="64" pattern="[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9_-]" autocomplete="username" required></div>' ."\n";
|
||||||
echo '<div><label for="register-password">密码</label>' ."\n";
|
echo '<div><label for="register-password">'.home_escape(t('home.password')).'</label>' ."\n";
|
||||||
echo '<input id="register-password" name="password" type="password" minlength="8" maxlength="72" autocomplete="new-password" required></div>' ."\n";
|
echo '<input id="register-password" name="password" type="password" minlength="8" maxlength="72" autocomplete="new-password" required></div>' ."\n";
|
||||||
echo '<div><label for="register-password-confirmation">确认密码</label>' ."\n";
|
echo '<div><label for="register-password-confirmation">'.home_escape(t('home.password_confirmation')).'</label>' ."\n";
|
||||||
echo '<input id="register-password-confirmation" name="password_confirmation" type="password" minlength="8" maxlength="72" autocomplete="new-password" required></div></div>' ."\n";
|
echo '<input id="register-password-confirmation" name="password_confirmation" type="password" minlength="8" maxlength="72" autocomplete="new-password" required></div></div>' ."\n";
|
||||||
echo '<button type="submit">注册</button></form>' ."\n";
|
echo '<button type="submit">'.home_escape(t('home.register')).'</button></form>' ."\n";
|
||||||
}
|
}
|
||||||
echo '</div>' ."\n";
|
echo '</div>' ."\n";
|
||||||
echo '<p class="hint">网页登录使用密码;Git clone、pull 和 push 使用用户名与 access token。</p>' ."\n";
|
echo '<p class="hint">'.home_escape(t('home.auth_hint')).'</p>' ."\n";
|
||||||
echo '</section>' ."\n";
|
echo '</section>' ."\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '<div class="account-bar"><p>当前账号:<strong>'.home_escape($user['username']).'</strong></p>' ."\n";
|
echo '<div class="account-bar"><p>'.home_escape(t('home.current_account')).':<strong>'
|
||||||
|
.home_escape($user['username']).'</strong></p>' ."\n";
|
||||||
echo '<div class="account-actions">';
|
echo '<div class="account-actions">';
|
||||||
if (auth_user_is_administrator($user)) {
|
if (auth_user_is_administrator($user)) {
|
||||||
echo '<a href="'.home_escape(rtrim((string) $url_base, '/').'/manage.php').'">管理</a>';
|
echo '<a href="'.home_escape(rtrim((string) $url_base, '/').'/manage.php').'">'
|
||||||
|
.home_escape(t('home.manage')).'</a>';
|
||||||
}
|
}
|
||||||
echo '<form method="post" action="'.$action.'">'.$csrf_field;
|
echo '<form method="post" action="'.$action.'">'.$csrf_field;
|
||||||
echo '<input type="hidden" name="action" value="logout">' ."\n";
|
echo '<input type="hidden" name="action" value="logout">' ."\n";
|
||||||
echo '<button class="button-danger" type="submit">退出</button></form></div></div>' ."\n";
|
echo '<button class="button-danger" type="submit">'.home_escape(t('home.logout')).'</button></form></div></div>' ."\n";
|
||||||
echo '<form class="token-form" method="post" action="'.$action.'">'.$csrf_field;
|
echo '<form class="token-form" method="post" action="'.$action.'">'.$csrf_field;
|
||||||
echo '<input type="hidden" name="action" value="create_token">' ."\n";
|
echo '<input type="hidden" name="action" value="create_token">' ."\n";
|
||||||
echo '<div class="field"><label for="token-name">新 Token 名称</label>' ."\n";
|
echo '<div class="field"><label for="token-name">'.home_escape(t('home.token_name')).'</label>' ."\n";
|
||||||
echo '<input id="token-name" name="token_name" maxlength="80" placeholder="工作电脑" required></div>' ."\n";
|
echo '<input id="token-name" name="token_name" maxlength="80" placeholder="'
|
||||||
echo '<button type="submit">创建 Token</button></form>' ."\n";
|
.home_escape(t('home.token_name_placeholder')).'" required></div>' ."\n";
|
||||||
|
echo '<button type="submit">'.home_escape(t('home.create_token')).'</button></form>' ."\n";
|
||||||
|
|
||||||
$tokens = auth_list_access_tokens($user['id']);
|
$tokens = auth_list_access_tokens($user['id']);
|
||||||
if ($tokens === FALSE) {
|
if ($tokens === FALSE) {
|
||||||
echo '<p class="notice notice-error">当前无法读取 access token 列表。</p>' ."\n";
|
echo '<p class="notice notice-error">'.home_escape(t('home.token_list_unavailable')).'</p>' ."\n";
|
||||||
} else if (!empty($tokens)) {
|
} else if (!empty($tokens)) {
|
||||||
echo '<ul class="token-list">' ."\n";
|
echo '<ul class="token-list">' ."\n";
|
||||||
foreach ($tokens as $token) {
|
foreach ($tokens as $token) {
|
||||||
$last_used = $token['last_used_at'] === NULL ? '从未使用' : '最后使用 '.$token['last_used_at'];
|
$last_used = $token['last_used_at'] === NULL
|
||||||
|
? t('home.token_never_used')
|
||||||
|
: t('home.token_last_used', array('time' => $token['last_used_at']));
|
||||||
|
$created = t('home.token_created_at', array('time' => $token['created_at']));
|
||||||
echo '<li><span><strong>'.home_escape($token['name']).'</strong><br>';
|
echo '<li><span><strong>'.home_escape($token['name']).'</strong><br>';
|
||||||
echo '<span class="hint">创建于 '.home_escape($token['created_at']).';'.home_escape($last_used).'</span></span>' ."\n";
|
echo '<span class="hint">'.home_escape($created).' · '.home_escape($last_used).'</span></span>' ."\n";
|
||||||
echo '<form method="post" action="'.$action.'">'.$csrf_field;
|
echo '<form method="post" action="'.$action.'">'.$csrf_field;
|
||||||
echo '<input type="hidden" name="action" value="revoke_token">' ."\n";
|
echo '<input type="hidden" name="action" value="revoke_token">' ."\n";
|
||||||
echo '<input type="hidden" name="token_id" value="'.home_escape($token['id']).'">' ."\n";
|
echo '<input type="hidden" name="token_id" value="'.home_escape($token['id']).'">' ."\n";
|
||||||
echo '<button class="button-danger" type="submit">撤销</button></form></li>' ."\n";
|
echo '<button class="button-danger" type="submit">'.home_escape(t('home.revoke')).'</button></form></li>' ."\n";
|
||||||
}
|
}
|
||||||
echo '</ul>' ."\n";
|
echo '</ul>' ."\n";
|
||||||
}
|
}
|
||||||
@@ -674,21 +653,21 @@ function home_send_creation($url_base, $configuration, $notice, $value, $visibil
|
|||||||
}
|
}
|
||||||
|
|
||||||
echo '<section class="create" aria-labelledby="create-title">' ."\n";
|
echo '<section class="create" aria-labelledby="create-title">' ."\n";
|
||||||
echo '<h2 id="create-title">创建仓库</h2>' ."\n";
|
echo '<h2 id="create-title">'.home_escape(t('home.create_repository')).'</h2>' ."\n";
|
||||||
if ($notice !== NULL) {
|
if ($notice !== NULL) {
|
||||||
echo '<p class="notice notice-'.home_escape($notice['type']).'" role="status">'
|
echo '<p class="notice notice-'.home_escape($notice['type']).'" role="status">'
|
||||||
.home_escape($notice['message']).'</p>' ."\n";
|
.home_escape($notice['message']).'</p>' ."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!home_creation_is_authorized($configuration)) {
|
if (!home_creation_is_authorized($configuration)) {
|
||||||
echo '<p class="lead">需要先登录应用账号,才能创建仓库。</p>' ."\n";
|
echo '<p class="lead">'.home_escape(t('home.create_login_required')).'</p>' ."\n";
|
||||||
echo '</section>' ."\n";
|
echo '</section>' ."\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = home_csrf_token($url_base, $configuration);
|
$token = home_csrf_token($url_base, $configuration);
|
||||||
if ($token === FALSE) {
|
if ($token === FALSE) {
|
||||||
echo '<p class="notice notice-error" role="status">当前无法初始化安全表单。</p>' ."\n";
|
echo '<p class="notice notice-error" role="status">'.home_escape(t('home.form_unavailable')).'</p>' ."\n";
|
||||||
echo '</section>' ."\n";
|
echo '</section>' ."\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -696,19 +675,19 @@ function home_send_creation($url_base, $configuration, $notice, $value, $visibil
|
|||||||
echo '<form method="post" action="'.home_escape(home_page_url($url_base)).'">' ."\n";
|
echo '<form method="post" action="'.home_escape(home_page_url($url_base)).'">' ."\n";
|
||||||
echo '<input type="hidden" name="csrf_token" value="'.home_escape($token).'">' ."\n";
|
echo '<input type="hidden" name="csrf_token" value="'.home_escape($token).'">' ."\n";
|
||||||
echo '<input type="hidden" name="action" value="create_repository">' ."\n";
|
echo '<input type="hidden" name="action" value="create_repository">' ."\n";
|
||||||
echo '<div class="field"><label for="repository-name">仓库名称</label>' ."\n";
|
echo '<div class="field"><label for="repository-name">'.home_escape(t('home.repository_name')).'</label>' ."\n";
|
||||||
echo '<input id="repository-name" name="repository_name" type="text" maxlength="68" '
|
echo '<input id="repository-name" name="repository_name" type="text" maxlength="68" '
|
||||||
.'pattern="[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?(?:\.git)?" '
|
.'pattern="[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?(?:\.git)?" '
|
||||||
.'placeholder="project" value="'
|
.'placeholder="project" value="'
|
||||||
.home_escape($value).'" autocomplete="off" required>' ."\n";
|
.home_escape($value).'" autocomplete="off" required>' ."\n";
|
||||||
echo '<p class="hint">可使用字母、数字、点、短横线和下划线;<code>.git</code> 后缀可省略。</p></div>' ."\n";
|
echo '<p class="hint">'.t('home.repository_name_hint').'</p></div>' ."\n";
|
||||||
echo '<fieldset class="visibility"><legend>可见性</legend><div class="visibility-options">' ."\n";
|
echo '<fieldset class="visibility"><legend>'.home_escape(t('home.visibility')).'</legend><div class="visibility-options">' ."\n";
|
||||||
echo '<label class="visibility-option"><input name="visibility" type="radio" value="public"'
|
echo '<label class="visibility-option"><input name="visibility" type="radio" value="public"'
|
||||||
.($visibility !== 'private' ? ' checked' : '').'><span>公开</span></label>' ."\n";
|
.($visibility !== 'private' ? ' checked' : '').'><span>'.home_escape(t('home.public')).'</span></label>' ."\n";
|
||||||
echo '<label class="visibility-option"><input name="visibility" type="radio" value="private"'
|
echo '<label class="visibility-option"><input name="visibility" type="radio" value="private"'
|
||||||
.($visibility === 'private' ? ' checked' : '').'><span>私有</span></label>' ."\n";
|
.($visibility === 'private' ? ' checked' : '').'><span>'.home_escape(t('home.private')).'</span></label>' ."\n";
|
||||||
echo '</div></fieldset>' ."\n";
|
echo '</div></fieldset>' ."\n";
|
||||||
echo '<button type="submit">创建仓库</button>' ."\n";
|
echo '<button type="submit">'.home_escape(t('home.create_repository')).'</button>' ."\n";
|
||||||
echo '</form>' ."\n";
|
echo '</form>' ."\n";
|
||||||
echo '</section>' ."\n";
|
echo '</section>' ."\n";
|
||||||
}
|
}
|
||||||
@@ -723,10 +702,14 @@ function home_send_repository_table(
|
|||||||
$csrf_token = $user === NULL ? FALSE : home_csrf_token($url_base, $configuration);
|
$csrf_token = $user === NULL ? FALSE : home_csrf_token($url_base, $configuration);
|
||||||
echo '<table>'."\n";
|
echo '<table>'."\n";
|
||||||
echo '<caption>'.home_escape($caption).'</caption>'."\n";
|
echo '<caption>'.home_escape($caption).'</caption>'."\n";
|
||||||
echo '<thead><tr><th scope="col">仓库</th><th scope="col">所有者</th><th scope="col">克隆地址</th>'
|
echo '<thead><tr><th scope="col">'.home_escape(t('home.th_repository')).'</th>'
|
||||||
.'<th scope="col">默认分支</th><th scope="col" class="count">分支</th>'
|
.'<th scope="col">'.home_escape(t('home.th_owner')).'</th>'
|
||||||
.'<th scope="col" class="count">标签</th><th scope="col">权限</th>'
|
.'<th scope="col">'.home_escape(t('home.th_clone_url')).'</th>'
|
||||||
.'<th scope="col">操作</th></tr></thead>' ."\n";
|
.'<th scope="col">'.home_escape(t('home.th_default_branch')).'</th>'
|
||||||
|
.'<th scope="col" class="count">'.home_escape(t('home.th_branches')).'</th>'
|
||||||
|
.'<th scope="col" class="count">'.home_escape(t('home.th_tags')).'</th>'
|
||||||
|
.'<th scope="col">'.home_escape(t('home.th_access')).'</th>'
|
||||||
|
.'<th scope="col">'.home_escape(t('home.th_actions')).'</th></tr></thead>' ."\n";
|
||||||
echo '<tbody>'."\n";
|
echo '<tbody>'."\n";
|
||||||
|
|
||||||
foreach ($repositories as $repository) {
|
foreach ($repositories as $repository) {
|
||||||
@@ -734,16 +717,16 @@ function home_send_repository_table(
|
|||||||
if ($summary['head'] !== NULL) {
|
if ($summary['head'] !== NULL) {
|
||||||
$head = '<code>'.home_escape($summary['head']).'</code>';
|
$head = '<code>'.home_escape($summary['head']).'</code>';
|
||||||
} else if ($summary['branches'] === 0) {
|
} else if ($summary['branches'] === 0) {
|
||||||
$head = '<span class="badge badge-quiet">空仓库</span>';
|
$head = '<span class="badge badge-quiet">'.home_escape(t('home.badge_empty')).'</span>';
|
||||||
} else {
|
} else {
|
||||||
$head = '<span class="badge badge-quiet">未指向分支</span>';
|
$head = '<span class="badge badge-quiet">'.home_escape(t('home.badge_no_branch')).'</span>';
|
||||||
}
|
}
|
||||||
$owner = $repository['options']['owner'] === NULL
|
$owner = $repository['options']['owner'] === NULL
|
||||||
? '<span class="badge badge-quiet">未设置</span>'
|
? '<span class="badge badge-quiet">'.home_escape(t('home.badge_unset')).'</span>'
|
||||||
: '<code>'.home_escape($repository['options']['owner']).'</code>';
|
: '<code>'.home_escape($repository['options']['owner']).'</code>';
|
||||||
$access = $repository['options']['push'] && $repository['options']['owner'] !== NULL
|
$access = $repository['options']['push'] && $repository['options']['owner'] !== NULL
|
||||||
? '<span class="badge badge-push">所有者可推送</span>'
|
? '<span class="badge badge-push">'.home_escape(t('home.badge_owner_push')).'</span>'
|
||||||
: '<span class="badge badge-quiet">只读</span>';
|
: '<span class="badge badge-quiet">'.home_escape(t('home.badge_read_only')).'</span>';
|
||||||
|
|
||||||
echo '<tr>';
|
echo '<tr>';
|
||||||
echo '<td>'.home_escape(basename($repository['url'])).'</td>';
|
echo '<td>'.home_escape(basename($repository['url'])).'</td>';
|
||||||
@@ -763,10 +746,10 @@ function home_send_repository_table(
|
|||||||
echo '<input type="hidden" name="repository_name" value="'
|
echo '<input type="hidden" name="repository_name" value="'
|
||||||
.home_escape(basename($repository['url'])).'">' ."\n";
|
.home_escape(basename($repository['url'])).'">' ."\n";
|
||||||
echo '<label class="confirm-delete"><input name="confirmation" type="checkbox" '
|
echo '<label class="confirm-delete"><input name="confirmation" type="checkbox" '
|
||||||
.'value="delete" required> 确认删除</label>' ."\n";
|
.'value="delete" required> '.home_escape(t('home.confirm_delete')).'</label>' ."\n";
|
||||||
echo '<button class="button-danger" type="submit">删除</button></form>';
|
echo '<button class="button-danger" type="submit">'.home_escape(t('home.delete')).'</button></form>';
|
||||||
} else {
|
} else {
|
||||||
echo '<span class="badge badge-quiet">无</span>';
|
echo '<span class="badge badge-quiet">'.home_escape(t('home.badge_none')).'</span>';
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
echo '</tr>'."\n";
|
echo '</tr>'."\n";
|
||||||
@@ -799,13 +782,10 @@ function home_send_repository_section(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function home_send_empty_notice() {
|
function home_send_empty_notice() {
|
||||||
echo <<<'HTML'
|
echo '<div class="empty">'."\n";
|
||||||
<div class="empty">
|
echo '<p>'.home_escape(t('home.empty_title')).'</p>'."\n";
|
||||||
<p>当前没有可读取的仓库。</p>
|
echo '<p>'.t('home.empty_body').'</p>'."\n";
|
||||||
<p>请复制 <code>config.php.sample</code> 为 <code>config.php</code>,在 <code>$repos</code>
|
echo '</div>'."\n";
|
||||||
中配置仓库路径,并确认仓库目录存在且 <code>read</code> 选项为 <code>TRUE</code>。</p>
|
|
||||||
</div>
|
|
||||||
HTML;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function home_send_usage($repositories, $prefix) {
|
function home_send_usage($repositories, $prefix) {
|
||||||
@@ -813,12 +793,11 @@ function home_send_usage($repositories, $prefix) {
|
|||||||
? $prefix.'/project.git'
|
? $prefix.'/project.git'
|
||||||
: $prefix.$repositories[0]['url'];
|
: $prefix.$repositories[0]['url'];
|
||||||
|
|
||||||
echo '<h2>使用方法</h2>'."\n";
|
echo '<h2>'.home_escape(t('home.usage_title')).'</h2>'."\n";
|
||||||
echo '<pre><code>git clone '.home_escape($example)."\n"
|
echo '<pre><code>git clone '.home_escape($example)."\n"
|
||||||
.'git pull'."\n"
|
.'git pull'."\n"
|
||||||
.'git push origin main</code></pre>'."\n";
|
.'git push origin main</code></pre>'."\n";
|
||||||
echo '<p class="lead">push 需要仓库启用 <code>push</code> 选项;启用认证时还需要'
|
echo '<p class="lead">'.t('home.usage_hint').'</p>'."\n";
|
||||||
.'使用账号用户名和 access token。</p>'."\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function home_render(
|
function home_render(
|
||||||
@@ -837,7 +816,7 @@ function home_render(
|
|||||||
header('X-Content-Type-Options: nosniff');
|
header('X-Content-Type-Options: nosniff');
|
||||||
header('Content-Security-Policy: default-src \'none\'; style-src \'unsafe-inline\'');
|
header('Content-Security-Policy: default-src \'none\'; style-src \'unsafe-inline\'');
|
||||||
|
|
||||||
home_send_head();
|
home_send_head($url_base);
|
||||||
if ($notice !== NULL) {
|
if ($notice !== NULL) {
|
||||||
echo '<p class="notice notice-'.home_escape($notice['type']).'" role="status">'
|
echo '<p class="notice notice-'.home_escape($notice['type']).'" role="status">'
|
||||||
.home_escape($notice['message']).'</p>' ."\n";
|
.home_escape($notice['message']).'</p>' ."\n";
|
||||||
@@ -847,26 +826,26 @@ function home_render(
|
|||||||
|
|
||||||
home_send_repository_section(
|
home_send_repository_section(
|
||||||
'public-repositories',
|
'public-repositories',
|
||||||
'公开仓库',
|
t('home.public_repositories'),
|
||||||
$repositories['public'],
|
$repositories['public'],
|
||||||
$prefix,
|
$prefix,
|
||||||
'当前没有公开仓库。',
|
t('home.no_public_repositories'),
|
||||||
$url_base,
|
$url_base,
|
||||||
$configuration);
|
$configuration);
|
||||||
if ($show_private) {
|
if ($show_private) {
|
||||||
home_send_repository_section(
|
home_send_repository_section(
|
||||||
'private-repositories',
|
'private-repositories',
|
||||||
'私有仓库',
|
t('home.private_repositories'),
|
||||||
$repositories['private'],
|
$repositories['private'],
|
||||||
$prefix,
|
$prefix,
|
||||||
'当前没有私有仓库。',
|
t('home.no_private_repositories'),
|
||||||
$url_base,
|
$url_base,
|
||||||
$configuration);
|
$configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
home_send_usage(array_merge($repositories['public'], $repositories['private']), $prefix);
|
home_send_usage(array_merge($repositories['public'], $repositories['private']), $prefix);
|
||||||
|
|
||||||
echo '<footer>详细安装、配置和安全说明见 <code>usage.md</code>。</footer>'."\n";
|
echo '<footer>'.t('home.footer').'</footer>'."\n";
|
||||||
echo '</body>'."\n".'</html>'."\n";
|
echo '</body>'."\n".'</html>'."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,6 +893,7 @@ if (!isset($auth)) {
|
|||||||
if (!is_array($auth)) {
|
if (!is_array($auth)) {
|
||||||
send_error(500, 'Internal Server Error', 'The authentication configuration is invalid.');
|
send_error(500, 'Internal Server Error', 'The authentication configuration is invalid.');
|
||||||
}
|
}
|
||||||
|
i18n_configure(isset($language) ? $language : NULL, $url_base);
|
||||||
auth_configure($auth, $url_base);
|
auth_configure($auth, $url_base);
|
||||||
|
|
||||||
if (!isset($managed_repositories)) {
|
if (!isset($managed_repositories)) {
|
||||||
|
|||||||
+42
-32
@@ -5,6 +5,10 @@
|
|||||||
endpoint, so it refuses to run once that file exists. */
|
endpoint, so it refuses to run once that file exists. */
|
||||||
|
|
||||||
require(__DIR__.'/lib/http.php');
|
require(__DIR__.'/lib/http.php');
|
||||||
|
require(__DIR__.'/lib/i18n.php');
|
||||||
|
|
||||||
|
// Installer has no config yet, so language follows the request/cookie/browser.
|
||||||
|
i18n_configure(NULL, rtrim(str_replace('\\', '/', dirname(install_page_url())), '/'));
|
||||||
|
|
||||||
function install_escape($value) {
|
function install_escape($value) {
|
||||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||||
@@ -115,34 +119,34 @@ function install_validate($input) {
|
|||||||
|
|
||||||
if ($input['url_base'] !== ''
|
if ($input['url_base'] !== ''
|
||||||
&& !preg_match('~^/[A-Za-z0-9._~/-]*[A-Za-z0-9._~-]$~D', $input['url_base'])) {
|
&& !preg_match('~^/[A-Za-z0-9._~/-]*[A-Za-z0-9._~-]$~D', $input['url_base'])) {
|
||||||
$errors[] = '基础路径必须以 / 开头,且不能以 / 结尾;部署在域名根路径时请留空。';
|
$errors[] = t('install.error_url_base');
|
||||||
}
|
}
|
||||||
if ($input['git_executable'] === '' || strlen($input['git_executable']) > 255
|
if ($input['git_executable'] === '' || strlen($input['git_executable']) > 255
|
||||||
|| preg_match('~[\x00-\x1F\x7F]~', $input['git_executable'])) {
|
|| preg_match('~[\x00-\x1F\x7F]~', $input['git_executable'])) {
|
||||||
$errors[] = 'Git 可执行文件路径无效。';
|
$errors[] = t('install.error_git_executable');
|
||||||
}
|
}
|
||||||
if (!install_host_is_valid($input['db_host'])) {
|
if (!install_host_is_valid($input['db_host'])) {
|
||||||
$errors[] = '数据库主机名无效。';
|
$errors[] = t('install.error_db_host');
|
||||||
}
|
}
|
||||||
if (!preg_match('~^[1-9][0-9]{0,4}$~D', $input['db_port']) || (int) $input['db_port'] > 65535) {
|
if (!preg_match('~^[1-9][0-9]{0,4}$~D', $input['db_port']) || (int) $input['db_port'] > 65535) {
|
||||||
$errors[] = '数据库端口无效。';
|
$errors[] = t('install.error_db_port');
|
||||||
}
|
}
|
||||||
if (!install_identifier_is_valid($input['db_name'])) {
|
if (!install_identifier_is_valid($input['db_name'])) {
|
||||||
$errors[] = '数据库名只能包含字母、数字、下划线和短横线,最多 64 个字符。';
|
$errors[] = t('install.error_db_name');
|
||||||
}
|
}
|
||||||
if (!install_identifier_is_valid($input['db_user'])) {
|
if (!install_identifier_is_valid($input['db_user'])) {
|
||||||
$errors[] = '数据库用户名只能包含字母、数字、下划线和短横线,最多 64 个字符。';
|
$errors[] = t('install.error_db_user');
|
||||||
}
|
}
|
||||||
if ($input['db_password'] === '' || strlen($input['db_password']) > 255) {
|
if ($input['db_password'] === '' || strlen($input['db_password']) > 255) {
|
||||||
$errors[] = '数据库密码不能为空,且最多 255 个字符。';
|
$errors[] = t('install.error_db_password');
|
||||||
}
|
}
|
||||||
if (auth_normalize_username($input['admin_username']) === FALSE) {
|
if (auth_normalize_username($input['admin_username']) === FALSE) {
|
||||||
$errors[] = '管理员用户名须为 3 至 64 个字母、数字、点、短横线或下划线。';
|
$errors[] = t('install.error_admin_username');
|
||||||
}
|
}
|
||||||
if (!auth_password_is_valid($input['admin_password'])) {
|
if (!auth_password_is_valid($input['admin_password'])) {
|
||||||
$errors[] = '管理员密码长度须为 8 至 72 个字符,且不能超过 72 字节。';
|
$errors[] = t('install.error_admin_password');
|
||||||
} else if (!hash_equals($input['admin_password'], $input['admin_password_confirmation'])) {
|
} else if (!hash_equals($input['admin_password'], $input['admin_password_confirmation'])) {
|
||||||
$errors[] = '两次输入的管理员密码不一致。';
|
$errors[] = t('install.error_admin_password_mismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
@@ -219,7 +223,8 @@ function install_config_contents($input) {
|
|||||||
return "<?php\n\n"
|
return "<?php\n\n"
|
||||||
."/* Generated by install.php. See config.php.sample for every option. */\n\n"
|
."/* Generated by install.php. See config.php.sample for every option. */\n\n"
|
||||||
."\$url_base = ".var_export($input['url_base'], TRUE).";\n"
|
."\$url_base = ".var_export($input['url_base'], TRUE).";\n"
|
||||||
."\$git_executable = ".var_export($input['git_executable'], TRUE).";\n\n"
|
."\$git_executable = ".var_export($input['git_executable'], TRUE).";\n"
|
||||||
|
."\$language = ".var_export($input['language'], TRUE).";\n\n"
|
||||||
."\$auth = array(\n"
|
."\$auth = array(\n"
|
||||||
." 'enabled' => TRUE,\n"
|
." 'enabled' => TRUE,\n"
|
||||||
." 'registration_enabled' => ".($input['registration_enabled'] ? 'TRUE' : 'FALSE').",\n"
|
." 'registration_enabled' => ".($input['registration_enabled'] ? 'TRUE' : 'FALSE').",\n"
|
||||||
@@ -278,51 +283,45 @@ function install_run($input) {
|
|||||||
try {
|
try {
|
||||||
$connection = install_connect(
|
$connection = install_connect(
|
||||||
install_database_dsn($input), $input['db_user'], $input['db_password']);
|
install_database_dsn($input), $input['db_user'], $input['db_password']);
|
||||||
$steps[] = array('success', '已使用数据库账号连接成功。');
|
$steps[] = array('success', t('install.step_connected'));
|
||||||
|
|
||||||
$import = install_import_schema($connection);
|
$import = install_import_schema($connection);
|
||||||
if ($import['status'] === 'schema_unreadable') {
|
if ($import['status'] === 'schema_unreadable') {
|
||||||
return array('errors' => array('无法读取 schema.mysql.sql。'), 'steps' => $steps);
|
return array('errors' => array(t('install.error_schema_unreadable')), 'steps' => $steps);
|
||||||
}
|
}
|
||||||
if ($import['status'] === 'schema_denied') {
|
if ($import['status'] === 'schema_denied') {
|
||||||
return array(
|
return array(
|
||||||
'errors' => array(
|
'errors' => array(t('install.error_schema_denied')),
|
||||||
'该数据库账号没有建表权限。请用主机面板的 phpMyAdmin 或数据库导入功能'
|
|
||||||
.'导入项目根目录的 schema.mysql.sql,然后重新提交本页面;'
|
|
||||||
.'安装器会自动识别已存在的表并跳过导入。'),
|
|
||||||
'steps' => $steps);
|
'steps' => $steps);
|
||||||
}
|
}
|
||||||
$steps[] = $import['status'] === 'schema_imported'
|
$steps[] = $import['status'] === 'schema_imported'
|
||||||
? array('success', '已导入 '.$import['tables'].' 张表。')
|
? array('success', t('install.step_schema_imported', array('count' => $import['tables'])))
|
||||||
: array('success', '检测到已存在的 pgit_ 表,跳过导入。');
|
: array('success', t('install.step_schema_present'));
|
||||||
|
|
||||||
if (!install_verify_delete_privilege($connection)) {
|
if (!install_verify_delete_privilege($connection)) {
|
||||||
return array(
|
return array(
|
||||||
'errors' => array(
|
'errors' => array(t('install.error_delete_privilege')),
|
||||||
'该数据库账号缺少 DELETE 权限,删除仓库和用户会失败。'
|
|
||||||
.'请在主机面板或数据库中为该账号授予 DELETE 权限后重试。'),
|
|
||||||
'steps' => $steps);
|
'steps' => $steps);
|
||||||
}
|
}
|
||||||
$steps[] = array('success', '已确认账号具备 SELECT、INSERT、UPDATE 和 DELETE 权限。');
|
$steps[] = array('success', t('install.step_privileges'));
|
||||||
|
|
||||||
$administrator = install_create_administrator(
|
$administrator = install_create_administrator(
|
||||||
$connection, $input['admin_username'], $input['admin_password']);
|
$connection, $input['admin_username'], $input['admin_password']);
|
||||||
$steps[] = $administrator === 'administrator_created'
|
$steps[] = $administrator === 'administrator_created'
|
||||||
? array('success', '管理员账号 '.$input['admin_username'].' 已创建。')
|
? array('success', t('install.step_admin_created', array('name' => $input['admin_username'])))
|
||||||
: array('success', '账号 '.$input['admin_username'].' 已存在,将其设为管理员。');
|
: array('success', t('install.step_admin_promoted', array('name' => $input['admin_username'])));
|
||||||
} catch (PDOException $exception) {
|
} catch (PDOException $exception) {
|
||||||
return array(
|
return array(
|
||||||
'errors' => array('数据库操作失败:'.$exception->getMessage()),
|
'errors' => array(t('install.error_database', array('message' => $exception->getMessage()))),
|
||||||
'steps' => $steps);
|
'steps' => $steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!install_write_config($input)) {
|
if (!install_write_config($input)) {
|
||||||
return array(
|
return array(
|
||||||
'errors' => array(
|
'errors' => array(t('install.error_config_write')),
|
||||||
'config.php 写入失败。请确认项目目录可写,或该文件是否已存在。'),
|
|
||||||
'steps' => $steps);
|
'steps' => $steps);
|
||||||
}
|
}
|
||||||
$steps[] = array('success', 'config.php 已写入,权限设为 0600。');
|
$steps[] = array('success', t('install.step_config_written'));
|
||||||
|
|
||||||
return array('errors' => array(), 'steps' => $steps, 'complete' => TRUE);
|
return array('errors' => array(), 'steps' => $steps, 'complete' => TRUE);
|
||||||
}
|
}
|
||||||
@@ -330,11 +329,11 @@ function install_run($input) {
|
|||||||
function install_send_head() {
|
function install_send_head() {
|
||||||
echo <<<'HTML'
|
echo <<<'HTML'
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="__LANG__">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>安装 - PHP Git 服务器</title>
|
<title>__TITLE__</title>
|
||||||
<style>
|
<style>
|
||||||
:root { color-scheme: light dark; --ink: #1f2530; --muted: #5b6472; --line: #d5dae1;
|
:root { color-scheme: light dark; --ink: #1f2530; --muted: #5b6472; --line: #d5dae1;
|
||||||
--accent: #176b43; --danger: #a43a3a; }
|
--accent: #176b43; --danger: #a43a3a; }
|
||||||
@@ -388,6 +387,7 @@ pre { padding: .7rem .9rem; overflow-x: auto; border: 1px solid var(--line);
|
|||||||
<body>
|
<body>
|
||||||
<h1>PHP Git 服务器安装</h1>
|
<h1>PHP Git 服务器安装</h1>
|
||||||
HTML;
|
HTML;
|
||||||
|
echo i18n_language_switcher(install_page_url())."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
function install_send_form($input, $errors) {
|
function install_send_form($input, $errors) {
|
||||||
@@ -431,6 +431,13 @@ function install_send_form($input, $errors) {
|
|||||||
.'<input id="git_executable" name="git_executable" type="text" value="'
|
.'<input id="git_executable" name="git_executable" type="text" value="'
|
||||||
.install_escape($input['git_executable']).'" required>'
|
.install_escape($input['git_executable']).'" required>'
|
||||||
.'<p class="hint">不可用时会回退到纯 PHP 实现。</p></div>' ."\n";
|
.'<p class="hint">不可用时会回退到纯 PHP 实现。</p></div>' ."\n";
|
||||||
|
echo '<div><label for="language">'.install_escape(t('install.label_language')).'</label>'
|
||||||
|
.'<select id="language" name="language">'
|
||||||
|
.'<option value=""'.($input['language'] === '' ? ' selected' : '').'>'
|
||||||
|
.install_escape(t('install.language_auto')).'</option>'
|
||||||
|
.'<option value="en"'.($input['language'] === 'en' ? ' selected' : '').'>English</option>'
|
||||||
|
.'<option value="zh"'.($input['language'] === 'zh' ? ' selected' : '').'>中文</option>'
|
||||||
|
.'</select></div>' ."\n";
|
||||||
echo '</div><div class="check"><input id="registration_enabled" name="registration_enabled" '
|
echo '</div><div class="check"><input id="registration_enabled" name="registration_enabled" '
|
||||||
.'type="checkbox" value="1"'.($input['registration_enabled'] ? ' checked' : '').'>'
|
.'type="checkbox" value="1"'.($input['registration_enabled'] ? ' checked' : '').'>'
|
||||||
.'<label for="registration_enabled">允许公开注册新账号</label></div>' ."\n";
|
.'<label for="registration_enabled">允许公开注册新账号</label></div>' ."\n";
|
||||||
@@ -514,6 +521,7 @@ require(__DIR__.'/lib/auth.php');
|
|||||||
/* Session must be started before any output so the cookie can be set. */
|
/* Session must be started before any output so the cookie can be set. */
|
||||||
install_start_session();
|
install_start_session();
|
||||||
|
|
||||||
|
ob_start('i18n_translate_markup');
|
||||||
header_nocache();
|
header_nocache();
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
header('X-Content-Type-Options: nosniff');
|
header('X-Content-Type-Options: nosniff');
|
||||||
@@ -534,6 +542,7 @@ $input = array(
|
|||||||
'git_executable' => 'git',
|
'git_executable' => 'git',
|
||||||
'registration_enabled' => TRUE,
|
'registration_enabled' => TRUE,
|
||||||
'session_cookie_secure' => FALSE,
|
'session_cookie_secure' => FALSE,
|
||||||
|
'language' => '',
|
||||||
'db_host' => '127.0.0.1',
|
'db_host' => '127.0.0.1',
|
||||||
'db_port' => '3306',
|
'db_port' => '3306',
|
||||||
'db_name' => 'php_git_server',
|
'db_name' => 'php_git_server',
|
||||||
@@ -567,6 +576,7 @@ $input = array(
|
|||||||
'git_executable' => trim(install_post_value('git_executable')),
|
'git_executable' => trim(install_post_value('git_executable')),
|
||||||
'registration_enabled' => install_post_checked('registration_enabled'),
|
'registration_enabled' => install_post_checked('registration_enabled'),
|
||||||
'session_cookie_secure' => install_post_checked('session_cookie_secure'),
|
'session_cookie_secure' => install_post_checked('session_cookie_secure'),
|
||||||
|
'language' => install_post_value('language'),
|
||||||
'db_host' => trim(install_post_value('db_host')),
|
'db_host' => trim(install_post_value('db_host')),
|
||||||
'db_port' => trim(install_post_value('db_port')),
|
'db_port' => trim(install_post_value('db_port')),
|
||||||
'db_name' => trim(install_post_value('db_name')),
|
'db_name' => trim(install_post_value('db_name')),
|
||||||
@@ -578,7 +588,7 @@ $input = array(
|
|||||||
|
|
||||||
$errors = install_validate($input);
|
$errors = install_validate($input);
|
||||||
if (!extension_loaded('pdo_mysql')) {
|
if (!extension_loaded('pdo_mysql')) {
|
||||||
$errors[] = 'PHP 未启用 pdo_mysql 扩展。';
|
$errors[] = t('install.error_pdo_mysql_missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
install_send_head();
|
install_send_head();
|
||||||
|
|||||||
+807
@@ -0,0 +1,807 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/* Bilingual user interface strings. English is the fallback: a key missing from
|
||||||
|
the active catalog is looked up in English, and an unknown key is returned as
|
||||||
|
is so a missing translation stays visible instead of rendering an empty page.
|
||||||
|
|
||||||
|
Only browser output is translated. Plain-text HTTP and Git protocol responses
|
||||||
|
stay in English because they are read by tooling rather than by people. */
|
||||||
|
|
||||||
|
function i18n_supported_languages() {
|
||||||
|
return array('en', 'zh');
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_fallback_language() {
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_cookie_name() {
|
||||||
|
return 'PHPGITSERVERLANG';
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_language_is_supported($value) {
|
||||||
|
return is_string($value) && in_array($value, i18n_supported_languages(), TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accept-Language is only inspected for a language tag, never a region, so
|
||||||
|
zh-Hans, zh-TW and zh all select the Chinese catalog. */
|
||||||
|
function i18n_detect_language() {
|
||||||
|
$header = isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])
|
||||||
|
? (string) $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
|
||||||
|
if ($header === '' || strlen($header) > 512) {
|
||||||
|
return i18n_fallback_language();
|
||||||
|
}
|
||||||
|
|
||||||
|
$best = i18n_fallback_language();
|
||||||
|
$best_quality = -1.0;
|
||||||
|
foreach (explode(',', $header) as $entry) {
|
||||||
|
$parts = explode(';', $entry);
|
||||||
|
$tag = strtolower(trim($parts[0]));
|
||||||
|
if ($tag === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$quality = 1.0;
|
||||||
|
for ($index = 1; $index < count($parts); $index += 1) {
|
||||||
|
$parameter = trim($parts[$index]);
|
||||||
|
if (strncasecmp($parameter, 'q=', 2) === 0) {
|
||||||
|
$quality = (float) substr($parameter, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$primary = explode('-', $tag);
|
||||||
|
$language = $primary[0] === '*' ? i18n_fallback_language() : $primary[0];
|
||||||
|
if (!i18n_language_is_supported($language)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($quality > $best_quality) {
|
||||||
|
$best = $language;
|
||||||
|
$best_quality = $quality;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $best_quality < 0 ? i18n_fallback_language() : $best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolution order: an explicit ?lang= request, a previously remembered choice,
|
||||||
|
the configured language, then Accept-Language. */
|
||||||
|
function i18n_configure($configured=NULL, $url_base='') {
|
||||||
|
$requested = isset($_GET['lang']) && is_string($_GET['lang']) ? $_GET['lang'] : '';
|
||||||
|
if (i18n_language_is_supported($requested)) {
|
||||||
|
i18n_remember_language($requested, $url_base);
|
||||||
|
return i18n_language($requested);
|
||||||
|
}
|
||||||
|
|
||||||
|
$remembered = isset($_COOKIE[i18n_cookie_name()])
|
||||||
|
&& is_string($_COOKIE[i18n_cookie_name()]) ? $_COOKIE[i18n_cookie_name()] : '';
|
||||||
|
if (i18n_language_is_supported($remembered)) {
|
||||||
|
return i18n_language($remembered);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i18n_language_is_supported($configured)) {
|
||||||
|
return i18n_language($configured);
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n_language(i18n_detect_language());
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_remember_language($language, $url_base) {
|
||||||
|
if (headers_sent()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = rtrim((string) $url_base, '/');
|
||||||
|
$secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== ''
|
||||||
|
&& strcasecmp((string) $_SERVER['HTTPS'], 'off') !== 0;
|
||||||
|
return setcookie(i18n_cookie_name(), $language, array(
|
||||||
|
'expires' => time() + 31536000,
|
||||||
|
'path' => $base === '' ? '/' : $base.'/',
|
||||||
|
'secure' => $secure,
|
||||||
|
'httponly' => FALSE,
|
||||||
|
'samesite' => 'Lax'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_language($language=NULL) {
|
||||||
|
static $active = NULL;
|
||||||
|
|
||||||
|
if ($language !== NULL) {
|
||||||
|
$active = i18n_language_is_supported($language)
|
||||||
|
? $language : i18n_fallback_language();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $active === NULL ? i18n_fallback_language() : $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_html_lang() {
|
||||||
|
return i18n_language() === 'zh' ? 'zh-CN' : 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
function t($key, $arguments=array()) {
|
||||||
|
$catalogs = i18n_catalogs();
|
||||||
|
$language = i18n_language();
|
||||||
|
|
||||||
|
if (isset($catalogs[$language][$key])) {
|
||||||
|
$value = $catalogs[$language][$key];
|
||||||
|
} else if (isset($catalogs[i18n_fallback_language()][$key])) {
|
||||||
|
$value = $catalogs[i18n_fallback_language()][$key];
|
||||||
|
} else {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($arguments)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$replacements = array();
|
||||||
|
foreach ($arguments as $name => $argument) {
|
||||||
|
$replacements['{'.$name.'}'] = (string) $argument;
|
||||||
|
}
|
||||||
|
return strtr($value, $replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Renders the language links. The caller supplies the page URL because each
|
||||||
|
entry point has its own address, and none of them use a query string. */
|
||||||
|
function i18n_language_switcher($url) {
|
||||||
|
$labels = array('en' => 'English', 'zh' => '中文');
|
||||||
|
$active = i18n_language();
|
||||||
|
$links = array();
|
||||||
|
|
||||||
|
foreach (i18n_supported_languages() as $language) {
|
||||||
|
$label = htmlspecialchars($labels[$language], ENT_QUOTES, 'UTF-8');
|
||||||
|
if ($language === $active) {
|
||||||
|
$links[] = '<strong lang="'.($language === 'zh' ? 'zh-CN' : 'en').'">'
|
||||||
|
.$label.'</strong>';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$href = htmlspecialchars($url.'?lang='.$language, ENT_QUOTES, 'UTF-8');
|
||||||
|
$links[] = '<a lang="'.($language === 'zh' ? 'zh-CN' : 'en').'" href="'.$href.'">'
|
||||||
|
.$label.'</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<span class="language-switcher">'.implode(' · ', $links).'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_translate_markup($markup) {
|
||||||
|
$markup = str_replace('__LANG__', htmlspecialchars(i18n_html_lang(), ENT_QUOTES, 'UTF-8'), $markup);
|
||||||
|
$markup = str_replace('__TITLE__', htmlspecialchars(t('install.title'), ENT_QUOTES, 'UTF-8'), $markup);
|
||||||
|
if (i18n_language() === 'en') {
|
||||||
|
$catalog = i18n_catalog_zh();
|
||||||
|
$english = i18n_catalog_en();
|
||||||
|
foreach ($catalog as $key => $source) {
|
||||||
|
if (isset($english[$key]) && $source !== $english[$key]) {
|
||||||
|
$markup = str_replace($source, $english[$key], $markup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $markup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_catalogs() {
|
||||||
|
static $catalogs = NULL;
|
||||||
|
|
||||||
|
if ($catalogs === NULL) {
|
||||||
|
$catalogs = array('en' => i18n_catalog_en(), 'zh' => i18n_catalog_zh());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $catalogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_catalog_en() {
|
||||||
|
return array(
|
||||||
|
'app.name' => 'PHP Git Server',
|
||||||
|
'language.label' => 'Language',
|
||||||
|
|
||||||
|
/* Shared account results, used by the home page and the installer. */
|
||||||
|
'notice.registered' => 'The account was registered and signed in.',
|
||||||
|
'notice.logged_in' => 'Signed in.',
|
||||||
|
'notice.logged_out' => 'Signed out.',
|
||||||
|
'notice.registration_disabled' => 'New account registration is disabled.',
|
||||||
|
'notice.invalid_username' => 'A username must be 3 to 64 letters, digits, dots,'
|
||||||
|
.' hyphens or underscores.',
|
||||||
|
'notice.invalid_password' => 'A password must be 8 to 72 characters and no more'
|
||||||
|
.' than 72 bytes.',
|
||||||
|
'notice.password_mismatch' => 'The two passwords do not match.',
|
||||||
|
'notice.username_exists' => 'That username already exists or is unavailable.',
|
||||||
|
'notice.invalid_credentials' => 'The username or password is incorrect.',
|
||||||
|
'notice.invalid_token_name' => 'A token name is required and may be at most 80'
|
||||||
|
.' characters.',
|
||||||
|
'notice.token_created' => 'The access token was created. Copy it now; it cannot be'
|
||||||
|
.' shown again after you leave this page.',
|
||||||
|
'notice.token_revoked' => 'The access token was revoked.',
|
||||||
|
'notice.invalid_token' => 'That access token does not exist or was already revoked.',
|
||||||
|
'notice.session_unavailable' => 'A secure session cannot be established right now.',
|
||||||
|
'notice.auth_database_unavailable' => 'The account database is unavailable right now.',
|
||||||
|
|
||||||
|
'notice.repository_invalid_name' => 'The repository name is not valid.',
|
||||||
|
'notice.repository_exists' => 'Repository {name} already exists.',
|
||||||
|
'notice.repository_create_busy' => 'Another repository is being created. Try again'
|
||||||
|
.' shortly.',
|
||||||
|
'notice.repository_root_unavailable' => 'The repository directory is unavailable or'
|
||||||
|
.' not writable.',
|
||||||
|
'notice.repository_git_unavailable' => 'The Git initialization service is unavailable'
|
||||||
|
.' right now.',
|
||||||
|
'notice.repository_metadata_unsaved' => 'Repository ownership could not be saved.'
|
||||||
|
.' Try again shortly.',
|
||||||
|
'notice.repository_create_failed' => 'Repository creation failed. Check the server'
|
||||||
|
.' log.',
|
||||||
|
'notice.repository_created' => 'Repository {name} was created.',
|
||||||
|
'notice.repository_deleted' => 'Repository {name} was deleted.',
|
||||||
|
'notice.repository_record_deleted' => 'The record for {name} was deleted. A missing'
|
||||||
|
.' or non-bare path was left unchanged.',
|
||||||
|
'notice.repository_delete_forbidden' => 'Only the repository owner can delete it.',
|
||||||
|
'notice.repository_configured_home' => 'That path is configured statically in'
|
||||||
|
.' config.php and cannot be deleted from the web page.',
|
||||||
|
'notice.repository_not_found' => 'The managed repository does not exist or is not'
|
||||||
|
.' finished yet.',
|
||||||
|
'notice.repository_busy' => 'The repository is busy with another operation. Try'
|
||||||
|
.' again shortly.',
|
||||||
|
'notice.repository_metadata_unavailable' => 'Repository ownership data is'
|
||||||
|
.' unavailable right now.',
|
||||||
|
'notice.repository_cleanup_failed' => 'The repository record was deleted but the'
|
||||||
|
.' leftover directory could not be cleaned up. Check the server log.',
|
||||||
|
'notice.repository_restore_failed' => 'Deletion failed and the repository directory'
|
||||||
|
.' could not be restored. Check the server log immediately.',
|
||||||
|
'notice.repository_delete_failed' => 'Repository deletion failed. Check the server'
|
||||||
|
.' log.',
|
||||||
|
|
||||||
|
/* Home page. */
|
||||||
|
'home.title' => 'PHP Git Server',
|
||||||
|
'home.lead' => 'The repositories below are published over HTTP with support for'
|
||||||
|
.' clone, fetch and pull, and push can be enabled per repository.',
|
||||||
|
'home.account_title' => 'Account and access tokens',
|
||||||
|
'home.form_unavailable' => 'A secure form cannot be initialized right now.',
|
||||||
|
'home.username' => 'Username',
|
||||||
|
'home.password' => 'Password',
|
||||||
|
'home.login' => 'Sign in',
|
||||||
|
'home.register_username' => 'New username',
|
||||||
|
'home.password_confirmation' => 'Confirm password',
|
||||||
|
'home.register' => 'Register',
|
||||||
|
'home.auth_hint' => 'Web sign-in uses your password. Git clone, pull and push use'
|
||||||
|
.' your username and an access token.',
|
||||||
|
'home.current_account' => 'Signed in as',
|
||||||
|
'home.manage' => 'Manage',
|
||||||
|
'home.logout' => 'Sign out',
|
||||||
|
'home.token_name' => 'New token name',
|
||||||
|
'home.token_name_placeholder' => 'work laptop',
|
||||||
|
'home.create_token' => 'Create token',
|
||||||
|
'home.token_list_unavailable' => 'The access token list cannot be read right now.',
|
||||||
|
'home.token_never_used' => 'never used',
|
||||||
|
'home.token_last_used' => 'last used {time}',
|
||||||
|
'home.token_created_at' => 'created {time}',
|
||||||
|
'home.revoke' => 'Revoke',
|
||||||
|
'home.create_repository' => 'Create repository',
|
||||||
|
'home.create_login_required' => 'Sign in to an application account to create a'
|
||||||
|
.' repository.',
|
||||||
|
'home.repository_name' => 'Repository name',
|
||||||
|
'home.repository_name_hint' => 'Letters, digits, dots, hyphens and underscores are'
|
||||||
|
.' allowed. The <code>.git</code> suffix is optional.',
|
||||||
|
'home.visibility' => 'Visibility',
|
||||||
|
'home.public' => 'Public',
|
||||||
|
'home.private' => 'Private',
|
||||||
|
'home.th_repository' => 'Repository',
|
||||||
|
'home.th_owner' => 'Owner',
|
||||||
|
'home.th_clone_url' => 'Clone URL',
|
||||||
|
'home.th_default_branch' => 'Default branch',
|
||||||
|
'home.th_branches' => 'Branches',
|
||||||
|
'home.th_tags' => 'Tags',
|
||||||
|
'home.th_access' => 'Access',
|
||||||
|
'home.th_actions' => 'Actions',
|
||||||
|
'home.badge_empty' => 'empty',
|
||||||
|
'home.badge_no_branch' => 'no branch',
|
||||||
|
'home.badge_unset' => 'unset',
|
||||||
|
'home.badge_owner_push' => 'owner can push',
|
||||||
|
'home.badge_read_only' => 'read only',
|
||||||
|
'home.confirm_delete' => 'Confirm deletion',
|
||||||
|
'home.delete' => 'Delete',
|
||||||
|
'home.badge_none' => 'none',
|
||||||
|
'home.public_repositories' => 'Public repositories',
|
||||||
|
'home.private_repositories' => 'Private repositories',
|
||||||
|
'home.no_public_repositories' => 'There are no public repositories yet.',
|
||||||
|
'home.no_private_repositories' => 'There are no private repositories yet.',
|
||||||
|
'home.empty_title' => 'No readable repository is configured.',
|
||||||
|
'home.empty_body' => 'Copy <code>config.php.sample</code> to <code>config.php</code>,'
|
||||||
|
.' set the repository paths in <code>$repos</code>, and make sure each'
|
||||||
|
.' repository directory exists with the <code>read</code> option set to'
|
||||||
|
.' <code>TRUE</code>.',
|
||||||
|
'home.usage_title' => 'Usage',
|
||||||
|
'home.usage_hint' => 'Push requires the <code>push</code> option on the repository.'
|
||||||
|
.' When authentication is enabled it also requires an account username and an'
|
||||||
|
.' access token.',
|
||||||
|
'home.footer' => 'See <code>usage.md</code> for installation, configuration and'
|
||||||
|
.' security details.',
|
||||||
|
|
||||||
|
/* Installer. */
|
||||||
|
'install.title' => 'Install - PHP Git Server',
|
||||||
|
'install.heading' => 'PHP Git Server installation',
|
||||||
|
'install.session_unavailable' => 'A secure session cannot be initialized, so the'
|
||||||
|
.' installation cannot continue.',
|
||||||
|
'install.lead' => 'No <code>config.php</code> was found in the project root. Prepare'
|
||||||
|
.' an empty database and an account for it, then fill in the fields below to'
|
||||||
|
.' import the schema, create the first administrator and generate the'
|
||||||
|
.' configuration file.',
|
||||||
|
'install.warning_http' => 'This is not an HTTPS connection. Passwords would be sent'
|
||||||
|
.' in clear text. Use HTTPS, or open the installer from the local machine only.',
|
||||||
|
'install.error_no_pdo_mysql' => 'The PHP <code>pdo_mysql</code> extension is not'
|
||||||
|
.' enabled, so the installation cannot continue. Install it and restart PHP.',
|
||||||
|
'install.error_not_writable' => 'The project directory is not writable, so'
|
||||||
|
.' <code>config.php</code> cannot be generated. Adjust the directory'
|
||||||
|
.' permissions and reload.',
|
||||||
|
'install.legend_application' => 'Application',
|
||||||
|
'install.label_url_base' => 'Base path',
|
||||||
|
'install.hint_url_base' => 'Leave empty when deploying at the domain root.',
|
||||||
|
'install.label_git_executable' => 'Git executable',
|
||||||
|
'install.hint_git_executable' => 'Falls back to the pure PHP implementation when'
|
||||||
|
.' unavailable.',
|
||||||
|
'install.label_language' => 'Interface language',
|
||||||
|
'install.language_auto' => 'Follow the browser (English fallback)',
|
||||||
|
'install.language_en' => 'English',
|
||||||
|
'install.language_zh' => 'Chinese',
|
||||||
|
'install.label_registration_enabled' => 'Allow public registration of new accounts',
|
||||||
|
'install.label_session_cookie_secure' => 'HTTPS terminates at a trusted reverse'
|
||||||
|
.' proxy (set a Secure cookie)',
|
||||||
|
'install.legend_database' => 'Database',
|
||||||
|
'install.hint_database' => 'Create the database and its account yourself. The'
|
||||||
|
.' installer does not create them and does not need root privileges. The'
|
||||||
|
.' account needs SELECT, INSERT, UPDATE and DELETE on that database. If it can'
|
||||||
|
.' also create tables, the installer imports the schema automatically.',
|
||||||
|
'install.label_db_host' => 'Host',
|
||||||
|
'install.hint_db_host' => 'Usually <code>localhost</code> on shared hosting.',
|
||||||
|
'install.label_db_port' => 'Port',
|
||||||
|
'install.label_db_name' => 'Database name',
|
||||||
|
'install.hint_db_name' => 'Panel-generated names are usually prefixed, for example'
|
||||||
|
.' <code>cpuser_git</code>.',
|
||||||
|
'install.label_db_user' => 'Database account',
|
||||||
|
'install.label_db_password' => 'Database account password',
|
||||||
|
'install.hint_schema_import' => 'If the account cannot create tables, import'
|
||||||
|
.' <code>schema.mysql.sql</code> with the phpMyAdmin in your hosting panel'
|
||||||
|
.' first, then submit this page. Existing tables are skipped.',
|
||||||
|
'install.legend_administrator' => 'Administrator account',
|
||||||
|
'install.hint_administrator' => 'This account is written to'
|
||||||
|
.' <code>$auth[\'administrators\']</code> and can reach the management page as'
|
||||||
|
.' soon as the installation finishes.',
|
||||||
|
'install.label_admin_username' => 'Username',
|
||||||
|
'install.label_admin_password' => 'Password',
|
||||||
|
'install.label_admin_password_confirmation' => 'Confirm password',
|
||||||
|
'install.button_submit' => 'Start installation',
|
||||||
|
|
||||||
|
'install.error_url_base' => 'The base path must start with / and must not end with'
|
||||||
|
.' /. Leave it empty when deploying at the domain root.',
|
||||||
|
'install.error_git_executable' => 'The Git executable path is not valid.',
|
||||||
|
'install.error_db_host' => 'The database host name is not valid.',
|
||||||
|
'install.error_db_port' => 'The database port is not valid.',
|
||||||
|
'install.error_db_name' => 'A database name may contain only letters, digits,'
|
||||||
|
.' underscores and hyphens, up to 64 characters.',
|
||||||
|
'install.error_db_user' => 'A database username may contain only letters, digits,'
|
||||||
|
.' underscores and hyphens, up to 64 characters.',
|
||||||
|
'install.error_db_password' => 'The database password is required and may be at'
|
||||||
|
.' most 255 characters.',
|
||||||
|
'install.error_admin_username' => 'An administrator username must be 3 to 64'
|
||||||
|
.' letters, digits, dots, hyphens or underscores.',
|
||||||
|
'install.error_admin_password' => 'An administrator password must be 8 to 72'
|
||||||
|
.' characters and no more than 72 bytes.',
|
||||||
|
'install.error_admin_password_mismatch' => 'The two administrator passwords do not'
|
||||||
|
.' match.',
|
||||||
|
'install.error_pdo_mysql_missing' => 'The PHP pdo_mysql extension is not enabled.',
|
||||||
|
'install.error_schema_unreadable' => 'schema.mysql.sql could not be read.',
|
||||||
|
'install.error_schema_denied' => 'This database account cannot create tables.'
|
||||||
|
.' Import schema.mysql.sql from the project root using the phpMyAdmin or the'
|
||||||
|
.' database import feature of your hosting panel, then submit this page again.'
|
||||||
|
.' The installer detects the existing tables and skips the import.',
|
||||||
|
'install.error_delete_privilege' => 'This database account is missing the DELETE'
|
||||||
|
.' privilege, so deleting repositories and users would fail. Grant DELETE to'
|
||||||
|
.' the account in your hosting panel or database, then try again.',
|
||||||
|
'install.error_database' => 'The database operation failed: {message}',
|
||||||
|
'install.error_config_write' => 'config.php could not be written. Check that the'
|
||||||
|
.' project directory is writable and that the file does not already exist.',
|
||||||
|
'install.step_connected' => 'Connected with the database account.',
|
||||||
|
'install.step_schema_imported' => 'Imported {count} tables.',
|
||||||
|
'install.step_schema_present' => 'Existing pgit_ tables were found, so the import'
|
||||||
|
.' was skipped.',
|
||||||
|
'install.step_privileges_ok' => 'Confirmed the account has SELECT, INSERT, UPDATE'
|
||||||
|
.' and DELETE.',
|
||||||
|
'install.step_admin_created' => 'Administrator account {username} was created.',
|
||||||
|
'install.step_admin_promoted' => 'Account {username} already existed and was made an'
|
||||||
|
.' administrator.',
|
||||||
|
'install.step_config_written' => 'config.php was written with mode 0600.',
|
||||||
|
'install.complete' => 'Installation complete.',
|
||||||
|
'install.next_steps_title' => 'Do this now',
|
||||||
|
'install.next_steps_remove' => 'Delete the installer so it cannot run again if the'
|
||||||
|
.' configuration is ever removed. With a shell:',
|
||||||
|
'install.next_steps_shared_hosting' => 'On shared hosting, delete'
|
||||||
|
.' <code>install.php</code> with the file manager in your panel or over FTP.',
|
||||||
|
'install.next_steps_permissions' => 'Confirm that <code>config.php</code> is'
|
||||||
|
.' readable only by the application process, and check the permissions on the'
|
||||||
|
.' <code>repos</code> directory. See <code>usage.md</code> for details.',
|
||||||
|
'install.link_home' => 'Go to the home page and sign in',
|
||||||
|
|
||||||
|
/* Management page. */
|
||||||
|
'manage.title' => 'Server management - PHP Git Server',
|
||||||
|
'manage.heading' => 'PHP Git Server / management',
|
||||||
|
'manage.administrator_label' => 'Administrator',
|
||||||
|
'manage.back_to_home' => 'Back to the repository home page',
|
||||||
|
'manage.create_user_title' => 'Create user',
|
||||||
|
'manage.create_user_lead' => 'An account created by an administrator is active'
|
||||||
|
.' immediately.',
|
||||||
|
'manage.label_username' => 'Username',
|
||||||
|
'manage.label_initial_password' => 'Initial password',
|
||||||
|
'manage.label_password_confirmation' => 'Confirm password',
|
||||||
|
'manage.button_create_user' => 'Create user',
|
||||||
|
'manage.users_title' => 'Users',
|
||||||
|
'manage.users_lead' => 'Deactivating an account immediately blocks web sign-in and'
|
||||||
|
.' access token authentication. A user\'s repositories must be handled before'
|
||||||
|
.' the user can be deleted.',
|
||||||
|
'manage.users_unavailable' => 'The user list cannot be read right now.',
|
||||||
|
'manage.users_empty' => 'There are no users yet.',
|
||||||
|
'manage.th_user' => 'User',
|
||||||
|
'manage.th_status' => 'Status',
|
||||||
|
'manage.th_repositories' => 'Repositories',
|
||||||
|
'manage.th_active_tokens' => 'Active tokens',
|
||||||
|
'manage.th_created_at' => 'Created',
|
||||||
|
'manage.th_account_actions' => 'Account actions',
|
||||||
|
'manage.th_password' => 'Password',
|
||||||
|
'manage.badge_administrator' => 'administrator',
|
||||||
|
'manage.badge_current_account' => 'current account',
|
||||||
|
'manage.badge_active' => 'active',
|
||||||
|
'manage.badge_inactive' => 'inactive',
|
||||||
|
'manage.button_deactivate' => 'Deactivate',
|
||||||
|
'manage.button_activate' => 'Activate',
|
||||||
|
'manage.button_revoke_all_tokens' => 'Revoke all tokens',
|
||||||
|
'manage.confirm_delete_account' => 'Confirm account deletion',
|
||||||
|
'manage.button_delete_user' => 'Delete user',
|
||||||
|
'manage.summary_reset_password' => 'Reset password',
|
||||||
|
'manage.placeholder_new_password' => 'New password',
|
||||||
|
'manage.placeholder_confirm_new_password' => 'Confirm new password',
|
||||||
|
'manage.button_save_password' => 'Save new password',
|
||||||
|
'manage.repositories_title' => 'Managed repositories',
|
||||||
|
'manage.repositories_lead' => 'These actions affect the database records and the'
|
||||||
|
.' managed bare repositories in the <code>repos</code> directory.',
|
||||||
|
'manage.repositories_unavailable' => 'The managed repository list cannot be read'
|
||||||
|
.' right now.',
|
||||||
|
'manage.repositories_empty' => 'There are no managed repositories yet.',
|
||||||
|
'manage.th_repository' => 'Repository',
|
||||||
|
'manage.th_owner_visibility' => 'Owner and visibility',
|
||||||
|
'manage.th_delete' => 'Delete',
|
||||||
|
'manage.aria_repository_owner' => 'Repository owner',
|
||||||
|
'manage.aria_repository_visibility' => 'Repository visibility',
|
||||||
|
'manage.user_deactivated_suffix' => ' (deactivated)',
|
||||||
|
'manage.button_save' => 'Save',
|
||||||
|
'manage.badge_not_deletable' => 'not deletable',
|
||||||
|
'manage.confirm_permanent_delete' => 'Confirm permanent deletion',
|
||||||
|
'manage.button_delete_repository' => 'Delete repository',
|
||||||
|
'manage.state_root_unavailable' => 'root unavailable',
|
||||||
|
'manage.state_configured' => 'statically configured',
|
||||||
|
'manage.state_record_only' => 'record only',
|
||||||
|
'manage.state_incomplete' => 'incomplete',
|
||||||
|
'manage.state_ready' => 'ready',
|
||||||
|
'manage.configured_title' => 'Configured repositories',
|
||||||
|
'manage.configured_lead' => 'These repositories come from <code>config.php</code>.'
|
||||||
|
.' The management page shows them read only and changes neither the'
|
||||||
|
.' configuration nor the files.',
|
||||||
|
'manage.configured_empty' => 'There are no configured repositories.',
|
||||||
|
'manage.th_url' => 'URL',
|
||||||
|
'manage.th_path' => 'Path',
|
||||||
|
'manage.th_visibility' => 'Visibility',
|
||||||
|
'manage.th_read_write' => 'Read / write',
|
||||||
|
'manage.value_unset' => 'unset',
|
||||||
|
'manage.value_read' => 'read',
|
||||||
|
'manage.value_read_disabled' => 'read disabled',
|
||||||
|
'manage.value_write' => 'write',
|
||||||
|
'manage.value_read_only' => 'read only',
|
||||||
|
'manage.public' => 'Public',
|
||||||
|
'manage.private' => 'Private',
|
||||||
|
|
||||||
|
'manage.user_created' => 'User {username} was created.',
|
||||||
|
'manage.user_updated' => 'The account status was updated.',
|
||||||
|
'manage.tokens_revoked' => 'Revoked {count} active access tokens.',
|
||||||
|
'manage.password_updated' => 'The account password was reset.',
|
||||||
|
'manage.user_deleted' => 'The user was deleted.',
|
||||||
|
'manage.repository_updated' => 'The repository owner and visibility were updated.',
|
||||||
|
'manage.repository_deleted' => 'Managed repository {name} was deleted.',
|
||||||
|
'manage.repository_record_deleted' => 'The record for {name} was deleted. A missing'
|
||||||
|
.' or non-bare path was left unchanged.',
|
||||||
|
'manage.username_exists' => 'That username already exists.',
|
||||||
|
'manage.invalid_user' => 'The user does not exist or the parameters are not valid.',
|
||||||
|
'manage.invalid_owner' => 'The new repository owner does not exist or is'
|
||||||
|
.' unavailable.',
|
||||||
|
'manage.invalid_repository' => 'The repository does not exist or the parameters are'
|
||||||
|
.' not valid.',
|
||||||
|
'manage.administrator_protected' => 'An administrator listed in the configuration'
|
||||||
|
.' cannot be deactivated or deleted.',
|
||||||
|
'manage.self_protected' => 'The signed-in account cannot be deactivated or deleted.',
|
||||||
|
'manage.user_owns_repositories' => 'This user still owns repositories. Transfer or'
|
||||||
|
.' delete them first.',
|
||||||
|
'manage.configured_owner' => 'This user owns a static repository from config.php.'
|
||||||
|
.' Change the owner in the configuration first.',
|
||||||
|
'manage.configured_repository' => 'That path is configured statically in config.php'
|
||||||
|
.' and cannot be deleted from the management page.',
|
||||||
|
'manage.confirmation_required' => 'The confirmation value is not valid. Confirm'
|
||||||
|
.' again.',
|
||||||
|
'manage.forbidden' => 'The repository permissions changed, so the operation was'
|
||||||
|
.' refused.',
|
||||||
|
'manage.not_found' => 'The managed repository directory or record does not exist.',
|
||||||
|
'manage.repository_busy' => 'The repository is busy with another operation. Try'
|
||||||
|
.' again shortly.',
|
||||||
|
'manage.root_unavailable' => 'The repository directory is unavailable or not'
|
||||||
|
.' writable.',
|
||||||
|
'manage.cleanup_failed' => 'The repository record was deleted but the leftover'
|
||||||
|
.' directory could not be cleaned up. Check the server log.',
|
||||||
|
'manage.restore_failed' => 'Deletion failed and the repository directory could not'
|
||||||
|
.' be restored. Check the server log immediately.',
|
||||||
|
'manage.database_unavailable' => 'The management database is unavailable right now.',
|
||||||
|
'manage.action_failed' => 'The operation failed. Check the server log.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function i18n_catalog_zh() {
|
||||||
|
return array(
|
||||||
|
'app.name' => 'PHP Git 服务器',
|
||||||
|
'language.label' => '语言',
|
||||||
|
|
||||||
|
'notice.registered' => '账号已注册并登录。',
|
||||||
|
'notice.logged_in' => '登录成功。',
|
||||||
|
'notice.logged_out' => '已退出登录。',
|
||||||
|
'notice.registration_disabled' => '当前不允许注册新账号。',
|
||||||
|
'notice.invalid_username' => '用户名须为 3 至 64 个字母、数字、点、短横线或下划线。',
|
||||||
|
'notice.invalid_password' => '密码长度须为 8 至 72 个字符,且不能超过 72 字节。',
|
||||||
|
'notice.password_mismatch' => '两次输入的密码不一致。',
|
||||||
|
'notice.username_exists' => '该用户名已存在或不可用。',
|
||||||
|
'notice.invalid_credentials' => '用户名或密码错误。',
|
||||||
|
'notice.invalid_token_name' => 'Token 名称不能为空,且最多 80 个字符。',
|
||||||
|
'notice.token_created' => 'Access token 已创建。请立即保存,关闭页面后无法再次查看。',
|
||||||
|
'notice.token_revoked' => 'Access token 已撤销。',
|
||||||
|
'notice.invalid_token' => 'Access token 不存在或已撤销。',
|
||||||
|
'notice.session_unavailable' => '当前无法建立安全会话。',
|
||||||
|
'notice.auth_database_unavailable' => '认证数据库当前不可用。',
|
||||||
|
|
||||||
|
'notice.repository_invalid_name' => '仓库名称格式无效。',
|
||||||
|
'notice.repository_exists' => '仓库 {name} 已存在。',
|
||||||
|
'notice.repository_create_busy' => '另一个仓库正在创建,请稍后重试。',
|
||||||
|
'notice.repository_root_unavailable' => '仓库存放目录不可用或不可写。',
|
||||||
|
'notice.repository_git_unavailable' => 'Git 初始化服务当前不可用。',
|
||||||
|
'notice.repository_metadata_unsaved' => '仓库所有权信息无法保存,请稍后重试。',
|
||||||
|
'notice.repository_create_failed' => '仓库创建失败,请检查服务器日志。',
|
||||||
|
'notice.repository_created' => '仓库 {name} 已创建。',
|
||||||
|
'notice.repository_deleted' => '仓库 {name} 已删除。',
|
||||||
|
'notice.repository_record_deleted' => '仓库记录 {name} 已删除,非 bare 路径未作改动。',
|
||||||
|
'notice.repository_delete_forbidden' => '只有仓库所有者可以删除该仓库。',
|
||||||
|
'notice.repository_configured_home' => '该路径由 config.php 静态配置,不能从网页删除。',
|
||||||
|
'notice.repository_not_found' => '托管仓库不存在或尚未创建完成。',
|
||||||
|
'notice.repository_busy' => '仓库正在执行其他操作,请稍后重试。',
|
||||||
|
'notice.repository_metadata_unavailable' => '仓库所有权信息当前不可用。',
|
||||||
|
'notice.repository_cleanup_failed' => '仓库记录已删除,但残留目录清理失败,请检查服务器日志。',
|
||||||
|
'notice.repository_restore_failed' => '删除失败且仓库目录无法恢复,请立即检查服务器日志。',
|
||||||
|
'notice.repository_delete_failed' => '仓库删除失败,请检查服务器日志。',
|
||||||
|
|
||||||
|
'home.title' => 'PHP Git 服务器',
|
||||||
|
'home.lead' => '通过 HTTP 发布下列 Git 仓库,支持 clone、fetch、pull,并可按仓库启用 push。',
|
||||||
|
'home.account_title' => '账号与 Access Token',
|
||||||
|
'home.form_unavailable' => '当前无法初始化安全表单。',
|
||||||
|
'home.username' => '用户名',
|
||||||
|
'home.password' => '密码',
|
||||||
|
'home.login' => '登录',
|
||||||
|
'home.register_username' => '注册用户名',
|
||||||
|
'home.password_confirmation' => '确认密码',
|
||||||
|
'home.register' => '注册',
|
||||||
|
'home.auth_hint' => '网页登录使用密码;Git clone、pull 和 push 使用用户名与 access token。',
|
||||||
|
'home.current_account' => '当前账号',
|
||||||
|
'home.manage' => '管理',
|
||||||
|
'home.logout' => '退出',
|
||||||
|
'home.token_name' => '新 Token 名称',
|
||||||
|
'home.token_name_placeholder' => '工作电脑',
|
||||||
|
'home.create_token' => '创建 Token',
|
||||||
|
'home.token_list_unavailable' => '当前无法读取 access token 列表。',
|
||||||
|
'home.token_never_used' => '从未使用',
|
||||||
|
'home.token_last_used' => '最后使用 {time}',
|
||||||
|
'home.token_created_at' => '创建于 {time}',
|
||||||
|
'home.revoke' => '撤销',
|
||||||
|
'home.create_repository' => '创建仓库',
|
||||||
|
'home.create_login_required' => '需要先登录应用账号,才能创建仓库。',
|
||||||
|
'home.repository_name' => '仓库名称',
|
||||||
|
'home.repository_name_hint' => '可使用字母、数字、点、短横线和下划线;<code>.git</code> 后缀可省略。',
|
||||||
|
'home.visibility' => '可见性',
|
||||||
|
'home.public' => '公开',
|
||||||
|
'home.private' => '私有',
|
||||||
|
'home.th_repository' => '仓库',
|
||||||
|
'home.th_owner' => '所有者',
|
||||||
|
'home.th_clone_url' => '克隆地址',
|
||||||
|
'home.th_default_branch' => '默认分支',
|
||||||
|
'home.th_branches' => '分支',
|
||||||
|
'home.th_tags' => '标签',
|
||||||
|
'home.th_access' => '权限',
|
||||||
|
'home.th_actions' => '操作',
|
||||||
|
'home.badge_empty' => '空仓库',
|
||||||
|
'home.badge_no_branch' => '未指向分支',
|
||||||
|
'home.badge_unset' => '未设置',
|
||||||
|
'home.badge_owner_push' => '所有者可推送',
|
||||||
|
'home.badge_read_only' => '只读',
|
||||||
|
'home.confirm_delete' => '确认删除',
|
||||||
|
'home.delete' => '删除',
|
||||||
|
'home.badge_none' => '无',
|
||||||
|
'home.public_repositories' => '公开仓库',
|
||||||
|
'home.private_repositories' => '私有仓库',
|
||||||
|
'home.no_public_repositories' => '当前没有公开仓库。',
|
||||||
|
'home.no_private_repositories' => '当前没有私有仓库。',
|
||||||
|
'home.empty_title' => '当前没有可读取的仓库。',
|
||||||
|
'home.empty_body' => '请复制 <code>config.php.sample</code> 为 <code>config.php</code>,'
|
||||||
|
.'在 <code>$repos</code> 中配置仓库路径,并确认仓库目录存在且 <code>read</code> '
|
||||||
|
.'选项为 <code>TRUE</code>。',
|
||||||
|
'home.usage_title' => '使用方法',
|
||||||
|
'home.usage_hint' => 'push 需要仓库启用 <code>push</code> 选项;启用认证时还需要'
|
||||||
|
.'使用账号用户名和 access token。',
|
||||||
|
'home.footer' => '详细安装、配置和安全说明见 <code>usage.md</code>。',
|
||||||
|
|
||||||
|
'install.title' => '安装 - PHP Git 服务器',
|
||||||
|
'install.heading' => 'PHP Git 服务器安装',
|
||||||
|
'install.session_unavailable' => '当前无法初始化安全会话,无法继续安装。',
|
||||||
|
'install.lead' => '检测到项目根目录没有 <code>config.php</code>。请先准备好一个空数据库'
|
||||||
|
.'和对应账号,填写以下信息即可导入表结构、创建首个管理员账号并生成配置文件。',
|
||||||
|
'install.warning_http' => '当前不是 HTTPS 连接。密码将以明文传输,'
|
||||||
|
.'建议改用 HTTPS 或仅从本机访问安装页面。',
|
||||||
|
'install.error_no_pdo_mysql' => 'PHP 未启用 <code>pdo_mysql</code> 扩展,'
|
||||||
|
.'安装无法继续。请先安装该扩展并重启 PHP。',
|
||||||
|
'install.error_not_writable' => '项目目录不可写,无法生成 <code>config.php</code>。'
|
||||||
|
.'请调整目录权限后刷新。',
|
||||||
|
'install.legend_application' => '应用',
|
||||||
|
'install.label_url_base' => '基础路径',
|
||||||
|
'install.hint_url_base' => '部署在域名根路径时留空。',
|
||||||
|
'install.label_git_executable' => 'Git 可执行文件',
|
||||||
|
'install.hint_git_executable' => '不可用时会回退到纯 PHP 实现。',
|
||||||
|
'install.label_language' => '界面语言',
|
||||||
|
'install.language_auto' => '跟随浏览器(默认英语)',
|
||||||
|
'install.language_en' => 'English',
|
||||||
|
'install.language_zh' => '中文',
|
||||||
|
'install.label_registration_enabled' => '允许公开注册新账号',
|
||||||
|
'install.label_session_cookie_secure' => 'HTTPS 在可信反向代理终止(设置 Secure Cookie)',
|
||||||
|
'install.legend_database' => '数据库',
|
||||||
|
'install.hint_database' => '请先自行创建好数据库和账号,安装器不会创建它们,也不需要 root 权限。'
|
||||||
|
.'账号需对该库具有 SELECT、INSERT、UPDATE 和 DELETE 权限;'
|
||||||
|
.'若还具有建表权限,安装器会自动导入表结构。',
|
||||||
|
'install.label_db_host' => '主机',
|
||||||
|
'install.hint_db_host' => '共享主机通常是 <code>localhost</code>。',
|
||||||
|
'install.label_db_port' => '端口',
|
||||||
|
'install.label_db_name' => '数据库名',
|
||||||
|
'install.hint_db_name' => '面板生成的名称通常带前缀,例如 <code>cpuser_git</code>。',
|
||||||
|
'install.label_db_user' => '数据库账号',
|
||||||
|
'install.label_db_password' => '数据库账号密码',
|
||||||
|
'install.hint_schema_import' => '若该账号没有建表权限,请先用面板的 phpMyAdmin 导入 '
|
||||||
|
.'<code>schema.mysql.sql</code>,再提交本页面;已存在的表会被自动跳过。',
|
||||||
|
'install.legend_administrator' => '管理员账号',
|
||||||
|
'install.hint_administrator' => '该账号会写入 <code>$auth[\'administrators\']</code>,'
|
||||||
|
.'安装完成后即可访问管理界面。',
|
||||||
|
'install.label_admin_username' => '用户名',
|
||||||
|
'install.label_admin_password' => '密码',
|
||||||
|
'install.label_admin_password_confirmation' => '确认密码',
|
||||||
|
'install.button_submit' => '开始安装',
|
||||||
|
|
||||||
|
'install.error_url_base' => '基础路径必须以 / 开头,且不能以 / 结尾;部署在域名根路径时请留空。',
|
||||||
|
'install.error_git_executable' => 'Git 可执行文件路径无效。',
|
||||||
|
'install.error_db_host' => '数据库主机名无效。',
|
||||||
|
'install.error_db_port' => '数据库端口无效。',
|
||||||
|
'install.error_db_name' => '数据库名只能包含字母、数字、下划线和短横线,最多 64 个字符。',
|
||||||
|
'install.error_db_user' => '数据库用户名只能包含字母、数字、下划线和短横线,最多 64 个字符。',
|
||||||
|
'install.error_db_password' => '数据库密码不能为空,且最多 255 个字符。',
|
||||||
|
'install.error_admin_username' => '管理员用户名须为 3 至 64 个字母、数字、点、短横线或下划线。',
|
||||||
|
'install.error_admin_password' => '管理员密码长度须为 8 至 72 个字符,且不能超过 72 字节。',
|
||||||
|
'install.error_admin_password_mismatch' => '两次输入的管理员密码不一致。',
|
||||||
|
'install.error_pdo_mysql_missing' => 'PHP 未启用 pdo_mysql 扩展。',
|
||||||
|
'install.error_schema_unreadable' => '无法读取 schema.mysql.sql。',
|
||||||
|
'install.error_schema_denied' => '该数据库账号没有建表权限。请用主机面板的 phpMyAdmin 或数据库导入功能'
|
||||||
|
.'导入项目根目录的 schema.mysql.sql,然后重新提交本页面;'
|
||||||
|
.'安装器会自动识别已存在的表并跳过导入。',
|
||||||
|
'install.error_delete_privilege' => '该数据库账号缺少 DELETE 权限,删除仓库和用户会失败。'
|
||||||
|
.'请在主机面板或数据库中为该账号授予 DELETE 权限后重试。',
|
||||||
|
'install.error_database' => '数据库操作失败:{message}',
|
||||||
|
'install.error_config_write' => 'config.php 写入失败。请确认项目目录可写,或该文件是否已存在。',
|
||||||
|
'install.step_connected' => '已使用数据库账号连接成功。',
|
||||||
|
'install.step_schema_imported' => '已导入 {count} 张表。',
|
||||||
|
'install.step_schema_present' => '检测到已存在的 pgit_ 表,跳过导入。',
|
||||||
|
'install.step_privileges_ok' => '已确认账号具备 SELECT、INSERT、UPDATE 和 DELETE 权限。',
|
||||||
|
'install.step_admin_created' => '管理员账号 {username} 已创建。',
|
||||||
|
'install.step_admin_promoted' => '账号 {username} 已存在,将其设为管理员。',
|
||||||
|
'install.step_config_written' => 'config.php 已写入,权限设为 0600。',
|
||||||
|
'install.complete' => '安装完成。',
|
||||||
|
'install.next_steps_title' => '请立即完成以下操作',
|
||||||
|
'install.next_steps_remove' => '删除安装脚本,避免它在配置被移除后再次可用。有 shell 时执行:',
|
||||||
|
'install.next_steps_shared_hosting' => '共享主机可用面板的文件管理器或 FTP 直接删除 '
|
||||||
|
.'<code>install.php</code>。',
|
||||||
|
'install.next_steps_permissions' => '确认 <code>config.php</code> 仅应用进程可读,'
|
||||||
|
.'并检查 <code>repos</code> 目录权限。详细说明见 <code>usage.md</code>。',
|
||||||
|
'install.link_home' => '进入首页并登录',
|
||||||
|
|
||||||
|
'manage.title' => '服务器管理 - PHP Git 服务器',
|
||||||
|
'manage.heading' => 'PHP Git 服务器 / 管理',
|
||||||
|
'manage.administrator_label' => '管理员',
|
||||||
|
'manage.back_to_home' => '返回仓库首页',
|
||||||
|
'manage.create_user_title' => '创建用户',
|
||||||
|
'manage.create_user_lead' => '管理员创建的账号会立即启用。',
|
||||||
|
'manage.label_username' => '用户名',
|
||||||
|
'manage.label_initial_password' => '初始密码',
|
||||||
|
'manage.label_password_confirmation' => '确认密码',
|
||||||
|
'manage.button_create_user' => '创建用户',
|
||||||
|
'manage.users_title' => '用户',
|
||||||
|
'manage.users_lead' => '停用会立即阻止网页登录和 Access Token 认证。删除用户前必须先处理其仓库。',
|
||||||
|
'manage.users_unavailable' => '当前无法读取用户列表。',
|
||||||
|
'manage.users_empty' => '当前没有用户。',
|
||||||
|
'manage.th_user' => '用户',
|
||||||
|
'manage.th_status' => '状态',
|
||||||
|
'manage.th_repositories' => '仓库',
|
||||||
|
'manage.th_active_tokens' => '有效 Token',
|
||||||
|
'manage.th_created_at' => '创建时间',
|
||||||
|
'manage.th_account_actions' => '账号操作',
|
||||||
|
'manage.th_password' => '密码',
|
||||||
|
'manage.badge_administrator' => '管理员',
|
||||||
|
'manage.badge_current_account' => '当前账号',
|
||||||
|
'manage.badge_active' => '启用',
|
||||||
|
'manage.badge_inactive' => '停用',
|
||||||
|
'manage.button_deactivate' => '停用',
|
||||||
|
'manage.button_activate' => '启用',
|
||||||
|
'manage.button_revoke_all_tokens' => '撤销全部 Token',
|
||||||
|
'manage.confirm_delete_account' => '确认删除账号',
|
||||||
|
'manage.button_delete_user' => '删除用户',
|
||||||
|
'manage.summary_reset_password' => '重置密码',
|
||||||
|
'manage.placeholder_new_password' => '新密码',
|
||||||
|
'manage.placeholder_confirm_new_password' => '确认新密码',
|
||||||
|
'manage.button_save_password' => '保存新密码',
|
||||||
|
'manage.repositories_title' => '托管仓库',
|
||||||
|
'manage.repositories_lead' => '此处操作数据库记录与 <code>repos</code> 目录中的受管 bare 仓库。',
|
||||||
|
'manage.repositories_unavailable' => '当前无法读取托管仓库列表。',
|
||||||
|
'manage.repositories_empty' => '当前没有托管仓库。',
|
||||||
|
'manage.th_repository' => '仓库',
|
||||||
|
'manage.th_owner_visibility' => '所有者与可见性',
|
||||||
|
'manage.th_delete' => '删除',
|
||||||
|
'manage.aria_repository_owner' => '仓库所有者',
|
||||||
|
'manage.aria_repository_visibility' => '仓库可见性',
|
||||||
|
'manage.user_deactivated_suffix' => '(已停用)',
|
||||||
|
'manage.button_save' => '保存',
|
||||||
|
'manage.badge_not_deletable' => '不可删除',
|
||||||
|
'manage.confirm_permanent_delete' => '确认永久删除',
|
||||||
|
'manage.button_delete_repository' => '删除仓库',
|
||||||
|
'manage.state_root_unavailable' => '根目录不可用',
|
||||||
|
'manage.state_configured' => '静态配置',
|
||||||
|
'manage.state_record_only' => '仅记录',
|
||||||
|
'manage.state_incomplete' => '未完成',
|
||||||
|
'manage.state_ready' => '可用',
|
||||||
|
'manage.configured_title' => '配置仓库',
|
||||||
|
'manage.configured_lead' => '这些仓库来自 <code>config.php</code>,管理界面只读显示,'
|
||||||
|
.'不修改配置或文件。',
|
||||||
|
'manage.configured_empty' => '当前没有配置仓库。',
|
||||||
|
'manage.th_url' => 'URL',
|
||||||
|
'manage.th_path' => '路径',
|
||||||
|
'manage.th_visibility' => '可见性',
|
||||||
|
'manage.th_read_write' => '读 / 写',
|
||||||
|
'manage.value_unset' => '未设置',
|
||||||
|
'manage.value_read' => '读',
|
||||||
|
'manage.value_read_disabled' => '禁用读取',
|
||||||
|
'manage.value_write' => '写',
|
||||||
|
'manage.value_read_only' => '只读',
|
||||||
|
'manage.public' => '公开',
|
||||||
|
'manage.private' => '私有',
|
||||||
|
|
||||||
|
'manage.user_created' => '用户 {username} 已创建。',
|
||||||
|
'manage.user_updated' => '用户状态已更新。',
|
||||||
|
'manage.tokens_revoked' => '已撤销 {count} 个有效 Access Token。',
|
||||||
|
'manage.password_updated' => '用户密码已重置。',
|
||||||
|
'manage.user_deleted' => '用户已删除。',
|
||||||
|
'manage.repository_updated' => '仓库所有者与可见性已更新。',
|
||||||
|
'manage.repository_deleted' => '托管仓库 {name} 已删除。',
|
||||||
|
'manage.repository_record_deleted' => '仓库记录 {name} 已删除;缺失或非 bare 路径未作改动。',
|
||||||
|
'manage.username_exists' => '该用户名已存在。',
|
||||||
|
'manage.invalid_user' => '用户不存在或参数无效。',
|
||||||
|
'manage.invalid_owner' => '新的仓库所有者不存在或不可用。',
|
||||||
|
'manage.invalid_repository' => '仓库不存在或参数无效。',
|
||||||
|
'manage.administrator_protected' => '配置中的管理员账号不能被停用或删除。',
|
||||||
|
'manage.self_protected' => '不能停用或删除当前登录账号。',
|
||||||
|
'manage.user_owns_repositories' => '该用户仍拥有仓库;请先转移或删除这些仓库。',
|
||||||
|
'manage.configured_owner' => '该用户是 config.php 静态仓库的所有者;请先在配置中更换所有者。',
|
||||||
|
'manage.configured_repository' => '该路径由 config.php 静态配置,不能从管理界面删除。',
|
||||||
|
'manage.confirmation_required' => '操作确认值无效,请重新确认。',
|
||||||
|
'manage.forbidden' => '仓库权限已变化,操作被拒绝。',
|
||||||
|
'manage.not_found' => '托管仓库目录或记录不存在。',
|
||||||
|
'manage.repository_busy' => '仓库正在执行其他操作,请稍后重试。',
|
||||||
|
'manage.root_unavailable' => '仓库存放目录不可用或不可写。',
|
||||||
|
'manage.cleanup_failed' => '仓库记录已删除,但残留目录清理失败,请检查服务器日志。',
|
||||||
|
'manage.restore_failed' => '删除失败且仓库目录无法恢复,请立即检查服务器日志。',
|
||||||
|
'manage.database_unavailable' => '管理数据库当前不可用。',
|
||||||
|
'manage.action_failed' => '操作失败,请检查服务器日志。');
|
||||||
|
}
|
||||||
+40
-65
@@ -7,6 +7,7 @@ if (!file_exists(__DIR__.'/config.php')) {
|
|||||||
|
|
||||||
require(__DIR__.'/config.php');
|
require(__DIR__.'/config.php');
|
||||||
require(__DIR__.'/lib/http.php');
|
require(__DIR__.'/lib/http.php');
|
||||||
|
require(__DIR__.'/lib/i18n.php');
|
||||||
require(__DIR__.'/lib/auth.php');
|
require(__DIR__.'/lib/auth.php');
|
||||||
require(__DIR__.'/lib/repository.php');
|
require(__DIR__.'/lib/repository.php');
|
||||||
|
|
||||||
@@ -89,69 +90,36 @@ function manage_redirect($url_base) {
|
|||||||
function manage_result_notice($result) {
|
function manage_result_notice($result) {
|
||||||
$name = isset($result['name']) ? $result['name'] : '';
|
$name = isset($result['name']) ? $result['name'] : '';
|
||||||
switch ($result['status']) {
|
switch ($result['status']) {
|
||||||
case 'user_created':
|
case 'user_created': return array('success', t('manage.user_created', array('username' => $result['username'])));
|
||||||
return array('success', '用户 '.$result['username'].' 已创建。');
|
case 'user_updated': return array('success', t('manage.user_updated'));
|
||||||
case 'user_updated':
|
case 'tokens_revoked': return array('success', t('manage.tokens_revoked', array('count' => $result['count'])));
|
||||||
return array('success', '用户状态已更新。');
|
case 'password_updated': return array('success', t('manage.password_updated'));
|
||||||
case 'tokens_revoked':
|
case 'user_deleted': return array('success', t('manage.user_deleted'));
|
||||||
return array('success', '已撤销 '.$result['count'].' 个有效 Access Token。');
|
case 'repository_updated': return array('success', t('manage.repository_updated'));
|
||||||
case 'password_updated':
|
case 'deleted': return array('success', t('manage.repository_deleted', array('name' => $name)));
|
||||||
return array('success', '用户密码已重置。');
|
case 'record_deleted': return array('success', t('manage.repository_record_deleted', array('name' => $name)));
|
||||||
case 'user_deleted':
|
case 'invalid_username': return array('error', t('notice.invalid_username'));
|
||||||
return array('success', '用户已删除。');
|
case 'invalid_password': return array('error', t('notice.invalid_password'));
|
||||||
case 'repository_updated':
|
case 'password_mismatch': return array('error', t('notice.password_mismatch'));
|
||||||
return array('success', '仓库所有者与可见性已更新。');
|
case 'username_exists': return array('error', t('manage.username_exists'));
|
||||||
case 'deleted':
|
case 'invalid_user': return array('error', t('manage.invalid_user'));
|
||||||
return array('success', '托管仓库 '.$name.' 已删除。');
|
case 'invalid_owner': return array('error', t('manage.invalid_owner'));
|
||||||
case 'record_deleted':
|
case 'invalid_repository': return array('error', t('manage.invalid_repository'));
|
||||||
return array(
|
case 'administrator_protected': return array('error', t('manage.administrator_protected'));
|
||||||
'success',
|
case 'self_protected': return array('error', t('manage.self_protected'));
|
||||||
'仓库记录 '.$name.' 已删除;缺失或非 bare 路径未作改动。');
|
case 'user_owns_repositories': return array('error', t('manage.user_owns_repositories'));
|
||||||
case 'invalid_username':
|
case 'configured_owner': return array('error', t('manage.configured_owner'));
|
||||||
return array('error', '用户名须为 3 至 64 个字母、数字、点、短横线或下划线。');
|
case 'configured_repository': return array('error', t('manage.configured_repository'));
|
||||||
case 'invalid_password':
|
case 'confirmation_required': return array('error', t('manage.confirmation_required'));
|
||||||
return array('error', '密码长度须为 8 至 72 个字符,且不能超过 72 字节。');
|
case 'forbidden': return array('error', t('manage.forbidden'));
|
||||||
case 'password_mismatch':
|
case 'not_found': return array('error', t('manage.not_found'));
|
||||||
return array('error', '两次输入的密码不一致。');
|
case 'repository_busy': return array('error', t('manage.repository_busy'));
|
||||||
case 'username_exists':
|
case 'root_unavailable': return array('error', t('manage.root_unavailable'));
|
||||||
return array('error', '该用户名已存在。');
|
case 'cleanup_failed': return array('error', t('manage.cleanup_failed'));
|
||||||
case 'invalid_user':
|
case 'restore_failed': return array('error', t('manage.restore_failed'));
|
||||||
return array('error', '用户不存在或参数无效。');
|
|
||||||
case 'invalid_owner':
|
|
||||||
return array('error', '新的仓库所有者不存在或不可用。');
|
|
||||||
case 'invalid_repository':
|
|
||||||
return array('error', '仓库不存在或参数无效。');
|
|
||||||
case 'administrator_protected':
|
|
||||||
return array('error', '配置中的管理员账号不能被停用或删除。');
|
|
||||||
case 'self_protected':
|
|
||||||
return array('error', '不能停用或删除当前登录账号。');
|
|
||||||
case 'user_owns_repositories':
|
|
||||||
return array('error', '该用户仍拥有仓库;请先转移或删除这些仓库。');
|
|
||||||
case 'configured_owner':
|
|
||||||
return array(
|
|
||||||
'error',
|
|
||||||
'该用户是 config.php 静态仓库的所有者;请先在配置中更换所有者。');
|
|
||||||
case 'configured_repository':
|
|
||||||
return array('error', '该路径由 config.php 静态配置,不能从管理界面删除。');
|
|
||||||
case 'confirmation_required':
|
|
||||||
return array('error', '操作确认值无效,请重新确认。');
|
|
||||||
case 'forbidden':
|
|
||||||
return array('error', '仓库权限已变化,操作被拒绝。');
|
|
||||||
case 'not_found':
|
|
||||||
return array('error', '托管仓库目录或记录不存在。');
|
|
||||||
case 'repository_busy':
|
|
||||||
return array('error', '仓库正在执行其他操作,请稍后重试。');
|
|
||||||
case 'root_unavailable':
|
|
||||||
return array('error', '仓库存放目录不可用或不可写。');
|
|
||||||
case 'cleanup_failed':
|
|
||||||
return array('error', '仓库记录已删除,但残留目录清理失败,请检查服务器日志。');
|
|
||||||
case 'restore_failed':
|
|
||||||
return array('error', '删除失败且仓库目录无法恢复,请立即检查服务器日志。');
|
|
||||||
case 'metadata_unavailable':
|
case 'metadata_unavailable':
|
||||||
case 'database_unavailable':
|
case 'database_unavailable': return array('error', t('manage.database_unavailable'));
|
||||||
return array('error', '管理数据库当前不可用。');
|
default: return array('error', t('manage.action_failed'));
|
||||||
default:
|
|
||||||
return array('error', '操作失败,请检查服务器日志。');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,11 +233,11 @@ function manage_handle_action($url_base, $definitions, $configuration, $administ
|
|||||||
function manage_send_head($url_base, $administrator) {
|
function manage_send_head($url_base, $administrator) {
|
||||||
echo <<<'HTML'
|
echo <<<'HTML'
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="__LANG__">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>服务器管理 - PHP Git 服务器</title>
|
<title>__TITLE__</title>
|
||||||
<style>
|
<style>
|
||||||
:root { color-scheme: light dark; --ink: #20252b; --muted: #65707d; --line: #d7dce2;
|
:root { color-scheme: light dark; --ink: #20252b; --muted: #65707d; --line: #d7dce2;
|
||||||
--surface: #f5f7f8; --accent: #176b43; --danger: #a43a3a; --warning: #8a6116; }
|
--surface: #f5f7f8; --accent: #176b43; --danger: #a43a3a; --warning: #8a6116; }
|
||||||
@@ -351,7 +319,8 @@ HTML;
|
|||||||
echo '<header><div class="header-inner"><div><h1>PHP Git 服务器 / 管理</h1>' ."\n";
|
echo '<header><div class="header-inner"><div><h1>PHP Git 服务器 / 管理</h1>' ."\n";
|
||||||
echo '<p class="identity">管理员:<strong>'.manage_escape($administrator['username'])
|
echo '<p class="identity">管理员:<strong>'.manage_escape($administrator['username'])
|
||||||
.'</strong></p></div>' ."\n";
|
.'</strong></p></div>' ."\n";
|
||||||
echo '<nav><a href="'.manage_escape(manage_home_url($url_base)).'">返回仓库首页</a>';
|
echo '<nav><a href="'.manage_escape(manage_home_url($url_base)).'">返回仓库首页</a> '
|
||||||
|
.i18n_language_switcher(manage_page_url($url_base));
|
||||||
echo '</nav></div></header>' ."\n";
|
echo '</nav></div></header>' ."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,6 +549,7 @@ function manage_render(
|
|||||||
header('Content-Security-Policy: default-src \'none\'; style-src \'unsafe-inline\'; '
|
header('Content-Security-Policy: default-src \'none\'; style-src \'unsafe-inline\'; '
|
||||||
.'form-action \'self\'; base-uri \'none\'; frame-ancestors \'none\'');
|
.'form-action \'self\'; base-uri \'none\'; frame-ancestors \'none\'');
|
||||||
|
|
||||||
|
ob_start();
|
||||||
manage_send_head($url_base, $administrator);
|
manage_send_head($url_base, $administrator);
|
||||||
echo '<main>' ."\n";
|
echo '<main>' ."\n";
|
||||||
if ($notice !== NULL && isset($notice['type'], $notice['message'])) {
|
if ($notice !== NULL && isset($notice['type'], $notice['message'])) {
|
||||||
@@ -597,6 +567,10 @@ function manage_render(
|
|||||||
$definitions);
|
$definitions);
|
||||||
manage_send_configured_repositories($url_base, $definitions);
|
manage_send_configured_repositories($url_base, $definitions);
|
||||||
echo '</main></body></html>' ."\n";
|
echo '</main></body></html>' ."\n";
|
||||||
|
$markup = ob_get_clean();
|
||||||
|
$markup = str_replace('__LANG__', manage_escape(i18n_html_lang()), $markup);
|
||||||
|
$markup = str_replace('__TITLE__', manage_escape(t('manage.title')), $markup);
|
||||||
|
echo i18n_translate_markup($markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($url_base)) {
|
if (!isset($url_base)) {
|
||||||
@@ -615,6 +589,7 @@ if (!is_array($managed_repositories)) {
|
|||||||
send_error(500, 'Internal Server Error', 'The managed repository configuration is invalid.');
|
send_error(500, 'Internal Server Error', 'The managed repository configuration is invalid.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i18n_configure(isset($language) ? $language : NULL, $url_base);
|
||||||
auth_configure($auth, $url_base);
|
auth_configure($auth, $url_base);
|
||||||
if (!auth_is_enabled()) {
|
if (!auth_is_enabled()) {
|
||||||
send_error(404, 'Not Found', 'Account authentication is disabled.');
|
send_error(404, 'Not Found', 'Account authentication is disabled.');
|
||||||
|
|||||||
Reference in New Issue
Block a user