refactor(web/group): group_v3 (#17)
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2022-11-11 14:01:21 +08:00 committed by GitHub
commit d7446b9fa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 837 additions and 629 deletions

View File

@ -48,15 +48,12 @@ if ($is_manager) {
'小组 ID', '小组 ID',
'', '',
function ($group_id, &$vdata) { function ($group_id, &$vdata) {
if (!validateUInt($group_id)) { $group = UOJGroup::query($group_id);
return '小组 ID 不合法';
}
$group = queryGroup($group_id);
if (!$group) { if (!$group) {
return '小组不存在'; return '小组不存在';
} }
$vdata['group_id'] = $group_id; $vdata['group'] = $group;
return ''; 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['align'] = 'compressed';
$add_group_to_contest_form->submit_button_config['text'] = '注册该小组中的用户'; $add_group_to_contest_form->submit_button_config['text'] = '注册该小组中的用户';
$add_group_to_contest_form->handle = function (&$vdata) { $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); UOJContest::cur()->userRegister($user);
} }
}; };
@ -177,7 +176,8 @@ if ($contest['cur_progress'] == CONTEST_NOT_STARTED) {
<?php endif ?> <?php endif ?>
<?php <?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) { if ($show_ip) {
$header_row .= '<th>remote_addr</th><th>http_x_forwarded_for</th>'; $header_row .= '<th>remote_addr</th><th>http_x_forwarded_for</th>';
} }

View File

