From b8e84f43f1a30345afdadfe219fa7b6a3ed003d3 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Mon, 20 Mar 2023 15:03:25 +0800 Subject: [PATCH] feat: fetch problem from luogu --- INSTALLATION.md | 6 + web/app/controllers/problem.php | 279 ++++++++++++++++++ web/app/controllers/problem.php.patch | 17 ++ web/app/controllers/problem_set.php | 224 ++++++++++++++ web/app/controllers/problem_set.php.patch | 57 ++++ .../controllers/problem_statement_manage.php | 78 +++++ .../problem_statement_manage.php.patch | 40 +++ web/app/libs/uoj-luogu-lib.php | 32 +- web/app/models/Curl.php | 2 - 9 files changed, 730 insertions(+), 5 deletions(-) create mode 100644 web/app/controllers/problem.php create mode 100644 web/app/controllers/problem.php.patch create mode 100644 web/app/controllers/problem_set.php create mode 100644 web/app/controllers/problem_set.php.patch create mode 100644 web/app/controllers/problem_statement_manage.php create mode 100644 web/app/controllers/problem_statement_manage.php.patch diff --git a/INSTALLATION.md b/INSTALLATION.md index 8914ec4..5b82355 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -1,3 +1,9 @@ # 安装 +## 前期准备 + +请先在服务器上安装 Node.js 18 LTS 和 `php-curl`。 + +## 修改源码 + _TODO_ diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php new file mode 100644 index 0000000..a639593 --- /dev/null +++ b/web/app/controllers/problem.php @@ -0,0 +1,279 @@ +比赛正在进行中

很遗憾,您尚未报名。比赛结束后再来看吧~

