S2OJ/web/app/controllers/problem_statement_manage.php

405 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
requirePHPLib('form');
Auth::check() || redirectToLogin();
UOJProblem::init(UOJRequest::get('id')) || UOJResponse::page404();
UOJProblem::cur()->userCanManage(Auth::user()) || UOJResponse::page403();
$tabs_info = [
'statement' => [
'name' => '题面',
'url' => UOJProblem::cur()->getUri('/manage/statement'),
],
'permissions' => [
'name' => '权限',
'url' => UOJProblem::cur()->getUri('/manage/permissions'),
],
];
if (UOJProblem::info('type') === 'local') {
$tabs_info['data'] = [
'name' => '数据',
'url' => UOJProblem::cur()->getUri('/manage/data'),
];
} else if (UOJProblem::info('type') === 'remote') {
//
}
$problem_content = UOJProblem::cur()->queryContent();
$problem_editor = new UOJBlogEditor();
$problem_editor->name = 'problem';
$problem_editor->blog_url = '/problem/' . UOJProblem::info('id');
$problem_editor->cur_data = [
'title' => UOJProblem::info('title'),
'content_md' => $problem_content['statement_md'],
'content' => $problem_content['statement'],
'tags' => UOJProblem::cur()->queryTags(),
'is_hidden' => UOJProblem::info('is_hidden')
];
$problem_editor->label_text = array_merge($problem_editor->label_text, [
'view blog' => '查看题目',
'blog visibility' => '题目可见性'
]);
$problem_editor->save = function ($data) {
DB::update([
"update problems",
"set", ["title" => $data['title']],
"where", ["id" => UOJProblem::info('id')]
]);
DB::update([
"update problems_contents",
"set", [
"statement" => $data['content'],
"statement_md" => $data['content_md']
], "where", ["id" => UOJProblem::info('id')]
]);
UOJProblem::cur()->updateTags($data['tags']);
if ($data['is_hidden'] != UOJProblem::info('is_hidden')) {
DB::update([
"update problems",
"set", ["is_hidden" => $data['is_hidden']],
"where", ["id" => UOJProblem::info('id')]
]);
DB::update([
"update submissions",
"set", ["is_hidden" => $data['is_hidden']],
"where", ["problem_id" => UOJProblem::info('id')]
]);
DB::update([
"update hacks",
"set", ["is_hidden" => $data['is_hidden']],
"where", ["problem_id" => UOJProblem::info('id')]
]);
}
};
$problem_editor->runAtServer();
$difficulty_form = new UOJForm('difficulty');
$difficulty_form->addSelect('difficulty', [
'div_class' => 'flex-grow-1',
'options' => [-1 => '暂无评定'] + array_combine(UOJProblem::$difficulty, UOJProblem::$difficulty),
'default_value' => UOJProblem::info('difficulty'),
]);
$difficulty_form->config['form']['class'] = 'd-flex';
$difficulty_form->config['submit_container']['class'] = 'ms-2';
$difficulty_form->handle = function () {
DB::update([
"update problems",
"set", [
"difficulty" => $_POST['difficulty'],
],
"where", [
"id" => UOJProblem::info('id'),
],
]);
};
$difficulty_form->runAtServer();
if (UOJProblem::info('type') == 'remote') {
$remote_online_judge = UOJProblem::cur()->getExtraConfig('remote_online_judge');
$remote_problem_id = UOJProblem::cur()->getExtraConfig('remote_problem_id');
$remote_provider = UOJRemoteProblem::$providers[$remote_online_judge];
$re_crawl_form = new UOJForm('re_crawl');
$re_crawl_form->appendHTML(<<<EOD
<ul>
<li>远程题库:{$remote_provider['name']}</li>
<li>远程题号:{$remote_problem_id}</li>
</ul>
EOD);
$re_crawl_form->config['submit_button']['text'] = '重新爬取';
$re_crawl_form->handle = function () use ($remote_online_judge, $remote_problem_id) {
try {
$data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id);
} catch (Exception $e) {
$data = null;
UOJLog::error($e->getMessage());
}
if ($data === null) {
UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。<a href="">返回</a>');
}
if ($data['difficulty'] == -1) {
$data['difficulty'] = UOJProblem::info('difficulty');
}
$submission_requirement = UOJRemoteProblem::getSubmissionRequirements($remote_online_judge);
$enc_submission_requirement = json_encode($submission_requirement);
$extra_config = [
'remote_online_judge' => $remote_online_judge,
'remote_problem_id' => $remote_problem_id,
'time_limit' => $data['time_limit'],
'memory_limit' => $data['memory_limit'],
];
$enc_extra_config = json_encode($extra_config, JSON_FORCE_OBJECT);
DB::update([
"update problems",
"set", [
"title" => $data['title'],
"submission_requirement" => $enc_submission_requirement,
"extra_config" => $enc_extra_config,
"difficulty" => $data['difficulty'] ?: -1,
],
"where", [
"id" => UOJProblem::info('id'),
],
]);
if ($data['type'] == 'pdf') {
file_put_contents(UOJContext::storagePath() . "/problem_resources/" . UOJProblem::info('id') . "/statement.pdf", $data['pdf_data']);
$data['statement'] = '<div data-pdf data-src="/problem/' . UOJProblem::info('id') . '/resources/statement.pdf"></div>' . "\n" . $data['statement'];
}
DB::update([
"update problems_contents",
"set", [
"remote_content" => HTML::purifier(['a' => ['target' => 'Enum#_blank']])->purify($data['statement']),
],
"where", [
"id" => UOJProblem::info('id'),
],
]);
UOJRemoteProblem::downloadImagesInRemoteContent(UOJProblem::info('id'));
redirectTo(UOJProblem::cur()->getUri());
};
$re_crawl_form->runAtServer();
$convert_local_form = new UOJForm('convert_local');
$convert_local_form->handle = function () {
DB::update([
"update problems",
"set", [
"type" => 'local',
"submission_requirement" => "{}",
"extra_config" => "{}",
],
"where", [
"id" => UOJProblem::info('id'),
],
]);
DB::update([
"update problems_contents",
"set", [
"remote_content" => '',
],
"where", [
"id" => UOJProblem::info('id'),
],
]);
};
$convert_local_form->config['submit_container']['class'] = '';
$convert_local_form->config['submit_button']['class'] = 'btn btn-danger';
$convert_local_form->config['submit_button']['text'] = '将本题转换为本地题目(不可逆)';
$convert_local_form->config['confirm']['text'] = '您真的要*不可逆*地将本题转换为本地题目吗?';
$convert_local_form->runAtServer();
}
?>
<?php echoUOJPageHeader('题面管理 - ' . HTML::stripTags(UOJProblem::info('title'))) ?>
<div class="row">
<!-- left col -->
<div class="col-lg-9">
<h1>
<?= UOJProblem::cur()->getTitle(['with' => 'id']) ?> 管理
</h1>
<div class="my-3">
<?= HTML::tablist($tabs_info, 'statement', 'nav-pills') ?>
</div>
<div class="card card-default">
<div class="card-body">
<?php $problem_editor->printHTML() ?>
</div>
</div>
<!-- 提示信息 -->
<div class="card mt-3">
<div class="card-body">
<h2 class="h3 card-title">提示</h2>
<ol>
<li>请勿引用不稳定的外部资源(如来自个人服务器的图片或文档等),可以上传至 <a href="<?= UOJProblem::cur()->getUri('/resources') ?>">题目资源</a> 中,以便备份及后期维护;</li>
<li>请勿在题面中直接插入大段 HTML 代码,这可能会破坏页面的显示,可以考虑使用 <a class="text-decoration-none" href="/apps/html2markdown" target="_blank">转换工具</a> 转换后再作修正;</li>
<li>图片上传推荐使用 <a class="text-decoration-none" href="/apps/image_hosting" target="_blank">S2OJ 图床</a>,以免后续产生外链图片大量失效的情况。</li>
</ol>
<p class="card-text">
更多内容请查看 S2OJ 用户手册中的「<a class="text-decoration-none" href="https://s2oj.github.io/#/manage/problem?id=%e4%bc%a0%e9%a2%98%e6%8c%87%e5%bc%95">传题指引</a>」部分。
</p>
</div>
</div>
</div>
<!-- right col -->
<aside class="col-lg-3 mt-3 mt-lg-0">
<div class="card card-default mb-2">
<ul class="nav nav-pills nav-fill flex-column" role="tablist">
<li class="nav-item text-start">
<a href="/problem/<?= UOJProblem::info('id') ?>" class="nav-link" role="tab">
<i class="bi bi-journal-text"></i>
<?= UOJLocale::get('problems::statement') ?>
</a>
</li>
<li class="nav-item text-start">
<a href="/problem/<?= UOJProblem::info('id') ?>#submit" class="nav-link" role="tab">
<i class="bi bi-upload"></i>
<?= UOJLocale::get('problems::submit') ?>
</a>
</li>
<li class="nav-item text-start">
<a href="/problem/<?= UOJProblem::info('id') ?>/solutions" class="nav-link" role="tab">
<i class="bi bi-journal-bookmark"></i>
<?= UOJLocale::get('problems::solutions') ?>
</a>
</li>
<li class="nav-item text-start">
<a class="nav-link" href="/submissions?problem_id=<?= UOJProblem::info('id') ?>">
<i class="bi bi-list-ul"></i>
<?= UOJLocale::get('submissions') ?>
</a>
</li>
<li class="nav-item text-start">
<a class="nav-link" href="/problem/<?= UOJProblem::info('id') ?>/statistics">
<i class="bi bi-graph-up"></i>
<?= UOJLocale::get('problems::statistics') ?>
</a>
</li>
<li class="nav-item text-start">
<a class="nav-link active" href="#" role="tab">
<i class="bi bi-sliders"></i>
<?= UOJLocale::get('problems::manage') ?>
</a>
</li>
</ul>
</div>
<div class="card mt-3">
<div class="card-header fw-bold">
标签填充
</div>
<div class="card-body">
<script>
function fillTag(tags) {
if (typeof tags === 'string') tags = [tags];
tags = tags.map(tag => tag.trim()).filter(Boolean);
var originalTags = $('#input-problem_tags')
.val()
.replace(//g, ',')
.split(',')
.map(tag => tag.trim())
.filter(Boolean);
var newTagsSet = new Set(originalTags.concat(tags));
$('#input-problem_tags').val(Array.from(newTagsSet.values()).join(', '));
$('#input-problem_tags').trigger('input');
}
</script>
<div class="row row-cols-4 row-cols-lg-2 g-2">
<?php foreach (UOJProblem::$categories as $category => $tags) : ?>
<?php $category_id = uniqid('category-'); ?>
<div class="d-inline-block" id="category-container-<?= $category_id ?>">
<button id="category-button-<?= $category_id ?>" class="btn btn-sm btn-light w-100" type="button"><?= $category ?></button>
</div>
<script>
$(document).ready(function() {
bootstrap.Popover.jQueryInterface.call($('#category-button-<?= $category_id ?>'), {
container: $('#category-container-<?= $category_id ?>'),
html: true,
placement: 'left',
animation: false,
trigger: 'manual',
fallbackPlacements: ['bottom', 'right'],
content: [
<?php foreach ($tags as $tag) : ?> '<?= $tag ?>', <?php endforeach ?>
].map(tag => ('<button class="btn btn-sm btn-light d-inline-block mr-1 mb-1" onclick="fillTag([\'<?= $category ?>\', \'' + tag + '\'])">' + tag + '</button>')).join(' '),
sanitizeFn(content) {
return content;
},
}).on("mouseenter", function() {
var _this = this;
$(this).popover("show");
$(this).siblings(".popover").on("mouseleave", function() {
$(_this).popover('hide');
});
}).on("mouseleave", function() {
var _this = this;
var check_popover_status = function() {
setTimeout(function() {
if (!$(".popover:hover").length) {
$(_this).popover("hide")
} else {
check_popover_status();
}
}, 50);
};
check_popover_status();
});
});
</script>
<?php endforeach ?>
</div>
</div>
<div class="card-footer text-muted small bg-transparent">
将鼠标悬浮至主分类上,点击弹出框中的对应标签即可将其填充至题目标签中。
</div>
</div>
<div class="card mt-3">
<div class="card-header fw-bold">
题目难度
</div>
<div class="card-body">
<?php $difficulty_form->printHTML() ?>
</div>
</div>
<?php if (UOJProblem::info('type') == 'remote') : ?>
<div class="card mt-3">
<div class="card-header fw-bold">
重新爬取题目信息
</div>
<div class="card-body">
<?php $re_crawl_form->printHTML() ?>
</div>
</div>
<div class="card mt-3 border-danger">
<div class="card-header fw-bold text-bg-danger border-danger">
转换为本地题目
</div>
<div class="card-body border-danger">
<?php $convert_local_form->printHTML() ?>
</div>
<div class="card-footer bg-transparent small text-muted border-danger">
转换为本地题目之后可以上传自行准备的测试数据进行评测。转换后不再将代码提交至远端 OJ 进行评测。该操作不可逆。
</div>
</div>
<?php endif ?>
</aside>
</div>
<?php echoUOJPageFooter() ?>