@ -4,41 +4,38 @@
requirePHPLib('judger'); requirePHPLib('judger');
requirePHPLib('data'); requirePHPLib('data');
if (!Auth::check()) { Auth::check() || redirectToLogin();
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"> <div class="row">
<!-- left col --> <!-- left col -->
<div class="col-lg-9"> <div class="col-lg-9">
<!-- title --> <!-- title -->
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<h1> <h1>
<?php if ($group['is_hidden']): ?> <?= UOJGroup::info('title') ?>
<span class="fs-5 text-danger">[隐藏]</span> <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 ?> <?php endif ?>
<?= $group['title'] ?>
<span class="fs-5">(ID: #<?= $group['id'] ?>)</span>
</h1> </h1>
<?php if (isSuperUser($myUser)): ?> <?php if (UOJGroup::cur()->userCanManage(Auth::user())) : ?>
<div class="text-end"> <div class="text-end">
<a class="btn btn-primary" href="/group/<?= $group['id'] ?>/manage" role="button"> <?=
<?= UOJLocale::get('problems::manage') ?> UOJGroup::cur()->getLink([
</a> 'where' => '/manage',
'class' => 'btn btn-primary',
'text' => UOJLocale::get('problems::manage'),
]);
?>
</div> </div>
<?php endif ?> <?php endif ?>
</div> </div>
@ -50,9 +47,9 @@
<h2 class="h3"> <h2 class="h3">
<?= UOJLocale::get('group announcement') ?> <?= UOJLocale::get('group announcement') ?>
</h2> </h2>
<?php if ($group['announcement']): ?> <?php if (UOJGroup::info('announcement')) : ?>
<div class="text-break"> <div class="text-break">
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line($group['announcement'])) ?> <?= HTML::purifier_inline()->purify(HTML::parsedown()->line(UOJGroup::info('announcement'))) ?>
</div> </div>
<?php else : ?> <?php else : ?>
<div class="text-muted"> <div class="text-muted">
@ -66,22 +63,21 @@
<div class="card-body"> <div class="card-body">
<h2 class="card-title h3"> <h2 class="card-title h3">
<?= UOJLocale::get('news') ?> <?= UOJLocale::get('news') ?>
</h5> </h2>
<ul class="mb-0"> <ul class="mb-0">
<?php foreach (UOJGroup::cur()->getLatestGroupmatesAcceptedSubmissionIds(Auth::user()) as $id) : ?>
<?php <?php
$current_ac = queryGroupCurrentAC($group['id']); $submission = UOJSubmission::query($id);
foreach ($current_ac as $ac) { $submission->setProblem();
echo '<li>'; $user = UOJUser::query($submission->info['submitter']);
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 '暂无最新动态';
}
?> ?>
<li>
<?= UOJUser::getLink($user) ?>
解决了问题
<?= $submission->problem->getLink(['with' => 'id']) ?>
(<time><?= $submission->info['submit_time'] ?></time>)
</li>
<?php endforeach ?>
</ul> </ul>
</div> </div>
</div> </div>
@ -90,17 +86,22 @@
<div class="card-body"> <div class="card-body">
<h2 class="card-title h3"> <h2 class="card-title h3">
<?= UOJLocale::get('assignments') ?> <?= UOJLocale::get('assignments') ?>
</h5> </h2>
<?php <?php
echoLongTable( echoLongTable(
['*'],
[ [
'groups_assignments.list_id as list_id', "groups_assignments",
'lists.title as title', "left join lists",
'groups_assignments.end_time as end_time' "on", [
"lists.id" => DB::raw("groups_assignments.list_id"),
]
], ],
'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')", "groups_assignments.group_id" => UOJGroup::info('id'),
'order by end_time desc, list_id desc', ["groups_assignments.end_time", ">", DB::raw("addtime(now(), '-168:00:00')")]
],
'order by groups_assignments.end_time desc, groups_assignments.list_id desc',
<<<EOD <<<EOD
<tr> <tr>
<th style="width:3em" class="text-center">ID</th> <th style="width:3em" class="text-center">ID</th>
@ -109,25 +110,25 @@
<th style="width:8em">结束时间</th> <th style="width:8em">结束时间</th>
</tr> </tr>
EOD, EOD,
function($row) use ($group) { function ($info) {
$end_time = DateTime::createFromFormat('Y-m-d H:i:s', $row['end_time']); $assignment = new UOJGroupAssignment($info, UOJGroup::cur());
echo '<tr>'; echo HTML::tag_begin('tr');
echo '<td class="text-center">', $row['list_id'], '</td>'; echo HTML::tag('td', ['class' => 'text-center'], $assignment->info['id']);
echo '<td>', '<a class="text-decoration-none" href="/group/', $group['id'], '/assignment/', $row['list_id'],'">', HTML::escape($row['title']), '</a>', '</td>'; echo HTML::tag('td', [], $assignment->getLink());
if ($end_time < UOJTime::$time_now) { if ($assignment->info['end_time'] < UOJTime::$time_now) {
echo '<td class="text-danger">已结束</td>'; echo HTML::tag('td', ['class' => 'text-danger'], '已结束');
} else { } 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 '</tr>'; echo HTML::tag_end('tr');
}, },
[ [
'echo_full' => true, 'echo_full' => true,
'div_classes' => ['table-responsive'], 'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle', 'mb-0'], 'table_classes' => ['table', 'align-middle', 'mb-0'],
] ],
); );
?> ?>
</div> </div>
@ -137,23 +138,19 @@ EOD,
<div class="card-body"> <div class="card-body">
<h2 class="card-title h3"> <h2 class="card-title h3">
<?= UOJLocale::get('top solver') ?> <?= UOJLocale::get('top solver') ?>
</h5> </h2>
<?php echoRanklist([ <?php UOJRanklist::printHTML([
'page_len' => 50, 'page_len' => 25,
'group_id' => $group_id, 'group_id' => UOJGroup::info('id'),
'by_accepted' => true,
'div_classes' => ['table-responsive', 'mb-3'],
'table_classes' => ['table', 'text-center', 'mb-0'],
]) ?> ]) ?>
</div> </div>
</div> </div>
<!-- end left col --> <!-- end left col -->
</div> </div>
<!-- right col --> <!-- right col -->
<aside class="col-lg-3 mt-3 mt-lg-0"> <aside class="col-lg-3 mt-3 mt-lg-0">
<?php uojIncludeView('sidebar'); ?> <?php uojIncludeView('sidebar') ?>
</aside> </aside>
</div> </div>