"); + } else { + $is_in_contest = true; + DB::update("update contests_registrants set has_participated = 1 where username = '{$myUser['username']}' and contest_id = {$contest['id']}"); + } + } else { + $ban_in_contest = !isProblemVisibleToUser($problem, $myUser); + } + } + } else { + if (!isProblemVisibleToUser($problem, $myUser)) { + become404Page(); + } + } + + $submission_requirement = json_decode($problem['submission_requirement'], true); + $problem_extra_config = getProblemExtraConfig($problem); + $custom_test_requirement = getProblemCustomTestRequirement($problem); + + if ($custom_test_requirement && Auth::check()) { + $custom_test_submission = DB::selectFirst("select * from custom_test_submissions where submitter = '".Auth::id()."' and problem_id = {$problem['id']} order by id desc limit 1"); + $custom_test_submission_result = json_decode($custom_test_submission['result'], true); + } + if ($custom_test_requirement && $_GET['get'] == 'custom-test-status-details' && Auth::check()) { + if ($custom_test_submission == null) { + echo json_encode(null); + } elseif ($custom_test_submission['status'] != 'Judged') { + echo json_encode(array( + 'judged' => false, + 'html' => getSubmissionStatusDetails($custom_test_submission) + )); + } else { + ob_start(); + $styler = new CustomTestSubmissionDetailsStyler(); + if (!hasViewPermission($problem_extra_config['view_details_type'], $myUser, $problem, $submission)) { + $styler->fade_all_details = true; + } + echoJudgementDetails($custom_test_submission_result['details'], $styler, 'custom_test_details'); + $result = ob_get_contents(); + ob_end_clean(); + echo json_encode(array( + 'judged' => true, + 'html' => getSubmissionStatusDetails($custom_test_submission), + 'result' => $result + )); + } + die(); + } + + $can_use_zip_upload = true; + foreach ($submission_requirement as $req) { + if ($req['type'] == 'source code') { + $can_use_zip_upload = false; + } + } + + function handleUpload($zip_file_name, $content, $tot_size) { + global $problem, $contest, $myUser, $is_in_contest; + + $content['config'][] = array('problem_id', $problem['id']); + if ($is_in_contest && $contest['extra_config']["contest_type"]!='IOI' && !isset($contest['extra_config']["problem_{$problem['id']}"])) { + $content['final_test_config'] = $content['config']; + $content['config'][] = array('test_sample_only', 'on'); + } + $esc_content = DB::escape(json_encode($content)); + + $language = '/'; + foreach ($content['config'] as $row) { + if (strEndWith($row[0], '_language')) { + $language = $row[1]; + break; + } + } + if ($language != '/') { + Cookie::set('uoj_preferred_language', $language, time() + 60 * 60 * 24 * 365, '/'); + } + $esc_language = DB::escape($language); + + $result = array(); + $result['status'] = "Waiting"; + $result_json = json_encode($result); + + if ($is_in_contest) { + DB::query("insert into submissions (problem_id, contest_id, submit_time, submitter, content, language, tot_size, status, result, is_hidden) values (${problem['id']}, ${contest['id']}, now(), '${myUser['username']}', '$esc_content', '$esc_language', $tot_size, '${result['status']}', '$result_json', 0)"); + } else { + DB::query("insert into submissions (problem_id, submit_time, submitter, content, language, tot_size, status, result, is_hidden) values (${problem['id']}, now(), '${myUser['username']}', '$esc_content', '$esc_language', $tot_size, '${result['status']}', '$result_json', {$problem['is_hidden']})"); + } + } + function handleCustomTestUpload($zip_file_name, $content, $tot_size) { + global $problem, $contest, $myUser; + + $content['config'][] = array('problem_id', $problem['id']); + $content['config'][] = array('custom_test', 'on'); + $esc_content = DB::escape(json_encode($content)); + + $language = '/'; + foreach ($content['config'] as $row) { + if (strEndWith($row[0], '_language')) { + $language = $row[1]; + break; + } + } + if ($language != '/') { + Cookie::set('uoj_preferred_language', $language, time() + 60 * 60 * 24 * 365, '/'); + } + $esc_language = DB::escape($language); + + $result = array(); + $result['status'] = "Waiting"; + $result_json = json_encode($result); + + DB::insert("insert into custom_test_submissions (problem_id, submit_time, submitter, content, status, result) values ({$problem['id']}, now(), '{$myUser['username']}', '$esc_content', '{$result['status']}', '$result_json')"); + } + + if ($can_use_zip_upload) { + $zip_answer_form = newZipSubmissionForm('zip_answer', + $submission_requirement, + 'uojRandAvaiableSubmissionFileName', + 'handleUpload'); + $zip_answer_form->extra_validator = function() { + global $ban_in_contest; + if ($ban_in_contest) { + return '请耐心等待比赛结束后题目对所有人可见了再提交'; + } + return ''; + }; + $zip_answer_form->succ_href = $is_in_contest ? "/contest/{$contest['id']}/submissions" : '/submissions'; + $zip_answer_form->runAtServer(); + } + + $answer_form = newSubmissionForm('answer', + $submission_requirement, + 'uojRandAvaiableSubmissionFileName', + 'handleUpload'); + $answer_form->extra_validator = function() { + global $ban_in_contest; + if ($ban_in_contest) { + return '请耐心等待比赛结束后题目对所有人可见了再提交'; + } + return ''; + }; + $answer_form->succ_href = $is_in_contest ? "/contest/{$contest['id']}/submissions" : '/submissions'; + $answer_form->runAtServer(); + + if ($custom_test_requirement) { + $custom_test_form = newSubmissionForm('custom_test', + $custom_test_requirement, + function() { + return uojRandAvaiableFileName('/tmp/'); + }, + 'handleCustomTestUpload'); + $custom_test_form->appendHTML(<< +EOD + ); + $custom_test_form->succ_href = 'none'; + $custom_test_form->extra_validator = function() { + global $ban_in_contest, $custom_test_submission; + if ($ban_in_contest) { + return '请耐心等待比赛结束后题目对所有人可见了再提交'; + } + if ($custom_test_submission && $custom_test_submission['status'] != 'Judged') { + return '上一个测评尚未结束'; + } + return ''; + }; + $custom_test_form->ctrl_enter_submit = true; + $custom_test_form->setAjaxSubmit(<<submit_button_config['text'] = UOJLocale::get('problems::run'); + $custom_test_form->runAtServer(); + } +?> + + + +
+ 时间限制: + 空间限制: +
+
+ +
+ + + + + + + + +

