feat: acm team account (#42)
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2023-02-15 17:00:49 +08:00 committed by GitHub
commit e8103b7b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 425 additions and 212 deletions

View File

@ -1039,9 +1039,8 @@ UNLOCK TABLES;
CREATE TABLE `user_info` ( CREATE TABLE `user_info` (
`usergroup` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'U', `usergroup` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'U',
`username` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, `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 '', `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, `email` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` char(32) 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, `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', `sex` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'U',
`ac_num` int NOT NULL DEFAULT 0, `ac_num` int NOT NULL DEFAULT 0,
`register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `register_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_login_time` datetime DEFAULT CURRENT_TIMESTAMP, `last_login_time` datetime DEFAULT NULL,
`last_visit_time` datetime DEFAULT CURRENT_TIMESTAMP, `last_visit_time` datetime DEFAULT NULL,
`expiration_time` datetime DEFAULT NULL, `expiration_time` datetime DEFAULT NULL,
`remote_addr` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `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 '', `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); EOD);
$register_tmp_user_form->runAtServer(); $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 = new UOJForm('change_password');
$change_password_form->addInput('p_username', [ $change_password_form->addInput('p_username', [
'label' => '用户名', 'label' => '用户名',
@ -1326,6 +1368,9 @@ if ($cur_tab == 'index') {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#new-tmp-user" data-bs-toggle="tab" data-bs-target="#new-tmp-user">新增临时用户</a> <a class="nav-link" href="#new-tmp-user" data-bs-toggle="tab" data-bs-target="#new-tmp-user">新增临时用户</a>
</li> </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"> <li class="nav-item">
<a class="nav-link" href="#reset-password" data-bs-toggle="tab" data-bs-target="#reset-password">重置密码</a> <a class="nav-link" href="#reset-password" data-bs-toggle="tab" data-bs-target="#reset-password">重置密码</a>
</li> </li>
@ -1385,7 +1430,7 @@ if ($cur_tab == 'index') {
function ($row) { function ($row) {
echo '<tr>'; echo '<tr>';
echo '<td>', UOJUser::getLink($row), '</td>'; echo '<td>', UOJUser::getLink($row), '</td>';
echo '<td>', HTML::escape($row['school']), '</td>'; echo '<td>', HTML::escape(UOJUser::getExtra($row, 'school')), '</td>';
echo '<td>'; echo '<td>';
switch ($row['usergroup']) { switch ($row['usergroup']) {
case 'S': case 'S':
@ -1451,6 +1496,24 @@ if ($cur_tab == 'index') {
</div> </div>
</div> </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 class="tab-pane" id="reset-password">
<div id="result-alert-reset-password" class="alert" role="alert" style="display: none"></div> <div id="result-alert-reset-password" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2"> <div class="row row-cols-1 row-cols-md-2">

View File

@ -40,35 +40,32 @@ if ($cur_tab == 'profile') {
$username = UOJLocale::get('username'); $username = UOJLocale::get('username');
$avatar = UOJLocale::get('avatar'); $avatar = UOJLocale::get('avatar');
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <div class="mb-3">
<label for="input-username" class="form-label">$username</label> <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> <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 id="help-username" class="form-text">用户名不能被修改。</div>
</div> </div>
EOD); EOD);
if (isSuperUser(Auth::user())) { if (isSuperUser(Auth::user()) && !isset($extra['acm'])) {
$update_profile_form->addInput( $update_profile_form->addInput('realname', [
'realname', 'div_class' => 'mb-3',
[ 'label' => UOJLocale::get('user::real name'),
'div_class' => 'mb-3', 'default_value' => $user['realname'],
'label' => UOJLocale::get('user::real name'), 'validator_php' => function ($realname, &$vdata) {
'default_value' => $user['realname'], $vdata['realname'] = $realname;
'validator_php' => function ($realname, &$vdata) {
$vdata['realname'] = $realname;
return ''; return '';
}, },
] ]);
);
} else { } else {
$real_name = UOJLocale::get('user::real name'); $real_name = UOJLocale::get('user::real name');
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <div class="mb-3">
<label for="input-realname" class="form-label">$real_name</label> <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> <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 id="help-realname" class="form-text">只有管理员才能修改用户的真实姓名。</div>
</div> </div>
EOD); EOD);
} }
if (isTmpUser($user)) { if (isTmpUser($user)) {
if (isSuperUser(Auth::user())) { if (isSuperUser(Auth::user())) {
@ -92,12 +89,12 @@ EOD);
} else { } else {
$expiration_time = UOJLocale::get('user::expiration time'); $expiration_time = UOJLocale::get('user::expiration time');
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label> <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> <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 id="help-expiration_time" class="form-text">只有管理员才能修改用户的账号过期时间。</div>
</div> </div>
EOD); EOD);
} }
} else { } else {
$expiration_time = UOJLocale::get('user::expiration time'); $expiration_time = UOJLocale::get('user::expiration time');
@ -105,12 +102,12 @@ EOD);
? '只有用户组别为「临时用户」的用户才能被修改过期时间。' ? '只有用户组别为「临时用户」的用户才能被修改过期时间。'
: '只有管理员才能修改用户的账号过期时间。'; : '只有管理员才能修改用户的账号过期时间。';
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <div class="mb-3">
<label for="input-expiration_time" class="form-label">$expiration_time</label> <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> <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 id="help-expiration_time" class="form-text">$expiration_help_text</div>
</div> </div>
EOD); EOD);
} }
$update_profile_form->addCheckboxes('avatar_source', [ $update_profile_form->addCheckboxes('avatar_source', [
'div_class' => 'mb-3', 'div_class' => 'mb-3',
@ -125,81 +122,69 @@ EOD);
], ],
'help' => UOJLocale::get('change avatar help'), 'help' => UOJLocale::get('change avatar help'),
]); ]);
$update_profile_form->addInput( $update_profile_form->addInput('email', [
'email', 'div_class' => 'mb-3',
[ 'type' => 'email',
'div_class' => 'mb-3', 'label' => UOJLocale::get('email'),
'type' => 'email', 'default_value' => $user['email'] ?: '',
'label' => UOJLocale::get('email'), 'validator_php' => function ($email, &$vdata) {
'default_value' => $user['email'] ?: '', if ($email && !validateEmail($email)) {
'validator_php' => function ($email, &$vdata) { return 'Email 格式不合法。';
if ($email && !validateEmail($email)) { }
return 'Email 格式不合法。';
}
$vdata['email'] = $email; $vdata['email'] = $email;
return ''; return '';
}, },
] ]);
); $update_profile_form->addInput('qq', [
$update_profile_form->addInput( 'div_class' => 'mb-3',
'qq', 'label' => UOJLocale::get('QQ'),
[ 'default_value' => $user['qq'] == 0 ? '' : $user['qq'],
'div_class' => 'mb-3', 'validator_php' => function ($qq, &$vdata) {
'label' => UOJLocale::get('QQ'), if ($qq && !validateQQ($qq)) {
'default_value' => $user['qq'] == 0 ? '' : $user['qq'], return 'QQ 格式不合法。';
'validator_php' => function ($qq, &$vdata) { }
if ($qq && !validateQQ($qq)) {
return 'QQ 格式不合法。';
}
$vdata['qq'] = $qq; $vdata['qq'] = $qq;
return ''; return '';
}, },
] ]);
); $update_profile_form->addInput('github', [
$update_profile_form->addInput( 'div_class' => 'mb-3',
'github', 'label' => 'GitHub',
[ 'default_value' => $extra['social']['github'] ?: '',
'div_class' => 'mb-3', 'validator_php' => function ($github, &$vdata) {
'label' => 'GitHub', if ($github && !validateGitHubUsername($github)) {
'default_value' => $extra['social']['github'] ?: '', return 'GitHub 用户名不合法。';
'validator_php' => function ($github, &$vdata) { }
if ($github && !validateGitHubUsername($github)) {
return 'GitHub 用户名不合法。';
}
$vdata['github'] = $github; $vdata['github'] = $github;
return ''; return '';
}, },
] ]);
);
if (isSuperUser(Auth::user())) { if (isSuperUser(Auth::user())) {
$update_profile_form->addInput( $update_profile_form->addInput('school', [
'school', 'div_class' => 'mb-3',
[ 'label' => UOJLocale::get('school'),
'div_class' => 'mb-3', 'default_value' => $extra['school'] ?: '',
'label' => UOJLocale::get('school'), 'validator_php' => function ($school, &$vdata) {
'default_value' => $user['school'] ?: '', $vdata['school'] = $school;
'validator_php' => function ($school, &$vdata) {
$vdata['school'] = $school;
return ''; return '';
}, },
] ]);
);
} else { } else {
$school = UOJLocale::get('school'); $school = UOJLocale::get('school');
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <div class="mb-3">
<label for="input-school" class="form-label">$school</label> <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> <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 id="help-school" class="form-text">只有管理员才能修改用户所属学校。</div>
</div> </div>
EOD); EOD);
} }
$update_profile_form->addCheckboxes('sex', [ $update_profile_form->addCheckboxes('sex', [
'div_class' => 'mb-3', 'div_class' => 'mb-3',
@ -214,57 +199,102 @@ EOD);
'F' => UOJLocale::get('female'), 'F' => UOJLocale::get('female'),
], ],
]); ]);
$update_profile_form->addInput( $update_profile_form->addInput('motto', [
'motto', 'div_class' => 'mb-3',
[ 'label' => UOJLocale::get('motto'),
'div_class' => 'mb-3', 'default_value' => $user['motto'] ?: '',
'label' => UOJLocale::get('motto'), 'validator_php' => function ($motto, &$vdata) {
'default_value' => $user['motto'] ?: '', if (!validateMotto($motto)) {
'validator_php' => function ($motto, &$vdata) { return '格言格式不合法';
if (!validateMotto($motto)) { }
return '格言格式不合法';
}
$vdata['motto'] = $motto; $vdata['motto'] = $motto;
return ''; return '';
}, },
] ]);
); $update_profile_form->addInput('codeforces', [
$update_profile_form->addInput( 'div_class' => 'mb-3',
'codeforces', 'label' => UOJLocale::get('codeforces handle'),
[ 'default_value' => $extra['social']['codeforces'] ?: '',
'div_class' => 'mb-3', 'validator_php' => function ($codeforces, &$vdata) {
'label' => UOJLocale::get('codeforces handle'), if ($codeforces && !validateUsername($codeforces)) {
'default_value' => $extra['social']['codeforces'] ?: '', return 'Codeforces 用户名格式不合法。';
'validator_php' => function ($codeforces, &$vdata) { }
if ($codeforces && !validateUsername($codeforces)) {
return 'Codeforces 用户名格式不合法。';
}
$vdata['codeforces'] = $codeforces; $vdata['codeforces'] = $codeforces;
return ''; return '';
}, },
] ]);
); $update_profile_form->addInput('website', [
$update_profile_form->addInput( 'div_class' => 'mb-3',
'website', 'label' => UOJLocale::get('user::website'),
[ 'default_value' => $extra['social']['website'] ?: '',
'div_class' => 'mb-3', 'validator_php' => function ($url, &$vdata) {
'label' => UOJLocale::get('user::website'), if ($url && !validateURL($url)) {
'default_value' => $extra['social']['website'] ?: '', return '链接格式不合法。';
'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') { if ($user['usergroup'] == 'B') {
$update_profile_form->appendHTML(<<<EOD $update_profile_form->appendHTML(<<<EOD
<div class="mb-3"> <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 = [ $data = [
'email' => $vdata['email'], 'email' => $vdata['email'],
'qq' => $vdata['qq'], 'qq' => $vdata['qq'],
@ -312,7 +342,6 @@ EOD);
if (isSuperUser(Auth::user())) { if (isSuperUser(Auth::user())) {
$data['realname'] = $vdata['realname']; $data['realname'] = $vdata['realname'];
$data['school'] = $vdata['school'];
if (isTmpUser($user)) { if (isTmpUser($user)) {
$data['expiration_time'] = $vdata['expiration_time']->format(UOJTime::FORMAT); $data['expiration_time'] = $vdata['expiration_time']->format(UOJTime::FORMAT);
@ -325,22 +354,28 @@ EOD);
"where", ["username" => $user['username']] "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([ DB::update([
"update user_info", "update user_info",
"set", [ "set", [
'extra' => DB::json_set( 'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
'extra',
'$.avatar_source',
$_POST['avatar_source'],
'$.social.github',
$vdata['github'],
'$.social.codeforces',
$vdata['codeforces'],
'$.social.website',
$vdata['website'],
'$.username_color',
$_POST['username_color']
),
], ],
"where", ["username" => $user['username']] "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']['class'] = 'btn btn-secondary';
$update_profile_form->config['submit_button']['text'] = '更新'; $update_profile_form->config['submit_button']['text'] = '更新';
$update_profile_form->setAjaxSubmit(<<<EOD $update_profile_form->setAjaxSubmit(<<<EOD
function(res) { function(res) {
if (res.status === 'success') { if (res.status === 'success') {
$('#result-alert') $('#result-alert')
.html('个人信息修改成功!') .html('个人信息修改成功!')
.addClass('alert-success') .addClass('alert-success')
.removeClass('alert-danger') .removeClass('alert-danger')
.show(); .show();
} else { } else {
$('#result-alert') $('#result-alert')
.html('个人信息修改失败。' + (res.message || '')) .html('个人信息修改失败。' + (res.message || ''))
.removeClass('alert-success') .removeClass('alert-success')
.addClass('alert-danger') .addClass('alert-danger')
.show(); .show();
} }
$(window).scrollTop(0); $(window).scrollTop(0);
} }
EOD); EOD);
$update_profile_form->runAtServer(); $update_profile_form->runAtServer();
} elseif ($cur_tab == 'password') { } elseif ($cur_tab == 'password') {
if (isset($_POST['submit-change_password']) && $_POST['submit-change_password'] == 'change_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) { function isTmpUser($user) {
return $user != null && $user['usergroup'] == 'T'; return $user != null && $user['usergroup'] == 'T';
} }
function isBannedUser($user) {
return $user != null && $user['usergroup'] == 'B';
}
function getProblemExtraConfig($problem) { function getProblemExtraConfig($problem) {
$extra_config = json_decode($problem['extra_config'], true); $extra_config = json_decode($problem['extra_config'], true);
@ -291,3 +294,12 @@ function getAbsoluteUrl($relativeUrl, $baseUrl) {
// return absolute URL // return absolute URL
return $scheme . '://' . $abs; 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]; return ['error' => '', 'store' => $comment];
} }
function validateNothing($x) {
return '';
}
function is_short_string($str) { function is_short_string($str) {
return is_string($str) && strlen($str) <= 256; return is_string($str) && strlen($str) <= 256;
} }

View File

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

View File

@ -50,18 +50,25 @@ class UOJUser {
} }
public static function register($user, $cfg = []) { public static function register($user, $cfg = []) {
$cfg += [
'extra' => [],
];
UOJUser::checkBasicInfo($user, $cfg); UOJUser::checkBasicInfo($user, $cfg);
$password = getPasswordToStore($user['password'], $user['username']); $password = getPasswordToStore($user['password'], $user['username']);
$extra = [
'school' => $user['school'] ?: null,
] + $cfg['extra'];
$info = [ $info = [
'username' => $user['username'], 'username' => $user['username'],
'realname' => $user['realname'] ?: '',
'email' => $user['email'], 'email' => $user['email'],
'school' => $user['school'] ?: '',
'password' => $password, 'password' => $password,
'svn_password' => uojRandString(20), 'svn_password' => uojRandString(20),
'register_time' => DB::now(), 'register_time' => DB::now(),
'extra' => '{}' 'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
]; ];
// 0 means non-existence, false means DB error. // 0 means non-existence, false means DB error.
if (DB::selectExists("select 1 from user_info") === 0) { if (DB::selectExists("select 1 from user_info") === 0) {
@ -77,10 +84,21 @@ class UOJUser {
} }
public static function registerTmpAccount($user, $cfg = []) { public static function registerTmpAccount($user, $cfg = []) {
$cfg += [
'extra' => [],
'check_email' => false,
'type' => 'student',
];
UOJUser::checkBasicInfo($user, $cfg); UOJUser::checkBasicInfo($user, $cfg);
if (!isset($user['expiration_time'])) {
throw new UOJInvalidArgumentException('无效账号过期时间');
}
$password = getPasswordToStore($user['password'], $user['username']); $password = getPasswordToStore($user['password'], $user['username']);
$extra = [ $extra = [
'school' => $user['school'] ?: null,
'permissions' => [ 'permissions' => [
'problems' => [ 'problems' => [
'view' => false, 'view' => false,
@ -115,18 +133,19 @@ class UOJUser {
'upload_image' => false, 'upload_image' => false,
], ],
], ],
]; ] + $cfg['extra'];
$info = [ $info = [
'username' => $user['username'], 'username' => $user['username'],
'usergroup' => 'T', 'usergroup' => 'T',
'usertype' => $cfg['type'],
'realname' => $user['realname'] ?: '',
'email' => $user['email'], 'email' => $user['email'],
'school' => $user['school'] ?: '',
'password' => $password, 'password' => $password,
'svn_password' => uojRandString(20), 'svn_password' => uojRandString(20),
'register_time' => DB::now(), 'register_time' => DB::now(),
'expiration_time' => $user['expiration_time'], 'expiration_time' => $user['expiration_time'],
'extra' => json_encode($extra), 'extra' => json_encode($extra, JSON_UNESCAPED_UNICODE),
]; ];
DB::insert([ DB::insert([
@ -139,32 +158,27 @@ class UOJUser {
} }
public static function registerTmpACMTeamAccount($team, $cfg = []) { public static function registerTmpACMTeamAccount($team, $cfg = []) {
$cfg += [
'check_email' => false,
'extra' => [],
'type' => 'team',
];
UOJUser::checkBasicInfo($team, $cfg); UOJUser::checkBasicInfo($team, $cfg);
if (!isset($team['expiration_time'])) { if (!isset($team['expiration_time'])) {
throw new UOJInvalidArgumentException('无效账号过期时间'); throw new UOJInvalidArgumentException('无效账号过期时间');
} }
$password = getPasswordToStore($team['password'], $team['username']); $cfg['extra'] += [
$team['extra'] = json_encode([
'acm' => [ 'acm' => [
'contest_name' => $team['contest_name'], 'contest_name' => $team['contest_name'],
'team_name' => $team['team_name'], 'team_name' => $team['team_name'],
'members' => $team['members'] 'members' => $team['members'],
] ],
], JSON_UNESCAPED_UNICODE); ];
DB::insert([ return UOJUser::registerTmpAccount($team, $cfg);
"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;
} }
public static function registerTmpACMTeamAccountFromText($text, $contest_name, $expiration_time) { public static function registerTmpACMTeamAccountFromText($text, $contest_name, $expiration_time) {
@ -194,12 +208,47 @@ class UOJUser {
'contest_name' => $contest_name, 'contest_name' => $contest_name,
'expiration_time' => $expiration_time, 'expiration_time' => $expiration_time,
'team_name' => $fields[3], 'team_name' => $fields[3],
'members' => $mem 'members' => $mem,
]; ];
return UOJUser::registerTmpACMTeamAccount($team); 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) { public static function getAccountStatus($user) {
if ($user['usergroup'] == 'B') { if ($user['usergroup'] == 'B') {
return 'banned'; 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); $extra = json_decode($user['extra'], true);
if ($extra === null) { if ($extra === null) {
$extra = []; $extra = [];
@ -341,7 +390,12 @@ class UOJUser {
'avatar_source' => 'gravatar', 'avatar_source' => 'gravatar',
'username_color' => isSuperUser($user) ? '#9d3dcf' : '#0d6efd', '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) { 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 `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 ''; MODIFY `motto` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
ALTER TABLE `user_info` ALTER TABLE `user_info`
CHANGE `last_login` `last_login_time` datetime DEFAULT CURRENT_TIMESTAMP, CHANGE `last_login` `last_login_time` datetime DEFAULT NULL,
CHANGE `last_visited` `last_visit_time` datetime DEFAULT CURRENT_TIMESTAMP; CHANGE `last_visited` `last_visit_time` datetime DEFAULT NULL;
ALTER TABLE `user_info` ALTER TABLE `user_info`
ADD `expiration_time` datetime DEFAULT NULL AFTER `last_visit_time`, ADD `expiration_time` datetime DEFAULT NULL AFTER `last_visit_time`,
ADD `extra` json NOT NULL; 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> </span>
<?php endif ?> <?php endif ?>
</li> </li>
<?php if ($user['school']) : ?> <?php if ($extra['school']) : ?>
<li class="list-group-item"> <li class="list-group-item">
<i class="bi bi-person-badge-fill me-1"></i> <i class="bi bi-person-badge-fill me-1"></i>
<?= $user['school'] ?> <?= $extra['school'] ?>
</li> </li>
<?php endif ?> <?php endif ?>
<?php if ($user['email']) : ?> <?php if ($user['email']) : ?>

View File

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