diff --git a/web/app/controllers/problem_data_configure.php b/web/app/controllers/problem_data_configure.php new file mode 100644 index 0000000..934be19 --- /dev/null +++ b/web/app/controllers/problem_data_configure.php @@ -0,0 +1,23 @@ +userCanManage(Auth::user()) || UOJResponse::page403(); + +$problem = UOJProblem::info(); +$problem_configure = new UOJProblemConfigure(UOJProblem::cur()); +$problem_configure->runAtServer(); +?> + +getTitle(['with' => 'id']))) ?> + +

+ getTitle(['with' => 'id']) ?> 数据配置 +

+ +printHTML() ?> + + diff --git a/web/app/controllers/problem_data_manage.php b/web/app/controllers/problem_data_manage.php index 88edaa6..7c63d96 100644 --- a/web/app/controllers/problem_data_manage.php +++ b/web/app/controllers/problem_data_manage.php @@ -90,58 +90,6 @@ if ($_POST['problem_data_file_submit'] == 'submit') { } } -// 添加配置文件 -if ($_POST['problem_settings_file_submit'] == 'submit') { - if ($_POST['use_builtin_checker'] and $_POST['n_tests']) { - $set_filename = "/var/uoj_data/upload/{$problem['id']}/problem.conf"; - $has_legacy = false; - if (file_exists($set_filename)) { - $has_legacy = true; - unlink($set_filename); - } - $setfile = fopen($set_filename, "w"); - fwrite($setfile, "use_builtin_judger on\n"); - if ($_POST['use_builtin_checker'] != 'ownchk') { - fwrite($setfile, "use_builtin_checker " . $_POST['use_builtin_checker'] . "\n"); - } - fwrite($setfile, "n_tests " . $_POST['n_tests'] . "\n"); - if ($_POST['n_ex_tests']) { - fwrite($setfile, "n_ex_tests " . $_POST['n_ex_tests'] . "\n"); - } else { - fwrite($setfile, "n_ex_tests 0\n"); - } - if ($_POST['n_sample_tests']) { - fwrite($setfile, "n_sample_tests " . $_POST['n_sample_tests'] . "\n"); - } else { - fwrite($setfile, "n_sample_tests 0\n"); - } - if (isset($_POST['input_pre'])) { - fwrite($setfile, "input_pre " . $_POST['input_pre'] . "\n"); - } - if (isset($_POST['input_suf'])) { - fwrite($setfile, "input_suf " . $_POST['input_suf'] . "\n"); - } - if (isset($_POST['output_pre'])) { - fwrite($setfile, "output_pre " . $_POST['output_pre'] . "\n"); - } - if (isset($_POST['output_suf'])) { - fwrite($setfile, "output_suf " . $_POST['output_suf'] . "\n"); - } - fwrite($setfile, "time_limit " . ($_POST['time_limit'] ?: 1) . "\n"); - fwrite($setfile, "memory_limit " . ($_POST['memory_limit'] ?: 256) . "\n"); - fclose($setfile); - if (!$has_legacy) { - echo ""; - } else { - echo ""; - } - } else { - $errmsg = "添加配置文件失败,请检查是否所有必填输入框都已填写!"; - becomeMsgPage('
' . $errmsg . '
返回'); - } -} - - $info_form = new UOJForm('info'); $attachment_url = UOJProblem::cur()->getAttachmentUri(); $info_form->appendHTML(<<上传数据
- + 试题配置
@@ -705,108 +653,4 @@ if ($problem['hackable']) { -getProblemConf() ?> - - - diff --git a/web/app/libs/uoj-data-lib.php b/web/app/libs/uoj-data-lib.php index 8fe456b..aad2fb7 100644 --- a/web/app/libs/uoj-data-lib.php +++ b/web/app/libs/uoj-data-lib.php @@ -201,7 +201,9 @@ class SyncProblemDataHandler { public function _updateProblemConf($new_problem_conf) { try { - putUOJConf("{$this->data_dir}/problem.conf", $new_problem_conf); + putUOJConf("{$this->upload_dir}/problem.conf", $new_problem_conf); + + $this->_sync(); return ''; } catch (Exception $e) { return $e->getMessage(); @@ -506,3 +508,7 @@ function dataAddHackPoint($problem, $uploaded_input_file, $uploaded_output_file, return (new SyncProblemDataHandler($problem, $user))->addHackPoint($uploaded_input_file, $uploaded_output_file, $reason); } + +function dataUpdateProblemConf($problem, $new_problem_conf) { + return (new SyncProblemDataHandler($problem))->updateProblemConf($new_problem_conf); +} diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index b6d2c51..78dabf6 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -125,6 +125,8 @@ class UOJForm { 'type' => 'text', 'div_class' => '', 'input_class' => 'form-control', + 'input_attrs' => [], + 'input_div_class' => null, 'default_value' => '', 'label' => '', 'label_class' => 'form-label', @@ -148,6 +150,10 @@ class UOJForm { ], $config['label']); } + if ($config['input_div_class'] !== null) { + $html .= HTML::tag_begin('div', ['class' => $config['input_div_class']]); + } + $html .= HTML::empty_tag('input', [ 'class' => $config['input_class'], 'type' => $config['type'], @@ -155,13 +161,17 @@ class UOJForm { 'id' => "input-$name", 'value' => $config['default_value'], 'placeholder' => $config['placeholder'], - ]); + ] + $config['input_attrs']); $html .= HTML::tag('div', ['class' => 'invalid-feedback', 'id' => "help-$name"], ''); if ($config['help']) { $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); } + if ($config['input_div_class'] !== null) { + $html .= HTML::tag_end('div'); + } + $html .= HTML::tag_end('div'); $this->add($name, $html, $config['validator_php'], $config['validator_js']); @@ -255,6 +265,7 @@ class UOJForm { $config += [ 'div_class' => '', 'select_class' => 'form-select', + 'select_div_class' => null, 'options' => [], 'default_value' => '', 'label' => '', @@ -275,6 +286,10 @@ class UOJForm { ], $config['label']); } + if ($config['select_div_class'] !== null) { + $html .= HTML::tag_begin('div', ['class' => $config['select_div_class']]); + } + // Select $html .= HTML::tag_begin('select', ['id' => "input-$name", 'name' => $name, 'class' => $config['select_class']]); @@ -293,6 +308,10 @@ class UOJForm { $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); } + if ($config['select_div_class'] !== null) { + $html .= HTML::tag_end('div'); + } + $html .= HTML::tag_end('div'); $this->add( @@ -503,6 +522,13 @@ class UOJForm { if (!$this->config['no_submit']) { echo HTML::tag_begin('div', ['class' => $this->config['submit_container']['class']]); + if ($this->config['back_button']['href'] !== null) { + echo HTML::tag('a', [ + 'class' => $this->config['back_button']['class'], + 'href' => $this->config['back_button']['href'] + ], '返回'); + } + echo HTML::tag('button', [ 'type' => 'submit', 'id' => "button-submit-{$this->form_name}", @@ -511,13 +537,6 @@ class UOJForm { 'class' => $this->config['submit_button']['class'] ], $this->config['submit_button']['text']); - if ($this->config['back_button']['href'] !== null) { - echo HTML::tag('a', [ - 'class' => $this->config['back_button']['class'], - 'href' => $this->config['back_button']['href'] - ], '返回'); - } - echo HTML::tag_end('div'); } diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php index ee0da7d..87c3870 100644 --- a/web/app/models/UOJProblem.php +++ b/web/app/models/UOJProblem.php @@ -663,6 +663,10 @@ class UOJProblem { return "/var/uoj_data/{$this->info['id']}"; } + public function getUploadFolderPath() { + return "/var/uoj_data/upload/{$this->info['id']}"; + } + public function getDataZipPath() { return "/var/uoj_data/{$this->info['id']}.zip"; } @@ -672,6 +676,10 @@ class UOJProblem { return "{$this->getDataFolderPath()}/$name"; } + public function getUploadFilePath($name = '') { + return "{$this->getUploadFolderPath()}/$name"; + } + public function getResourcesFolderPath() { return UOJContext::storagePath() . "/problem_resources/" . $this->info['id']; } diff --git a/web/app/models/UOJProblemConfigure.php b/web/app/models/UOJProblemConfigure.php new file mode 100644 index 0000000..e99b261 --- /dev/null +++ b/web/app/models/UOJProblemConfigure.php @@ -0,0 +1,202 @@ + '自定义校验器', + 'ncmp' => 'ncmp: 单行或多行整数序列', + 'wcmp' => 'wcmp: 单行或多行字符串序列', + 'fcmp' => 'fcmp: 单行或多行数据(不忽略行末空格,但忽略文末回车)', + 'bcmp' => 'bcmp: 逐字节比较', + 'uncmp' => 'uncmp: 单行或多行整数集合', + 'yesno' => 'yesno: YES、NO 序列(不区分大小写)', + 'rcmp4' => 'rcmp4: 浮点数序列,绝对或相对误差在 1e-4 以内则视为答案正确', + 'rcmp6' => 'rcmp6: 浮点数序列,绝对或相对误差在 1e-6 以内则视为答案正确', + 'rcmp9' => 'rcmp9: 浮点数序列,绝对或相对误差在 1e-9 以内则视为答案正确', + ]; + + public static $supported_score_types = [ + 'int' => '整数,每个测试点的部分分向下取整', + 'real-0' => '整数,每个测试点的部分分四舍五入到整数', + 'real-1' => '实数,四舍五入到小数点后 1 位', + 'real-2' => '实数,四舍五入到小数点后 2 位', + 'real-3' => '实数,四舍五入到小数点后 3 位', + 'real-4' => '实数,四舍五入到小数点后 4 位', + 'real-5' => '实数,四舍五入到小数点后 5 位', + 'real-6' => '实数,四舍五入到小数点后 6 位', + 'real-7' => '实数,四舍五入到小数点后 7 位', + 'real-8' => '实数,四舍五入到小数点后 8 位', + ]; + + private static function getCardHeader($title) { + return << +
+
+ {$title} +
+
+ EOD; + } + + private static function getCardFooter() { + return << +
+
+ EOD; + } + + public function __construct(UOJProblem $problem) { + $this->problem = $problem; + $problem_conf = $this->problem->getProblemConf('data'); + if (!($problem_conf instanceof UOJProblemConf)) { + $problem_conf = new UOJProblemConf([]); + } + $this->problem_conf = $problem_conf; + + $this->href = "/problem/{$this->problem->info['id']}/manage/data"; + + $this->simple_form = new UOJForm('simple'); + + $this->simple_form->appendHTML(static::getCardHeader('基本信息')); + $this->addSelect($this->simple_form, 'use_builtin_judger', ['on' => '默认', 'off' => '自定义 Judger'], '测评逻辑', 'on'); + $this->addSelect($this->simple_form, 'use_builtin_checker', self::$supported_checkers, '比对函数', 'ncmp'); + $this->addSelect($this->simple_form, 'score_type', self::$supported_score_types, '测试点分数数值类型', 'int'); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('数据配置')); + $this->addNumberInput($this->simple_form, 'n_tests', '数据点个数', 10); + $this->addNumberInput($this->simple_form, 'n_ex_tests', '额外数据点个数', 0); + $this->addNumberInput($this->simple_form, 'n_sample_tests', '样例数据点个数', 0); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('文件配置')); + $this->addTextInput($this->simple_form, 'input_pre', '输入文件名称', ''); + $this->addTextInput($this->simple_form, 'input_suf', '输入文件后缀', ''); + $this->addTextInput($this->simple_form, 'output_pre', '输出文件名称', ''); + $this->addTextInput($this->simple_form, 'output_suf', '输出文件后缀', ''); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('运行时限制')); + $this->addTimeLimitInput($this->simple_form, 'time_limit', '时间限制', 1, ['help' => '单位为秒,至多三位小数。']); + $this->addNumberInput($this->simple_form, 'memory_limit', '内存限制', 256, ['help' => '单位为 MiB。']); + $this->addNumberInput($this->simple_form, 'output_limit', '输出长度限制', 64, ['help' => '单位为 MiB。']); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->succ_href = $this->href; + $this->simple_form->config['form']['class'] = 'row gy-3 mt-2'; + $this->simple_form->config['submit_container']['class'] = 'col-12 text-center mt-3'; + $this->simple_form->config['back_button']['href'] = $this->href; + $this->simple_form->config['back_button']['class'] = 'btn btn-secondary me-2'; + + $this->simple_form->handle = fn (&$vdata) => $this->onUpload($vdata); + } + + public function addSelect(UOJForm $form, $key, $options, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addSelect($key, [ + 'options' => $options, + 'label' => $label, + 'div_class' => 'row', + 'label_class' => 'col-form-label col-4', + 'select_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + ] + $cfg); + } + + public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'type' => 'number', + 'label' => $label, + 'div_class' => 'row', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + return validateInt($x) ? '' : '必须为一个整数'; + }, + ] + $cfg); + } + + public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'type' => 'number', + 'label' => $label, + 'input_attrs' => ['step' => 0.001], + 'div_class' => 'row', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + if (!validateUFloat($x)) { + return '必须为整数或小数,且值大于等于零'; + } elseif (round($x * 1000) != $x * 1000) { + return '至多包含三位小数'; + } else { + return ''; + } + }, + ] + $cfg); + } + + public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'label' => $label, + 'div_class' => 'row', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + return ctype_graph($x) ? '' : '必须仅包含除空格以外的可见字符'; + }, + ] + $cfg); + } + + public function runAtServer() { + $this->simple_form->runAtServer(); + } + + public function onUpload(array &$vdata) { + $conf = $this->problem_conf->conf; + + foreach (array_keys($this->conf_keys) as $key) { + $val = UOJRequest::post($key); + if ($key === 'use_builtin_judger') { + if ($val === 'off') { + unset($conf[$key]); + } else { + $conf[$key] = $val; + } + } elseif ($key === 'use_builtin_checker') { + if ($val === 'ownchk') { + unset($conf[$key]); + } else { + $conf[$key] = $val; + } + } else { + if ($val !== '') { + $conf[$key] = $val; + } + } + } + + $err = dataUpdateProblemConf($this->problem->info, $conf); + if ($err) { + UOJResponse::message('
' . $err . '
返回'); + } + } + + public function printHTML() { + $this->simple_form->printHTML(); + } +} diff --git a/web/app/route.php b/web/app/route.php index b276b93..ab0d726 100644 --- a/web/app/route.php +++ b/web/app/route.php @@ -27,6 +27,7 @@ Route::group( Route::any('/problem/{id}/manage/statement', '/problem_statement_manage.php'); Route::any('/problem/{id}/manage/permissions', '/problem_permissions_manage.php'); Route::any('/problem/{id}/manage/data', '/problem_data_manage.php'); + Route::any('/problem/{id}/manage/data/configure', '/problem_data_configure.php'); Route::any('/download/testlib.h', '/download.php?type=testlib.h'); Route::any('/download/problem/{id}/data.zip', '/download.php?type=problem'); Route::any('/download/problem/{id}/attachment.zip', '/download.php?type=attachment');