#.

+ + + + +
+
+
+
+
+
+ + printHTML(); ?> +
+
+ + printHTML(); ?> +
+ +
+
+ printHTML(); ?> +
+ +
+ diff --git a/web/app/controllers/problem.php.patch b/web/app/controllers/problem.php.patch new file mode 100644 index 0000000..7926a38 --- /dev/null +++ b/web/app/controllers/problem.php.patch @@ -0,0 +1,17 @@ +--- UOJ-System/web/app/controllers/problem.php 2022-12-30 09:54:05.452022649 +0800 ++++ UOJ-Luogu-RemoteJudge/web/app/controllers/problem.php 2023-03-20 15:01:32.363789172 +0800 +@@ -208,9 +208,14 @@ + ?> + + +
+ 时间限制: diff --git a/web/app/controllers/problem_set.php b/web/app/controllers/problem_set.php new file mode 100644 index 0000000..37b57c6 --- /dev/null +++ b/web/app/controllers/problem_set.php @@ -0,0 +1,224 @@ +handle = function() { + DB::query("insert into problems (title, is_hidden, submission_requirement) values ('New Problem', 1, '{}')"); + $id = DB::insert_id(); + DB::query("insert into problems_contents (id, statement, statement_md) values ($id, '', '')"); + dataNewProblem($id); + }; + $new_problem_form->submit_button_config['align'] = 'right'; + $new_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; + $new_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); + $new_problem_form->submit_button_config['smart_confirm'] = ''; + $new_problem_form->runAtServer(); + + $new_luogu_problem_form = new UOJForm('new_luogu_problem'); + $new_luogu_problem_form->addVInput('luogu_pid', 'text', '洛谷题号', '', function($id) { + if (!validateLuoguProblemId($id)) { + return '题目 ID 不合法'; + } + + return ''; + }, null); + $new_luogu_problem_form->handle = function() { + try { + newLuoguRemoteProblem($_POST['luogu_pid']); + } catch (Exception $e) { + becomeMsgPage('

添加失败

' . $e->getMessage() . '
'); + } + }; + // $new_luogu_problem_form->submit_button_config['align'] = 'right'; + $new_luogu_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; + $new_luogu_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); + $new_luogu_problem_form->submit_button_config['confirm'] = '新增来自洛谷的题目'; + $new_luogu_problem_form->runAtServer(); + } + + function echoProblem($problem) { + global $myUser; + if (isProblemVisibleToUser($problem, $myUser)) { + echo ''; + if ($problem['submission_id']) { + echo ''; + } else { + echo ''; + } + echo '#', $problem['id'], ''; + echo ''; + if ($problem['is_hidden']) { + echo ' [隐藏] '; + } + echo '', $problem['title'], ''; + if (isset($_COOKIE['show_tags_mode'])) { + foreach (queryProblemTags($problem['id']) as $tag) { + echo '', '', HTML::escape($tag), '', ''; + } + } + echo ''; + if (isset($_COOKIE['show_submit_mode'])) { + $perc = $problem['submit_num'] > 0 ? round(100 * $problem['ac_num'] / $problem['submit_num']) : 0; + echo <<×{$problem['ac_num']} + ×{$problem['submit_num']} + +
+
{$perc}%
+
+ +EOD; + } + echo '', getClickZanBlock('P', $problem['id'], $problem['zan']), ''; + echo ''; + } + } + + $cond = array(); + + $search_tag = null; + + $cur_tab = isset($_GET['tab']) ? $_GET['tab'] : 'all'; + if ($cur_tab == 'template') { + $search_tag = "模板题"; + } + if (isset($_GET['tag'])) { + $search_tag = $_GET['tag']; + } + if ($search_tag) { + $cond[] = "'".DB::escape($search_tag)."' in (select tag from problems_tags where problems_tags.problem_id = problems.id)"; + } + if (isset($_GET["search"])) { + $cond[]="title like '%".DB::escape($_GET["search"])."%' or id like '%".DB::escape($_GET["search"])."%'"; + } + + if ($cond) { + $cond = join($cond, ' and '); + } else { + $cond = '1'; + } + + $header = ''; + $header .= 'ID'; + $header .= ''.UOJLocale::get('problems::problem').''; + if (isset($_COOKIE['show_submit_mode'])) { + $header .= ''.UOJLocale::get('problems::ac').''; + $header .= ''.UOJLocale::get('problems::submit').''; + $header .= ''.UOJLocale::get('problems::ac ratio').''; + } + $header .= ''.UOJLocale::get('appraisal').''; + $header .= ''; + + $tabs_info = array( + 'all' => array( + 'name' => UOJLocale::get('problems::all problems'), + 'url' => "/problems" + ), + 'template' => array( + 'name' => UOJLocale::get('problems::template problems'), + 'url' => "/problems/template" + ) + ); + + /* + 3, + 'table_classes' => array('table', 'table-bordered', 'table-hover', 'table-striped'), + 'print_after_table' => function() { + global $myUser; + if (isSuperUser($myUser)) { + global $new_problem_form; + $new_problem_form->printHTML(); + } + }, + 'head_pagination' => true + ) + ); +?>*/ + + $pag_config = array('page_len' => 100); + $pag_config['col_names'] = array('*'); + $pag_config['table_name'] = "problems left join best_ac_submissions on best_ac_submissions.submitter = '{$myUser['username']}' and problems.id = best_ac_submissions.problem_id"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = "order by id asc"; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); +?> + +
+
+ +
+
+ + +
+
+ pagination(); ?> +
+
+
+ +'; + echo ''; + echo ''; + echo $header; + echo ''; + echo ''; + + foreach ($pag->get() as $idx => $row) { + echoProblem($row); + echo "\n"; + } + if ($pag->isEmpty()) { + echo ''; + } + + echo ''; + echo '
'.UOJLocale::get('none').'
'; + echo '
'; + + if (isSuperUser($myUser)) { + echo '
'; + $new_problem_form->printHTML(); + + echo '
'; + echo '
'; + echo '
'; + $new_luogu_problem_form->printHTML(); + echo '
'; + echo '
'; + echo ''; + } + + echo $pag->pagination(); +?> + diff --git a/web/app/controllers/problem_set.php.patch b/web/app/controllers/problem_set.php.patch new file mode 100644 index 0000000..bcf9871 --- /dev/null +++ b/web/app/controllers/problem_set.php.patch @@ -0,0 +1,57 @@ +--- UOJ-System/web/app/controllers/problem_set.php 2023-03-19 21:20:30.679254485 +0800 ++++ UOJ-Luogu-RemoteJudge/web/app/controllers/problem_set.php 2023-03-20 14:43:44.884987235 +0800 +@@ -2,6 +2,7 @@ + requirePHPLib('form'); + requirePHPLib('judger'); + requirePHPLib('data'); ++ requirePHPLib('luogu'); + + if (isSuperUser($myUser)) { + $new_problem_form = new UOJForm('new_problem'); +@@ -15,8 +16,28 @@ + $new_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; + $new_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); + $new_problem_form->submit_button_config['smart_confirm'] = ''; +- + $new_problem_form->runAtServer(); ++ ++ $new_luogu_problem_form = new UOJForm('new_luogu_problem'); ++ $new_luogu_problem_form->addVInput('luogu_pid', 'text', '洛谷题号', '', function($id) { ++ if (!validateLuoguProblemId($id)) { ++ return '题目 ID 不合法'; ++ } ++ ++ return ''; ++ }, null); ++ $new_luogu_problem_form->handle = function() { ++ try { ++ newLuoguRemoteProblem($_POST['luogu_pid']); ++ } catch (Exception $e) { ++ becomeMsgPage('

