diff --git a/.config.php b/.config.php new file mode 100644 index 0000000..74a55c9 --- /dev/null +++ b/.config.php @@ -0,0 +1,45 @@ + [ + 'oj-name' => '石家庄二中信息学在线评测系统', + 'oj-name-short' => 'S2OJ', + 'administrator' => 'root', + 'admin-email' => 'admin@sjzezoj.com', + 'QQ-group' => '', + 'ICP-license' => '冀ICP备2020028886号', + ], + 'database' => [ + 'database' => 'app_uoj233', + 'username' => 'root', + 'password' => 'root', + 'host' => 'uoj-db', + 'port' => '3306', + ], + 'security' => [ + 'user' => [ + 'client_salt' => 'salt_0', + ], + 'cookie' => [ + 'checksum_salt' => ['salt_1', 'salt_2', 'salt_3'], + ], + ], + 'mail' => [ + 'noreply' => [ + 'username' => 'noreply@local_uoj.ac', + 'password' => '_mail_noreply_password_', + 'host' => 'smtp.local_uoj.ac', + 'secure' => 'tls', + 'port' => 587, + ] + ], + 'judger' => [ + 'socket' => [ + 'port' => '2333', + 'password' => '_judger_socket_password_' + ], + ], + 'switch' => [ + 'blog-domain-mode' => 3, + 'open-register' => false, + ], +]; diff --git a/.gitignore b/.gitignore index 8264c45..e4d53e4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ uoj_data_1/ uoj_data_2/ .php-cs-fixer.cache docker-compose.local.yml +.config.php +.config.development.php +.config.local.php diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index 9fb0e3a..12b362a 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -979,6 +979,17 @@ CREATE TABLE `upgrades` ( LOCK TABLES `upgrades` WRITE; /*!40000 ALTER TABLE `upgrades` DISABLE KEYS */; +INSERT INTO `upgrades` (`name`, `status`, `updated_at`) VALUES + ('3_parsedown', 'up', now()), + ('4_image_hosting', 'up', now()), + ('6_user_info_v2', 'up', now()), + ('8_group_v2', 'up', now()), + ('9_list_v2', 'up', now()), + ('14_sync_from_uoj.ac', 'up', now()), + ('16_list_v3', 'up', now()), + ('18_user_permissions', 'up', now()), + ('20_problem_difficulty', 'up', now()), + ('21_problem_difficulty', 'up', now()); /*!40000 ALTER TABLE `upgrades` ENABLE KEYS */; UNLOCK TABLES; diff --git a/docker-compose.development.yml b/docker-compose.development.yml index 90fb54b..31daa8d 100644 --- a/docker-compose.development.yml +++ b/docker-compose.development.yml @@ -60,15 +60,6 @@ services: volumes: - ./uoj_data/web/data:/var/uoj_data - ./uoj_data/web/storage:/opt/uoj/web/app/storage + - ./.config.development.php:/opt/uoj/web/app/.config.php ports: - "80:80" - environment: - - UOJ_PROTOCOL=http - - DATABASE_HOST=uoj-db - - DATABASE_PASSWORD=root - - JUDGER_SOCKET_PORT=2333 - - JUDGER_SOCKET_PASSWORD=_judger_socket_password_ - - SALT_0=salt_0 - - SALT_1=salt_1 - - SALT_2=salt_2 - - SALT_3=salt_3 diff --git a/docker-compose.yml b/docker-compose.yml index f96fde1..1da0339 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,15 +43,6 @@ services: volumes: - ./uoj_data/web/data:/var/uoj_data - ./uoj_data/web/storage:/opt/uoj/web/app/storage + - ./.config.php:/opt/uoj/web/app/.config.php ports: - "80:80" - environment: - - UOJ_PROTOCOL=https - - DATABASE_HOST=uoj-db - - DATABASE_PASSWORD=root - - JUDGER_SOCKET_PORT=2333 - - JUDGER_SOCKET_PASSWORD=_judger_socket_password_ - - SALT_0=salt_0 - - SALT_1=salt_1 - - SALT_2=salt_2 - - SALT_3=salt_3 diff --git a/web/app/.default-config.php b/web/app/.default-config.php index dc84dbf..fab8e0e 100644 --- a/web/app/.default-config.php +++ b/web/app/.default-config.php @@ -20,21 +20,21 @@ return [ 'domain' => null, 'main' => [ 'protocol' => 'http', - 'host' => '_httpHost_', - 'port' => '80/443' + 'host' => UOJContext::requestDomain(), + 'port' => '80/443', ], 'blog' => [ 'protocol' => 'http', - 'host' => '_httpHost_', - 'port' => '80/443' + 'host' => UOJContext::requestDomain(), + 'port' => '80/443', ] ], 'security' => [ 'user' => [ - 'client_salt' => 'salt0' + 'client_salt' => 'salt0', ], 'cookie' => [ - 'checksum_salt' => ['salt1', 'salt2', 'salt3'] + 'checksum_salt' => ['salt1', 'salt2', 'salt3'], ], ], 'mail' => [ @@ -43,17 +43,17 @@ return [ 'password' => '_mail_noreply_password_', 'host' => 'smtp.local_uoj.ac', 'secure' => 'tls', - 'port' => 587 + 'port' => 587, ] ], 'judger' => [ 'socket' => [ 'port' => '233', - 'password' => '_judger_socket_password_' + 'password' => '_judger_socket_password_', ] ], 'switch' => [ 'blog-domain-mode' => 3, - 'open-register' => false - ] + 'open-register' => false, + ], ]; diff --git a/web/app/controllers/add_contest.php b/web/app/controllers/add_contest.php index ae5b6a7..64a2a2f 100644 --- a/web/app/controllers/add_contest.php +++ b/web/app/controllers/add_contest.php @@ -79,19 +79,20 @@ $time_form->succ_href = "/contests"; $time_form->runAtServer(); ?> - +
-

添加比赛

+

+ +