View File

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

View File

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

View File

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

View File

@ -17,6 +17,34 @@ class UOJContest {
return new UOJContest($info); 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() { public static function finalTest() {
$contest = self::info(); $contest = self::info();
@ -436,6 +464,15 @@ class UOJContest {
return "/contest/{$this->info['id']}{$where}"; 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() { public function redirectToAnnouncementBlog() {
$url = getContestBlogLink($this->info, '公告'); $url = getContestBlogLink($this->info, '公告');
if ($url !== null) { 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; $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() { public function getProblemIDs() {
return array_map(fn ($x) => $x['problem_id'], DB::selectAll([ return array_map(fn ($x) => $x['problem_id'], DB::selectAll([
DB::lc(), "select problem_id from lists_problems", DB::lc(), "select problem_id from lists_problems",

View File

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

View File

@ -5,9 +5,21 @@ class UOJRanklist {
$cfg += [ $cfg += [
'top10' => false, 'top10' => false,
'card' => false, 'card' => false,
'group_id' => null,
'page_len' => 50,
]; ];
$conds = [];
if ($cfg['group_id']) {
$conds[] = [
"username", "in", DB::rawtuple(UOJGroup::query($cfg['group_id'])->getUsernames()),
];
}
if (empty($conds)) {
$conds = '1'; $conds = '1';
}
$last_user = null; $last_user = null;
$parsedown = HTML::parsedown(); $parsedown = HTML::parsedown();
@ -55,7 +67,6 @@ class UOJRanklist {
$last_user = $user; $last_user = $user;
}; };
$pag_config = [ $pag_config = [
'get_row_index' => '', 'get_row_index' => '',
'table_name' => 'user_info', 'table_name' => 'user_info',
@ -68,7 +79,7 @@ class UOJRanklist {
$pag_config['tail'] .= ' limit 10'; $pag_config['tail'] .= ' limit 10';
$pag_config['echo_full'] = ''; $pag_config['echo_full'] = '';
} else { } else {
$pag_config['page_len'] = 50; $pag_config['page_len'] = $cfg['page_len'];
} }
$pag = new Paginator($pag_config); $pag = new Paginator($pag_config);
@ -102,9 +113,21 @@ class UOJRanklist {
public static function printTableHTML($cfg = []) { public static function printTableHTML($cfg = []) {
$cfg += [ $cfg += [
'top10' => false, 'top10' => false,
'group_id' => null,
'page_len' => 100,
]; ];
$conds = [];
if ($cfg['group_id']) {
$conds[] = [
"username", "in", DB::rawtuple(UOJGroup::query($cfg['group_id'])->getUsernames()),
];
}
if (empty($conds)) {
$conds = '1'; $conds = '1';
}
$header_row = ''; $header_row = '';
$header_row .= '<tr>'; $header_row .= '<tr>';
@ -157,7 +180,7 @@ class UOJRanklist {
$tail .= ' limit 10'; $tail .= ' limit 10';
$table_config['echo_full'] = ''; $table_config['echo_full'] = '';
} else { } 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); 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 * Need to be consistent with the member function userCanView
*/ */
public static function sqlForUserCanView(array $user = null, UOJProblem $problem = null) { 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. // 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)"; return "(submissions.is_hidden = true or submissions.is_hidden = false)";
} elseif ($problem) { } elseif ($problem) {

View File

@ -313,34 +313,6 @@ if (!isset($ShowPageHeader)) {
<?php endif ?> <?php endif ?>
<?php uojIncludeView($PageNav, array('REQUIRE_LIB' => $REQUIRE_LIB)) ?> <?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 ?> <?php endif ?>

View File

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

View File

@ -12,9 +12,7 @@
<?php endif ?> <?php endif ?>
<h3> <h3>
<?= $user['username'] ?> <?= $user['username'] ?>
<span class="fs-6 align-middle" <span class="fs-6 align-middle" <?php if ($user['sex'] == 'M') : ?> style="color: blue"><i class="bi bi-gender-male"></i>
<?php if ($user['sex'] == 'M'): ?>
style="color: blue"><i class="bi bi-gender-male"></i>
<?php elseif ($user['sex'] == 'F') : ?> <?php elseif ($user['sex'] == 'F') : ?>
style="color: red"><i class="bi bi-gender-female"></i> style="color: red"><i class="bi bi-gender-female"></i>
<?php else : ?> <?php else : ?>
@ -58,7 +56,9 @@
<?php endif ?> <?php endif ?>
<?php if ($user['qq']) : ?> <?php if ($user['qq']) : ?>
<li class="list-group-item"> <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> <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"> <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']) ?> <?= HTML::escape($user['qq']) ?>
</a> </a>
@ -74,7 +74,10 @@
<?php endif ?> <?php endif ?>
<?php if ($extra['social']['codeforces']) : ?> <?php if ($extra['social']['codeforces']) : ?>
<li class="list-group-item d-flex align-items-center"> <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 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> <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;"> <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'] ?> <?= $extra['social']['codeforces'] ?>
@ -137,7 +140,8 @@
</ul> </ul>
<div class="card-footer bg-transparent"> <div class="card-footer bg-transparent">
<?php $last_visit_time = strtotime($user['last_visit_time']) ?> <?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"> <span class="text-success">
<i class="bi bi-circle-fill me-1"></i> <i class="bi bi-circle-fill me-1"></i>
<?= UOJLocale::get('user::online') ?> <?= UOJLocale::get('user::online') ?>
@ -149,8 +153,8 @@
</span> </span>
<?php if ($last_visit_time > 0) : ?> <?php if ($last_visit_time > 0) : ?>
<span class="text-muted small"> <span class="text-muted small">
, <?= UOJLocale::get('user::last active at') ?> (<?= UOJLocale::get('user::last active at') ?>
<?= HTML::relative_time_str($last_visit_time, 0) ?> <?= HTML::relative_time_str($last_visit_time, 0) ?>)
</span> </span>
<?php endif ?> <?php endif ?>
<?php endif ?> <?php endif ?>
@ -188,7 +192,7 @@
</nav> </nav>
<?php if (!isset($is_blog_aboutme)) : ?> <?php if (!isset($is_blog_aboutme)) : ?>
<?php $groups = queryGroupsOfUser($user['username']) ?> <?php $groups = UOJGroup::queryGroupsOfUser($user['username']) ?>
<div class="card mb-2"> <div class="card mb-2">
<div class="card-body"> <div class="card-body">
<h4 class="card-title"> <h4 class="card-title">
@ -197,9 +201,7 @@
<ul class="mb-0"> <ul class="mb-0">
<?php foreach ($groups as $group) : ?> <?php foreach ($groups as $group) : ?>
<li> <li>
<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>"> <?= $group->getLink() ?>
<?= $group['title'] ?>
</a>
</li> </li>
<?php endforeach ?> <?php endforeach ?>
<?php if (empty($groups)) : ?> <?php if (empty($groups)) : ?>
@ -228,8 +230,10 @@ while ($row = DB::fetch($_result)) {
<div id="accepted-graph" style="font-size: 14px"></div> <div id="accepted-graph" style="font-size: 14px"></div>
<script> <script>
var accepted_graph_data = [ var accepted_graph_data = [
<?php foreach ($result as $key => $val): ?> <?php foreach ($result as $key => $val) : ?> {
{ date: '<?= $key ?>', count: <?= $val ?> }, date: '<?= $key ?>',
count: <?= $val ?>
},
<?php endforeach ?> <?php endforeach ?>
]; ];

View File

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