添加失败

' . $e->getMessage() . '
'); ++ } ++ }; ++ // $new_luogu_problem_form->submit_button_config['align'] = 'right'; ++ $new_luogu_problem_form->submit_button_config['class_str'] = 'btn btn-primary'; ++ $new_luogu_problem_form->submit_button_config['text'] = UOJLocale::get('problems::add new'); ++ $new_luogu_problem_form->submit_button_config['confirm'] = '新增来自洛谷的题目'; ++ $new_luogu_problem_form->runAtServer(); + } + + function echoProblem($problem) { +@@ -186,7 +207,16 @@ + echo ''; + + if (isSuperUser($myUser)) { ++ echo '
'; + $new_problem_form->printHTML(); ++ ++ echo '
'; ++ echo '
'; ++ echo '
'; ++ $new_luogu_problem_form->printHTML(); ++ echo '
'; ++ echo '
'; ++ echo ''; + } + + echo $pag->pagination(); diff --git a/web/app/controllers/problem_statement_manage.php b/web/app/controllers/problem_statement_manage.php new file mode 100644 index 0000000..94ac575 --- /dev/null +++ b/web/app/controllers/problem_statement_manage.php @@ -0,0 +1,78 @@ +name = 'problem'; + $problem_editor->blog_url = "/problem/{$problem['id']}"; + $problem_editor->cur_data = array( + 'title' => $problem['title'], + 'content_md' => $problem_content['statement_md'], + 'content' => $problem_content['statement'], + 'tags' => $problem_tags, + 'is_hidden' => $problem['is_hidden'] + ); + $problem_editor->label_text = array_merge($problem_editor->label_text, array( + 'view blog' => '查看题目', + 'blog visibility' => '题目可见性' + )); + + $problem_editor->save = function($data) { + global $problem, $problem_tags; + DB::update("update problems set title = '".DB::escape($data['title'])."' where id = {$problem['id']}"); + DB::update("update problems_contents set statement = '".DB::escape($data['content'])."', statement_md = '".DB::escape($data['content_md'])."' where id = {$problem['id']}"); + + if ($data['tags'] !== $problem_tags) { + DB::delete("delete from problems_tags where problem_id = {$problem['id']}"); + foreach ($data['tags'] as $tag) { + DB::insert("insert into problems_tags (problem_id, tag) values ({$problem['id']}, '".DB::escape($tag)."')"); + } + } + if ($data['is_hidden'] != $problem['is_hidden'] ) { + DB::update("update problems set is_hidden = {$data['is_hidden']} where id = {$problem['id']}"); + DB::update("update submissions set is_hidden = {$data['is_hidden']} where problem_id = {$problem['id']}"); + DB::update("update hacks set is_hidden = {$data['is_hidden']} where problem_id = {$problem['id']}"); + } + }; + + $problem_editor->runAtServer(); + + if ($problem['type'] == 'luogu') { + $refetch_problem_form = new UOJForm('refetch_luogu'); + $refetch_problem_form->submit_button_config['align'] = 'left'; + $refetch_problem_form->submit_button_config['class_str'] = 'btn btn-danger'; + $refetch_problem_form->submit_button_config['text'] = '重新拉取题目'; + $refetch_problem_form->handle = function() use ($problem) { + try { + refetchLuoguProblemInfo($problem['id']); + } catch (Exception $e) { + becomeMsgPage('

