From 098e488fe3b3549f86b88d4db81e6bf3f9dd1873 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Wed, 15 Feb 2023 16:46:47 +0800 Subject: [PATCH] feat: acm team account --- db/app_uoj233.sql | 7 +- web/app/controllers/super_manage.php | 65 +++- web/app/controllers/user_info_edit.php | 391 +++++++++++---------- web/app/libs/uoj-utility-lib.php | 12 + web/app/libs/uoj-validate-lib.php | 4 + web/app/models/UOJForm.php | 3 +- web/app/models/UOJUser.php | 102 ++++-- web/app/upgrade/14_sync_from_uoj.ac/up.sql | 4 +- web/app/views/user-info.php | 4 +- web/css/uoj-bs5.css | 10 + 10 files changed, 390 insertions(+), 212 deletions(-) diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index 16bbb20..0f2dfcc 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -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 '', diff --git a/web/app/controllers/super_manage.php b/web/app/controllers/super_manage.php index f4a7d2e..d5c8112 100644 --- a/web/app/controllers/super_manage.php +++ b/web/app/controllers/super_manage.php @@ -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' => '一行一个队伍,格式形如 ACM233_team23,123456,ACM233_team23@uoj.ac,Mike代表队,2,李麦克,麦麦大学,张麦克,克克大学,依次表示用户名、密码、邮箱、队伍名、队伍人数、每个队员的姓名和单位信息。', + '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') { + @@ -1385,7 +1430,7 @@ if ($cur_tab == 'index') { function ($row) { echo ''; echo '', UOJUser::getLink($row), ''; - echo '', HTML::escape($row['school']), ''; + echo '', HTML::escape(UOJUser::getExtra($row, 'school')), ''; echo ''; switch ($row['usergroup']) { case 'S': @@ -1451,6 +1496,24 @@ if ($cur_tab == 'index') { +
+
+
+ printHTML() ?> +
+
+
注意事项
+
    +
  • 此处创建的用户只能在 ACM 比赛中使用,如需创建其他用户,请查看其他选项卡。
  • +
  • 用户名推荐格式为 ACM + 比赛 ID + _team + 队伍编号,如用于比赛 233 的第 23 号队伍可以设置为 ACM233_team23
  • +
  • 请提醒用户及时修改初始密码。请勿设置过于简单的初始密码。
  • +
  • 我们推荐在创建账号时输入号主的电子邮件地址以便后期发生忘记密码等情况时进行验证,如果不设置电子邮件地址,将该列留空即可。
  • +
  • 临时账号不具有任何权限,只能查看、参加已经用户报名了的比赛。创建账号后可以在「修改个人信息」页面中的「特权」选项卡为用户分配权限。特别地,如果该用户是外校学生,那么您可能需要禁用其 所有权限,并为其手动报名比赛。
  • +
  • 注意分隔符为英文逗号。解析时调用的是 PHP 的 str_getcsv 函数,遇到特殊字符请遵照 CSV 格式进行编码。推荐在 Excel 中导出 CSV 格式的文件后再将内容复制到此处。
  • +
+
+
+
diff --git a/web/app/controllers/user_info_edit.php b/web/app/controllers/user_info_edit.php index fd3526f..99aed0a 100644 --- a/web/app/controllers/user_info_edit.php +++ b/web/app/controllers/user_info_edit.php @@ -40,35 +40,32 @@ if ($cur_tab == 'profile') { $username = UOJLocale::get('username'); $avatar = UOJLocale::get('avatar'); $update_profile_form->appendHTML(<< - - -
用户名不能被修改。
-
-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; +
+ + +
用户名不能被修改。
+
+ 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); +
+ + +
只有管理员才能修改用户的真实姓名。
+
+ 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); +
+ + +
只有管理员才能修改用户的账号过期时间。
+
+ EOD); } } else { $expiration_time = UOJLocale::get('user::expiration time'); @@ -105,12 +102,12 @@ EOD); ? '只有用户组别为「临时用户」的用户才能被修改过期时间。' : '只有管理员才能修改用户的账号过期时间。'; $update_profile_form->appendHTML(<< - - -
$expiration_help_text
- -EOD); +
+ + +
$expiration_help_text
+
+ 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); +
+ + +
只有管理员才能修改用户所属学校。
+
+ 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(<< + + +
只有超级用户才能修改 ACM 队伍名称。
+ + EOD); + $update_profile_form->appendHTML(<< + + +
只有超级用户才能修改 ACM 队伍信息。
+ + EOD); + } + } - return ''; - }, - ] - ); if ($user['usergroup'] == 'B') { $update_profile_form->appendHTML(<< @@ -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(<<runAtServer(); } elseif ($cur_tab == 'password') { if (isset($_POST['submit-change_password']) && $_POST['submit-change_password'] == 'change_password') { diff --git a/web/app/libs/uoj-utility-lib.php b/web/app/libs/uoj-utility-lib.php index ed0aa05..480bc9e 100644 --- a/web/app/libs/uoj-utility-lib.php +++ b/web/app/libs/uoj-utility-lib.php @@ -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); +} diff --git a/web/app/libs/uoj-validate-lib.php b/web/app/libs/uoj-validate-lib.php index d83ab17..73f4173 100644 --- a/web/app/libs/uoj-validate-lib.php +++ b/web/app/libs/uoj-validate-lib.php @@ -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; } diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index 98043d1..68e02a6 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -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']) { diff --git a/web/app/models/UOJUser.php b/web/app/models/UOJUser.php index aa09029..782ec8a 100644 --- a/web/app/models/UOJUser.php +++ b/web/app/models/UOJUser.php @@ -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) { diff --git a/web/app/upgrade/14_sync_from_uoj.ac/up.sql b/web/app/upgrade/14_sync_from_uoj.ac/up.sql index 9a972a4..8d69501 100644 --- a/web/app/upgrade/14_sync_from_uoj.ac/up.sql +++ b/web/app/upgrade/14_sync_from_uoj.ac/up.sql @@ -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; diff --git a/web/app/views/user-info.php b/web/app/views/user-info.php index 61af02b..aba3571 100644 --- a/web/app/views/user-info.php +++ b/web/app/views/user-info.php @@ -38,10 +38,10 @@ - +
  • - +
  • diff --git a/web/css/uoj-bs5.css b/web/css/uoj-bs5.css index b89020c..c65c49e 100644 --- a/web/css/uoj-bs5.css +++ b/web/css/uoj-bs5.css @@ -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; +}