mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-22 12:58:40 +00:00
feat: add problems_solutions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
a5d632c21c
commit
44b3840e5f
@ -632,6 +632,28 @@ LOCK TABLES `problems_permissions` WRITE;
|
||||
/*!40000 ALTER TABLE `problems_permissions` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `problems_solutions`
|
||||
--
|
||||
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `problems_solutions` (
|
||||
`problem_id` int(11) NOT NULL,
|
||||
`blog_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`problem_id`, `blog_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Dumping data for table `problems_solutions`
|
||||
--
|
||||
|
||||
LOCK TABLES `problems_solutions` WRITE;
|
||||
/*!40000 ALTER TABLE `problems_solutions` DISABLE KEYS */;
|
||||
/*!40000 ALTER TABLE `problems_solutions` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
--
|
||||
-- Table structure for table `problems_tags`
|
||||
--
|
||||
|
@ -314,6 +314,9 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
|
||||
<?php if ($custom_test_requirement): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="#tab-custom-test" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-console"></span> <?= UOJLocale::get('problems::custom test') ?></a></li>
|
||||
<?php endif ?>
|
||||
<?php if (!$contest): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/solutions" role="tab"><?= UOJLocale::get('problems::solutions') ?></a></li>
|
||||
<?php endif ?>
|
||||
<?php if (hasProblemPermission($myUser, $problem)): ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/statement" role="tab"><?= UOJLocale::get('problems::manage') ?></a></li>
|
||||
<?php endif ?>
|
||||
@ -408,6 +411,14 @@ $('#contest-countdown').countdown(<?= $contest['end_time']->getTimestamp() - UOJ
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if (!$contest): ?>
|
||||
<li class="nav-item text-start">
|
||||
<a href="/problem/<?= $problem['id'] ?>/solutions" class="nav-link" role="tab">
|
||||
<i class="bi bi-journal-bookmark"></i>
|
||||
<?= UOJLocale::get('problems::solutions') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if (hasProblemPermission($myUser, $problem)): ?>
|
||||
<li class="nav-item text-start">
|
||||
<a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/statement" role="tab">
|
||||
|
280
web/app/controllers/problem_solutions.php
Normal file
280
web/app/controllers/problem_solutions.php
Normal file
@ -0,0 +1,280 @@
|
||||
<?php
|
||||
requirePHPLib('form');
|
||||
requirePHPLib('judger');
|
||||
|
||||
if (!Auth::check()) {
|
||||
become403Page(UOJLocale::get('need login'));
|
||||
}
|
||||
|
||||
if (!validateUInt($_GET['id']) || !($problem = queryProblemBrief($_GET['id']))) {
|
||||
become404Page();
|
||||
}
|
||||
|
||||
$problem_extra_config = getProblemExtraConfig($problem);
|
||||
$solution_viewable = hasViewSolutionPermission($problem_extra_config['view_solution_type'], $myUser, $problem);
|
||||
$solution_submittable = hasViewSolutionPermission($problem_extra_config['submit_solution_type'], $myUser, $problem);
|
||||
|
||||
if (!$solution_viewable) {
|
||||
become403Page();
|
||||
}
|
||||
|
||||
$REQUIRE_LIB['bootstrap5'] = '';
|
||||
|
||||
function removeSolutionForm($blog_id) {
|
||||
$res_form = new UOJForm("remove_solution_{$blog_id}");
|
||||
$res_form->addHidden("blog_id", $blog_id, function($blog_id) {
|
||||
global $myUser, $problem;
|
||||
|
||||
if (!validateUInt($blog_id)) {
|
||||
return '博客 ID 不是有效的数字';
|
||||
}
|
||||
|
||||
$blog = queryBlog($blog_id);
|
||||
if (!$blog) {
|
||||
return '博客不存在';
|
||||
}
|
||||
|
||||
if (!hasProblemPermission($myUser, $problem)) {
|
||||
if ($blog['poster'] != $myUser['username']) {
|
||||
return '您只能删除自己的题解';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$res_form->handle = function() {
|
||||
global $myUser, $problem;
|
||||
|
||||
$blog_id = $_POST["blog_id"];
|
||||
DB::query("delete from problems_solutions where problem_id = {$problem['id']} and blog_id = {$blog_id}");
|
||||
$blog = queryBlog($blog_id);
|
||||
|
||||
if ($blog['poster'] != $myUser['username']) {
|
||||
$blog_link = getBlogLink($blog['id']);
|
||||
$poster_user_link = getUserLink($blog['poster']);
|
||||
$admin_user_link = getUserLink($myUser['username']);
|
||||
$content = <<<EOD
|
||||
<p>{$poster_user_link} 您好:</p>
|
||||
<p>您为问题 <a href="/problem/{$problem['id']}">#{$problem['id']} ({$problem['title']})</a> 提交的题解 {$blog_link} 已经被管理员 {$admin_user_link} 移除。 </p>
|
||||
EOD;
|
||||
sendSystemMsg($blog['poster'], '题解移除通知', $content);
|
||||
}
|
||||
};
|
||||
$res_form->submit_button_config['margin_class'] = 'mt-0';
|
||||
$res_form->submit_button_config['class_str'] = 'btn btn-link text-decoration-none text-danger p-0';
|
||||
$res_form->submit_button_config['text'] = '移除';
|
||||
$res_form->submit_button_config['align'] = 'inline';
|
||||
|
||||
return $res_form;
|
||||
}
|
||||
|
||||
if ($solution_submittable) {
|
||||
$add_new_solution_form = new UOJForm('add_new_solution');
|
||||
$add_new_solution_form->addVInput('blog_id_2', 'text', '博客 ID', '',
|
||||
function ($x) {
|
||||
global $myUser, $problem, $solution_submittable;
|
||||
|
||||
if (!validateUInt($x)) {
|
||||
return 'ID 不合法';
|
||||
}
|
||||
|
||||
$blog = queryBlog($x);
|
||||
if (!$blog) {
|
||||
return '博客不存在';
|
||||
}
|
||||
|
||||
if (!isSuperUser($myUser)) {
|
||||
if ($blog['poster'] != $myUser['username']) {
|
||||
if ($blog['is_hidden']) {
|
||||
return '博客不存在';
|
||||
}
|
||||
|
||||
return '只能提交本人撰写的博客';
|
||||
}
|
||||
}
|
||||
|
||||
if ($blog['is_hidden']) {
|
||||
return '只能提交公开的博客';
|
||||
}
|
||||
|
||||
if (querySolution($problem['id'], $x)) {
|
||||
return '该题解已提交';
|
||||
}
|
||||
|
||||
if (!$solution_submittable) {
|
||||
return '您无权提交题解';
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
null
|
||||
);
|
||||
$add_new_solution_form->submit_button_config['text'] = '发布';
|
||||
$add_new_solution_form->submit_button_config['align'] = 'center';
|
||||
$add_new_solution_form->handle = function() {
|
||||
global $problem, $myUser;
|
||||
|
||||
$blog_id_2 = $_POST['blog_id_2'];
|
||||
$problem_id = $problem['id'];
|
||||
|
||||
DB::insert("insert into problems_solutions (problem_id, blog_id) values ({$problem_id}, {$blog_id_2})");
|
||||
};
|
||||
$add_new_solution_form->runAtServer();
|
||||
}
|
||||
|
||||
$pag_config = array('page_len' => 5);
|
||||
$pag_config['col_names'] = array('blog_id', 'content', 'poster', 'post_time', 'zan');
|
||||
$pag_config['table_name'] = "problems_solutions inner join blogs on problems_solutions.blog_id = blogs.id";
|
||||
$pag_config['cond'] = "problem_id = {$problem['id']} and is_hidden = 0";
|
||||
$pag_config['tail'] = "order by zan desc, post_time desc, id asc";
|
||||
$pag = new Paginator($pag_config);
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach ($pag->get() as $idx => $row) {
|
||||
$rows[$idx] = $row;
|
||||
if ($row['poster'] == $myUser['username'] || hasProblemPermission($myUser, $problem)) {
|
||||
$removeForm = removeSolutionForm($row['blog_id']);
|
||||
$removeForm->runAtServer();
|
||||
$rows[$idx]['removeForm'] = $removeForm;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
$REQUIRE_LIB['mathjax'] = '';
|
||||
$REQUIRE_LIB['hljs'] = '';
|
||||
?>
|
||||
|
||||
<?php echoUOJPageHeader(UOJLocale::get('problems::solutions') . ' - ' . HTML::stripTags($problem['title'])) ?>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- Left col -->
|
||||
<div class="col-lg-9">
|
||||
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-body">
|
||||
<h1 class="h2 card-title text-center">
|
||||
#<?= $problem['id']?>. <?= $problem['title'] ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<li class="list-group-item">
|
||||
<?php $poster = queryUser($row['poster']); ?>
|
||||
<div class="mb-3">
|
||||
<span class="me-2 d-inline-block">
|
||||
<a class="text-decoration-none" href="<?= HTML::url('/user/profile/'.$poster['username']) ?>">
|
||||
<img src="<?= HTML::avatar_addr($poster, 64) ?>" width="32" height="32" class="rounded" />
|
||||
</a>
|
||||
<?= getUserLink($poster['username']) ?>
|
||||
</span>
|
||||
<span class="text-muted small d-inline-block">
|
||||
<?= UOJLocale::get('post time') ?>:
|
||||
<time class="text-muted"><?= $row['post_time'] ?></time>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="markdown-body">
|
||||
<?= $row['content'] ?>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-end">
|
||||
<?php if (isset($row['removeForm'])): ?>
|
||||
<?php $row['removeForm']->printHTML(); ?>
|
||||
<?php endif ?>
|
||||
<a class="text-decoration-none" href="/blogs/<?= $row['blog_id'] ?>">在 Ta 的博客上查看</a>
|
||||
<?= getClickZanBlock('B', $row['blog_id'], $row['zan']) ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
<?php if ($pag->isEmpty()): ?>
|
||||
<div class="text-center py-2">
|
||||
暂无题解
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="text-center">
|
||||
<?= $pag->pagination(); ?>
|
||||
</div>
|
||||
|
||||
<!-- End left col -->
|
||||
</div>
|
||||
|
||||
<!-- Right col -->
|
||||
<aside class="col 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/<?= $problem['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="#" class="nav-link active" role="tab">
|
||||
<i class="bi bi-journal-bookmark"></i>
|
||||
<?= UOJLocale::get('problems::solutions') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php if (hasProblemPermission($myUser, $problem)): ?>
|
||||
<li class="nav-item text-start">
|
||||
<a class="nav-link" href="/problem/<?= $problem['id'] ?>/manage/statement" role="tab">
|
||||
<i class="bi bi-sliders"></i>
|
||||
<?= UOJLocale::get('problems::manage') ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card card-default mb-2">
|
||||
<ul class="nav nav-fill flex-column">
|
||||
<li class="nav-item text-start">
|
||||
<a class="nav-link" href="<?= HTML::url("/download.php?type=problem&id={$problem['id']}") ?>">
|
||||
<i class="bi bi-hdd-stack"></i>
|
||||
测试数据
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-start">
|
||||
<a class="nav-link" href="<?= HTML::url("/download.php?type=attachment&id={$problem['id']}") ?>">
|
||||
<i class="bi bi-download"></i>
|
||||
附件下载
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-start">
|
||||
<a class="nav-link" href="/problem/<?= $problem['id'] ?>/statistics">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
<?= UOJLocale::get('problems::statistics') ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card-footer bg-transparent">
|
||||
评价:<?= getClickZanBlock('P', $problem['id'], $problem['zan']) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isset($add_new_solution_form)): ?>
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-header bg-transparent">
|
||||
增加题解
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php $add_new_solution_form->printHTML(); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php uojIncludeView('sidebar', array()); ?>
|
||||
|
||||
<!-- End right col -->
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
@ -430,7 +430,7 @@ EOD
|
||||
} else {
|
||||
echo '<div class="text-', $this->submit_button_config['align'], '">';
|
||||
}
|
||||
echo '<button type="submit" id="button-submit-', $this->form_name, '" name="submit-', $this->form_name, '" value="', $this->form_name, '" class="mt-2 ', $this->submit_button_config['class_str'], '">', $this->submit_button_config['text'], '</button>';
|
||||
echo '<button type="submit" id="button-submit-', $this->form_name, '" name="submit-', $this->form_name, '" value="', $this->form_name, '" class="', (isset($this->submit_button_config['margin_class']) ? $this->submit_button_config['margin_class'] : 'mt-2'), ' ', $this->submit_button_config['class_str'], '">', $this->submit_button_config['text'], '</button>';
|
||||
if ($this->submit_button_config['align'] == 'offset') {
|
||||
echo '</div>';
|
||||
}
|
||||
|
@ -25,6 +25,19 @@ function hasViewPermission($str, $user, $problem, $submission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasViewSolutionPermission($str, $user, $problem) {
|
||||
if (isSuperUser($user)) {
|
||||
return true;
|
||||
}
|
||||
if ($str == 'ALL') {
|
||||
return true;
|
||||
}
|
||||
if ($str == 'ALL_AFTER_AC') {
|
||||
return hasAC($user, $problem);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasContestPermission($user, $contest) {
|
||||
if ($user == null) {
|
||||
return false;
|
||||
@ -87,6 +100,10 @@ function queryProblemInList($list_id, $problem_id) {
|
||||
return DB::selectFirst("select * from lists_problems where list_id='$blog_id' and problem_id='$problem_id'", MYSQLI_ASSOC);
|
||||
}
|
||||
|
||||
function querySolution($problem_id, $blog_id) {
|
||||
return DB::selectFirst("select * from problems_solutions where blog_id='$blog_id' and problem_id='$problem_id'", MYSQLI_ASSOC);
|
||||
}
|
||||
|
||||
function queryContestProblemRank($contest, $problem) {
|
||||
if (!DB::selectFirst("select * from contests_problems where contest_id = {$contest['id']} and problem_id = {$problem['id']}")) {
|
||||
return null;
|
||||
|
@ -181,7 +181,9 @@ function getProblemExtraConfig($problem) {
|
||||
$default_extra_config = array(
|
||||
'view_content_type' => 'ALL',
|
||||
'view_all_details_type' => 'ALL',
|
||||
'view_details_type' => 'ALL'
|
||||
'view_details_type' => 'ALL',
|
||||
'view_solution_type' => 'ALL',
|
||||
'submit_solution_type' => 'ALL_AFTER_AC',
|
||||
);
|
||||
|
||||
mergeConfig($extra_config, $default_extra_config);
|
||||
|
@ -50,6 +50,7 @@ return [
|
||||
'title' => 'Title',
|
||||
'content' => 'Content',
|
||||
'time' => 'Time',
|
||||
'post time' => 'Post time',
|
||||
'none' => 'None',
|
||||
'user profile' => 'User profile',
|
||||
'send private message' => 'Send private message',
|
||||
|
@ -50,6 +50,7 @@ return [
|
||||
'title' => '标题',
|
||||
'content' => '内容',
|
||||
'time' => '时间',
|
||||
'post time' => '发布时间',
|
||||
'none' => '无',
|
||||
'user profile' => '用户信息',
|
||||
'send private message' => '发送私信',
|
||||
|
@ -17,6 +17,7 @@ return [
|
||||
'statement' => 'Statement',
|
||||
'custom test' => 'Custom Test',
|
||||
'manage' => 'Manage',
|
||||
'solutions' => 'Solutions',
|
||||
'statistics' => 'Statistics',
|
||||
'run' => 'Run',
|
||||
'source code' => 'Source code',
|
||||
|
@ -18,6 +18,7 @@ return [
|
||||
'manage' => '管理',
|
||||
'submissions statistics' => '统计提交情况',
|
||||
'statistics' => '统计',
|
||||
'solutions' => '题解',
|
||||
'run' => '运行',
|
||||
'source code' => '源文件',
|
||||
'text file' => '文本文件',
|
||||
|
@ -14,6 +14,7 @@ Route::group([
|
||||
Route::any('/problems', '/problem_set.php');
|
||||
Route::any('/problems/template', '/problem_set.php?tab=template');
|
||||
Route::any('/problem/{id}', '/problem.php');
|
||||
Route::any('/problem/{id}/solutions', '/problem_solutions.php');
|
||||
Route::any('/problem/{id}/statistics', '/problem_statistics.php');
|
||||
Route::any('/problem/{id}/manage/statement', '/problem_statement_manage.php');
|
||||
Route::any('/problem/{id}/manage/managers', '/problem_managers_manage.php');
|
||||
|
@ -16,7 +16,7 @@
|
||||
?>
|
||||
<link rel="stylesheet" type="text/css" href="<?= HTML::url('/css/markdown.css') ?>">
|
||||
<h2><?= $extra_text ?><a class="header-a" href="<?= HTML::blog_url(UOJContext::userid(), '/post/'.$blog['id']) ?>"><?= $blog['title'] ?></a></h2>
|
||||
<div><?= $blog['post_time'] ?> <strong>By</strong> <?= getUserLink($blog['poster']) ?></div>
|
||||
<div><?= $blog['post_time'] ?> <strong>By</strong> <?= getUserLink($blog['poster']) ?> (<strong>博客 ID: </strong> <?= $blog['id'] ?>)</div>
|
||||
<?php if (!$show_title_only): ?>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
|
@ -160,3 +160,56 @@ label {
|
||||
.markdown-body table th[align='right'] {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
form.uoj-form-inline {
|
||||
display: inline-block;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
form.uoj-form-narrow label.col-sm-2 {
|
||||
max-width: 100% !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
form.uoj-form-narrow div.col-sm-3 {
|
||||
max-width: 100% !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
form.form-horizontal {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed div.form-group {
|
||||
display: inline-block;
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed div.form-group label.col-sm-2 {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
width: 6em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed div.form-group div.col-sm-3 {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
width: 16em;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed div.text-center,
|
||||
form.uoj-form-compressed div.text-compressed,
|
||||
form.uoj-form-compressed button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
form.uoj-form-compressed button {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user