refactor(web/group): group_v3

This commit is contained in:
Baoshuo Ren 2022-11-11 08:20:33 +08:00
parent 91fb9c8e01
commit 287889b60f
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
17 changed files with 837 additions and 629 deletions

View File

@ -48,15 +48,12 @@ if ($is_manager) {
'小组 ID',
'',
function ($group_id, &$vdata) {
if (!validateUInt($group_id)) {
return '小组 ID 不合法';
}
$group = queryGroup($group_id);
$group = UOJGroup::query($group_id);
if (!$group) {
return '小组不存在';
}
$vdata['group_id'] = $group_id;
$vdata['group'] = $group;
return '';
},
@ -65,9 +62,11 @@ if ($is_manager) {
$add_group_to_contest_form->submit_button_config['align'] = 'compressed';
$add_group_to_contest_form->submit_button_config['text'] = '注册该小组中的用户';
$add_group_to_contest_form->handle = function (&$vdata) {
$users = queryGroupUsers($vdata['group_id']);
$usernames = $vdata['group']->getUsernames();
foreach ($usernames as $username) {
$user = UOJUser::query($username);
foreach ($users as $user) {
UOJContest::cur()->userRegister($user);
}
};
@ -177,7 +176,8 @@ if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
<?php endif ?>
<?php
$header_row = '<tr><th>#</th><th>' . UOJLocale::get('username') . '</th>';
$header_row = '<tr>';
$header_row .= '<th>#</th><th>' . UOJLocale::get('username') . '</th>';
if ($show_ip) {
$header_row .= '<th>remote_addr</th><th>http_x_forwarded_for</th>';
}

View File

@ -1,160 +1,157 @@
<?php
requireLib('bootstrap5');
requirePHPLib('form');
requirePHPLib('judger');
requirePHPLib('data');
requireLib('bootstrap5');
requirePHPLib('form');
requirePHPLib('judger');
requirePHPLib('data');
if (!Auth::check()) {
redirectToLogin();
}
Auth::check() || redirectToLogin();
UOJGroup::init(UOJRequest::get('id')) || UOJResponse::page404();
UOJGroup::cur()->userCanView(Auth::user(), ['ensure' => true]);
?>
$group_id = $_GET['id'];
if (!validateUInt($group_id) || !($group = queryGroup($group_id))) {
become404Page();
}
if (!isSuperUser($myUser) && $group['is_hidden']) {
become403Page();
}
?>
<?php echoUOJPageHeader(UOJLocale::get('groups')) ?>
<?php echoUOJPageHeader('小组:' . UOJGroup::info('title')) ?>
<div class="row">
<!-- left col -->
<div class="col-lg-9">
<!-- left col -->
<div class="col-lg-9">
<!-- title -->
<div class="d-flex justify-content-between">
<h1>
<?= UOJGroup::info('title') ?>
<span class="fs-5">(ID: #<?= UOJGroup::info('id') ?>)</span>
<?php if (UOJGroup::info('is_hidden')) : ?>
<span class="badge text-bg-danger fs-6">
<i class="bi bi-eye-slash-fill"></i>
<?= UOJLocale::get('hidden') ?>
</span>
<?php endif ?>
</h1>
<!-- title -->
<div class="d-flex justify-content-between">
<h1>
<?php if ($group['is_hidden']): ?>
<span class="fs-5 text-danger">[隐藏]</span>
<?php endif ?>
<?= $group['title'] ?>
<span class="fs-5">(ID: #<?= $group['id'] ?>)</span>
</h1>
<?php if (isSuperUser($myUser)): ?>
<div class="text-end">
<a class="btn btn-primary" href="/group/<?= $group['id'] ?>/manage" role="button">
<?= UOJLocale::get('problems::manage') ?>
</a>
</div>
<?php endif ?>
</div>
<!-- end title -->
<!-- main content -->
<div class="card mb-3">
<div class="card-body">
<h2 class="h3">
<?= UOJLocale::get('group announcement') ?>
</h2>
<?php if ($group['announcement']): ?>
<div class="text-break">
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line($group['announcement'])) ?>
<?php if (UOJGroup::cur()->userCanManage(Auth::user())) : ?>
<div class="text-end">
<?=
UOJGroup::cur()->getLink([
'where' => '/manage',
'class' => 'btn btn-primary',
'text' => UOJLocale::get('problems::manage'),
]);
?>
</div>
<?php endif ?>
</div>
<?php else: ?>
<div class="text-muted">
<?= UOJLocale::get('none') ?>
<!-- end title -->
<!-- main content -->
<div class="card mb-3">
<div class="card-body">
<h2 class="h3">
<?= UOJLocale::get('group announcement') ?>
</h2>
<?php if (UOJGroup::info('announcement')) : ?>
<div class="text-break">
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line(UOJGroup::info('announcement'))) ?>
</div>
<?php else : ?>
<div class="text-muted">
<?= UOJLocale::get('none') ?>
</div>
<?php endif ?>
</div>
</div>
<?php endif ?>
<div class="card mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('news') ?>
</h2>
<ul class="mb-0">
<?php foreach (UOJGroup::cur()->getLatestGroupmatesAcceptedSubmissionIds(Auth::user()) as $id) : ?>
<?php
$submission = UOJSubmission::query($id);
$submission->setProblem();
$user = UOJUser::query($submission->info['submitter']);
?>
<li>
<?= UOJUser::getLink($user) ?>
解决了问题
<?= $submission->problem->getLink(['with' => 'id']) ?>
(<time><?= $submission->info['submit_time'] ?></time>)
</li>
<?php endforeach ?>
</ul>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('assignments') ?>
</h2>
<?php
echoLongTable(
['*'],
[
"groups_assignments",
"left join lists",
"on", [
"lists.id" => DB::raw("groups_assignments.list_id"),
]
],
[
"groups_assignments.group_id" => UOJGroup::info('id'),
["groups_assignments.end_time", ">", DB::raw("addtime(now(), '-168:00:00')")]
],
'order by groups_assignments.end_time desc, groups_assignments.list_id desc',
<<<EOD
<tr>
<th style="width:3em" class="text-center">ID</th>
<th style="width:12em">标题</th>
<th style="width:4em">状态</th>
<th style="width:8em">结束时间</th>
</tr>
EOD,
function ($info) {
$assignment = new UOJGroupAssignment($info, UOJGroup::cur());
echo HTML::tag_begin('tr');
echo HTML::tag('td', ['class' => 'text-center'], $assignment->info['id']);
echo HTML::tag('td', [], $assignment->getLink());
if ($assignment->info['end_time'] < UOJTime::$time_now) {
echo HTML::tag('td', ['class' => 'text-danger'], '已结束');
} else {
echo HTML::tag('td', ['class' => 'text-success'], '进行中');
}
echo HTML::tag('td', [], $assignment->info['end_time_str']);
echo HTML::tag_end('tr');
},
[
'echo_full' => true,
'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle', 'mb-0'],
],
);
?>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('top solver') ?>
</h2>
<?php UOJRanklist::printHTML([
'page_len' => 25,
'group_id' => UOJGroup::info('id'),
]) ?>
</div>
</div>
<!-- end left col -->
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('news') ?>
</h5>
<ul class="mb-0">
<?php
$current_ac = queryGroupCurrentAC($group['id']);
foreach ($current_ac as $ac) {
echo '<li>';
echo getUserLink($ac['submitter']);
echo ' 解决了问题 ';
echo '<a class="text-decoration-none" href="/problem/', $ac['problem_id'], '">', $ac['problem_title'], '</a> ';
echo '<time class="time">(', $ac['submit_time'], ')</time>';
echo '</li>';
}
if (count($current_ac) == 0) {
echo '暂无最新动态';
}
?>
</ul>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('assignments') ?>
</h5>
<?php
echoLongTable(
[
'groups_assignments.list_id as list_id',
'lists.title as title',
'groups_assignments.end_time as end_time'
],
'groups_assignments left join lists on lists.id = groups_assignments.list_id',
"groups_assignments.group_id = {$group['id']} and groups_assignments.end_time > addtime(now(), '-168:00:00')",
'order by end_time desc, list_id desc',
<<<EOD
<tr>
<th style="width:3em" class="text-center">ID</th>
<th style="width:12em">标题</th>
<th style="width:4em">状态</th>
<th style="width:8em">结束时间</th>
</tr>
EOD,
function($row) use ($group) {
$end_time = DateTime::createFromFormat('Y-m-d H:i:s', $row['end_time']);
echo '<tr>';
echo '<td class="text-center">', $row['list_id'], '</td>';
echo '<td>', '<a class="text-decoration-none" href="/group/', $group['id'], '/assignment/', $row['list_id'],'">', HTML::escape($row['title']), '</a>', '</td>';
if ($end_time < UOJTime::$time_now) {
echo '<td class="text-danger">已结束</td>';
} else {
echo '<td class="text-success">进行中</td>';
}
echo '<td>', $end_time->format('Y-m-d H:i:s'), '</td>';
echo '</tr>';
},
[
'echo_full' => true,
'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle', 'mb-0'],
]
);
?>
</div>
</div>
<div class="card card-default mb-3">
<div class="card-body">
<h2 class="card-title h3">
<?= UOJLocale::get('top solver') ?>
</h5>
<?php echoRanklist([
'page_len' => 50,
'group_id' => $group_id,
'by_accepted' => true,
'div_classes' => ['table-responsive', 'mb-3'],
'table_classes' => ['table', 'text-center', 'mb-0'],
]) ?>
</div>
</div>
<!-- end left col -->
</div>
<!-- right col -->
<aside class="col-lg-3 mt-3 mt-lg-0">
<?php uojIncludeView('sidebar'); ?>
</aside>
<!-- right col -->
<aside class="col-lg-3 mt-3 mt-lg-0">
<?php uojIncludeView('sidebar') ?>
</aside>
</div>

View File

@ -11,23 +11,13 @@ if (!validateUInt($group_id)) {
become404Page();
}
UOJList::init(UOJRequest::get('list_id')) || UOJResponse::page404();
$assignment = queryAssignmentByGroupListID($group_id, UOJList::info('id'));
if (!$assignment) {
become404Page();
}
$group = queryGroup($assignment['group_id']);
$list = UOJList::info();
if (($group['is_hidden'] || $list['is_hidden']) && !isSuperUser($myUser)) {
become403Page();
}
UOJGroup::init(UOJRequest::get('id')) || UOJResponse::page404();
UOJGroupAssignment::init(UOJRequest::get('list_id')) || UOJResponse::page404();
UOJGroupAssignment::cur()->valid() || UOJResponse::page404();
UOJGroupAssignment::cur()->userCanView(['ensure' => true]);
?>
<?php echoUOJPageHeader(UOJLocale::get('assignments')) ?>
<?php echoUOJPageHeader(UOJLocale::get('assignments') . ' ' . UOJGroupAssignment::info('title')) ?>
<div class="row">
<!-- left col -->
@ -36,48 +26,48 @@ if (($group['is_hidden'] || $list['is_hidden']) && !isSuperUser($myUser)) {
<small class="fs-4">作业:</small><?= UOJList::info('title') ?>
</h1>
<ul class="mt-3">
<li>对应题单:<a class="text-decoration-none" href="<?= HTML::url('/list/' . UOJList::info('id')) ?>">#<?= UOJList::info('id') ?></a></li>
<li>所属小组:<a class="text-decoration-none" href="<?= HTML::url('/group/' . $group['id']) ?>"><?= $group['title'] ?></a></li>
<li>结束时间:<?= $assignment['end_time'] ?></li>
<li>对应题单:<a href="<?= HTML::url('/list/' . UOJGroupAssignment::info('id')) ?>">#<?= UOJGroupAssignment::info('id') ?></a></li>
<li>所属小组:<?= UOJGroup::cur()->getLink() ?>
<li>结束时间:<?= UOJGroupAssignment::info('end_time_str') ?></li>
</ul>
<?php
$problems = UOJList::cur()->getProblemIDs();
$users = queryGroupUsers($group['id']);
$usernames = [];
$problems = UOJGroupAssignment::cur()->getProblemIDs();
$usernames = UOJGroup::cur()->getUsernames();
$n_users = count($users);
$submission_end_time = min(new DateTime(), DateTime::createFromFormat('Y-m-d H:i:s', $assignment['end_time']));
$submission_end_time = min(new DateTime(), UOJGroupAssignment::info('end_time'));
foreach ($users as $user) {
$usernames[] = $user['username'];
}
// standings: rank => [total_score, user => [username, realname], scores[]]
// standings: rank => [total_score, [username, realname], scores[]]
$standings = [];
foreach ($usernames as $username) {
$user = UOJUser::query($username);
$row = ['total_score' => 0];
$scores = [];
$row = [0, [$user['username'], $user['realname']], []];
$row['user'] = [
'username' => $user['username'],
'realname' => $user['realname'],
];
$cond = "submitter = '{$user['username']}' AND unix_timestamp(submit_time) <= " . $submission_end_time->getTimestamp();
$conds = DB::land([
"submitter" => $user['username'],
["unix_timestamp(submit_time)", "<=", $submission_end_time->getTimestamp()],
]);
foreach ($problems as $problem_id) {
$submission = DB::selectFirst("SELECT id, score FROM submissions WHERE problem_id = $problem_id AND $cond ORDER BY score DESC, id DESC");
$submission = DB::selectFirst([
"select", DB::fields(["id", "score"]),
"from submissions",
"where", [
"problem_id" => $problem_id,
$conds,
],
"order by score desc, id desc",
]);
if ($submission) {
$row['scores'][] = [
'submission_id' => (int)$submission['id'],
'score' => (int)$submission['score'],
$row[2][] = [
(int)$submission['id'],
(int)$submission['score'],
];
$row['total_score'] += $submission['score'];
$row[0] += $submission['score'];
} else {
$row['scores'][] = null;
$row[2][] = null;
}
}
@ -85,11 +75,11 @@ if (($group['is_hidden'] || $list['is_hidden']) && !isSuperUser($myUser)) {
}
usort($standings, function ($lhs, $rhs) {
if ($lhs['total_score'] != $rhs['total_score']) {
return $rhs['total_score'] - $lhs['total_score'];
if ($lhs[0] != $rhs[0]) {
return $rhs[0] - $lhs[0];
}
return strcmp($lhs['user']['username'], $rhs['user']['username']);
return strcmp($lhs[1][0], $rhs[1][0]);
});
?>
@ -112,26 +102,26 @@ if (($group['is_hidden'] || $list['is_hidden']) && !isSuperUser($myUser)) {
function(row) {
var col_tr = '';
if (row['total_score'] == problems.length * 100) {
if (row[0] == problems.length * 100) {
col_tr += '<tr class="table-success">';
} else {
col_tr += '<tr>';
}
col_tr += '<td>' + getUserLink(row['user']['username'], row['user']['realname']) + '</td>';
col_tr += '<td>' + getUserLink(row[1][0], row[1][1]) + '</td>';
col_tr += '<td>' +
'<span class="uoj-score" data-max="' + (problems.length * 100) + '" style="color:' + getColOfScore(row['total_score'] / problems.length) + '">' + row['total_score'] + '</span>' +
'<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 < row['scores'].length; i++) {
var col = row['scores'][i];
for (var i = 0; i < row[2].length; i++) {
var col = row[2][i];
if (col) {
if (col['score'] == 100) {
if (col[1] == 100) {
col_tr += '<td class="table-success">';
} else {
col_tr += '<td>';
}
col_tr += '<a class="text-decoration-none uoj-score" href="/submission/' + col['submission_id'] + '" style="color:' + getColOfScore(col['score']) + '">' + col['score'] + '</a>';
col_tr += '<a class="text-decoration-none uoj-score" href="/submission/' + col[0] + '" style="color:' + getColOfScore(col[1]) + '">' + col[1] + '</a>';
col_tr += '</td>';
} else {
col_tr += '<td></td>';

View File

@ -1,45 +1,33 @@
<?php
if (!Auth::check()) {
redirectToLogin();
}
requireLib('bootstrap5');
requirePHPLib('form');
requirePHPLib('judger');
requirePHPLib('data');
$group_id = $_GET['id'];
if (!validateUInt($group_id) || !($group = queryGroup($group_id))) {
become404Page();
}
Auth::check() || redirectToLogin();
UOJGroup::init(UOJRequest::get('id')) || UOJResponse::page404();
UOJGroup::cur()->userCanManage(Auth::user()) || UOJResponse::page403();
if (!isSuperUser($myUser)) {
become403Page();
}
if (isset($_GET['tab'])) {
$cur_tab = $_GET['tab'];
} else {
$cur_tab = 'profile';
}
$cur_tab = UOJRequest::get('tab', 'is_string', 'profile');
$tabs_info = [
'profile' => [
'name' => '基本信息',
'url' => "/group/{$group['id']}/manage/profile",
'url' => '/group/' . UOJGroup::info('id') . '/manage/profile',
],
'assignments' => [
'name' => '作业管理',
'url' => "/group/{$group['id']}/manage/assignments",
'url' => '/group/' . UOJGroup::info('id') . '/manage/assignments',
],
'users' => [
'name' => '用户管理',
'url' => "/group/{$group['id']}/manage/users",
'url' => '/group/' . UOJGroup::info('id') . '/manage/users',
]
];
if (!isset($tabs_info[$cur_tab])) {
become404Page();
UOJResponse::page404();
}
if ($cur_tab == 'profile') {
@ -48,7 +36,7 @@ if ($cur_tab == 'profile') {
'name',
'text',
'名称',
$group['title'],
UOJGroup::info('title'),
function ($title, &$vdata) {
if ($title == '') {
return '名称不能为空';
@ -72,11 +60,11 @@ if ($cur_tab == 'profile') {
$update_profile_form->addVCheckboxes('is_hidden', [
'0' => '公开',
'1' => '隐藏',
], '可见性', $group['is_hidden']);
], '可见性', UOJGroup::info('is_hidden'));
$update_profile_form->addVTextArea(
'announcement',
'公告',
$group['announcement'],
UOJGroup::info('announcement'),
function ($announcement, &$vdata) {
if (strlen($announcement) > 3000) {
return '公告过长';
@ -88,12 +76,18 @@ if ($cur_tab == 'profile') {
},
null
);
$update_profile_form->handle = function ($vdata) use ($group) {
$esc_title = DB::escape($vdata['title']);
$is_hidden = $_POST['is_hidden'];
$esc_announcement = DB::escape($vdata['announcement']);
DB::update("UPDATE `groups` SET title = '$esc_title', is_hidden = '$is_hidden', announcement = '$esc_announcement' WHERE id = {$group['id']}");
$update_profile_form->handle = function ($vdata) {
DB::update([
"update `groups`",
"set", [
"title" => $vdata['title'],
"is_hidden" => $_POST['is_hidden'],
"announcement" => $vdata['announcement'],
],
"where", [
"id" => UOJGroup::info('id'),
],
]);
dieWithJsonData(['status' => 'success', 'message' => '修改成功']);
};
@ -121,17 +115,20 @@ EOD);
$update_profile_form->runAtServer();
} elseif ($cur_tab == 'assignments') {
if (isset($_POST['submit-remove_assignment']) && $_POST['submit-remove_assignment'] == 'remove_assignment') {
$list_id = $_POST['list_id'];
$list_id = UOJRequest::post('list_id');
if (!validateUInt($list_id)) {
dieWithAlert('题单 ID 不合法。');
$list = UOJGroupAssignment::query($list_id);
if (!$list || !$list->valid()) {
dieWithAlert('题单不合法。');
}
if (!queryAssignmentByGroupListID($group['id'], $list_id)) {
dieWithAlert('该题单不在作业中。');
}
DB::delete("DELETE FROM `groups_assignments` WHERE `list_id` = $list_id AND `group_id` = {$group['id']}");
DB::delete([
"delete from groups_assignments",
"where", [
"list_id" => $list->info['id'],
"group_id" => UOJGroup::info('id'),
],
]);
dieWithAlert('移除成功!');
}
@ -142,7 +139,7 @@ EOD);
'text',
'题单 ID',
'',
function ($list_id, &$vdata) use ($group) {
function ($list_id, &$vdata) {
if (!validateUInt($list_id)) {
return '题单 ID 不合法';
}
@ -157,7 +154,7 @@ EOD);
return '题单是隐藏的';
}
if (queryAssignmentByGroupListID($group['id'], $list->info['id'])) {
if (UOJGroup::cur()->hasAssignment($list)) {
return '该题单已经在作业中';
}
@ -186,10 +183,16 @@ EOD);
},
null
);
$add_new_assignment_form->handle = function (&$vdata) use ($group) {
$esc_end_time = DB::escape($vdata['end_time']->format('Y-m-d H:i:s'));
DB::insert("insert into groups_assignments (group_id, list_id, end_time) values ({$group['id']}, '{$vdata['list_id']}', '{$esc_end_time}')");
$add_new_assignment_form->handle = function (&$vdata) {
DB::insert([
"insert into groups_assignments",
DB::bracketed_fields(["group_id", "list_id", "end_time"]),
"values", DB::tuple([
UOJGroup::info('id'),
$vdata['list_id'],
$vdata['end_time']->format('Y-m-d H:i:s'),
]),
]);
dieWithJsonData([
'status' => 'success',
@ -220,24 +223,26 @@ EOD);
$add_new_assignment_form->runAtServer();
$hidden_time = new DateTime();
$hidden_time->sub(new DateInterval('P7D'));
$hidden_time->sub(new DateInterval('P3D'));
} elseif ($cur_tab == 'users') {
if (isset($_POST['submit-remove_user']) && $_POST['submit-remove_user'] == 'remove_user') {
$username = $_POST['remove_username'];
$user = UOJUser::query(UOJRequest::post('remove_username'));
if (!validateUsername($username)) {
dieWithAlert('用户名不合法。');
}
if (!queryUser($username)) {
if (!$user) {
dieWithAlert('用户不存在。');
}
if (!queryUserInGroup($group['id'], $username)) {
if (!UOJGroup::cur()->hasUser($user)) {
dieWithAlert('该用户不在小组中。');
}
DB::delete("DELETE FROM `groups_users` WHERE `username` = '$username' AND `group_id` = {$group['id']}");
DB::delete([
"delete from groups_users",
"where", [
"username" => $user['username'],
"group_id" => UOJGroup::info('id'),
],
]);
dieWithAlert('移除成功!');
}
@ -249,21 +254,17 @@ EOD);
'用户名',
'',
function ($username, &$vdata) {
global $group_id;
$user = UOJUser::query($username);
if (!validateUsername($username)) {
return '用户名不合法';
if (!$user) {
return '用户不存在。';
}
if (!queryUser($username)) {
return '用户不存在';
}
if (queryUserInGroup($group_id, $username)) {
if (UOJGroup::cur()->hasUser($user)) {
return '该用户已经在小组中';
}
$vdata['username'] = $username;
$vdata['username'] = $user['username'];
return '';
},
@ -271,8 +272,16 @@ EOD);
);
$add_new_user_form->submit_button_config['class_str'] = 'btn btn-secondary mt-3';
$add_new_user_form->submit_button_config['text'] = '添加';
$add_new_user_form->handle = function (&$vdata) use ($group) {
DB::insert("insert into groups_users (group_id, username) values ({$group['id']}, '{$vdata['username']}')");
$add_new_user_form->handle = function (&$vdata) {
DB::insert([
"insert into groups_users",
DB::bracketed_fields(["group_id", "username"]),
"values",
DB::tuple([
UOJGroup::info('id'),
$vdata['username']
]),
]);
dieWithJsonData(['status' => 'success', 'message' => '已将用户名为 ' . $vdata['username'] . ' 的用户添加到本小组。']);
};
@ -298,24 +307,25 @@ EOD);
$add_new_user_form->runAtServer();
}
?>
<?php echoUOJPageHeader('管理 - ' . $group['title']); ?>
<?php echoUOJPageHeader('管理 - ' . UOJGroup::info('title')); ?>
<h1 class="d-block d-md-inline-block">
<?= $group['title'] ?>
<small class="fs-5">(ID: #<?= $group['id'] ?>)</small>
<?= UOJGroup::info('title') ?>
<small class="fs-5">(ID: #<?= UOJGroup::info('id') ?>)</small>
管理
</h1>
<div class="row mt-4">
<!-- left col -->
<div class="col-md-3">
<?= HTML::navListGroup($tabs_info, $cur_tab) ?>
<a class="btn btn-light d-block mt-2 w-100 text-start text-primary" style="--bs-btn-hover-bg: #d3d4d570; --bs-btn-hover-border-color: transparent;" href="<?= HTML::url("/group/{$group['id']}") ?>">
<i class="bi bi-arrow-left"></i> 返回
</a>
<?=
UOJGroup::cur()->getLink([
'class' => 'btn btn-light d-block mt-2 w-100 text-start text-primary uoj-back-btn',
'text' => '<i class="bi bi-arrow-left"></i> 返回',
]);
?>
</div>
<!-- end left col -->
@ -358,42 +368,41 @@ EOD);
echoLongTable(
['*'],
'groups_assignments',
"group_id = {$group['id']}",
["group_id" => UOJGroup::info('id')],
'order by end_time desc, list_id desc',
<<<EOD
<tr>
<th style="width:4em" class="text-center">题单 ID</th>
<th style="width:12em">标题</th>
<th style="width:4em">状态</th>
<th style="width:8em">结束时间</th>
<th style="width:8em">操作</th>
</tr>
EOD,
function ($row) use ($group, $hidden_time) {
$list = UOJList::query($row['list_id']);
$end_time = DateTime::createFromFormat('Y-m-d H:i:s', $row['end_time']);
<tr>
<th style="width:4em" class="text-center">题单 ID</th>
<th style="width:12em">标题</th>
<th style="width:4em">状态</th>
<th style="width:8em">结束时间</th>
<th style="width:8em">操作</th>
</tr>
EOD,
function ($row) use ($hidden_time) {
$assignment = UOJGroupAssignment::query($row['list_id']);
echo '<tr>';
echo '<td class="text-center">', $list->info['id'], '</td>';
echo '<td>';
echo '<a class="text-decoration-none" href="/group/', $group['id'], '/assignment/', $list->info['id'], '">', $list->info['title'], '</a>';
if ($list->info['is_hidden']) {
echo HTML::tag_begin('tr');
echo HTML::tag('td', ['class' => 'text-center'], $assignment->info['id']);
echo HTML::tag_begin('td');
echo $assignment->getLink();
if ($assignment->info['is_hidden']) {
echo ' <span class="badge text-bg-danger"><i class="bi bi-eye-slash-fill"></i> ', UOJLocale::get('hidden'), '</span> ';
}
echo '</td>';
if ($end_time < $hidden_time) {
echo '<td class="text-secondary">已隐藏</td>';
} elseif ($end_time < UOJTime::$time_now) {
echo '<td class="text-danger">已结束</td>';
echo HTML::tag_end('td');
if ($assignment->info['end_time'] < $hidden_time) {
echo HTML::tag('td', ['class' => 'text-secondary'], '已隐藏');
} elseif ($assignment->info['end_time'] < UOJTime::$time_now) {
echo HTML::tag('td', ['class' => 'text-danger'], '已结束');
} else {
echo '<td class="text-success">进行中</td>';
echo HTML::tag('td', ['class' => 'text-success'], '进行中');
}
echo '<td>', $end_time->format('Y-m-d H:i:s'), '</td>';
echo HTML::tag('td', [], $assignment->info['end_time_str']);
echo '<td>';
echo ' <a class="text-decoration-none d-inline-block align-middle" href="/list/', $list->info['id'], '/manage">编辑</a> ';
echo ' <form class="d-inline-block" method="POST" onsubmit=\'return confirm("你真的要移除这份作业(题单 #', $list->info['id'], ')吗?移除作业不会删除题单。")\'>'
. '<input type="hidden" name="_token" value="' . crsf_token() . '">'
. '<input type="hidden" name="list_id" value="' . $list->info['id'] . '">'
echo ' <a class="text-decoration-none d-inline-block align-middle" href="/list/', $assignment->info['id'], '/manage">编辑</a> ';
echo ' <form class="d-inline-block" method="POST" onsubmit=\'return confirm("你真的要移除这份作业(题单 #', $assignment->info['id'], ')吗?移除作业不会删除题单。")\'>'
. HTML::hiddenToken()
. '<input type="hidden" name="list_id" value="' . $assignment->info['id'] . '">'
. '<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-remove_assignment" value="remove_assignment">移除</button>'
. '</form>';
echo '</td>';
@ -447,17 +456,17 @@ EOD,
echoLongTable(
['*'],
'groups_users',
"group_id = {$group['id']}",
["group_id" => UOJGroup::info('id')],
'order by username asc',
<<<EOD
<tr>
<th>用户名</th>
<th>操作</th>
</tr>
EOD,
function ($row) use ($group) {
echo '<tr>';
echo '<td>', getUserLink($row['username']), '</td>';
<tr>
<th>用户名</th>
<th>操作</th>
</tr>
EOD,
function ($row) {
echo HTML::tag_begin('tr');
echo HTML::tag('td', [], UOJUser::getLink($row['username']));
echo '<td>';
echo '<form class="d-inline-block" method="POST" onsubmit=\'return confirm("你真的要从小组中移除这个用户吗?")\'>'
. '<input type="hidden" name="_token" value="' . crsf_token() . '">'
@ -465,7 +474,7 @@ EOD,
. '<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-remove_user" value="remove_user">移除</button>'
. '</form>';
echo '</td>';
echo '</tr>';
echo HTML::tag_end('tr');
},
[
'page_len' => 20,

View File

@ -23,26 +23,30 @@ if (isSuperUser($myUser)) {
function getListTR($info) {
$list = new UOJList($info);
$problems = $list->getProblemIDs();
$accepted = DB::selectCount([
"select count(*)",
"from best_ac_submissions",
"where", [
"submitter" => Auth::id(),
["problem_id", "in", DB::tuple($problems)],
],
]);
if (Auth::check() && !empty($problems)) {
$accepted = DB::selectCount([
"select count(*)",
"from best_ac_submissions",
"where", [
"submitter" => Auth::id(),
["problem_id", "in", DB::rawtuple($problems)],
],
]);
} else {
$accepted = -1;
}
$html = HTML::tag_begin('tr', ['class' => 'text-center']);
$html .= HTML::tag('td', ['class' => $accepted == count($problems) ? 'table-success' : ''], "#{$list->info['id']}");
$html .= HTML::tag_begin('td', ['class' => 'text-start']);
$html .= HTML::tag('a', ['href' => "/list/{$list->info['id']}"], $list->info['title']);
$html .= $list->getLink();
if ($list->info['is_hidden']) {
$html .= ' <span class="badge text-bg-danger"><i class="bi bi-eye-slash-fill"></i> ' . UOJLocale::get('hidden') . '</span> ';
}
foreach ($list->queryTags() as $tag) {
$html .= ' <a class="uoj-list-tag"><span class="badge text-bg-secondary">' . $tag['tag'] . '</span></a> ';
}
$html .= HTML::tag('td', [], $accepted);
$html .= HTML::tag('td', [], max(0, $accepted));
$html .= HTML::tag('td', [], count($problems));
$html .= HTML::tag_end('td');

View File

@ -52,46 +52,6 @@ function queryContest($id) {
return DB::selectFirst("select * from contests where id = $id", MYSQLI_ASSOC);
}
function queryGroup($id) {
return DB::selectFirst("select * from `groups` where id = $id", MYSQLI_ASSOC);
}
function queryGroupUsers($id) {
return DB::selectAll("SELECT * FROM groups_users WHERE group_id = $id");
}
function queryUserInGroup($group_id, $username) {
return DB::selectFirst("select * from groups_users where username='$username' and group_id='$group_id'", MYSQLI_ASSOC);
}
function queryGroupsOfUser($username) {
return DB::selectAll([
"select", DB::fields([
"title" => "groups.title",
"id" => "groups.id",
]),
"from groups_users",
"inner join `groups`", "on", [
"groups_users.group_id" => DB::raw("groups.id"),
],
"where", [
"groups_users.username" => $username,
"groups.is_hidden" => false,
],
]);
}
function queryGroupmateCurrentAC($username) {
return DB::selectAll("select a.problem_id as problem_id, a.submitter as submitter, a.submission_id as submission_id, b.submit_time as submit_time, c.group_id as group_id, c.group_name as group_name, d.title as problem_title, b.submit_time as submit_time, e.realname as realname from best_ac_submissions a inner join submissions b on (a.submission_id = b.id) inner join (select a.username as username, any_value(a.group_id) as group_id, any_value(c.title) as group_name from groups_users a inner join (select a.group_id as group_id from groups_users a inner join `groups` b on a.group_id = b.id where a.username = '$username' and b.is_hidden = 0) b on a.group_id = b.group_id inner join `groups` c on a.group_id = c.id group by a.username) c on a.submitter = c.username inner join problems d on (a.problem_id = d.id and d.is_hidden = 0) inner join user_info e on a.submitter = e.username where b.submit_time > addtime(now(), '-360:00:00') order by b.submit_time desc limit 10", MYSQLI_ASSOC);
}
function queryGroupCurrentAC($group_id) {
return DB::selectAll("select a.problem_id as problem_id, a.submitter as submitter, a.submission_id as submission_id, b.submit_time as submit_time, d.title as problem_title, b.submit_time as submit_time, e.realname as realname from best_ac_submissions a inner join submissions b on (a.submission_id = b.id) inner join groups_users c on (a.submitter = c.username and c.group_id = $group_id) inner join problems d on (a.problem_id = d.id and d.is_hidden = 0) inner join user_info e on (a.submitter = e.username) where b.submit_time > addtime(now(), '-360:00:00') order by b.submit_time desc limit 10", MYSQLI_ASSOC);
}
function queryGroupAssignments($group_id) {
return DB::selectAll("select a.list_id as list_id, a.end_time as end_time, b.title from groups_assignments a left join lists b on a.list_id = b.id where a.group_id = $group_id order by a.end_time asc", MYSQLI_ASSOC);
}
function queryGroupActiveAssignments($group_id) {
return DB::selectAll("select a.group_id as group_id, a.list_id as list_id, a.end_time as end_time, b.title from groups_assignments a left join lists b on a.list_id = b.id where a.group_id = $group_id and a.end_time >= addtime(now(), '-168:00:00') order by a.end_time asc", MYSQLI_ASSOC);
}
function queryAssignmentByGroupListID($group_id, $list_id) {
return DB::selectFirst("select * from groups_assignments where list_id='$list_id' and group_id='$group_id'", MYSQLI_ASSOC);
}
function queryBlog($id) {
return DB::selectFirst("select * from blogs where id='$id'", MYSQLI_ASSOC);
}

View File

@ -17,6 +17,34 @@ class UOJContest {
return new UOJContest($info);
}
public static function queryUpcomingContestIds(array $user = null, $limit = -1) {
return array_map(fn ($x) => $x['id'], DB::selectAll([
"select id from contests",
"where", [
"status" => "unfinished",
],
"order by start_time asc, id asc",
$limit == -1 ? "" : DB::limit($limit),
]));
}
public static function userCanManageSomeContest(array $user = null) {
if (!$user) {
return false;
}
if (isSuperUser($user)) {
return true;
}
return DB::selectFirst([
DB::lc(), "select 1 from contests_permissions",
"where", [
'username' => $user['username']
], DB::limit(1)
]) != null;
}
public static function finalTest() {
$contest = self::info();
@ -436,6 +464,15 @@ class UOJContest {
return "/contest/{$this->info['id']}{$where}";
}
public function getLink($cfg = []) {
$cfg += [
'where' => '',
'class' => '',
];
return HTML::tag('a', ['class' => $cfg['class'], 'href' => $this->getUri($cfg['where'])], $this->info['name']);
}
public function redirectToAnnouncementBlog() {
$url = getContestBlogLink($this->info, '公告');
if ($url !== null) {

131
web/app/models/UOJGroup.php Normal file
View File

@ -0,0 +1,131 @@
<?php
class UOJGroup {
use UOJDataTrait;
public static function query($id) {
if (!isset($id) || !validateUInt($id)) {
return null;
}
$info = DB::selectFirst([
"select * from `groups`",
"where", ["id" => $id]
]);
if (!$info) {
return null;
}
return new UOJGroup($info);
}
public static function queryGroupsOfUser(array $user = null) {
if ($user == null) {
return [];
}
return array_map(fn ($x) => UOJGroup::query($x['group_id']), DB::selectAll([
DB::lc(), "select group_id from groups_users",
"where", ['username' => $user['username']],
"order by group_id"
]));
}
public function __construct($info) {
$this->info = $info;
}
public function userCanManage(array $user = null) {
return isSuperUser($user);
}
public function userCanView(array $user = null, array $cfg = []) {
$cfg += ['ensure' => false];
if ($this->info['is_hidden'] && !$this->userCanManage($user)) {
$cfg['ensure'] && UOJResponse::page404();
return false;
}
return true;
}
public function getUri($where = '') {
return "/group/{$this->info['id']}{$where}";
}
public function getLink($cfg = []) {
$cfg += [
'where' => '',
'class' => '',
'text' => $this->info['title'],
];
return HTML::tag('a', [
'href' => $this->getUri($cfg['where']),
'class' => $cfg['class'],
], $cfg['text']);
}
public function getUsernames() {
return array_map(fn ($x) => $x['username'], DB::selectAll([
DB::lc(), "select username from groups_users",
"where", ['group_id' => $this->info['id']],
"order by username",
]));
}
public function getLatestGroupmatesAcceptedSubmissionIds(array $user = null, int $limit = 10) {
return array_map(fn ($x) => $x['id'], DB::selectAll([
"select", DB::fields(["id" => "max(id)"]),
"from submissions",
"where", [
"score" => 100,
["submitter", "in", DB::rawtuple($this->getUsernames())],
UOJSubmission::sqlForUserCanView($user),
],
"group by problem_id",
"order by id desc",
]));
}
public function getAssignmentIds($limit = -1) {
return array_map(fn ($x) => $x['list_id'], DB::selectAll([
DB::lc(), "select list_id from groups_assignments",
"where", ['group_id' => $this->info['id']],
"order by end_time desc, list_id asc",
$limit == -1 ? "" : DB::limit($limit),
]));
}
public function getActiveAssignmentIds($limit = -1) {
return array_map(fn ($x) => $x['list_id'], DB::selectAll([
DB::lc(), "select list_id from groups_assignments",
"where", [
"group_id" => $this->info['id'],
["end_time", ">=", DB::raw("addtime(now(), '-72:00:00')")],
],
"order by end_time desc, list_id asc",
$limit == -1 ? "" : DB::limit($limit),
]));
}
public function hasUser(array $user = null) {
if ($user == null) {
return false;
}
return DB::selectFirst([
DB::lc(), "select 1 from groups_users",
"where", [
'group_id' => $this->info['id'],
'username' => $user['username'],
],
]) != null;
}
public function hasAssignment(UOJList $assignment) {
return DB::selectFirst([
DB::lc(), "select 1 from groups_assignments",
"where", [
'group_id' => $this->info['id'],
'list_id' => $assignment->info['id']
]
]) != null;
}
}

View File

@ -0,0 +1,76 @@
<?php
class UOJGroupAssignment extends UOJList {
public $group = null;
public static function query($id, UOJGroup $group = null) {
$list = parent::query($id);
if ($list === null) {
return $list;
}
if ($group === null) {
$group = UOJGroup::cur();
}
return new UOJGroupAssignment($list->info, $group);
}
public function __construct($info, UOJGroup $group) {
parent::__construct($info);
$this->group = $group;
$this->completeInfo();
}
public function completeInfo() {
if ($this->info['end_time_str']) {
return;
}
if (!$this->info['end_time']) {
$this->info['end_time'] = DB::selectSingle([
"select end_time from groups_assignments",
"where", [
"list_id" => $this->info['id'],
"group_id" => $this->group->info['id'],
],
]);
}
$this->info['end_time_str'] = $this->info['end_time'] ?: UOJTime::$time_now_str;
$this->info['end_time'] = new DateTime($this->info['end_time_str']);
}
public function valid() {
return $this->group && $this->group->hasAssignment($this);
}
public function getUri($where = '') {
return $this->group->getUri("/assignment/{$this->info['id']}");
}
public function getLink($cfg = []) {
$cfg += [
'class' => '',
'text' => $this->info['title'],
'with' => 'none',
];
if ($cfg['with'] == 'sup') {
if ($this->info['end_time'] < UOJTime::$time_now) {
$cfg['text'] .= HTML::tag('sup', ["class" => "fw-normal text-danger ms-1"], 'overdue');
} elseif ($this->info['end_time']->getTimestamp() - UOJTime::$time_now->getTimestamp() < 86400) {
$cfg['text'] .= HTML::tag('sup', ["class" => "fw-normal text-danger ms-1"], 'soon');
}
}
return HTML::tag('a', [
'href' => $this->getUri(),
'class' => $cfg['class'],
], $cfg['text']);
}
public function userCanView(array $user = null, $cfg = []) {
$cfg += ['ensure' => false];
return parent::userCanView($user, $cfg) && $this->group->userCanView($user, $cfg);
}
}

View File

@ -22,6 +22,24 @@ class UOJList {
$this->info = $info;
}
public function getUri($where = '') {
return "/list/{$this->info['id']}{$where}";
}
public function getLink($cfg = []) {
$cfg += [
'where' => '',
'class' => '',
'text' => $this->info['title'],
'with' => 'id',
];
return HTML::tag('a', [
'href' => $this->getUri($cfg['where']),
'class' => $cfg['class'],
], $cfg['text']);
}
public function getProblemIDs() {
return array_map(fn ($x) => $x['problem_id'], DB::selectAll([
DB::lc(), "select problem_id from lists_problems",

View File

@ -30,11 +30,17 @@ class UOJProblem {
if (!$user) {
return false;
}
return DB::selectFirst([
DB::lc(), "select 1 from problems_permissions",
"where", [
'username' => $user['username']
], DB::limit(1)
]) != null || DB::selectFirst([
DB::lc(), "select 1 from problems",
"where", [
"uploader" => $user['username'],
], DB::limit(1),
]) != null;
}

View File

@ -5,9 +5,21 @@ class UOJRanklist {
$cfg += [
'top10' => false,
'card' => false,
'group_id' => null,
'page_len' => 50,
];
$conds = '1';
$conds = [];
if ($cfg['group_id']) {
$conds[] = [
"username", "in", DB::rawtuple(UOJGroup::query($cfg['group_id'])->getUsernames()),
];
}
if (empty($conds)) {
$conds = '1';
}
$last_user = null;
$parsedown = HTML::parsedown();
@ -55,7 +67,6 @@ class UOJRanklist {
$last_user = $user;
};
$pag_config = [
'get_row_index' => '',
'table_name' => 'user_info',
@ -68,7 +79,7 @@ class UOJRanklist {
$pag_config['tail'] .= ' limit 10';
$pag_config['echo_full'] = '';
} else {
$pag_config['page_len'] = 50;
$pag_config['page_len'] = $cfg['page_len'];
}
$pag = new Paginator($pag_config);
@ -102,9 +113,21 @@ class UOJRanklist {
public static function printTableHTML($cfg = []) {
$cfg += [
'top10' => false,
'group_id' => null,
'page_len' => 100,
];
$conds = '1';
$conds = [];
if ($cfg['group_id']) {
$conds[] = [
"username", "in", DB::rawtuple(UOJGroup::query($cfg['group_id'])->getUsernames()),
];
}
if (empty($conds)) {
$conds = '1';
}
$header_row = '';
$header_row .= '<tr>';
@ -157,7 +180,7 @@ class UOJRanklist {
$tail .= ' limit 10';
$table_config['echo_full'] = '';
} else {
$table_config['page_len'] = 100;
$table_config['page_len'] = $cfg['page_len'];
}
echoLongTable($col_names, 'user_info', $conds, $tail, $header_row, $print_row, $table_config);

View File

@ -38,7 +38,7 @@ class UOJSubmission {
* Need to be consistent with the member function userCanView
*/
public static function sqlForUserCanView(array $user = null, UOJProblem $problem = null) {
if (isSuperUser($user)) {
if (isSuperUser($user) || isProblemManager($user)) {
// MySQL can find appropriate keys to speed up the query if we write "true" in this way.
return "(submissions.is_hidden = true or submissions.is_hidden = false)";
} elseif ($problem) {

View File

@ -313,34 +313,6 @@ if (!isset($ShowPageHeader)) {
<?php endif ?>
<?php uojIncludeView($PageNav, array('REQUIRE_LIB' => $REQUIRE_LIB)) ?>
<?php if (!isset($REQUIRE_LIB['bootstrap5'])) : ?>
<?php if (Auth::check()) : ?>
<?php $groups = queryGroupsOfUser(Auth::id()); ?>
<?php if (count($groups)) : ?>
<div class="card card-default mb-2" id="group-user-announcements">
<div class="card-header">
小组公告
</div>
<ul class="list-group list-group-flush">
<?php foreach ($groups as $group) : ?>
<?php $group_announcement = DB::selectSingle("select announcement from `groups` where id = {$group['id']}"); ?>
<li class="list-group-item">
<a href="<?= HTML::url('/group/' . $group['id']) ?>">
<b><?= $group['title'] ?></b>
</a>
<?php if ($group_announcement) : ?>
<div><?= HTML::purifier_inline()->purify($group_announcement) ?></div>
<?php else : ?>
<div>(暂无公告)</div>
<?php endif ?>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
<?php endif ?>
<?php endif ?>
<?php endif ?>

View File

@ -1,6 +1,11 @@
<?php
$purifier = HTML::purifier_inline();
$parsedown = HTML::parsedown();
?>
<?php if (Auth::check()) : ?>
<?php if (!isset($groups_hidden)) : ?>
<?php $groups = queryGroupsOfUser(Auth::id()); ?>
<?php $groups = UOJGroup::queryGroupsOfUser(Auth::user()); ?>
<?php if (!empty($groups)) : ?>
<div class="card card-default mb-2" id="group-user-announcements">
<div class="card-header fw-bold bg-transparent">
@ -8,18 +13,11 @@
</div>
<ul class="list-group list-group-flush">
<?php foreach ($groups as $group) : ?>
<?php
$group_announcement = DB::selectSingle("select announcement from `groups` where id = {$group['id']}");
$purifier = HTML::purifier_inline();
$parsedown = HTML::parsedown();
?>
<li class="list-group-item">
<a class="fw-bold text-decoration-none" href="<?= HTML::url('/group/' . $group['id']) ?>">
<?= $group['title'] ?>
</a>
<?php if ($group_announcement) : ?>
<?= $group->getLink(['class' => 'fw-bold']) ?>
<?php if ($group->info['announcement']) : ?>
<div class="text-break">
<?= $purifier->purify($parsedown->line($group_announcement)) ?>
<?= $purifier->purify($parsedown->line($group->info['announcement'])) ?>
</div>
<?php else : ?>
<div class="text-muted">
@ -35,19 +33,13 @@
<?php
$assignments = [];
foreach ($groups as $group) {
$assignments = array_merge($assignments, queryGroupActiveAssignments($group['id']));
$assignments = array_merge($assignments, array_map(fn ($x) => UOJGroupAssignment::query($x, $group), $group->getActiveAssignmentIds()));
}
usort($assignments, function ($a, $b) {
$end_time_a = DateTime::createFromFormat('Y-m-d H:i:s', $a['end_time']);
$end_time_b = DateTime::createFromFormat('Y-m-d H:i:s', $b['end_time']);
return $end_time_b->getTimestamp() - $end_time_a->getTimestamp();
});
usort($assignments, fn ($a, $b) => $b->info['end_time']->getTimestamp() - $a->info['end_time']->getTimestamp());
$assignments = array_slice($assignments, 0, 5);
?>
<?php if (count($assignments)) : ?>
<?php if (!empty($assignments)) : ?>
<div class="card card-default mb-2" id="group-assignments">
<div class="card-header fw-bold bg-transparent">
<?= UOJLocale::get('assignments') ?>
@ -55,17 +47,9 @@
<ul class="list-group list-group-flush">
<?php foreach ($assignments as $assignment) : ?>
<li class="list-group-item">
<?php $end_time = DateTime::createFromFormat('Y-m-d H:i:s', $assignment['end_time']); ?>
<a href="<?= HTML::url('/group/' . $assignment['group_id'] . '/assignment/' . $assignment['list_id']) ?>" class="fw-bold text-decoration-none">
<?= $assignment['title'] ?>
<?php if ($end_time < UOJTime::$time_now) : ?>
<sup class="fw-normal text-danger">overdue</sup>
<?php elseif ($end_time->getTimestamp() - UOJTime::$time_now->getTimestamp() < 86400) : ?>
<sup class="fw-normal text-danger">soon</sup>
<?php endif ?>
</a>
<?= $assignment->getLink(['class' => 'fw-bold', 'with' => 'sup']) ?>
<div class="text-end small text-muted">
截止时间: <?= $end_time->format('Y-m-d H:i') ?>
截止时间: <?= $assignment->info['end_time']->format('Y-m-d H:i') ?>
</div>
</li>
<?php endforeach ?>
@ -79,36 +63,28 @@
<?php if (Auth::check()) : ?>
<?php if (!isset($upcoming_contests_hidden)) : ?>
<?php
$upcoming_contests = DB::selectAll("SELECT * FROM contests WHERE status = 'unfinished' ORDER BY start_time ASC, id ASC LIMIT 7");
?>
<?php $upcoming_contests = UOJContest::queryUpcomingContestIds(Auth::user(), 5); ?>
<div class="card card-default mb-2" id="group-user-announcements">
<div class="card-header fw-bold bg-transparent">
近期比赛
</div>
<?php $count = 0; ?>
<ul class="list-group list-group-flush">
<?php foreach ($upcoming_contests as $contest) : ?>
<?php genMoreContestInfo($contest) ?>
<?php if ($contest['cur_progress'] == CONTEST_NOT_STARTED || $contest['cur_progress'] == CONTEST_IN_PROGRESS) : ?>
<?php $count++; ?>
<?php foreach ($upcoming_contests as $id) : ?>
<?php $contest = UOJContest::query($id); ?>
<?php if ($contest->info['cur_progress'] == CONTEST_NOT_STARTED || $contest->info['cur_progress'] == CONTEST_IN_PROGRESS) : ?>
<li class="list-group-item text-center">
<a class="fw-bold text-decoration-none" href="<?= HTML::url('/contest/' . $contest['id']) ?>">
<?= $contest['name'] ?>
</a>
<?= $contest->getLink(['class' => 'fw-bold']) ?>
<div class="small">
<?php if ($contest['cur_progress'] == CONTEST_IN_PROGRESS) : ?>
<?php if ($contest->info['cur_progress'] == CONTEST_IN_PROGRESS) : ?>
<?= UOJLocale::get('contests::in progress') ?>
<?php else : ?>
<?php
$rest_seconds = $contest['start_time']->getTimestamp() - UOJTime::$time_now->getTimestamp();
?>
<?php $rest_seconds = $contest->info['start_time']->getTimestamp() - UOJTime::$time_now->getTimestamp(); ?>
<?php if ($rest_seconds > 86400) : ?>
<?= UOJLocale::get('contests::will start in x days', ceil($rest_seconds / 86400)) ?>
<?php else : ?>
<div id="contest-<?= $contest['id'] ?>-countdown"></div>
<div id="contest-<?= $contest->info['id'] ?>-countdown"></div>
<script>
$('#contest-<?= $contest['id'] ?>-countdown').countdown(<?= $rest_seconds ?>, function() {}, 'inherit', false);
$('#contest-<?= $contest->info['id'] ?>-countdown').countdown(<?= $rest_seconds ?>, function() {}, 'inherit', false);
</script>
<?php endif ?>
<?php endif ?>
@ -116,7 +92,7 @@
</li>
<?php endif ?>
<?php endforeach ?>
<?php if ($count == 0) : ?>
<?php if (empty($upcoming_contests)) : ?>
<li class="list-group-item text-center">
<?= UOJLocale::get('none') ?>
</li>

View File

@ -5,19 +5,17 @@
<div class="card">
<img class="card-img-top" alt="Avatar of <?= $user['username'] ?>" src="<?= HTML::avatar_addr($user, 512) ?>" />
<div class="card-body">
<?php if ($user['usergroup'] == 'S'): ?>
<span class="badge bg-secondary">
<?= UOJLocale::get('user::admin') ?>
</span>
<?php if ($user['usergroup'] == 'S') : ?>
<span class="badge bg-secondary">
<?= UOJLocale::get('user::admin') ?>
</span>
<?php endif ?>
<h3>
<?= $user['username'] ?>
<span class="fs-6 align-middle"
<?php if ($user['sex'] == 'M'): ?>
style="color: blue"><i class="bi bi-gender-male"></i>
<?php elseif ($user['sex'] == 'F'): ?>
<span class="fs-6 align-middle" <?php if ($user['sex'] == 'M') : ?> style="color: blue"><i class="bi bi-gender-male"></i>
<?php elseif ($user['sex'] == 'F') : ?>
style="color: red"><i class="bi bi-gender-female"></i>
<?php else: ?>
<?php else : ?>
>
<?php endif ?>
</span>
@ -27,130 +25,136 @@
</div>
</div>
<ul class="list-group list-group-flush">
<?php if ($user['realname']): ?>
<li class="list-group-item">
<i class="bi bi-person-fill me-1"></i>
<?= $user['realname'] ?>
</li>
<?php if ($user['realname']) : ?>
<li class="list-group-item">
<i class="bi bi-person-fill me-1"></i>
<?= $user['realname'] ?>
</li>
<?php endif ?>
<?php if ($user['school']): ?>
<li class="list-group-item">
<i class="bi bi-person-badge-fill me-1"></i>
<?= $user['school'] ?>
</li>
<?php if ($user['school']) : ?>
<li class="list-group-item">
<i class="bi bi-person-badge-fill me-1"></i>
<?= $user['school'] ?>
</li>
<?php endif ?>
<?php if ($user['usertype']): ?>
<li class="list-group-item">
<i class="bi bi-key-fill me-1"></i>
<?php foreach (explode(',', $user['usertype']) as $idx => $type): ?>
<?php if ($idx): ?>,<?php endif ?>
<span><?= UOJLocale::get('user::' . str_replace('_', ' ', $type)) ?: HTML::escape($type) ?></span>
<?php endforeach ?>
</li>
<?php if ($user['usertype']) : ?>
<li class="list-group-item">
<i class="bi bi-key-fill me-1"></i>
<?php foreach (explode(',', $user['usertype']) as $idx => $type) : ?>
<?php if ($idx) : ?>,<?php endif ?>
<span><?= UOJLocale::get('user::' . str_replace('_', ' ', $type)) ?: HTML::escape($type) ?></span>
<?php endforeach ?>
</li>
<?php endif ?>
<?php if ($user['email']): ?>
<li class="list-group-item">
<i class="bi bi-envelope-fill me-1"></i>
<a class="text-decoration-none text-body" href="mailto:<?= HTML::escape($user['email']) ?>">
<?= HTML::escape($user['email']) ?>
</a>
</li>
<?php endif ?>
<?php if ($user['qq']): ?>
<li class="list-group-item">
<i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path d="M433.754 420.445c-11.526 1.393-44.86-52.741-44.86-52.741 0 31.345-16.136 72.247-51.051 101.786 16.842 5.192 54.843 19.167 45.803 34.421-7.316 12.343-125.51 7.881-159.632 4.037-34.122 3.844-152.316 8.306-159.632-4.037-9.045-15.25 28.918-29.214 45.783-34.415-34.92-29.539-51.059-70.445-51.059-101.792 0 0-33.334 54.134-44.859 52.741-5.37-.65-12.424-29.644 9.347-99.704 10.261-33.024 21.995-60.478 40.144-105.779C60.683 98.063 108.982.006 224 0c113.737.006 163.156 96.133 160.264 214.963 18.118 45.223 29.912 72.85 40.144 105.778 21.768 70.06 14.716 99.053 9.346 99.704z" fill="currentColor"/></svg></i>
<a class="text-decoration-none text-body" href="http://wpa.qq.com/msgrd?v=3&uin=<?= HTML::escape($user['qq']) ?>&site=qq&menu=yes" target="_blank">
<?= HTML::escape($user['qq']) ?>
</a>
</li>
<?php endif ?>
<?php if ($extra['social']['github']): ?>
<li class="list-group-item">
<i class="bi bi-github me-1"></i>
<a class="text-decoration-none text-body" href="https://github.com/<?= HTML::escape($extra['social']['github']) ?>" target="_blank">
<?= HTML::escape($extra['social']['github']) ?>
</a>
</li>
<?php endif ?>
<?php if ($extra['social']['codeforces']): ?>
<li class="list-group-item d-flex align-items-center">
<div class="flex-shrink-0"><i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" width="16" height="16"><title>Codeforces</title><path d="M4.5 7.5C5.328 7.5 6 8.172 6 9v10.5c0 .828-.672 1.5-1.5 1.5h-3C.673 21 0 20.328 0 19.5V9c0-.828.673-1.5 1.5-1.5h3zm9-4.5c.828 0 1.5.672 1.5 1.5v15c0 .828-.672 1.5-1.5 1.5h-3c-.827 0-1.5-.672-1.5-1.5v-15c0-.828.673-1.5 1.5-1.5h3zm9 7.5c.828 0 1.5.672 1.5 1.5v7.5c0 .828-.672 1.5-1.5 1.5h-3c-.828 0-1.5-.672-1.5-1.5V12c0-.828.672-1.5 1.5-1.5h3z" fill="currentColor"/></svg></i>&nbsp;</div>
<div>
<a id="codeforces-profile-link" class="text-decoration-none" href="https://codeforces.com/profile/<?= $extra['social']['codeforces'] ?>" target="_blank" style="color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;">
<?= $extra['social']['codeforces'] ?>
<?php if ($user['email']) : ?>
<li class="list-group-item">
<i class="bi bi-envelope-fill me-1"></i>
<a class="text-decoration-none text-body" href="mailto:<?= HTML::escape($user['email']) ?>">
<?= HTML::escape($user['email']) ?>
</a>
<div id="codeforces-rating" style="font-family: verdana, arial, sans-serif; line-height: 1.2em; text-transform: capitalize;"></div>
</div>
<script>
function getRatingColor(rating) {
if (rating >= 2400) return 'ff0000';
if (rating >= 2100) return 'ff8c00';
if (rating >= 1900) return 'aa00aa';
if (rating >= 1600) return '0000ff';
if (rating >= 1400) return '03a89e';
if (rating >= 1200) return '008000';
return '808080';
}
function showCodeforcesRating(handle, rating, text) {
var color = '#' + getRatingColor(rating);
$('#codeforces-profile-link')
.html(rating >= 3000 ? ('<span style="color:#000!important">' + handle[0] + '</span>' + handle.substring(1)) : handle)
.css('color', color)
.css('font-family', 'Helvetica Neue, Helvetica, Arial, sans-serif')
.css('font-size', '1.1em')
.css('font-weight', 'bold');
$('#codeforces-rating')
.html(text + ', ' + rating)
.css('color', color);
}
function processCodeforcesInfoData(data) {
if (!data || data.status !== 'OK' || !data.result || !data.result.length) return;
var result = data.result[0];
if (result.rating) {
showCodeforcesRating(result.handle, result.rating, result.rank);
} else {
showCodeforcesRating(result.handle, 0, 'Unrated');
}
}
$(document).ready(function() {
$.get('https://codeforces.com/api/user.info?handles=<?= $extra['social']['codeforces'] ?>', function(data) {
processCodeforcesInfoData(data);
});
});
</script>
</li>
</li>
<?php endif ?>
<?php if ($extra['social']['website']): ?>
<li class="list-group-item">
<i class="bi bi-link-45deg me-1"></i>
<a class="text-decoration-none text-body text-break" href="<?= HTML::escape($extra['social']['website']) ?>" target="_blank">
<?= HTML::escape($extra['social']['website']) ?>
</a>
</li>
<?php if ($user['qq']) : ?>
<li class="list-group-item">
<i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16">
<path d="M433.754 420.445c-11.526 1.393-44.86-52.741-44.86-52.741 0 31.345-16.136 72.247-51.051 101.786 16.842 5.192 54.843 19.167 45.803 34.421-7.316 12.343-125.51 7.881-159.632 4.037-34.122 3.844-152.316 8.306-159.632-4.037-9.045-15.25 28.918-29.214 45.783-34.415-34.92-29.539-51.059-70.445-51.059-101.792 0 0-33.334 54.134-44.859 52.741-5.37-.65-12.424-29.644 9.347-99.704 10.261-33.024 21.995-60.478 40.144-105.779C60.683 98.063 108.982.006 224 0c113.737.006 163.156 96.133 160.264 214.963 18.118 45.223 29.912 72.85 40.144 105.778 21.768 70.06 14.716 99.053 9.346 99.704z" fill="currentColor" />
</svg></i>
<a class="text-decoration-none text-body" href="http://wpa.qq.com/msgrd?v=3&uin=<?= HTML::escape($user['qq']) ?>&site=qq&menu=yes" target="_blank">
<?= HTML::escape($user['qq']) ?>
</a>
</li>
<?php endif ?>
<?php if ($extra['social']['github']) : ?>
<li class="list-group-item">
<i class="bi bi-github me-1"></i>
<a class="text-decoration-none text-body" href="https://github.com/<?= HTML::escape($extra['social']['github']) ?>" target="_blank">
<?= HTML::escape($extra['social']['github']) ?>
</a>
</li>
<?php endif ?>
<?php if ($extra['social']['codeforces']) : ?>
<li class="list-group-item d-flex align-items-center">
<div class="flex-shrink-0"><i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" width="16" height="16">
<title>Codeforces</title>
<path d="M4.5 7.5C5.328 7.5 6 8.172 6 9v10.5c0 .828-.672 1.5-1.5 1.5h-3C.673 21 0 20.328 0 19.5V9c0-.828.673-1.5 1.5-1.5h3zm9-4.5c.828 0 1.5.672 1.5 1.5v15c0 .828-.672 1.5-1.5 1.5h-3c-.827 0-1.5-.672-1.5-1.5v-15c0-.828.673-1.5 1.5-1.5h3zm9 7.5c.828 0 1.5.672 1.5 1.5v7.5c0 .828-.672 1.5-1.5 1.5h-3c-.828 0-1.5-.672-1.5-1.5V12c0-.828.672-1.5 1.5-1.5h3z" fill="currentColor" />
</svg></i>&nbsp;</div>
<div>
<a id="codeforces-profile-link" class="text-decoration-none" href="https://codeforces.com/profile/<?= $extra['social']['codeforces'] ?>" target="_blank" style="color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;">
<?= $extra['social']['codeforces'] ?>
</a>
<div id="codeforces-rating" style="font-family: verdana, arial, sans-serif; line-height: 1.2em; text-transform: capitalize;"></div>
</div>
<script>
function getRatingColor(rating) {
if (rating >= 2400) return 'ff0000';
if (rating >= 2100) return 'ff8c00';
if (rating >= 1900) return 'aa00aa';
if (rating >= 1600) return '0000ff';
if (rating >= 1400) return '03a89e';
if (rating >= 1200) return '008000';
return '808080';
}
function showCodeforcesRating(handle, rating, text) {
var color = '#' + getRatingColor(rating);
$('#codeforces-profile-link')
.html(rating >= 3000 ? ('<span style="color:#000!important">' + handle[0] + '</span>' + handle.substring(1)) : handle)
.css('color', color)
.css('font-family', 'Helvetica Neue, Helvetica, Arial, sans-serif')
.css('font-size', '1.1em')
.css('font-weight', 'bold');
$('#codeforces-rating')
.html(text + ', ' + rating)
.css('color', color);
}
function processCodeforcesInfoData(data) {
if (!data || data.status !== 'OK' || !data.result || !data.result.length) return;
var result = data.result[0];
if (result.rating) {
showCodeforcesRating(result.handle, result.rating, result.rank);
} else {
showCodeforcesRating(result.handle, 0, 'Unrated');
}
}
$(document).ready(function() {
$.get('https://codeforces.com/api/user.info?handles=<?= $extra['social']['codeforces'] ?>', function(data) {
processCodeforcesInfoData(data);
});
});
</script>
</li>
<?php endif ?>
<?php if ($extra['social']['website']) : ?>
<li class="list-group-item">
<i class="bi bi-link-45deg me-1"></i>
<a class="text-decoration-none text-body text-break" href="<?= HTML::escape($extra['social']['website']) ?>" target="_blank">
<?= HTML::escape($extra['social']['website']) ?>
</a>
</li>
<?php endif ?>
</ul>
<div class="card-footer bg-transparent">
<?php $last_visit_time = strtotime($user['last_visit_time']) ?>
<?php if (time() - $last_visit_time < 60 * 15): // 15 mins ?>
<?php if (time() - $last_visit_time < 60 * 15) : // 15 mins
?>
<span class="text-success">
<i class="bi bi-circle-fill me-1"></i>
<?= UOJLocale::get('user::online') ?>
</span>
<?php else: ?>
<?php else : ?>
<span class="text-danger">
<i class="bi bi-circle-fill me-1"></i>
<?= UOJLocale::get('user::offline') ?>
</span>
<?php if ($last_visit_time > 0): ?>
<?php if ($last_visit_time > 0) : ?>
<span class="text-muted small">
, <?= UOJLocale::get('user::last active at') ?>
<?= HTML::relative_time_str($last_visit_time, 0) ?>
(<?= UOJLocale::get('user::last active at') ?>
<?= HTML::relative_time_str($last_visit_time, 0) ?>)
</span>
<?php endif ?>
<?php endif ?>
@ -159,109 +163,109 @@
</div>
<div class="col-md-9 mt-2 mt-md-0">
<nav class="nav mb-2">
<?php if (Auth::check()): ?>
<?php if (Auth::id() != $user['username']): ?>
<?php if (Auth::check()) : ?>
<?php if (Auth::id() != $user['username']) : ?>
<a class="nav-link" href="/user_msg?enter=<?= $user['username'] ?>">
<i class="bi bi-chat-left-dots"></i>
<?= UOJLocale::get('send private message') ?>
</a>
<?php endif ?>
<?php if (Auth::id() == $user['username'] || isSuperUser(Auth::user())): ?>
<?php if (Auth::id() == $user['username'] || isSuperUser(Auth::user())) : ?>
<a class="nav-link" href="/user/<?= $user['username'] ?>/edit">
<i class="bi bi-pencil"></i>
<?= UOJLocale::get('modify my profile') ?>
</a>
<?php endif ?>
<?php endif ?>
<?php if (!isset($is_blog_aboutme)): ?>
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/') ?>">
<i class="bi bi-arrow-right-square"></i>
<?= UOJLocale::get('visit his blog', $user['username']) ?>
</a>
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>">
<i class="bi bi-arrow-right-square"></i>
<?= UOJLocale::get('contests::contest self reviews') ?>
</a>
<?php if (!isset($is_blog_aboutme)) : ?>
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/') ?>">
<i class="bi bi-arrow-right-square"></i>
<?= UOJLocale::get('visit his blog', $user['username']) ?>
</a>
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>">
<i class="bi bi-arrow-right-square"></i>
<?= UOJLocale::get('contests::contest self reviews') ?>
</a>
<?php endif ?>
</nav>
<?php if (!isset($is_blog_aboutme)): ?>
<?php $groups = queryGroupsOfUser($user['username']) ?>
<div class="card mb-2">
<div class="card-body">
<h4 class="card-title">
<?= UOJLocale::get('user::belongs to these groups') ?>
</h4>
<ul class="mb-0">
<?php foreach ($groups as $group): ?>
<li>
<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>">
<?= $group['title'] ?>
</a>
</li>
<?php endforeach ?>
<?php if (empty($groups)): ?>
<?= UOJLocale::get('none') ?>
<?php endif ?>
</ul>
</div>
</div>
<?php if (!isset($is_blog_aboutme)) : ?>
<?php $groups = UOJGroup::queryGroupsOfUser($user['username']) ?>
<div class="card mb-2">
<div class="card-body">
<h4 class="card-title">
<?= UOJLocale::get('user::belongs to these groups') ?>
</h4>
<ul class="mb-0">
<?php foreach ($groups as $group) : ?>
<li>
<?= $group->getLink() ?>
</li>
<?php endforeach ?>
<?php if (empty($groups)) : ?>
<?= UOJLocale::get('none') ?>
<?php endif ?>
</ul>
</div>
</div>
<?php endif ?>
<div class="card mb-2">
<div class="card-body">
<?php
$_result = DB::query("select date_format(submit_time, '%Y-%m-%d'), problem_id from submissions where submitter = '{$user['username']}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
$result = [];
$vis = [];
$cnt = 0;
while ($row = DB::fetch($_result)) {
$cnt++;
$result[$row["date_format(submit_time, '%Y-%m-%d')"]]++;
}
?>
<?php
$_result = DB::query("select date_format(submit_time, '%Y-%m-%d'), problem_id from submissions where submitter = '{$user['username']}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
$result = [];
$vis = [];
$cnt = 0;
while ($row = DB::fetch($_result)) {
$cnt++;
$result[$row["date_format(submit_time, '%Y-%m-%d')"]]++;
}
?>
<h4 class="card-title">
<?= UOJLocale::get('n accepted in last year', $cnt) ?>
</h4>
<div id="accepted-graph" style="font-size: 14px"></div>
<script>
var accepted_graph_data = [
<?php foreach ($result as $key => $val): ?>
{ date: '<?= $key ?>', count: <?= $val ?> },
<?php foreach ($result as $key => $val) : ?> {
date: '<?= $key ?>',
count: <?= $val ?>
},
<?php endforeach ?>
];
$(document).ready(function () {
$(document).ready(function() {
$('#accepted-graph').CalendarHeatmap(accepted_graph_data, {});
});
</script>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<?php $ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}' order by id") ?>
<h4 class="card-title">
<?= UOJLocale::get('accepted problems').': '.UOJLocale::get('n problems in total', count($ac_problems))?>
</h4>
<ul class="nav uoj-ac-problems-list">
<?php foreach ($ac_problems as $problem): ?>
<li class="nav-item">
<a class="nav-link rounded uoj-ac-problems-list-item" href="/problem/<?= $problem['problem_id'] ?>" role="button">
#<?= $problem['problem_id'] ?>. <?= $problem['title'] ?>
</a>
</li>
<?php endforeach ?>
<div class="card-body">
<?php $ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}' order by id") ?>
<h4 class="card-title">
<?= UOJLocale::get('accepted problems') . ': ' . UOJLocale::get('n problems in total', count($ac_problems)) ?>
</h4>
<ul class="nav uoj-ac-problems-list">
<?php foreach ($ac_problems as $problem) : ?>
<li class="nav-item">
<a class="nav-link rounded uoj-ac-problems-list-item" href="/problem/<?= $problem['problem_id'] ?>" role="button">
#<?= $problem['problem_id'] ?>. <?= $problem['title'] ?>
</a>
</li>
<?php endforeach ?>
<?php if (empty($ac_problems)): ?>
<?= UOJLocale::get('none'); ?>
<?php endif ?>
</ul>
</div>
<?php if (empty($ac_problems)) : ?>
<?= UOJLocale::get('none'); ?>
<?php endif ?>
</ul>
</div>
</div>
<?php if (isSuperUser(Auth::user())): ?>
<?php if (isSuperUser(Auth::user())) : ?>
<div class="card card-default">
<ul class="list-group list-group-flush">
<li class="list-group-item">

View File

@ -452,3 +452,8 @@ form.uoj-bs4-form-compressed button {
.CodeMirror-linenumber {
font-size: 14px !important;
}
.uoj-back-btn {
--bs-btn-hover-bg: #d3d4d570;
--bs-btn-hover-border-color: transparent;
}