refactor(contest/self_reviews): allow admins edit participants' self_reviews
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2023-02-07 16:52:51 +08:00
parent 58ba28d9b9
commit 946ea8283f
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
2 changed files with 154 additions and 113 deletions

View File

@ -236,37 +236,35 @@ if ($cur_tab == 'dashboard') {
$reply_question = null; $reply_question = null;
} }
} elseif ($cur_tab == 'self_reviews') { } elseif ($cur_tab == 'self_reviews') {
if (UOJContest::cur()->userHasMarkedParticipated(Auth::user())) { $self_reviews_update_form = new UOJForm('self_reviews_update');
$self_reviews_update_form = new UOJForm('self_review_update');
$self_reviews_update_form->config['ctrl_enter_submit'] = true; $self_reviews_update_form->config['ctrl_enter_submit'] = true;
$self_reviews_update_form->addHidden('self_reviews_update__username', '', function ($username, &$vdata) {
if (!validateUsername($username)) {
return '无效用户名';
}
$contest_problems = DB::selectAll([ if ($username != Auth::id() && !UOJContest::cur()->userCanManage(Auth::user())) {
return '权限不足';
}
return '';
}, null);
$contest_problems = array_map(fn ($row) => UOJContestProblem::query($row['problem_id']), DB::selectAll([
"select problem_id", "select problem_id",
"from", "contests_problems", "from", "contests_problems",
"where", ["contest_id" => $contest['id']], "where", ["contest_id" => $contest['id']],
"order by level, problem_id" "order by level, problem_id"
]); ]));
for ($i = 0; $i < count($contest_problems); $i++) {
$contest_problems[$i]['problem'] = UOJContestProblem::query($contest_problems[$i]['problem_id']);
}
for ($i = 0; $i < count($contest_problems); $i++) { foreach ($contest_problems as $cp) {
$content = DB::selectSingle([ $self_reviews_update_form->addTextArea('self_reviews_update__problem_' . $cp->getLetter(), [
"select content",
"from", "contests_reviews",
"where", [
"contest_id" => $contest['id'],
"problem_id" => $contest_problems[$i]['problem_id'],
"poster" => Auth::id(),
],
]);
$self_reviews_update_form->addTextArea('self_review_update__problem_' . $contest_problems[$i]['problem']->getLetter(), [
'div_class' => 'mb-3', 'div_class' => 'mb-3',
'label' => '<b>' . $contest_problems[$i]['problem']->getLetter() . '</b>: ' . $contest_problems[$i]['problem']->info['title'], 'label' => $cp->getTitle(['with' => 'letter']),
'default_value' => $content, 'default_value' => '',
'validator_php' => function ($content) { 'validator_php' => function ($content) {
if (strlen($content) > 200) { if (strlen($content) > 200) {
return '总结不能超过200字'; return '长度超过限制';
} }
return ''; return '';
@ -274,59 +272,49 @@ if ($cur_tab == 'dashboard') {
]); ]);
} }
$content = DB::selectSingle([ $self_reviews_update_form->addTextArea('self_reviews_update__overall', [
"select content",
"from", "contests_reviews",
"where", [
"contest_id" => $contest['id'],
"problem_id" => -1,
"poster" => Auth::id(),
],
]);
$self_reviews_update_form->addTextArea('self_review_update__overall', [
'label' => '比赛总结', 'label' => '比赛总结',
'default_value' => $content,
'validator_php' => function ($content) { 'validator_php' => function ($content) {
if (strlen($content) > 200) { if (strlen($content) > 300) {
return '总结不能超过200字'; return '长度超过限制';
} }
return ''; return '';
}, },
]); ]);
$self_reviews_update_form->handle = function () use ($contest, $contest_problems) { $self_reviews_update_form->handle = function (&$vdata) use ($contest_problems) {
for ($i = 0; $i < count($contest_problems); $i++) { foreach ($contest_problems as $cp) {
if (isset($_POST['self_review_update__problem_' . $contest_problems[$i]['problem']->getLetter()])) { DB::update([
DB::query([
"replace into contests_reviews", "replace into contests_reviews",
"(contest_id, problem_id, poster, content)", DB::bracketed_fields([
"contest_id",
"problem_id",
"poster",
"content"
]),
"values", DB::tuple([ "values", DB::tuple([
$contest['id'], UOJContest::info('id'),
$contest_problems[$i]['problem_id'], $cp->info['id'],
Auth::id(), $_POST['self_reviews_update__username'],
$_POST['self_review_update__problem_' . $contest_problems[$i]['problem']->getLetter()], $_POST['self_reviews_update__problem_' . $cp->getLetter()],
]), ]),
]); ]);
} }
}
if (isset($_POST['self_review_update__overall'])) { DB::update([
DB::query([
"replace into contests_reviews", "replace into contests_reviews",
"(contest_id, problem_id, poster, content)", "(contest_id, problem_id, poster, content)",
"values", DB::tuple([ "values", DB::tuple([
$contest['id'], UOJContest::info('id'),
-1, -1,
Auth::id(), $_POST['self_reviews_update__username'],
$_POST['self_review_update__overall'], $_POST['self_reviews_update__overall'],
]), ]),
]); ]);
}
}; };
$self_reviews_update_form->runAtServer(); $self_reviews_update_form->runAtServer();
}
} }
function echoDashboard() { function echoDashboard() {
@ -469,9 +457,12 @@ function echoStandings($is_after_contest_query = false) {
} }
function echoSelfReviews() { function echoSelfReviews() {
global $contest; global $contest, $self_reviews_update_form;
uojIncludeView('contest-reviews', ['contest' => $contest] + UOJContest::cur()->queryResult()); uojIncludeView('contest-reviews', [
'contest' => $contest,
'self_reviews_update_form' => $self_reviews_update_form,
] + UOJContest::cur()->queryResult());
} }
?> ?>
<?php echoUOJPageHeader($tabs_info[$cur_tab]['name'] . ' - ' . UOJContest::info('name') . ' - ' . UOJLocale::get('contests::contest')) ?> <?php echoUOJPageHeader($tabs_info[$cur_tab]['name'] . ' - ' . UOJContest::info('name') . ' - ' . UOJLocale::get('contests::contest')) ?>
@ -538,17 +529,7 @@ function echoSelfReviews() {
</div> </div>
</div> </div>
<?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings') : ?> <?php if ($cur_tab == 'standings' || $cur_tab == 'after_contest_standings' || $cur_tab == 'self_reviews') : ?>
<?php elseif ($cur_tab == 'self_reviews') : ?>
<?php if (isset($self_reviews_update_form)) : ?>
<hr />
<div class="col-md-6">
<h4>修改我的赛后总结</h4>
<div class="small">赛后总结支持 Markdown 语法。</div>
<?php $self_reviews_update_form->printHTML(); ?>
</div>
<?php endif ?>
<?php else : ?> <?php else : ?>
<div class="d-md-none"> <div class="d-md-none">
<hr /> <hr />

View File

@ -5,7 +5,9 @@ $parsedown = HTML::parsedown(['username_with_color' => true]);
$purifier = HTML::purifier_inline(); $purifier = HTML::purifier_inline();
foreach ($contest_data['people'] as $person) { foreach ($contest_data['people'] as $person) {
$reviews[$person[0]] = []; $reviews[$person[0]] = [
'_can_edit' => Auth::id() == $person[0] || UOJContest::cur()->userCanManage(Auth::user()),
];
foreach ($contest_data['problems'] as $problem) { foreach ($contest_data['problems'] as $problem) {
$content = DB::selectSingle([ $content = DB::selectSingle([
@ -18,7 +20,8 @@ foreach ($contest_data['people'] as $person) {
], ],
]); ]);
$reviews[$person[0]][$problem] = $purifier->purify($parsedown->line($content)); $reviews[$person[0]][$problem]['raw'] = $content;
$reviews[$person[0]][$problem]['html'] = $purifier->purify($parsedown->line($content));
} }
$content = DB::selectSingle([ $content = DB::selectSingle([
@ -31,19 +34,34 @@ foreach ($contest_data['people'] as $person) {
], ],
]); ]);
$reviews[$person[0]]['all'] = $purifier->purify($parsedown->line($content)); $reviews[$person[0]]['_all']['raw'] = $content;
$reviews[$person[0]]['_all']['html'] = $purifier->purify($parsedown->line($content));
} }
?> ?>
<div id="standings"></div> <div id="standings"></div>
<div class="modal fade" id="UpdateSelfReviewsModal" tabindex="-1" aria-labelledby="UpdateSelfReviewsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="UpdateSelfReviewsModalLabel">更新赛后总结</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<?php $self_reviews_update_form->printHTML(); ?>
</div>
</div>
</div>
</div>
<script type="text/javascript"> <script type="text/javascript">
var contest_id = <?= $contest['id'] ?>; var contest_id = <?= $contest['id'] ?>;
var standings = <?= json_encode($standings) ?>; var standings = <?= json_encode($standings) ?>;
var score = <?= json_encode($score) ?>; var score = <?= json_encode($score) ?>;
var problems = <?= json_encode($contest_data['problems']) ?>; var problems = <?= json_encode($contest_data['problems']) ?>;
var reviews = <?= json_encode($reviews) ?>; var reviews = <?= json_encode($reviews, JSON_FORCE_OBJECT) ?>;
var standings_config = <?= json_encode(isset($standings_config) ? $standings_config : ['_config' => true]) ?>; var standings_config = <?= json_encode(isset($standings_config) ? $standings_config : [], JSON_FORCE_OBJECT) ?>;
$(document).ready(function() { $(document).ready(function() {
$("#standings").long_table( $("#standings").long_table(
@ -59,33 +77,75 @@ foreach ($contest_data['people'] as $person) {
'<th style="width:16em">赛后总结</th>' + '<th style="width:16em">赛后总结</th>' +
'</tr>', '</tr>',
function(row) { function(row) {
var col_tr = '<tr>'; if (reviews[row[2][0]]['_can_edit']) {
col_tr += '<td>' + row[3] + '</td>'; var button_update_review = $('<button class="btn btn-sm btn-outline-secondary ms-2" />').append('<i class="bi bi-pencil"></i>');
col_tr += '<td>' + getUserLink(row[2][0], row[2][1]) + '</td>';
col_tr += '<td>' + '<span class="uoj-score" data-max="' + problems.length * 100 + '" style="color:' + getColOfScore(row[0] / problems.length) + '">' + row[0] + '</span>' + '</td>';
for (var i = 0; i < problems.length; i++) {
col_tr += '<td class="align-text-top">';
col = score[row[2][0]][i];
if (col != undefined) {
col_tr += '<div>';
if (col[2]) col_tr += '<a href="/submission/' + col[2] + '" class="uoj-score" style="color:' + getColOfScore(col[0]) + '">' + col[0] + '</a>'; button_update_review.click(function() {
else col_tr += '<span class="uoj-score" style="color:' + getColOfScore(col[0]) + '">' + col[0] + '</span>'; $('#UpdateSelfReviewsModalLabel').text('更新 ' + row[2][0] + ' 的赛后总结');
$('#input-self_reviews_update__username').val(row[2][0]);
col_tr += '</div>'; $.map(problems, function(col, idx) {
$('#input-self_reviews_update__problem_' + String.fromCharCode('A'.charCodeAt(0) + idx)).val(reviews[row[2][0]][col]['raw'] || '');
});
$('#input-self_reviews_update__overall').val(reviews[row[2][0]]['_all']['raw'] || '');
$('#UpdateSelfReviewsModal').modal('show');
});
} else { } else {
col_tr += '<div>&nbsp;</div>'; var button_update_review = '';
}
var res = $('<tr />')
.append($('<td />').append(row[3]))
.append($('<td />').append(getUserLink(row[2][0], row[2][1])).append(button_update_review))
.append($('<td />').append($('<span class="uoj-score" />').attr('data-max', problems.length * 100).css('color', getColOfScore(row[0] / problems.length)).append(row[0])));
for (var i = 0; i < problems.length; i++) {
var td = $('<td class="align-text-top" />');
col = score[row[2][0]][i];
if (col != undefined) {
td.append($('<div />').append($('<a class="uoj-score" />').attr('href', '/submission/' + col[2]).attr('data-max', 100).css('color', getColOfScore(col[0])).append(col[0])));
if (col[2]) {
td.append(
$('<div />').append(
$('<a class="uoj-score" />')
.attr('href', '/submission/' + col[2])
.attr('data-max', 100)
.css('color', getColOfScore(col[0]))
.append(col[0])
)
);
} else {
td.append(
$('<div />').append(
$('<span class="uoj-score" />')
.data('max', 100)
.css('color', getColOfScore(col[0]))
.append(col[0])
)
);
}
} else {
td.append($('<div />').append('&nbsp;'));
} }
if (reviews[row[2][0]][problems[i]]) { if (reviews[row[2][0]][problems[i]]) {
col_tr += '<div class="mt-2 pt-2 border-top">' + reviews[row[2][0]][problems[i]] + '</div>'; td.append($('<div class="mt-2 pt-2 border-top" />').append(reviews[row[2][0]][problems[i]]['html']));
} }
col_tr += '</td>'; res.append(td);
} }
col_tr += '<td>' + reviews[row[2][0]]['all'] + '</td>';
col_tr += '</tr>'; if (reviews[row[2][0]]['_all']) {
return col_tr; res.append($('<td />').append(reviews[row[2][0]]['_all']['html']));
} else {
res.append($('<td />').append('&nbsp;'));
}
return res.uoj_highlight();
}, { }, {
div_classes: standings_config.div_classes ? standings_config.div_classes : ['table-responsive', 'card', 'my-3'], div_classes: standings_config.div_classes ? standings_config.div_classes : ['table-responsive', 'card', 'my-3'],
table_classes: standings_config.table_classes ? standings_config.table_classes : ['table', 'table-bordered', 'text-center', 'align-middle', 'uoj-table', 'uoj-standings-table', 'mb-0'], table_classes: standings_config.table_classes ? standings_config.table_classes : ['table', 'table-bordered', 'text-center', 'align-middle', 'uoj-table', 'uoj-standings-table', 'mb-0'],