- printHTML(); ?> + printHTML() ?>
-
@@ -101,6 +102,7 @@ $time_form->runAtServer(); +
diff --git a/web/app/controllers/app/html2markdown.php b/web/app/controllers/app/html2markdown.php new file mode 100644 index 0000000..5cb76d1 --- /dev/null +++ b/web/app/controllers/app/html2markdown.php @@ -0,0 +1,105 @@ + + + + +

+ +

+ + + +
+
+
+
+ +
+
+ +
+
+
+ +
+ + + + + + + diff --git a/web/app/controllers/image_hosting/get_image.php b/web/app/controllers/app/image_hosting/get_image.php similarity index 100% rename from web/app/controllers/image_hosting/get_image.php rename to web/app/controllers/app/image_hosting/get_image.php diff --git a/web/app/controllers/image_hosting/index.php b/web/app/controllers/app/image_hosting/index.php similarity index 99% rename from web/app/controllers/image_hosting/index.php rename to web/app/controllers/app/image_hosting/index.php index 759e9fc..c6c60dd 100644 --- a/web/app/controllers/image_hosting/index.php +++ b/web/app/controllers/app/image_hosting/index.php @@ -466,6 +466,10 @@ $pag = new Paginator($pag_config); diff --git a/web/app/controllers/group.php b/web/app/controllers/group.php index 8bde983..82e4eed 100644 --- a/web/app/controllers/group.php +++ b/web/app/controllers/group.php @@ -49,7 +49,7 @@ UOJGroup::cur()->userCanView(Auth::user(), ['ensure' => true]);
- purify(HTML::parsedown()->line(UOJGroup::info('announcement'))) ?> + purify(HTML::parsedown(['username_with_color' => true])->line(UOJGroup::info('announcement'))) ?>
diff --git a/web/app/controllers/html2markdown.php b/web/app/controllers/html2markdown.php deleted file mode 100644 index 4206260..0000000 --- a/web/app/controllers/html2markdown.php +++ /dev/null @@ -1,46 +0,0 @@ - - - - -

- -

