Compare commits

...

3 Commits

Author SHA1 Message Date
e8103b7b9a
feat: acm team account (#42)
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-15 17:00:49 +08:00
2cd81ab055
chore: add upgrader for #42 2023-02-15 16:58:53 +08:00
098e488fe3
feat: acm team account 2023-02-15 16:46:47 +08:00
12 changed files with 425 additions and 212 deletions

View File

@ -1039,9 +1039,8 @@ UNLOCK TABLES;
CREATE TABLE `user_info` (
`usergroup` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'U',
`username` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`usertype` enum('student','teacher','system','banned') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'student',
`usertype` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'student',
`realname` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`school` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`email` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` char(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`svn_password` char(10) COLLATE utf8mb4_unicode_ci NOT NULL,
@ -1049,8 +1048,8 @@ CREATE TABLE `user_info` (
`sex` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'U',
`ac_num` int NOT NULL DEFAULT 0,
`register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_login_time` datetime DEFAULT CURRENT_TIMESTAMP,
`last_visit_time` datetime DEFAULT CURRENT_TIMESTAMP,
`last_login_time` datetime DEFAULT NULL,
`last_visit_time` datetime DEFAULT NULL,
`expiration_time` datetime DEFAULT NULL,
`remote_addr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`http_x_forwarded_for` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',

View File

@ -527,6 +527,48 @@ if ($cur_tab == 'index') {
EOD);
$register_tmp_user_form->runAtServer();
$register_tmp_acm_team_form = new UOJForm('register_tmp_acm_team');
$register_tmp_acm_team_form->addTextArea('register_tmp_acm_team_table', [
'label' => '队伍信息',
'input_class' => 'form-control font-monospace overflow-x-scroll overflow-wrap-normal white-space-pre',
'help' => '一行一个队伍,格式形如 <code>ACM233_team23,123456,ACM233_team23@uoj.ac,Mike代表队,2,李麦克,麦麦大学,张麦克,克克大学</code>,依次表示用户名、密码、邮箱、队伍名、队伍人数、每个队员的姓名和单位信息。',
'validator_php' => 'validateNothing',
]);
$register_tmp_acm_team_form->addInput('expiration_time', [
'div_class' => 'mt-3',
'label' => '过期时间',
'default_value' => UOJTime::time2str((new DateTime())->add(new DateInterval('P7D'))->setTime(0, 0, 0)),
'validator_php' => function ($str, &$vdata) {
try {
$vdata['expiration_time'] = new DateTime($str);
} catch (Exception $e) {
return '无效时间格式';
}
return '';
},
]);
$register_tmp_acm_team_form->handle = function (&$vdata) {
$expiration_time = $vdata['expiration_time']->format('Y-m-d H:i:s');
$msg = '';
foreach (explode("\n", UOJRequest::post('register_tmp_acm_team_table')) as $raw_line) {
try {
if (trim($raw_line) === '') {
continue;
}
$team = UOJUser::registerTmpACMTeamAccountFromText($raw_line, '', $expiration_time);
$msg .= $team['username'] . ': success';
} catch (Exception $e) {
$msg .= $raw_line . ': ' . $e->getMessage();
}
$msg .= "\n";
}
UOJResponse::message(HTML::tag('pre', [], $msg));
};
$register_tmp_acm_team_form->runAtServer();
$change_password_form = new UOJForm('change_password');
$change_password_form->addInput('p_username', [
'label' => '用户名',
@ -1326,6 +1368,9 @@ if ($cur_tab == 'index') {
<li class="nav-item">
<a class="nav-link" href="#new-tmp-user" data-bs-toggle="tab" data-bs-target="#new-tmp-user">新增临时用户</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#new-acm-team-tmp-user" data-bs-toggle="tab" data-bs-target="#new-acm-team-tmp-user">新增 ACM 比赛用临时团队用户</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#reset-password" data-bs-toggle="tab" data-bs-target="#reset-password">重置密码</a>
</li>
@ -1385,7 +1430,7 @@ if ($cur_tab == 'index') {
function ($row) {
echo '<tr>';
echo '<td>', UOJUser::getLink($row), '</td>';
echo '<td>', HTML::escape($row['school']), '</td>';
echo '<td>', HTML::escape(UOJUser::getExtra($row, 'school')), '</td>';
echo '<td>';
switch ($row['usergroup']) {
case 'S':
@ -1451,6 +1496,24 @@ if ($cur_tab == 'index') {
</div>
</div>
</div>
<div class="tab-pane" id="new-acm-team-tmp-user">
<div class="row">
<div class="col-md-8">
<?php $register_tmp_acm_team_form->printHTML() ?>
</div>
<div class="col-md-4">
<h5>注意事项</h5>
<ul class="mb-0">
<li><b>此处创建的用户只能在 ACM 比赛中使用,如需创建其他用户,请查看其他选项卡。</b></li>
<li>用户名推荐格式为 <code>ACM</code> + 比赛 ID + <code>_team</code> + 队伍编号,如用于比赛 233 的第 23 号队伍可以设置为 <code>ACM233_team23</code></li>
<li>请提醒用户及时修改初始密码。请勿设置过于简单的初始密码。</li>
<li>我们推荐在创建账号时输入号主的电子邮件地址以便后期发生忘记密码等情况时进行验证,如果不设置电子邮件地址,将该列留空即可。</li>
<li>临时账号不具有任何权限,只能查看、参加已经用户报名了的比赛。创建账号后可以在「修改个人信息」页面中的「特权」选项卡为用户分配权限。特别地,如果该用户是外校学生,那么您可能需要禁用其 <b>所有权限</b>,并为其手动报名比赛。</li>
<li>注意分隔符为英文逗号。解析时调用的是 PHP <code>str_getcsv</code> 函数,遇到特殊字符请遵照 CSV 格式进行编码。<b>推荐在 Excel 中导出 CSV 格式的文件后再将内容复制到此处。</b></li>
</ul>
</div>
</div>
</div>
<div class="tab-pane" id="reset-password">
<div id="result-alert-reset-password" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">

View File

@ -40,35 +40,32 @@ if ($cur_tab == 'profile') {
$username = UOJLocale::get('username');
$avatar = UOJLocale::get('avatar');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-username" class="form-label">$username</label>
<input type="text" class="form-control" id="input-username" aria-describedby="help-username" value="{$user['username']}" disabled>
<div id="help-username" class="form-text">用户名不能被修改。</div>
</div>
EOD);
if (isSuperUser(Auth::user())) {
$update_profile_form->addInput(
'realname',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('user::real name'),
'default_value' => $user['realname'],
'validator_php' => function ($realname, &$vdata) {
$vdata['realname'] = $realname;
<div class="mb-3">
<label for="input-username" class="form-label">$username</label>
<input type="text" class="form-control" id="input-username" aria-describedby="help-username" value="{$user['username']}" disabled>
<div id="help-username" class="form-text">用户名不能被修改。</div>
</div>
EOD);
if (isSuperUser(Auth::user()) && !isset($extra['acm'])) {
$update_profile_form->addInput('realname', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('user::real name'),
'default_value' => $user['realname'],
'validator_php' => function ($realname, &$vdata) {
$vdata['realname'] = $realname;
return '';
},
]
);
return '';
},
]);
} else {
$real_name = UOJLocale::get('user::real name');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-realname" class="form-label">$real_name</label>
<input type="text" class="form-control" id="input-realname" aria-describedby="help-realname" value="{$user['realname']}" disabled>
<div id="help-realname" class="form-text">只有管理员才能修改用户的真实姓名。</div>
</div>
EOD);
<div class="mb-3">
<label for="input-realname" class="form-label">$real_name</label>
<input type="text" class="form-control" id="input-realname" aria-describedby="help-realname" value="{$user['realname']}" disabled>
<div id="help-realname" class="form-text">只有管理员才能修改用户的真实姓名。</div>
</div>
EOD);
}
if (isTmpUser($user)) {
if (isSuperUser(Auth::user())) {
@ -92,12 +89,12 @@ EOD);
} else {
$expiration_time = UOJLocale::get('user::expiration time');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label>
<input type="text" class="form-control" id="input-expiration_time" aria-describedby="help-expiration_time" value="{$user['expiration_time']}" disabled>
<div id="help-expiration_time" class="form-text">只有管理员才能修改用户的账号过期时间。</div>
</div>
EOD);
<div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label>
<input type="text" class="form-control" id="input-expiration_time" aria-describedby="help-expiration_time" value="{$user['expiration_time']}" disabled>
<div id="help-expiration_time" class="form-text">只有管理员才能修改用户的账号过期时间。</div>
</div>
EOD);
}
} else {
$expiration_time = UOJLocale::get('user::expiration time');
@ -105,12 +102,12 @@ EOD);
? '只有用户组别为「临时用户」的用户才能被修改过期时间。'
: '只有管理员才能修改用户的账号过期时间。';
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label>
<input type="text" class="form-control" id="input-expiration_time" aria-describedby="help-expiration_time" value="永不过期" disabled>
<div id="help-expiration_time" class="form-text">$expiration_help_text</div>
</div>
EOD);
<div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label>
<input type="text" class="form-control" id="input-expiration_time" aria-describedby="help-expiration_time" value="永不过期" disabled>
<div id="help-expiration_time" class="form-text">$expiration_help_text</div>
</div>
EOD);
}
$update_profile_form->addCheckboxes('avatar_source', [
'div_class' => 'mb-3',
@ -125,81 +122,69 @@ EOD);
],
'help' => UOJLocale::get('change avatar help'),
]);
$update_profile_form->addInput(
'email',
[
'div_class' => 'mb-3',
'type' => 'email',
'label' => UOJLocale::get('email'),
'default_value' => $user['email'] ?: '',
'validator_php' => function ($email, &$vdata) {
if ($email && !validateEmail($email)) {
return 'Email 格式不合法。';
}
$update_profile_form->addInput('email', [
'div_class' => 'mb-3',
'type' => 'email',
'label' => UOJLocale::get('email'),
'default_value' => $user['email'] ?: '',
'validator_php' => function ($email, &$vdata) {
if ($email && !validateEmail($email)) {
return 'Email 格式不合法。';
}
$vdata['email'] = $email;
$vdata['email'] = $email;
return '';
},
]
);
$update_profile_form->addInput(
'qq',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('QQ'),
'default_value' => $user['qq'] == 0 ? '' : $user['qq'],
'validator_php' => function ($qq, &$vdata) {
if ($qq && !validateQQ($qq)) {
return 'QQ 格式不合法。';
}
return '';
},
]);
$update_profile_form->addInput('qq', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('QQ'),
'default_value' => $user['qq'] == 0 ? '' : $user['qq'],
'validator_php' => function ($qq, &$vdata) {
if ($qq && !validateQQ($qq)) {
return 'QQ 格式不合法。';
}
$vdata['qq'] = $qq;
$vdata['qq'] = $qq;
return '';
},
]
);
$update_profile_form->addInput(
'github',
[
'div_class' => 'mb-3',
'label' => 'GitHub',
'default_value' => $extra['social']['github'] ?: '',
'validator_php' => function ($github, &$vdata) {
if ($github && !validateGitHubUsername($github)) {
return 'GitHub 用户名不合法。';
}
return '';
},
]);
$update_profile_form->addInput('github', [
'div_class' => 'mb-3',
'label' => 'GitHub',
'default_value' => $extra['social']['github'] ?: '',
'validator_php' => function ($github, &$vdata) {
if ($github && !validateGitHubUsername($github)) {
return 'GitHub 用户名不合法。';
}
$vdata['github'] = $github;
$vdata['github'] = $github;
return '';
},
]
);
return '';
},
]);
if (isSuperUser(Auth::user())) {
$update_profile_form->addInput(
'school',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('school'),
'default_value' => $user['school'] ?: '',
'validator_php' => function ($school, &$vdata) {
$vdata['school'] = $school;
$update_profile_form->addInput('school', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('school'),
'default_value' => $extra['school'] ?: '',
'validator_php' => function ($school, &$vdata) {
$vdata['school'] = $school;
return '';
},
]
);
return '';
},
]);
} else {
$school = UOJLocale::get('school');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-school" class="form-label">$school</label>
<input type="text" class="form-control" id="input-school" aria-describedby="help-school" value="{$user['school']}" disabled>
<div id="help-school" class="form-text">只有管理员才能修改用户所属学校。</div>
</div>
EOD);
<div class="mb-3">
<label for="input-school" class="form-label">$school</label>
<input type="text" class="form-control" id="input-school" aria-describedby="help-school" value="{$extra['school']}" disabled>
<div id="help-school" class="form-text">只有管理员才能修改用户所属学校。</div>
</div>
EOD);
}
$update_profile_form->addCheckboxes('sex', [
'div_class' => 'mb-3',
@ -214,57 +199,102 @@ EOD);
'F' => UOJLocale::get('female'),
],
]);
$update_profile_form->addInput(
'motto',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('motto'),
'default_value' => $user['motto'] ?: '',
'validator_php' => function ($motto, &$vdata) {
if (!validateMotto($motto)) {
return '格言格式不合法';
}
$update_profile_form->addInput('motto', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('motto'),
'default_value' => $user['motto'] ?: '',
'validator_php' => function ($motto, &$vdata) {
if (!validateMotto($motto)) {
return '格言格式不合法';
}
$vdata['motto'] = $motto;
$vdata['motto'] = $motto;
return '';
},
]
);
$update_profile_form->addInput(
'codeforces',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('codeforces handle'),
'default_value' => $extra['social']['codeforces'] ?: '',
'validator_php' => function ($codeforces, &$vdata) {
if ($codeforces && !validateUsername($codeforces)) {
return 'Codeforces 用户名格式不合法。';
}
return '';
},
]);
$update_profile_form->addInput('codeforces', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('codeforces handle'),
'default_value' => $extra['social']['codeforces'] ?: '',
'validator_php' => function ($codeforces, &$vdata) {
if ($codeforces && !validateUsername($codeforces)) {
return 'Codeforces 用户名格式不合法。';
}
$vdata['codeforces'] = $codeforces;
$vdata['codeforces'] = $codeforces;
return '';
},
]
);
$update_profile_form->addInput(
'website',
[
'div_class' => 'mb-3',
'label' => UOJLocale::get('user::website'),
'default_value' => $extra['social']['website'] ?: '',
'validator_php' => function ($url, &$vdata) {
if ($url && !validateURL($url)) {
return '链接格式不合法。';
}
return '';
},
]);
$update_profile_form->addInput('website', [
'div_class' => 'mb-3',
'label' => UOJLocale::get('user::website'),
'default_value' => $extra['social']['website'] ?: '',
'validator_php' => function ($url, &$vdata) {
if ($url && !validateURL($url)) {
return '链接格式不合法。';
}
$vdata['website'] = $url;
$vdata['website'] = $url;
return '';
},
]);
if (isset($extra['acm'])) {
$team_name = $extra['acm']['team_name'];
$team_info_text = UOJUser::convertACMTeamInfoToText($extra['acm']['members']);
if (isSuperUser(Auth::user())) {
$update_profile_form->addInput('acm_team_name', [
'div_class' => 'mb-3',
'label' => 'ACM 队伍名称',
'input_class' => 'form-control',
'default_value' => $team_name,
'validator_php' => function ($team_name, &$vdata) {
if (!is_string($team_name)) {
return '不合法的输入。';
}
$vdata['acm_team_name'] = $team_name;
return '';
},
]);
$update_profile_form->addInput('acm_members', [
'div_class' => 'mb-3',
'label' => 'ACM 队伍成员',
'input_class' => 'form-control font-monospace',
'default_value' => $team_info_text,
'validator_php' => function ($team_info_text, &$vdata) {
try {
$vdata['acm_members'] = UOJUser::parseACMTeamInfoFromText($team_info_text);
} catch (Exception $e) {
return $e->getMessage();
}
return '';
},
]);
} else {
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-acm_team_name" class="form-label">ACM 队伍名称</label>
<input type="text" class="form-control" id="input-acm_team_name" aria-describedby="help-acm_team_name" value="{$team_name}" disabled>
<div id="help-acm_team_name" class="form-text">只有超级用户才能修改 ACM 队伍名称。</div>
</div>
EOD);
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-acm_members" class="form-label">ACM 队伍成员</label>
<input type="text" class="form-control" id="input-acm_members" aria-describedby="help-acm_members" value="{$team_info_text}" disabled>
<div id="help-acm_members" class="form-text">只有超级用户才能修改 ACM 队伍信息。</div>
</div>
EOD);
}
}
return '';
},
]
);
if ($user['usergroup'] == 'B') {
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
@ -302,7 +332,7 @@ EOD);
],
]);
}
$update_profile_form->handle = function (&$vdata) use ($user) {
$update_profile_form->handle = function (&$vdata) use ($user, $extra) {
$data = [
'email' => $vdata['email'],
'qq' => $vdata['qq'],
@ -312,7 +342,6 @@ EOD);
if (isSuperUser(Auth::user())) {
$data['realname'] = $vdata['realname'];
$data['school'] = $vdata['school'];
if (isTmpUser($user)) {
$data['expiration_time'] = $vdata['expiration_time']->format(UOJTime::FORMAT);
@ -325,22 +354,28 @@ EOD);
"where", ["username" => $user['username']]
]);
$extra['avatar_source'] = $_POST['avatar_source'];
$extra['social']['github'] = $vdata['github'];
$extra['social']['codeforces'] = $vdata['codeforces'];
$extra['social']['website'] = $vdata['website'];
if (!(isTmpUser(Auth::user()) || isBannedUser(Auth::user()))) {
$extra['username_color'] = $_POST['username_color'];
}
if (isSuperUser(Auth::user())) {
$extra['school'] = $vdata['school'];
if (isset($extra['acm'])) {
$extra['acm']['team_name'] = $vdata['acm_team_name'];
$extra['acm']['members'] = $vdata['acm_members'];
}
}
DB::update([
"update user_info",
"set", [
'extra' => DB::json_set(
'extra',
'$.avatar_source',
$_POST['avatar_source'],
'$.social.github',
$vdata['github'],
'$.social.codeforces',
$vdata['codeforces'],
'$.social.website',
$vdata['website'],
'$.username_color',
$_POST['username_color']
),
'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
],
"where", ["username" => $user['username']]
]);
@ -351,24 +386,24 @@ EOD);
$update_profile_form->config['submit_button']['class'] = 'btn btn-secondary';
$update_profile_form->config['submit_button']['text'] = '更新';
$update_profile_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert')
.html('个人信息修改成功!')
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert')
.html('个人信息修改失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
function(res) {
if (res.status === 'success') {
$('#result-alert')
.html('个人信息修改成功!')
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert')
.html('个人信息修改失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$(window).scrollTop(0);
}
EOD);
$(window).scrollTop(0);
}
EOD);
$update_profile_form->runAtServer();
} elseif ($cur_tab == 'password') {
if (isset($_POST['submit-change_password']) && $_POST['submit-change_password'] == 'change_password') {

View File

@ -156,6 +156,9 @@ function isSuperUser($user) {
function isTmpUser($user) {
return $user != null && $user['usergroup'] == 'T';
}
function isBannedUser($user) {
return $user != null && $user['usergroup'] == 'B';
}
function getProblemExtraConfig($problem) {
$extra_config = json_decode($problem['extra_config'], true);
@ -291,3 +294,12 @@ function getAbsoluteUrl($relativeUrl, $baseUrl) {
// return absolute URL
return $scheme . '://' . $abs;
}
function array_to_csv($data, $delimiter = ',', $enclosure = '"', $escape_char = "\\") {
$f = fopen('php://memory', 'r+');
foreach ($data as $item) {
fputcsv($f, $item, $delimiter, $enclosure, $escape_char);
}
rewind($f);
return stream_get_contents($f);
}

View File

@ -109,6 +109,10 @@ function validateCommentId($id) {
return ['error' => '', 'store' => $comment];
}
function validateNothing($x) {
return '';
}
function is_short_string($str) {
return is_string($str) && strlen($str) <= 256;
}

View File

@ -182,6 +182,7 @@ class UOJForm {
'type' => 'text',
'div_class' => '',
'input_class' => 'form-control',
'input_attrs' => [],
'default_value' => '',
'label' => '',
'label_class' => 'form-label',
@ -211,7 +212,7 @@ class UOJForm {
'name' => $name,
'id' => "input-$name",
'placeholder' => $config['placeholder'],
], HTML::escape($config['default_value']));
] + $config['input_attrs'], HTML::escape($config['default_value']));
$html .= HTML::tag('div', ['class' => 'invalid-feedback', 'id' => "help-$name"], '');
if ($config['help']) {

View File

@ -50,18 +50,25 @@ class UOJUser {
}
public static function register($user, $cfg = []) {
$cfg += [
'extra' => [],
];
UOJUser::checkBasicInfo($user, $cfg);
$password = getPasswordToStore($user['password'], $user['username']);
$extra = [
'school' => $user['school'] ?: null,
] + $cfg['extra'];
$info = [
'username' => $user['username'],
'realname' => $user['realname'] ?: '',
'email' => $user['email'],
'school' => $user['school'] ?: '',
'password' => $password,
'svn_password' => uojRandString(20),
'register_time' => DB::now(),
'extra' => '{}'
'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
];
// 0 means non-existence, false means DB error.
if (DB::selectExists("select 1 from user_info") === 0) {
@ -77,10 +84,21 @@ class UOJUser {
}
public static function registerTmpAccount($user, $cfg = []) {
$cfg += [
'extra' => [],
'check_email' => false,
'type' => 'student',
];
UOJUser::checkBasicInfo($user, $cfg);
if (!isset($user['expiration_time'])) {
throw new UOJInvalidArgumentException('无效账号过期时间');
}
$password = getPasswordToStore($user['password'], $user['username']);
$extra = [
'school' => $user['school'] ?: null,
'permissions' => [
'problems' => [
'view' => false,
@ -115,18 +133,19 @@ class UOJUser {
'upload_image' => false,
],
],
];
] + $cfg['extra'];
$info = [
'username' => $user['username'],
'usergroup' => 'T',
'usertype' => $cfg['type'],
'realname' => $user['realname'] ?: '',
'email' => $user['email'],
'school' => $user['school'] ?: '',
'password' => $password,
'svn_password' => uojRandString(20),
'register_time' => DB::now(),
'expiration_time' => $user['expiration_time'],
'extra' => json_encode($extra),
'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
];
DB::insert([
@ -139,32 +158,27 @@ class UOJUser {
}
public static function registerTmpACMTeamAccount($team, $cfg = []) {
$cfg += [
'check_email' => false,
'extra' => [],
'type' => 'team',
];
UOJUser::checkBasicInfo($team, $cfg);
if (!isset($team['expiration_time'])) {
throw new UOJInvalidArgumentException('无效账号过期时间');
}
$password = getPasswordToStore($team['password'], $team['username']);
$team['extra'] = json_encode([
$cfg['extra'] += [
'acm' => [
'contest_name' => $team['contest_name'],
'team_name' => $team['team_name'],
'members' => $team['members']
]
], JSON_UNESCAPED_UNICODE);
'members' => $team['members'],
],
];
DB::insert([
"insert into user_info",
"(usergroup, username, email, password, svn_password, register_time, expiration_time, extra)",
"values", DB::tuple([
'T', $team['username'], $team['email'], $password, uojRandString(20),
DB::now(), $team['expiration_time'], $team['extra']
])
]);
return $team;
return UOJUser::registerTmpAccount($team, $cfg);
}
public static function registerTmpACMTeamAccountFromText($text, $contest_name, $expiration_time) {
@ -194,12 +208,47 @@ class UOJUser {
'contest_name' => $contest_name,
'expiration_time' => $expiration_time,
'team_name' => $fields[3],
'members' => $mem
'members' => $mem,
];
return UOJUser::registerTmpACMTeamAccount($team);
}
public static function convertACMTeamInfoToText($mem) {
$csv_array = [count($mem)];
foreach ($mem as $member) {
$csv_array[] = $member['name'];
$csv_array[] = $member['organization'];
}
return array_to_csv([$csv_array]);
}
public static function parseACMTeamInfoFromText($text) {
$fields = array_map('trim', str_getcsv($text));
if (empty($fields)) {
throw new UOJInvalidArgumentException('格式不合规范');
}
$num = (int)$fields[0];
if (count($fields) != 1 + $num * 2) {
throw new UOJInvalidArgumentException('格式不合规范');
}
$mem = [];
for ($i = 0; $i < $num; $i++) {
$mem[] = [
'name' => $fields[1 + $i * 2],
'organization' => $fields[1 + $i * 2 + 1]
];
}
return $mem;
}
public static function getAccountStatus($user) {
if ($user['usergroup'] == 'B') {
return 'banned';
@ -320,7 +369,7 @@ class UOJUser {
});
}
public static function getExtra($user) {
public static function getExtra($user, $key = null) {
$extra = json_decode($user['extra'], true);
if ($extra === null) {
$extra = [];
@ -341,7 +390,12 @@ class UOJUser {
'avatar_source' => 'gravatar',
'username_color' => isSuperUser($user) ? '#9d3dcf' : '#0d6efd',
]);
return $extra;
if ($key == null) {
return $extra;
}
return $extra[$key];
}
public static function checkVisibility(string $code, array $user, ?array $viewer) {

View File

@ -195,8 +195,8 @@ ALTER TABLE `user_info`
MODIFY `remember_token` char(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
MODIFY `motto` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
ALTER TABLE `user_info`
CHANGE `last_login` `last_login_time` datetime DEFAULT CURRENT_TIMESTAMP,
CHANGE `last_visited` `last_visit_time` datetime DEFAULT CURRENT_TIMESTAMP;
CHANGE `last_login` `last_login_time` datetime DEFAULT NULL,
CHANGE `last_visited` `last_visit_time` datetime DEFAULT NULL;
ALTER TABLE `user_info`
ADD `expiration_time` datetime DEFAULT NULL AFTER `last_visit_time`,
ADD `extra` json NOT NULL;

View File

@ -0,0 +1,8 @@
ALTER TABLE
`user_info`
MODIFY
`usertype` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'student',
MODIFY
`last_login_time` datetime DEFAULT NULL,
MODIFY
`last_visit_time` datetime DEFAULT NULL;

View File

@ -0,0 +1,27 @@
<?php
return function ($type) {
DB::init();
if ($type == 'up') {
$users = DB::selectAll("select * from user_info");
foreach ($users as $user) {
$extra = UOJUser::getExtra($user);
$extra['school'] = $user['school'];
DB::update([
"update user_info",
"set", [
"extra" => json_encode($extra, JSON_UNESCAPED_UNICODE)
],
"where", [
"username" => $user['username']
]
]);
echo "Updated user {$user['username']}. \n";
}
}
};

View File

@ -38,10 +38,10 @@
</span>
<?php endif ?>
</li>
<?php if ($user['school']) : ?>
<?php if ($extra['school']) : ?>
<li class="list-group-item">
<i class="bi bi-person-badge-fill me-1"></i>
<?= $user['school'] ?>
<?= $extra['school'] ?>
</li>
<?php endif ?>
<?php if ($user['email']) : ?>

View File

@ -478,3 +478,13 @@ form.form-horizontal {
.dropzone .dz-preview .dz-progress {
height: 6px !important;
}
/* Utils */
.overflow-wrap-normal {
overflow-wrap: normal !important;
}
.white-space-pre {
white-space: pre !important;
}