拉取失败

' . $e->getMessage() . '
'); + } + }; + $refetch_problem_form->runAtServer(); + } +?> + +

# : 管理

+ +printHTML() ?> + + printHTML() ?> + + diff --git a/web/app/controllers/problem_statement_manage.php.patch b/web/app/controllers/problem_statement_manage.php.patch new file mode 100644 index 0000000..97a988f --- /dev/null +++ b/web/app/controllers/problem_statement_manage.php.patch @@ -0,0 +1,40 @@ +--- UOJ-System/web/app/controllers/problem_statement_manage.php 2023-03-19 21:20:30.679254485 +0800 ++++ UOJ-Luogu-RemoteJudge/web/app/controllers/problem_statement_manage.php 2023-03-20 15:02:04.084987421 +0800 +@@ -1,5 +1,7 @@ + runAtServer(); ++ ++ if ($problem['type'] == 'luogu') { ++ $refetch_problem_form = new UOJForm('refetch_luogu'); ++ $refetch_problem_form->submit_button_config['align'] = 'left'; ++ $refetch_problem_form->submit_button_config['class_str'] = 'btn btn-danger'; ++ $refetch_problem_form->submit_button_config['text'] = '重新拉取题目'; ++ $refetch_problem_form->handle = function() use ($problem) { ++ try { ++ refetchLuoguProblemInfo($problem['id']); ++ } catch (Exception $e) { ++ becomeMsgPage('

拉取失败

' . $e->getMessage() . '
'); ++ } ++ }; ++ $refetch_problem_form->runAtServer(); ++ } + ?> + +

# : 管理

+@@ -55,4 +72,7 @@ + + + printHTML() ?> ++ ++ printHTML() ?> ++ + diff --git a/web/app/libs/uoj-luogu-lib.php b/web/app/libs/uoj-luogu-lib.php index 527f76a..b7d1214 100644 --- a/web/app/libs/uoj-luogu-lib.php +++ b/web/app/libs/uoj-luogu-lib.php @@ -62,7 +62,6 @@ function fetchLuoguProblemBasicInfo($pid) { $curl->set('CURLOPT_HTTPHEADER', [ 'User-Agent: ' . LUOGU_USER_AGENT, - 'Content-Type: application/json', 'Accept: application/json', ]); @@ -92,17 +91,44 @@ function newLuoguRemoteProblem($pid) { )); $esc_extra_config = json_encode(array( "luogu_pid" => $pid, + "time_limit" => $problem['time_limit'], + "memory_limit" => $problem['memory_limit'], "view_content_type" => "ALL", "view_details_type" => "ALL", )); - DB::query("insert into problems (title, is_hidden, submission_requirement, extra_config, hackable, type) values ('" . DB::escape($problem['title']) . "', 1, '" . DB::escape($esc_submission_requirements) . "', '" . DB::escape($esc_extra_config) . "', 0, 'luogu')"); + DB::insert("insert into problems (title, is_hidden, submission_requirement, extra_config, hackable, type) values ('" . DB::escape($problem['title']) . "', 1, '" . DB::escape($esc_submission_requirements) . "', '" . DB::escape($esc_extra_config) . "', 0, 'luogu')"); $id = DB::insert_id(); - DB::query("insert into problems_contents (id, statement, statement_md) values ($id, '" . DB::escape($problem['statement']) . "', '" . DB::escape($problem['statement_md']) . "')"); + DB::insert("insert into problems_contents (id, statement, statement_md) values ($id, '" . DB::escape($problem['statement']) . "', '" . DB::escape($problem['statement_md']) . "')"); dataNewProblem($id); return $id; } + +function refetchLuoguProblemInfo($id) { + $problem = queryProblemBrief($id); + $problem_extra_config = getProblemExtraConfig($problem); + $pid = $problem_extra_config['luogu_pid']; + + $luogu_problem = fetchLuoguProblemBasicInfo($pid); + + $esc_submission_requirements = json_encode(array( + array( + "name" => "answer", + "type" => "source code", + "file_name" => "answer.code", + "languages" => LUOGU_SUPPORTED_LANGUAGES, + ), + )); + mergeConfig($problem_extra_config, array( + "time_limit" => $luogu_problem['time_limit'], + "memory_limit" => $luogu_problem['memory_limit'], + )); + $esc_extra_config = json_encode($problem_extra_config); + + DB::update("update problems set title = '" . DB::escape($luogu_problem['title']) . "', submission_requirement = '" . DB::escape($esc_submission_requirements) . "', extra_config = '" . DB::escape($esc_extra_config) . "' where id = {$problem['id']}"); + DB::update("update problems_contents set statement = '" . DB::escape($luogu_problem['statement']) . "', statement_md = '" . DB::escape($luogu_problem['statement_md']) . "' where id = {$problem['id']}"); +} diff --git a/web/app/models/Curl.php b/web/app/models/Curl.php index d8386b4..dfcf063 100644 --- a/web/app/models/Curl.php +++ b/web/app/models/Curl.php @@ -3,8 +3,6 @@ // from https://github.com/wenpeng/curl // requires php-curl module -use Exception; - class Curl { private $post; private $retry = 0;