- - - -
-
-
-
- -
-
- -
-
-
- -
- - - - - - diff --git a/web/app/controllers/list_manage.php b/web/app/controllers/list_manage.php index 757c722..c158934 100644 --- a/web/app/controllers/list_manage.php +++ b/web/app/controllers/list_manage.php @@ -318,7 +318,7 @@ EOD); echo HTML::tag_begin('tr'); echo HTML::tag('td', ['class' => 'text-center'], $problem->info['id']); echo HTML::tag_begin('td'); - echo $problem->getLink(); + echo $problem->getLink(['with' => 'none']); if ($problem->info['is_hidden']) { echo ' ', UOJLocale::get('hidden'), ' '; } diff --git a/web/app/controllers/lists.php b/web/app/controllers/lists.php index 2e81ef3..a24c626 100644 --- a/web/app/controllers/lists.php +++ b/web/app/controllers/lists.php @@ -9,16 +9,28 @@ Auth::check() || redirectToLogin(); UOJUser::checkPermission(Auth::user(), 'lists.view') || UOJResponse::page403(); if (UOJList::userCanCreateList(Auth::user())) { - $new_list_form = new UOJBs4Form('new_list'); + $new_list_form = new UOJForm('new_list'); $new_list_form->handle = function () { - DB::insert("insert into lists (title, is_hidden) values ('未命名题单', 1)"); - $id = DB::insert_id(); - DB::insert("insert into lists_contents (id, content, content_md) values ($id, '', '')"); + DB::insert([ + "insert into lists", + DB::bracketed_fields(['title', 'is_hidden']), + "values", + DB::tuple(['未命名题单', 1]), + ]); + $list_id = DB::insert_id(); + DB::insert([ + "insert into lists_contents", + DB::bracketed_fields(['id', 'content', 'content_md']), + "values", + DB::tuple([$list_id, '', '']), + ]); + redirectTo("/list/{$list_id}"); + die(); }; - $new_list_form->submit_button_config['align'] = 'right'; - $new_list_form->submit_button_config['class_str'] = 'btn btn-primary'; - $new_list_form->submit_button_config['text'] = UOJLocale::get('problems::add new list'); - $new_list_form->submit_button_config['smart_confirm'] = ''; + $new_list_form->config['submit_container']['class'] = 'text-end'; + $new_list_form->config['submit_button']['class'] = 'btn btn-primary'; + $new_list_form->config['submit_button']['text'] = UOJLocale::get('problems::add new list'); + $new_list_form->config['confirm']['smart'] = true; $new_list_form->runAtServer(); } diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php index 8ce857b..40757b3 100644 --- a/web/app/controllers/problem.php +++ b/web/app/controllers/problem.php @@ -100,7 +100,7 @@ if (UOJContest::cur()) { $submission_requirement = UOJProblem::cur()->getSubmissionRequirement(); $custom_test_requirement = UOJProblem::cur()->getCustomTestRequirement(); -$custom_test_enabled = false; // $custom_test_requirement && $pre_submit_check_ret === true; +$custom_test_enabled = $custom_test_requirement && $pre_submit_check_ret === true; function handleUpload($zip_file_name, $content, $tot_size) { global $is_participating; @@ -193,7 +193,7 @@ if ($pre_submit_check_ret === true && !$no_more_submission) { 'FS::randomAvailableSubmissionFileName', 'handleUpload' ); - $zip_answer_form->extra_validators[] = $submission_extra_validator; + $zip_answer_form->extra_validator = $submission_extra_validator; $zip_answer_form->succ_href = $is_participating ? '/contest/' . UOJContest::info('id') . '/submissions' : '/submissions'; $zip_answer_form->runAtServer(); } @@ -385,46 +385,64 @@ if (UOJContest::cur()) {
diff --git a/web/app/controllers/problem_set.php b/web/app/controllers/problem_set.php index 0edda88..47623eb 100644 --- a/web/app/controllers/problem_set.php +++ b/web/app/controllers/problem_set.php @@ -73,6 +73,9 @@ EOD; ]) ]); dataNewProblem($id); + + redirectTo("/problem/{$id}/manage/statement"); + die(); }; $new_problem_form->submit_button_config['align'] = 'right'; $new_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; diff --git a/web/app/controllers/problem_solutions.php b/web/app/controllers/problem_solutions.php index 8889288..143c6f0 100644 --- a/web/app/controllers/problem_solutions.php +++ b/web/app/controllers/problem_solutions.php @@ -101,6 +101,29 @@ if (UOJProblem::cur()->userCanManage(Auth::user()) || UOJProblem::cur()->userPer ]); }; $add_new_solution_form->runAtServer(); + + if (UOJUser::checkPermission(Auth::user(), 'blogs.create')) { + $quick_add_new_solution_form = new UOJForm('quick_add_new_solution'); + $quick_add_new_solution_form->config['submit_container']['class'] = ''; + $quick_add_new_solution_form->config['submit_button']['class'] = 'btn btn-link text-decoration-none p-0'; + $quick_add_new_solution_form->config['submit_button']['text'] = '快速新建文章'; + $quick_add_new_solution_form->handle = function () { + DB::insert([ + "insert into blogs", + "(title, content, content_md, poster, is_hidden, post_time, active_time)", + "values", DB::tuple([ + '【题解】' . UOJProblem::cur()->getTitle(), '', '', + Auth::id(), false, DB::now(), DB::now() + ]) + ]); + + $blog_id = DB::insert_id(); + + redirectTo(HTML::blog_url(Auth::id(), "/post/{$blog_id}/write")); + die(); + }; + $quick_add_new_solution_form->runAtServer(); + } } $pag_config = [ @@ -262,18 +285,16 @@ $pag = new Paginator($pag_config); 您当前无法为本题新增题解。
- + + + - - + - + diff --git a/web/app/controllers/problem_statement_manage.php b/web/app/controllers/problem_statement_manage.php index 7726722..3aa2e63 100644 --- a/web/app/controllers/problem_statement_manage.php +++ b/web/app/controllers/problem_statement_manage.php @@ -174,6 +174,85 @@ $difficulty_form->runAtServer(); +
+
+ 标签填充 +
+
+ + +
+ $tags) : ?> + + +
+ +
+ + + +
+
+ +
+
题目难度 @@ -183,7 +262,6 @@ $difficulty_form->runAtServer();
- diff --git a/web/app/controllers/reset_pw.php b/web/app/controllers/reset_pw.php index 59080fe..0331f96 100644 --- a/web/app/controllers/reset_pw.php +++ b/web/app/controllers/reset_pw.php @@ -1,39 +1,55 @@ add(new DateInterval('P3D')) < UOJTime::$time_now) { + becomeMsgPage('链接已过期'); +} + +function resetPassword() { + global $user; + + if (!isset($_POST['newPW']) || !validatePassword($_POST['newPW'])) { + return '操作失败,无效密码'; } - function resetPassword() { - list($username, $check_code) = explode('.', base64url_decode($_GET['p'])); - if (!isset($_POST['newPW']) || !validatePassword($_POST['newPW'])) { - return '操作失败,无效密码'; - } - if (!isset($username) || !validateUsername($username)) { - return '不明错误'; - } - if (!isset($check_code)) { - return '不明错误'; - } - - $newPW = $_POST['newPW']; - $user = UOJUser::query($username); - if ($user == null) { - return '不明错误'; - } - if ($check_code !== md5($user['username'] . '+' . $user['password'])) { - return '不明错误'; - } - $newPW = getPasswordToStore($newPW, $user['username']); - DB::update("update user_info set password = '$newPW' where username = '{$user['username']}'"); - return 'ok'; - } - if (isset($_POST['reset'])) { - die(resetPassword()); - } - ?> + + $newPW = $_POST['newPW']; + $newPW = getPasswordToStore($newPW, $user['username']); + + DB::update([ + "update user_info", + "set", [ + "password" => $newPW, + "extra" => DB::json_remove('extra', '$.reset_password_check_code', '$.reset_password_time'), + ], + "where", [ + "username" => $user['username'], + ], + ]); + + return 'ok'; +} +if (isset($_POST['reset'])) { + die(resetPassword()); +} +?> +$REQUIRE_LIB['dialog'] = ''; +$REQUIRE_LIB['md5'] = ''; +?>
@@ -44,60 +60,60 @@ - -
-
-
-
+
+
+ +
+
diff --git a/web/app/controllers/subdomain/blog/blog.php b/web/app/controllers/subdomain/blog/blog.php index 5150047..9399add 100644 --- a/web/app/controllers/subdomain/blog/blog.php +++ b/web/app/controllers/subdomain/blog/blog.php @@ -201,7 +201,9 @@ $comments_pag = new Paginator([ "order by id" ]); foreach ($replies as $idx => $reply) { - $replies[$idx]['poster_realname'] = UOJUser::query($reply['poster'])['realname']; + $reply_user = UOJUser::query($reply['poster']); + $replies[$idx]['poster_realname'] = $reply_user['realname']; + $replies[$idx]['poster_username_color'] = UOJUser::getUserColor($reply_user); $replies[$idx]['content'] = getCommentContentToDisplay($reply); } $replies_json = json_encode($replies); diff --git a/web/app/controllers/subdomain/blog/self_reviews.php b/web/app/controllers/subdomain/blog/self_reviews.php index 894a394..60f6652 100644 --- a/web/app/controllers/subdomain/blog/self_reviews.php +++ b/web/app/controllers/subdomain/blog/self_reviews.php @@ -32,7 +32,7 @@ $header_row .= '' . UOJLocale::get('contests::problem sel $header_row .= '' . UOJLocale::get('contests::contest self review') . ''; $header_row .= ''; -$parsedown = HTML::parsedown(); +$parsedown = HTML::parsedown(['username_with_color' => true]); $purifier = HTML::purifier_inline(); $print_row = function ($row) use ($parsedown, $purifier) { diff --git a/web/app/controllers/submission.php b/web/app/controllers/submission.php index 7385144..5f6dcdd 100644 --- a/web/app/controllers/submission.php +++ b/web/app/controllers/submission.php @@ -168,7 +168,9 @@ if ($perm['manager_view']) { ?> - echoContent() ?> +
+ echoContent() ?> +

@@ -221,17 +223,17 @@ if (UOJSubmission::cur()->hasJudged()) { ?>

- - printHTML() ?> - + + printHTML() ?> + - - printHTML() ?> - + + printHTML() ?> + - - printHTML() ?> - + + printHTML() ?> +
diff --git a/web/app/controllers/super_manage.php b/web/app/controllers/super_manage.php index 113fb1c..c5a61e7 100644 --- a/web/app/controllers/super_manage.php +++ b/web/app/controllers/super_manage.php @@ -516,7 +516,7 @@ EOD); 'new_tmp_expiration_time', 'text', '过期时间', - (new DateTime())->add(new DateInterval('P7D'))->format('Y-m-d H:i:s'), + UOJTime::time2str((new DateTime())->add(new DateInterval('P7D'))->setTime(0, 0, 0)), function ($str, &$vdata) { try { $vdata['expiration_time'] = new DateTime($str); @@ -657,6 +657,7 @@ EOD); ); $change_usergroup_form->addVSelect('op_type', [ 'banneduser' => '设为封禁用户', + 'tempuser' => '设为临时用户', 'normaluser' => '设为普通用户', 'superuser' => '设为超级用户', ], '操作类型', ''); @@ -666,15 +667,59 @@ EOD); switch ($_POST['op_type']) { case 'banneduser': - DB::update("update user_info set usergroup = 'B', usertype = 'banned' where username = '{$username}'"); - $usergroup = '被封禁的用户'; + DB::update([ + "update user_info", + "set", [ + "usergroup" => "B", + "usertype" => "banned", + "expiration_time" => null, + ], + "where", [ + "username" => $username, + ], + ]); + $usergroup = '被封禁的用户,该用户将无法再次登录系统'; + break; + case 'tempuser': + DB::update([ + "update user_info", + "set", [ + "usergroup" => "T", + "usertype" => "student", + "expiration_time" => DB::now(), + ], + "where", [ + "username" => $username, + ], + ]); + $usergroup = '临时用户,请前往个人信息编辑页面修改过期时间'; break; case 'normaluser': - DB::update("update user_info set usergroup = 'U', usertype = 'student' where username = '{$username}'"); + DB::update([ + "update user_info", + "set", [ + "usergroup" => "U", + "usertype" => "student", + "expiration_time" => null, + ], + "where", [ + "username" => $username, + ], + ]); $usergroup = '普通用户'; break; case 'superuser': - DB::update("update user_info set usergroup = 'S', usertype = 'student' where username = '{$username}'"); + DB::update([ + "update user_info", + "set", [ + "usergroup" => "S", + "usertype" => "student", + "expiration_time" => null, + ], + "where", [ + "username" => $username, + ], + ]); $usergroup = '超级用户'; break; } @@ -1336,7 +1381,7 @@ EOD); EOD, function ($row) { echo ''; - echo '', '', $row['username'], '', ''; + echo '', UOJUser::getLink($row), ''; echo '', HTML::escape($row['school']), ''; echo ''; switch ($row['usergroup']) { @@ -1429,7 +1474,8 @@ EOD);
注意事项
diff --git a/web/app/controllers/user_info_edit.php b/web/app/controllers/user_info_edit.php index 6301c87..189862a 100644 --- a/web/app/controllers/user_info_edit.php +++ b/web/app/controllers/user_info_edit.php @@ -266,6 +266,43 @@ EOD); }, ] ); + if ($user['usergroup'] == 'B') { + $update_profile_form->appendHTML(<< + + +
被封禁的用户无法修改用户名颜色。
+ + EOD); + } else if ($user['usergroup'] == 'T') { + $update_profile_form->appendHTML(<< + + +
临时用户无法修改用户名颜色。
+ + EOD); + } else { + $additional_colors = []; + + if (isSuperUser($user)) { + $additional_colors['#9d3dcf'] = '紫色 - #9d3dcf'; + } + + $update_profile_form->addSelect('username_color', [ + 'div_class' => 'mb-3', + 'label' => '用户名颜色', + 'default_value' => $extra['username_color'], + 'options' => $additional_colors + [ + '#0d6efd' => '蓝色 - #0d6efd', + '#2da44e' => '绿色 - #2da44e', + '#e85aad' => '粉色 - #e85aad', + '#f32a38' => '红色 - #f32a38', + '#f57c00' => '橙色 - #f57c00', + '#00acc1' => '青色 - #00acc1', + ], + ]); + } $update_profile_form->handle = function (&$vdata) use ($user) { $data = [ 'email' => $vdata['email'], @@ -301,7 +338,9 @@ EOD); '$.social.codeforces', $vdata['codeforces'], '$.social.website', - $vdata['website'] + $vdata['website'], + '$.username_color', + $_POST['username_color'] ), ], "where", ["username" => $user['username']] @@ -376,6 +415,17 @@ EOD); } $update_user_permissions_form->appendHTML(HTML::tag('span', [], UOJLocale::get('user::user group'))); $update_user_permissions_form->appendHTML(HTML::tag('span', ['class' => 'd-inline-block ms-3'], $type_text)); + $update_user_permissions_form->addSelect('user_type', [ + 'label' => '账号类型', + 'options' => [ + 'student' => '学生', + 'teacher' => '老师', + 'system' => '系统', + ], + 'div_class' => 'my-3 row gy-2 gx-3 align-items-center', + 'label_class' => 'form-label col-auto', + 'select_class' => 'form-select w-auto col-auto', + ]); $update_user_permissions_form->appendHTML(HTML::tag('h3', ['class' => 'h5 mt-3'], '题目')); $update_user_permissions_form->addCheckbox('problems__view', [ 'checked' => $extra['permissions']['problems']['view'], @@ -707,6 +757,7 @@ EOD); DB::update([ "update user_info", "set", [ + "usertype" => $_POST['user_type'], "extra" => json_encode($extra), ], "where", [ diff --git a/web/app/libs/uoj-contest-lib.php b/web/app/libs/uoj-contest-lib.php index 1b53904..f360bf0 100644 --- a/web/app/libs/uoj-contest-lib.php +++ b/web/app/libs/uoj-contest-lib.php @@ -87,14 +87,23 @@ function queryContestData($contest, $config = []) { $people = []; if ($contest['extra_config']['individual_or_team'] == 'individual') { - $people = DB::selectAll([ - "select contests_registrants.username, user_info.realname from contests_registrants", + $res = DB::selectAll([ + "select contests_registrants.username, user_info.realname, user_info.extra, user_info.usergroup from contests_registrants", "inner join user_info on contests_registrants.username = user_info.username", "where", [ "contest_id" => $contest['id'], "has_participated" => 1 ] ], DB::NUM); + foreach ($res as $row) { + $extra = json_decode($row[2], true); + $people[] = [ + $row[0], + trim(HTML::escape($row[1])), + null, + UOJUser::getUserColor2($row[3], $extra['username_color']), + ]; + } } elseif ($contest['extra_config']['individual_or_team'] == 'team') { $res = DB::selectAll([ "select user_info.username, null, user_info.extra from contests_registrants, user_info", @@ -106,11 +115,15 @@ function queryContestData($contest, $config = []) { ], DB::NUM); foreach ($res as $row) { $extra = json_decode($row[2], true); - $row[2] = [ - 'team_name' => $extra['acm']['team_name'], - 'members' => $extra['acm']['members'] + $people[] = [ + $row[0], + null, + [ + 'team_name' => $extra['acm']['team_name'], + 'members' => $extra['acm']['members'], + ], + null, ]; - $people[] = $row; } } @@ -373,7 +386,7 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $cfg = []) } } - // standings: rank => score, penalty, [username, realname], virtual_rank, ?review + // standings: rank => score, penalty, [username, realname, null|array, null|color], virtual_rank, ?review $standings = []; foreach ($contest_data['people'] as $person) { $cur = array(0, 0, $person); diff --git a/web/app/libs/uoj-form-lib.php b/web/app/libs/uoj-form-lib.php index 1e03485..620da94 100644 --- a/web/app/libs/uoj-form-lib.php +++ b/web/app/libs/uoj-form-lib.php @@ -735,7 +735,7 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle $stat = $zip_file->statName($req['file_name']); if ($req['type'] == 'source code') { - $max_size = isset($req['size']) ? (int)$req['size'] : 50; + $max_size = isset($req['size']) ? (int)$req['size'] : 100; if ($stat['size'] > $max_size * 1024) { $zip_file->close(); unlink(UOJContext::storagePath() . $zip_file_name); diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php index 1eb63f3..02242c0 100644 --- a/web/app/libs/uoj-html-lib.php +++ b/web/app/libs/uoj-html-lib.php @@ -1009,62 +1009,6 @@ function echoUOJPageFooter($config = array()) { uojIncludeView('page-footer', $config); } -function echoRanklist($config = []) { - $header_row = ''; - $header_row .= ''; - $header_row .= '#'; - $header_row .= '' . UOJLocale::get('username') . ''; - $header_row .= '' . UOJLocale::get('motto') . ''; - $header_row .= '' . UOJLocale::get('solved') . ''; - $header_row .= ''; - - $parsedown = HTML::parsedown(); - $purifier = HTML::purifier_inline(); - $users = []; - $print_row = function ($user, $now_cnt) use (&$users, $config, $purifier, $parsedown) { - if (!$users) { - if ($now_cnt == 1) { - $rank = 1; - } else { - $rank = DB::selectCount("select count(*) from (select b.username as username, count(*) as accepted from best_ac_submissions a inner join user_info b on a.submitter = b.username group by username) as derived where accepted > {$user['ac_num']}") + 1; - } - } else { - $rank = $now_cnt; - } - - $user['rank'] = $rank; - - echo ''; - echo '' . $user['rank'] . ''; - echo '' . UOJUser::getLink($user['username']) . ''; - echo ""; - echo $purifier->purify($parsedown->line($user['motto'])); - echo ""; - echo '' . $user['ac_num'] . ''; - echo ''; - - $users[] = $user; - }; - - $from = 'user_info'; - $col_names = ['user_info.username as username', 'ac_num', 'motto']; - $cond = '1'; - $tail = 'group by user_info.username order by ac_num desc, user_info.username asc'; - - if (isset($config['group_id'])) { - $group_id = $config['group_id']; - $from = "user_info inner join groups_users on (user_info.username = groups_users.username and groups_users.group_id = {$group_id})"; - $config['pagination_cond'] = "group_id = {$group_id}"; - } - - if (isset($config['top10'])) { - $tail .= ' limit 10'; - } - - $config['get_row_index'] = ''; - echoLongTable($col_names, $from, $cond, $tail, $header_row, $print_row, $config); -} - // ===== uoj.ac ===== function echoJudgmentDetails($raw_details, $styler, $name) { diff --git a/web/app/locale/contests/en.php b/web/app/locale/contests/en.php index 8421782..da44fcb 100644 --- a/web/app/locale/contests/en.php +++ b/web/app/locale/contests/en.php @@ -1,5 +1,6 @@ 'New Contest', 'current or upcoming contests' => 'Current or upcoming contests', 'ended contests' => 'Ended contests', 'back to the contest' => 'Back to the contest', diff --git a/web/app/locale/contests/zh-cn.php b/web/app/locale/contests/zh-cn.php index e09aad0..6adefb6 100644 --- a/web/app/locale/contests/zh-cn.php +++ b/web/app/locale/contests/zh-cn.php @@ -1,5 +1,6 @@ '新建比赛', 'current or upcoming contests' => '正在进行或即将到来的比赛', 'ended contests' => '已结束的比赛', 'back to the contest' => '返回比赛', diff --git a/web/app/locale/problems/en.php b/web/app/locale/problems/en.php index 9522bc4..a7c1884 100644 --- a/web/app/locale/problems/en.php +++ b/web/app/locale/problems/en.php @@ -52,4 +52,7 @@ return [ 'hacks to me' => 'Hacks to me', 'difficulty' => 'Difficulty', 'show difficulty' => 'Show difficulty', + 'tags' => 'Tags', + 'historical score' => 'Historical Score', + 'uploader' => 'Uploader', ]; diff --git a/web/app/locale/problems/zh-cn.php b/web/app/locale/problems/zh-cn.php index 797ed51..580882f 100644 --- a/web/app/locale/problems/zh-cn.php +++ b/web/app/locale/problems/zh-cn.php @@ -52,4 +52,7 @@ return [ 'hacks to me' => '我的被Hack记录', 'difficulty' => '难度', 'show difficulty' => '显示难度', + 'tags' => '标签', + 'historical score' => '历史分数', + 'uploader' => '上传者', ]; diff --git a/web/app/models/HTML.php b/web/app/models/HTML.php index 537a8e0..df75d89 100644 --- a/web/app/models/HTML.php +++ b/web/app/models/HTML.php @@ -436,7 +436,11 @@ class HTML { $def->addElement('footer', 'Block', 'Flow', 'Common'); $extra_allowed_html = [ - 'span' => ['data-realname' => 'Text', 'data-uoj-username' => 'Number'], + 'span' => [ + 'class' => 'Enum#uoj-username', + 'data-realname' => 'Text', + 'data-color' => 'Color', + ], 'img' => ['width' => 'Text'], ]; @@ -463,7 +467,11 @@ class HTML { 'small' => [], 'del' => [], 'br' => [], - 'span' => ['data-realname' => 'Text', 'data-uoj-username' => 'Number'], + 'span' => [ + 'class' => 'Enum#uoj-username', + 'data-realname' => 'Text', + 'data-color' => 'Color', + ], ]; $allowed_elements = []; @@ -490,8 +498,8 @@ class HTML { return new HTMLPurifier($config); } - public static function parsedown() { - return new UOJMarkdown([ + public static function parsedown($config = []) { + return new UOJMarkdown($config + [ 'math' => [ 'enabled' => true, 'matchSingleDollar' => true diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index 893ecf3..b22e743 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -258,7 +258,7 @@ class UOJForm { 'options' => [], 'default_value' => '', 'label' => '', - 'label_class' => 'form-check-label', + 'label_class' => 'form-label', 'help' => '', 'help_class' => 'form-text', 'disabled' => false, diff --git a/web/app/models/UOJMarkdown.php b/web/app/models/UOJMarkdown.php index e4f32d7..6528f81 100644 --- a/web/app/models/UOJMarkdown.php +++ b/web/app/models/UOJMarkdown.php @@ -1,10 +1,12 @@ options['username_with_color'] = $options['username_with_color'] ?: false; + // https://gist.github.com/ShNURoK42/b5ce8baa570975db487c $this->InlineTypes['@'][] = 'UserMention'; $this->inlineMarkerList .= '@'; @@ -19,18 +21,18 @@ class UOJMarkdown extends ParsedownMath { } // https://github.com/taufik-nurrohman/parsedown-extra-plugin/blob/1653418c5a9cf5277cd28b0b23ba2d95d18e9bc4/ParsedownExtraPlugin.php#L347-L358 - protected function doGetContent($Element) { - if (isset($Element['text'])) { - return $Element['text']; - } - if (isset($Element['rawHtml'])) { - return $Element['rawHtml']; - } - if (isset($Element['handler']['argument'])) { - return implode("\n", (array) $Element['handler']['argument']); - } - return null; - } + protected function doGetContent($Element) { + if (isset($Element['text'])) { + return $Element['text']; + } + if (isset($Element['rawHtml'])) { + return $Element['rawHtml']; + } + if (isset($Element['handler']['argument'])) { + return implode("\n", (array) $Element['handler']['argument']); + } + return null; + } // https://github.com/taufik-nurrohman/parsedown-extra-plugin/blob/1653418c5a9cf5277cd28b0b23ba2d95d18e9bc4/ParsedownExtraPlugin.php#L369-L378 protected function doSetAttributes(&$Element, $From, $Args = array()) { @@ -52,27 +54,35 @@ class UOJMarkdown extends ParsedownMath { } // https://gist.github.com/ShNURoK42/b5ce8baa570975db487c - protected function inlineUserMention($Excerpt) { - if (preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) { - if (($user = UOJUser::query($matches[1])) && $user['usergroup'] != 'B') { - return [ - 'extent' => strlen($matches[0]), - 'element' => [ - 'name' => 'span', - 'text' => '@' . $user['username'], - 'attributes' => [ - 'class' => 'uoj-username', - 'data-realname' => $user['realname'], - 'data-uoj-username' => 1, - ], - ], - ]; - } + protected function inlineUserMention($Excerpt) { + if (preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) { + $mentioned_user = UOJUser::query($matches[1]); - return [ - 'extent' => strlen($matches[0]), - 'markup' => $matches[0], - ]; - } - } + if ($mentioned_user) { + $color = '#0d6efd'; + + if ($this->options['username_with_color']) { + $color = UOJUser::getUserColor($mentioned_user); + } + + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => 'span', + 'text' => '@' . $mentioned_user['username'], + 'attributes' => [ + 'class' => 'uoj-username', + 'data-realname' => UOJUser::getRealname($mentioned_user), + 'data-color' => $color, + ], + ], + ]; + } + + return [ + 'extent' => strlen($matches[0]), + 'markup' => $matches[0], + ]; + } + } } diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php index e200a14..c0bd929 100644 --- a/web/app/models/UOJProblem.php +++ b/web/app/models/UOJProblem.php @@ -22,6 +22,7 @@ class UOJProblem { 2300, 2400, 2500, + 2600, 2700, 2900, 3100, @@ -43,13 +44,254 @@ class UOJProblem { 2300 => '#ff8000', 2400 => '#ff8000', 2500 => '#ff8000', + 2600 => '#ff0000', 2700 => '#ff0000', 2900 => '#ff0000', - 3100 => '#ff0000', + 3100 => '#aa0000', 3300 => '#aa0000', 3500 => '#aa0000', ]; + public static array $categories = [ + '算法基础' => [ + '暴力', + '枚举', + '模拟', + '递归与分治', + '贪心', + '排序', + '前缀和与差分', + '二分', + '倍增', + '构造', + '打表', + ], + '搜索' => [ + '深度优先搜索', + '广度优先搜索', + '双向搜索', + '启发式搜索', + 'A*', + 'IDA*', + '迭代加深', + '回溯法', + 'Dancing Links', + ], + '动态规划' => [ + '记忆化搜索', + '线性 DP', + '背包 DP', + '区间 DP', + '树形 DP', + '状压 DP', + '数位 DP', + 'DAG 上 DP', + '插头 DP', + '概率 DP', + '单调队列优化 DP', + '斜率优化 DP', + '四边形不等式优化 DP', + ], + '计算几何' => [ + 'Pick 定理', + '三角剖分', + '凸包', + '扫描线', + '旋转卡壳', + '半平面交', + '平面最近点对', + '随机增量法', + '反演变换', + ], + '数学' => [ + '位运算', + '快速幂', + '高精度', + '生成函数', + '指数生成函数', + '向量', + '矩阵', + '高斯消元', + '线性基', + '线性规划', + '容斥', + '组合计数', + '离散对数', + '单纯形算法', + '概率', + '置换群', + '斐波那契数列', + '牛顿迭代法', + '数值积分', + '分块打表', + ], + '数论' => [ + '最大公约数', + '分解质因数', + '欧拉函数', + '筛法', + '欧拉定理', + '费马小定理', + '类欧几里得算法', + '翡蜀定理', + '乘法逆元', + '线性同余方程', + 'Meissel-Lehmer 算法', + '二次剩余', + 'BSGS', + '原根', + '卢卡斯定理', + '莫比乌斯反演', + '拉格朗日反演', + '杜教筛', + 'Powerful Number 筛', + 'Min_25 筛', + '洲阁筛', + '连分数', + 'Stern-Brocot 数与 Farey 序列', + 'Pell 方程', + ], + '字符串' => [ + '字符串哈希', + '字典树', + 'KMP', + 'Boyer-Moore', + 'Z 函数(扩展 KMP)', + 'AC 自动机', + '后缀数组', + '后缀自动机', + '后缀平衡树', + '广义后缀自动机', + 'Manacher', + '回文树', + '序列自动机', + '最小表示法', + 'Lyndon 分解', + ], + '图论' => [ + '拓扑排序', + '最短路', + 'K 短路', + '同余最短路', + '虚树', + '树分治', + '动态树分治', + '树哈希', + '树上启发式合并', + 'AHU 算法', + '矩阵树定理', + '最小生成树', + '最小树形图', + '最小直径生成树', + '斯坦纳树', + '拆点', + '差分约束', + '强连通分量', + '双连通分量', + '割点与桥', + '圆方树', + '2-SAT', + '欧拉图', + '哈密顿图', + '最小环', + '平面图', + '网络流', + '最大流', + '最小割', + '费用流', + '上下界网络流', + 'Stoer-Wagner 算法', + '二分图', + '二分图最大匹配', + '二分图最大权匹配', + '一般图最大匹配', + '一般图最大权匹配', + 'Prufer 序列', + 'LGV 引理', + '弦图', + ], + '组合数学' => [ + '排列组合', + '卡特兰数', + '斯特林数', + '贝尔数', + '伯努利数', + '康托展开', + '容斥原理', + '抽屉原理', + '欧拉数', + ], + '数据结构' => [ + '栈', + '队列', + '链表', + '哈希表', + '并查集', + '二叉堆', + '配对堆', + '树状数组', + '线段树', + '平衡树', + '左偏树', + '块状数组', + '块状链表', + '树分块', + 'Sqrt Tree', + '可持久化数据结构', + '单调栈', + '单调队列', + 'ST 表', + '树套树', + '李超线段树', + '区间最值操作与区间历史最值', + '划分树', + '跳表', + 'K-D Tree', + '珂朵莉树', + '动态树', + '析合树', + ], + '多项式' => [ + '拉格朗日插值', + '快速傅里叶变换', + '快速数论变换', + '快速沃尔什变换', + '多项式求逆', + '多项式开方', + '多项式除法与取模', + '多项式对数函数与指数函数', + '多项式牛顿迭代', + '多项式多点求值与快速插值', + '多项式三角函数', + '多项式反三角函数', + '常系数齐次线性递推', + ], + '博弈论' => [ + '不平等博弈', + 'SG 函数', + 'Nim 游戏', + 'Anti-Nim', + '纳什均衡', + ], + '杂项' => [ + '构造', + '离散化', + 'CDQ 分治', + '整体二分', + '分块', + '莫队', + '分数规划', + '随机化', + '模拟退火', + '爬山法', + '悬线法', + '编译原理', + '复杂度分析', + '语义分析', + '底层优化', + ], + ]; + public static function query($id) { if (!isset($id) || !validateUInt($id)) { return null; diff --git a/web/app/models/UOJRanklist.php b/web/app/models/UOJRanklist.php index 2aadd7a..b423fc7 100644 --- a/web/app/models/UOJRanklist.php +++ b/web/app/models/UOJRanklist.php @@ -22,7 +22,7 @@ class UOJRanklist { } $last_user = null; - $parsedown = HTML::parsedown(); + $parsedown = HTML::parsedown(['username_with_color' => true]); $purifier = HTML::purifier_inline(); $print_row = function ($user, $now_cnt) use (&$last_user, &$conds, &$parsedown, &$purifier) { if ($last_user === null) { @@ -138,7 +138,7 @@ class UOJRanklist { $header_row .= ''; $last_user = null; - $parsedown = HTML::parsedown(); + $parsedown = HTML::parsedown(['username_with_color' => true]); $purifier = HTML::purifier_inline(); $print_row = function ($user, $now_cnt) use (&$last_user, &$conds, &$parsedown, &$purifier) { if ($last_user === null) { diff --git a/web/app/models/UOJUser.php b/web/app/models/UOJUser.php index 46e955e..29ba52f 100644 --- a/web/app/models/UOJUser.php +++ b/web/app/models/UOJUser.php @@ -210,6 +210,43 @@ class UOJUser { } } + public static function getRealname($user) { + $realname = $user['realname']; + + if ($user['usertype'] == 'teacher') { + $realname .= '老师'; + } + + return $realname; + } + + public static function getUserColor($user) { + $extra = UOJUser::getExtra($user); + + return UOJUser::getUserColor2($user['usergroup'], $extra['username_color']); + } + + public static function getUserColor2($usergroup, $custom_color = null) { + if ($usergroup == 'B') { + return '#996600'; + } + + if ($usergroup == 'T') { + return '#707070'; + } + + if ($usergroup == 'S') { + return $custom_color ?: '#9d3dcf'; + } + + // 前管理员设置颜色为紫色的,颜色改为蓝色 + if ($custom_color == '#9d3dcf') { + return '#0d6efd'; + } + + return $custom_color ?: '#0d6efd'; + } + public static function getLink($user) { if (is_string($user)) { $info = UOJUser::query($user); @@ -221,14 +258,18 @@ class UOJUser { } } - if ($user['usergroup'] == 'B') { - return HTML::tag('a', ['class' => 'text-danger fw-bold', 'href' => "/user/{$user['username']}"], $user['username']); - } + $realname = UOJUser::getRealname($user); // 未登录不可查看真实姓名 - $realname = Auth::check() ? $user['realname'] : ''; + if (!Auth::check()) { + $realname = ''; + } - return HTML::tag('span', ['class' => 'uoj-username', 'data-realname' => trim(HTML::escape($realname))], $user['username']); + return HTML::tag('span', [ + 'class' => 'uoj-username', + 'data-color' => UOJUser::getUserColor($user), + 'data-realname' => trim(HTML::escape($realname)), + ], $user['username']); } public static function getUpdatedExtraVisitHistory($history, $cur) { @@ -290,6 +331,7 @@ class UOJUser { 'show_email' => 'all', 'show_qq' => 'all', 'avatar_source' => 'gravatar', + 'username_color' => isSuperUser($user) ? '#9d3dcf' : '#0d6efd', ]); return $extra; } @@ -340,6 +382,7 @@ class UOJUser { $extra = UOJUser::getExtra($user); $cur = [ 'addr' => $info['remote_addr'], + 'forwarded_addr' => $info['http_x_forwarded_for'], 'ua' => substr($info['http_user_agent'], 0, UOJUser::MAX_UA_LEN), 'last' => UOJTime::$time_now_str ]; diff --git a/web/app/route.php b/web/app/route.php index b90f26c..1f11278a 100644 --- a/web/app/route.php +++ b/web/app/route.php @@ -92,9 +92,9 @@ Route::group( Route::any('/click-zan', '/click_zan.php'); // Apps - Route::any('/image_hosting', '/image_hosting/index.php'); - Route::get('/image_hosting/{image_name}.png', '/image_hosting/get_image.php'); - Route::any('/html2markdown', '/html2markdown.php'); + Route::any('/image_hosting', '/app/image_hosting/index.php'); + Route::get('/image_hosting/{image_name}.png', '/app/image_hosting/get_image.php'); + Route::any('/html2markdown', '/app/html2markdown.php'); } ); diff --git a/web/app/vendor/erusev/parsedown/Parsedown.php b/web/app/vendor/erusev/parsedown/Parsedown.php index 1b9d6d5..9f7eb5f 100644 --- a/web/app/vendor/erusev/parsedown/Parsedown.php +++ b/web/app/vendor/erusev/parsedown/Parsedown.php @@ -19,6 +19,8 @@ class Parsedown const version = '1.7.4'; + protected $options = []; + # ~ function text($text) diff --git a/web/app/views/contest-reviews.php b/web/app/views/contest-reviews.php index 7b73701..d0478e0 100644 --- a/web/app/views/contest-reviews.php +++ b/web/app/views/contest-reviews.php @@ -1,7 +1,7 @@ true]); $purifier = HTML::purifier_inline(); foreach ($contest_data['people'] as $person) { diff --git a/web/app/views/contest-standings.php b/web/app/views/contest-standings.php index 35ee895..b5240f8 100644 --- a/web/app/views/contest-standings.php +++ b/web/app/views/contest-standings.php @@ -35,7 +35,7 @@ function(row) { var col_tr = ''; col_tr += '' + row[3] + ''; - col_tr += '' + getUserLink(row[2][0], row[2][1]) + ''; + col_tr += '' + getUserLink(row[2][0], row[2][1], row[2][3]) + ''; col_tr += '' + '
' + row[0] + '
' + '
' + getPenaltyTimeStr(row[1]) + '
'; for (var i = 0; i < problems.length; i++) { col = score[row[2][0]][i]; diff --git a/web/app/views/page-footer.php b/web/app/views/page-footer.php index 35a9a77..5d806ce 100644 --- a/web/app/views/page-footer.php +++ b/web/app/views/page-footer.php @@ -4,11 +4,6 @@ if (!isset($ShowPageFooter)) { } ?> - - + diff --git a/web/app/views/sidebar.php b/web/app/views/sidebar.php index 956c64a..1923266 100644 --- a/web/app/views/sidebar.php +++ b/web/app/views/sidebar.php @@ -1,6 +1,6 @@ true]); ?> diff --git a/web/app/views/user-info.php b/web/app/views/user-info.php index 36623be..48375e1 100644 --- a/web/app/views/user-info.php +++ b/web/app/views/user-info.php @@ -21,7 +21,7 @@
- purify(HTML::parsedown()->line($user['motto'])) ?> + purify(HTML::parsedown(['username_with_color' => true])->line($user['motto'])) ?>