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

This commit is contained in:
Baoshuo Ren 2022-10-21 22:06:26 +08:00 committed by GitHub
commit b6b8efd5fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 726 additions and 478 deletions

View File

@ -366,7 +366,7 @@ UNLOCK TABLES;
CREATE TABLE `countdowns` ( CREATE TABLE `countdowns` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` text NOT NULL, `title` text NOT NULL,
`endtime` datetime NOT NULL, `end_time` datetime NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@ -448,6 +448,21 @@ CREATE TABLE `groups` (
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `groups_assignments`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `groups_assignments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`list_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
`end_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
-- --
-- Table structure for table `groups_users` -- Table structure for table `groups_users`
-- --
@ -461,22 +476,6 @@ CREATE TABLE `groups_users` (
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `assignments`
--
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8mb4 */;
CREATE TABLE `assignments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`list_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
`create_time` datetime NOT NULL,
`deadline` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
-- --
-- Table structure for table `hacks` -- Table structure for table `hacks`
-- --

View File

@ -1,4 +1,5 @@
<?php <?php
requireLib('bootstrap5');
requirePHPLib('form'); requirePHPLib('form');
requirePHPLib('judger'); requirePHPLib('judger');
requirePHPLib('data'); requirePHPLib('data');
@ -16,20 +17,19 @@
become404Page(); become404Page();
} }
if (!isset($_COOKIE['bootstrap4'])) { if (!isSuperUser($myUser) && $group['is_hidden']) {
$REQUIRE_LIB['bootstrap5'] = ''; become403Page();
} }
?> ?>
<?php echoUOJPageHeader(UOJLocale::get('groups')) ?> <?php echoUOJPageHeader(UOJLocale::get('groups')) ?>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<div class="row"> <div class="row">
<!-- left col -->
<div class="col-lg-9"> <div class="col-lg-9">
<div class="d-flex justify-content-between">
<?php endif ?>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?> <!-- title -->
<div class="d-flex justify-content-between">
<h1 class="h2"> <h1 class="h2">
<?php if ($group['is_hidden']): ?> <?php if ($group['is_hidden']): ?>
<span class="fs-5 text-danger">[隐藏]</span> <span class="fs-5 text-danger">[隐藏]</span>
@ -37,57 +37,48 @@
<?= $group['title'] ?> <?= $group['title'] ?>
<span class="fs-5">(ID: #<?= $group['id'] ?>)</span> <span class="fs-5">(ID: #<?= $group['id'] ?>)</span>
</h1> </h1>
<?php else: ?>
<h2 style="margin-top: 24px"><?= $group['title'] ?></h2>
<p>(<b>小组 ID</b>: <?= $group['id'] ?>)</p>
<?php endif ?>
<?php if (isSuperUser($myUser)): ?> <?php if (isSuperUser($myUser)): ?>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<div class="text-end"> <div class="text-end">
<a class="btn btn-primary" href="/group/<?= $group['id'] ?>/manage" role="button"> <a class="btn btn-primary" href="/group/<?= $group['id'] ?>/manage" role="button">
<?= UOJLocale::get('problems::manage') ?> <?= UOJLocale::get('problems::manage') ?>
</a> </a>
</div> </div>
<?php endif ?>
</div>
<!-- end title -->
<!-- main content -->
<div class="card mb-3">
<div class="card-body">
<h2 class="h4">
<?= UOJLocale::get('group announcement') ?>
</h2>
<?php if ($group['announcement']): ?>
<div class="text-break">
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line($group['announcement'])) ?>
</div>
<?php else: ?> <?php else: ?>
<ul class="nav nav-tabs" role="tablist"> <div class="text-muted">
<li class="nav-item"><a class="nav-link" href="/group/<?= $group['id'] ?>/manage" role="tab">管理</a></li> <?= UOJLocale::get('none') ?>
</ul>
<?php endif ?>
<?php endif ?>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
</div> </div>
<?php endif ?> <?php endif ?>
</div>
</div>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?> <div class="card mb-3">
<div class="card card-default mb-3">
<div class="card-body"> <div class="card-body">
<h2 class="card-title h4"> <h2 class="card-title h4">
<?php else: ?>
<div class="row">
<div class="col-sm-12 mt-4">
<h5>
<?php endif ?>
<?= UOJLocale::get('news') ?> <?= UOJLocale::get('news') ?>
</h5> </h5>
<ul <ul class="mb-0">
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
class="mb-0"
<?php endif ?>
>
<?php <?php
$current_ac = queryGroupCurrentAC($group['id']); $current_ac = queryGroupCurrentAC($group['id']);
foreach ($current_ac as $ac) { foreach ($current_ac as $ac) {
echo '<li>'; echo '<li>';
echo getUserLink($ac['submitter']); echo getUserLink($ac['submitter']);
echo ' 解决了问题 '; echo ' 解决了问题 ';
echo '<a '; echo '<a class="text-decoration-none" href="/problem/', $ac['problem_id'], '">', $ac['problem_title'], '</a> ';
if (isset($REQUIRE_LIB['bootstrap5'])) {
echo ' class="text-decoration-none" ';
}
echo ' href="/problem/', $ac['problem_id'], '">', $ac['problem_title'], '</a> ';
echo '<time class="time">(', $ac['submit_time'], ')</time>'; echo '<time class="time">(', $ac['submit_time'], ')</time>';
echo '</li>'; echo '</li>';
} }
@ -99,94 +90,72 @@
</div> </div>
</div> </div>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<div class="card card-default mb-3"> <div class="card card-default mb-3">
<div class="card-body"> <div class="card-body">
<h2 class="card-title h4"> <h2 class="card-title h4">
<?php else: ?>
<div class="row">
<div class="col-sm-12 mt-4">
<h5>
<?php endif ?>
<?= UOJLocale::get('assignments') ?> <?= UOJLocale::get('assignments') ?>
</h5> </h5>
<ul
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
class="mb-0"
<?php endif ?>
>
<?php <?php
$assignments = queryGroupActiveAssignments($group['id']);
foreach ($assignments as $ass) {
$ddl = DateTime::createFromFormat('Y-m-d H:i:s', $ass['deadline']);
$create_time = DateTime::createFromFormat('Y-m-d H:i:s', $ass['create_time']);
$now = new DateTime(); $now = new DateTime();
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, $now) {
$end_time = DateTime::createFromFormat('Y-m-d H:i:s', $row['end_time']);
echo '<li>'; echo '<tr>';
echo '<a '; echo '<td class="text-center">', $row['list_id'], '</td>';
if (isset($REQUIRE_LIB['bootstrap5'])) { echo '<td>', '<a class="text-decoration-none" href="/group/', $group['id'], '/assignment/', $row['list_id'],'">', HTML::escape($row['title']), '</a>', '</td>';
echo ' class="text-decoration-none" '; if ($end_time < $now) {
} echo '<td class="text-danger">已结束</td>';
echo ' href="/problem_list/', $ass['list_id'], '">', $ass['title'], ' (题单 #', $ass['list_id'], ')</a>'; } else {
echo '<td class="text-success">进行中</td>';
if ($ddl < $now) {
echo '<sup style="color:red">&nbsp;overdue</sup>';
} elseif ($ddl->getTimestamp() - $now->getTimestamp() < 86400) { // 1d
echo '<sup style="color:red">&nbsp;soon</sup>';
} elseif ($now->getTimestamp() - $create_time->getTimestamp() < 86400) { // 1d
echo '<sup style="color:red">&nbsp;new</sup>';
}
$ddl_str = $ddl->format('Y-m-d H:i');
echo ' (截止时间: ', $ddl_str, '<a ';
if (isset($REQUIRE_LIB['bootstrap5'])) {
echo ' class="text-decoration-none" ';
}
echo ' href="/group/',$group['id'],'/assignment/', $ass['list_id'], '">查看完成情况</a>)';
echo '</li>';
}
if (count($assignments) == 0) {
echo '<p>暂无作业</p>';
} }
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'],
]
);
?> ?>
</ul>
</div> </div>
</div> </div>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<div class="card card-default mb-3"> <div class="card card-default mb-3">
<div class="card-body"> <div class="card-body">
<h2 class="card-title h4"> <h2 class="card-title h4">
<?php else: ?>
<div class="row">
<div class="col-sm-12 mt-4">
<h5>
<?php endif ?>
<?= UOJLocale::get('top solver') ?> <?= UOJLocale::get('top solver') ?>
</h5> </h5>
<?php echoRanklist(array( <?php echoRanklist([
'echo_full' => true, 'page_len' => 50,
'group_id' => $group_id, 'group_id' => $group_id,
'by_accepted' => true, 'by_accepted' => true,
'table_classes' => isset($REQUIRE_LIB['bootstrap5']) 'table_classes' => ['table', 'text-center', 'mb-0'],
? array('table', 'text-center', 'mb-0') ]) ?>
: array('table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center')
)) ?>
</div> </div>
</div> </div>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<!-- 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', array()); ?> <?php uojIncludeView('sidebar'); ?>
</aside> </aside>
</div> </div>
<?php endif ?>
<?php echoUOJPageFooter() ?> <?php echoUOJPageFooter() ?>

View File

@ -40,85 +40,140 @@
<small class="fs-4">作业:</small><?= $list['title'] ?> <small class="fs-4">作业:</small><?= $list['title'] ?>
</h1> </h1>
<ul class="mt-3"> <ul class="mt-3">
<li>对应题单:<a class="text-decoration-none" href="<?= HTML::url('/problem_list/'.$list['id']) ?>">#<?= $list['id'] ?></a></li>
<li>所属小组:<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>"><?= $group['title'] ?></a></li> <li>所属小组:<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>"><?= $group['title'] ?></a></li>
<li>开始时间:<?= $assignment['create_time'] ?></li> <li>结束时间:<?= $assignment['end_time'] ?></li>
<li>结束时间:<?= $assignment['deadline'] ?></li>
</ul> </ul>
<?php <?php
$query = DB::query("select problem_id from lists_problems where list_id = {$list['id']}"); $problems = DB::selectAll("select problem_id from lists_problems where list_id = {$list['id']}");
$users = queryGroupUsers($group['id']);
$problem_ids = []; $problem_ids = [];
while ($row = DB::fetch($query)) { $usernames = [];
$problem_ids[] = $row['problem_id']; $n_users = count($users);
$n_problems = count($problems);
$submission_end_time = min(new DateTime(), DateTime::createFromFormat('Y-m-d H:i:s', $assignment['end_time']))->format('Y-m-d H:i:s');
foreach ($problems as $problem) {
$problem_ids[] = $problem['problem_id'];
} }
$header_row = ''; sort($problem_ids);
$header_row .= '<tr>';
$header_row .= '<th style="width: 10em;">'.UOJLocale::get('username').'</th>'; foreach ($users as $user) {
$header_row .= '<th style="width: 2em;">'.UOJLocale::get('contests::total score').'</th>'; $usernames[] = $user['username'];
foreach ($problem_ids as $problem_id) {
$header_row .= '<th style="width: 2em;">' . '<a class="text-decoration-none" href="'.HTML::url('/problem/'.$problem_id).'">#'.$problem_id.'</a>' . '</th>';
} }
$header_row .= '</tr>';
$print_row = function($row) use ($problem_ids) { // standings: rank => [user => [username, realname], scores[], rank]
$username = $row['username']; $standings = [];
$scores = []; foreach ($usernames as $username) {
$sum = 0; $user = queryUser($username);
$total_score = count($problem_ids) * 100; $row = ['total_score' => 0];
$query = DB::query("SELECT MAX(id), problem_id, MAX(score) FROM submissions WHERE (problem_id, score) IN (SELECT problem_id, MAX(score) FROM submissions WHERE submitter = '{$username}' AND problem_id IN (".implode(',', $problem_ids).") GROUP BY problem_id) AND submitter = '{$username}' GROUP BY problem_id");
while ($row = DB::fetch($query)) { $row['user'] = [
$scores[$row['problem_id']] = [ 'username' => $user['username'],
'submission_id' => $row['MAX(id)'], 'realname' => $user['realname'],
'score' => $row['MAX(score)'],
]; ];
$sum += $row['MAX(score)'];
}
if ($sum == $total_score) {
echo '<tr class="table-success">';
} else {
echo '<tr>';
}
echo '<td>' . getUserLink($username) . '</td>';
echo '<td>';
echo '<span class="uoj-score" data-max="', $total_score, '">', $sum, '</span>';
echo '</td>';
foreach ($problem_ids as $problem_id) { foreach ($problem_ids as $problem_id) {
if (!isset($scores[$problem_id])) { $cond = "submitter = '{$user['username']}' AND problem_id = $problem_id AND submit_time <= '$submission_end_time'";
echo '<td>'; $submission = DB::selectFirst("SELECT id, score FROM submissions INNER JOIN (SELECT MAX(score) AS score FROM submissions WHERE $cond) AS max USING (score) WHERE $cond ORDER BY submit_time DESC");
} else {
if ($scores[$problem_id]['score'] == 100) {
echo '<td class="table-success">';
} else {
echo '<td>';
}
echo '<a class="text-decoration-none uoj-score" href="'.HTML::url('/submission/'.$scores[$problem_id]['submission_id']).'">'.$scores[$problem_id]['score'].'</a>';
}
echo '</td>';
}
echo '</tr>';
};
$from = "user_info a inner join groups_users b on (b.group_id = {$group['id']} and a.username = b.username)"; if ($submission) {
$col_names = array('a.username as username'); $row['scores'][] = [
$cond = "1"; 'submission_id' => $submission['id'],
$tail = "order by a.username asc"; 'score' => intval($submission['score']),
$config = [
'page_len' => 50,
'div_classes' => ['card', 'my-3', 'table-responsive', 'text-center'],
'table_classes' => ['table', 'uoj-table', 'mb-0'],
]; ];
$row['total_score'] += $submission['score'];
} else {
$row['scores'][] = null;
}
}
echoLongTable($col_names, $from, $cond, $tail, $header_row, $print_row, $config); $standings[] = $row;
}
usort($standings, function($lhs, $rhs) {
if ($lhs['total_score'] != $rhs['total_score']) {
return $rhs['total_score'] - $lhs['total_score'];
}
return strcmp($lhs['user']['username'], $rhs['user']['username']);
});
?> ?>
<!-- end left col --> <div id="standings"></div>
<script>
var n_problems = <?= $n_problems ?>;
var max_total_score = <?= $n_problems * 100 ?>;
var standings = <?= json_encode($standings) ?>;
$('#standings').long_table(
standings,
1,
'<tr>' +
'<th style="width:10em"><?= UOJLocale::get('username') ?></th>' +
'<th style="width:2em"><?= UOJLocale::get('contests::total score') ?></th>' +
<?php foreach ($problem_ids as $problem_id): ?>
'<th style="width:2em">' +
'<a class="text-decoration-none" href="<?= HTML::url('/problem/' . $problem_id) ?>">#<?= $problem_id ?></a>' +
'</th>' +
<?php endforeach ?>
'</tr>',
function(row) {
var col_tr = '';
if (row['total_score'] == max_total_score) {
col_tr += '<tr class="table-success">';
} else {
col_tr += '<tr>';
}
col_tr += '<td>' + getUserLink(row['user']['username'], row['user']['realname']) + '</td>';
col_tr += '<td>' +
'<span class="uoj-score" data-max="' + max_total_score + '" style="color:' + getColOfScore(row['total_score'] / n_problems) + '">' + row['total_score'] + '</span>' +
'</td>';
for (var i = 0; i < row['scores'].length; i++) {
var col = row['scores'][i];
if (col) {
if (col['score'] == 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 += '</td>';
} else {
col_tr += '<td></td>';
}
}
col_tr += '</tr>';
return col_tr;
},
{
div_classes: ['card', 'my-3', 'table-responsive', 'text-center'],
table_classes: ['table', 'uoj-table', 'table-bordered', 'mb-0'],
page_len: 50,
print_before_table: function() {
var html = '';
html += '<div class="card-header bg-transparent text-muted text-start small">' +
'成绩统计截止时间:<?= $submission_end_time ?>' +
'</div>';
return html;
}
}
);
</script>
</div> </div>
<!-- end left col -->
<aside class="col-lg-3 mt-3 mt-lg-0"> <aside class="col-lg-3 mt-3 mt-lg-0">
<!-- right col --> <!-- right col -->

View File

@ -3,6 +3,7 @@
redirectToLogin(); redirectToLogin();
} }
requireLib('bootstrap5');
requirePHPLib('form'); requirePHPLib('form');
requirePHPLib('judger'); requirePHPLib('judger');
requirePHPLib('data'); requirePHPLib('data');
@ -16,258 +17,458 @@
become403Page(); become403Page();
} }
$group_editor = new UOJBlogEditor(); if (isset($_GET['tab'])) {
$group_editor->name = 'group'; $cur_tab = $_GET['tab'];
$group_editor->blog_url = null; } else {
$group_editor->cur_data = array( $cur_tab = 'profile';
'title' => $group['title'],
'tags' => array(),
'is_hidden' => $group['is_hidden']
);
$group_editor->label_text = array_merge($group_editor->label_text, array(
'view blog' => '保存小组信息',
'blog visibility' => '小组可见性'
));
$group_editor->show_editor = false;
$group_editor->show_tags = false;
$group_editor->save = function($data) {
global $group_id, $group;
DB::update("update `groups` set title = '".DB::escape($data['title'])."' where id = {$group_id}");
if ($data['is_hidden'] != $group['is_hidden'] ) {
DB::update("update `groups` set is_hidden = {$data['is_hidden']} where id = {$group_id}");
}
};
$group_editor->runAtServer();
$add_new_user_form = new UOJForm('add_new_user');
$add_new_user_form->addInput('new_username', 'text', '用户名', '',
function ($x) {
global $group_id;
if (!validateUsername($x)) {
return '用户名不合法';
}
$user = queryUser($x);
if (!$user) {
return '用户不存在';
} }
if (queryUserInGroup($group_id, $x)) { $tabs_info = [
return '该用户已经在小组中'; 'profile' => [
'name' => '基本信息',
'url' => "/group/{$group['id']}/manage/profile",
],
'assignments' => [
'name' => '作业管理',
'url' => "/group/{$group['id']}/manage/assignments",
],
'users' => [
'name' => '用户管理',
'url' => "/group/{$group['id']}/manage/users",
]
];
if (!isset($tabs_info[$cur_tab])) {
become404Page();
} }
if ($cur_tab == 'profile') {
$update_profile_form = new UOJForm('update_profile');
$update_profile_form->addVInput('name', 'text', '名称', $group['title'],
function($title, &$vdata) {
if ($title == '') {
return '名称不能为空';
}
if (strlen($title) > 100) {
return '名称过长';
}
if (HTML::escape($title) === '') {
return '无效编码';
}
$vdata['title'] = $title;
return ''; return '';
}, },
null null
); );
$add_new_user_form->submit_button_config['align'] = 'compressed'; $update_profile_form->addVCheckboxes('is_hidden', [
$add_new_user_form->submit_button_config['text'] = '添加到小组'; '0' => '公开',
$add_new_user_form->handle = function() { '1' => '隐藏',
global $group_id, $myUser; ], '可见性', $group['is_hidden']);
$username = $_POST['new_username']; $update_profile_form->addVTextArea('announcement', '公告', $group['announcement'],
function($announcement, &$vdata) {
DB::insert("insert into groups_users (group_id, username) values ({$group_id}, '{$username}')"); if (strlen($announcement) > 3000) {
}; return '公告过长';
$add_new_user_form->runAtServer();
$delete_user_form = new UOJForm('delete_user');
$delete_user_form->addInput('del_username', 'text', '用户名', '',
function ($x) {
global $group_id;
if (!validateUsername($x)) {
return '用户名不合法';
}
if (!queryUserInGroup($group_id, $x)) {
return '该用户不在小组中';
} }
$vdata['announcement'] = $announcement;
return ''; return '';
}, }, null);
null $update_profile_form->handle = function($vdata) use ($group) {
); $esc_title = DB::escape($vdata['title']);
$delete_user_form->submit_button_config['align'] = 'compressed'; $is_hidden = $_POST['is_hidden'];
$delete_user_form->submit_button_config['text'] = '从小组中删除'; $esc_announcement = $vdata['announcement'];
$delete_user_form->handle = function() {
global $group_id, $myUser;
$username = $_POST['del_username'];
DB::query("delete from groups_users where username='{$username}' and group_id={$group_id}"); DB::update("UPDATE `groups` SET title = '$esc_title', is_hidden = '$is_hidden', announcement = '$esc_announcement' WHERE id = {$group['id']}");
dieWithJsonData(['status' => 'success', 'message' => '修改成功']);
}; };
$delete_user_form->runAtServer(); $update_profile_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert')
.html('小组信息修改成功!')
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert')
.html('小组信息修改失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$(window).scrollTop(0);
}
EOD);
$update_profile_form->submit_button_config['margin_class'] = 'mt-3';
$update_profile_form->submit_button_config['text'] = '更新';
$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'];
if (!validateUInt($list_id)) {
dieWithAlert('题单 ID 不合法。');
}
if (!queryAssignmentByGroupListID($group['id'], $list_id)) {
dieWithAlert('该题单不在作业中。');
}
DB::delete("DELETE FROM `groups_assignments` WHERE `list_id` = $list_id AND `group_id` = {$group['id']}");
dieWithAlert('移除成功!');
}
$add_new_assignment_form = new UOJForm('add_new_assignment'); $add_new_assignment_form = new UOJForm('add_new_assignment');
$add_new_assignment_form->addInput('new_assignment_list_id', 'text', '题单 ID', '', $add_new_assignment_form->addVInput('new_assignment_list_id', 'text', '题单 ID', '',
function ($x) { function ($list_id, &$vdata) use ($group) {
global $group_id; if (!validateUInt($list_id)) {
if (!validateUInt($x)) {
return '题单 ID 不合法'; return '题单 ID 不合法';
} }
$list = queryProblemList($x);
if (!$list) { if (!($list = queryProblemList($list_id))) {
return '题单不存在'; return '题单不存在';
} }
if ($list['is_hidden'] != 0) { if ($list['is_hidden'] != 0) {
return '题单是隐藏的'; return '题单是隐藏的';
} }
if (queryAssignmentByGroupListID($group_id, $x)) { if (queryAssignmentByGroupListID($group['id'], $list_id)) {
return '该题单已经在作业中'; return '该题单已经在作业中';
} }
$vdata['list_id'] = $list_id;
return ''; return '';
}, },
null null
); );
$default_ddl = new DateTime(); $default_end_time = new DateTime();
$default_ddl->setTime(17, 0, 0); $default_end_time->setTime(17, 0, 0);
$default_ddl->add(new DateInterval("P7D")); $default_end_time->add(new DateInterval("P7D"));
$add_new_assignment_form->addInput('new_assignment_deadline', 'text', '截止时间', $default_ddl->format('Y-m-d H:i'), $add_new_assignment_form->addVInput('new_assignment_end_time', 'text', '截止时间', $default_end_time->format('Y-m-d H:i'),
function ($x) { function ($end_time, &$vdata) {
$ddl = DateTime::createFromFormat('Y-m-d H:i', $x); try {
if (!$ddl) { $vdata['end_time'] = new DateTime($end_time);
return '截止时间格式不正确,请以类似 "2020-10-1 17:00" 的格式输入'; } catch (Exception $e) {
return '无效时间格式';
} }
return ''; return '';
}, },
null null
); );
$add_new_assignment_form->submit_button_config['align'] = 'compressed'; $add_new_assignment_form->handle = function(&$vdata) use ($group) {
$add_new_assignment_form->submit_button_config['text'] = '添加作业'; $esc_end_time = DB::escape($vdata['end_time']->format('Y-m-d H:i:s'));
$add_new_assignment_form->handle = function() {
global $group_id, $myUser;
$list_id = $_POST['new_assignment_list_id'];
$ddl = DateTime::createFromFormat('Y-m-d H:i', $_POST['new_assignment_deadline']);
$ddl_str = $ddl->format('Y-m-d H:i:s');
DB::insert("insert into assignments (group_id, list_id, create_time, deadline) values ({$group_id}, '{$list_id}', now(), '{$ddl_str}')"); DB::insert("insert into groups_assignments (group_id, list_id, end_time) values ({$group['id']}, '{$vdata['list_id']}', '{$esc_end_time}')");
dieWithJsonData([
'status' => 'success',
'message' => '题单 #' . $vdata['list_id'] . ' 已经被添加到作业列表中,结束时间为 ' . $vdata['end_time']->format('Y-m-d H:i:s') . '。'
]);
}; };
$add_new_assignment_form->runAtServer(); $add_new_assignment_form->submit_button_config['margin_class'] = 'mt-3';
$add_new_assignment_form->submit_button_config['text'] = '添加';
$add_new_assignment_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert')
.html('作业添加成功!' + (res.message || ''))
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert')
.html('作业添加失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$remove_assignment_form = new UOJForm('remove_assignment'); $(window).scrollTop(0);
$remove_assignment_form->addInput('remove_assignment_list_id', 'text', '题单 ID', '', }
function ($x) { EOD);
$add_new_assignment_form->runAtServer();
} elseif ($cur_tab == 'users') {
if (isset($_POST['submit-remove_user']) && $_POST['submit-remove_user'] == 'remove_user') {
$username = $_POST['remove_username'];
if (!validateUsername($username)) {
dieWithAlert('用户名不合法。');
}
if (!queryUser($username)) {
dieWithAlert('用户不存在。');
}
if (!queryUserInGroup($group['id'], $username)) {
dieWithAlert('该用户不在小组中。');
}
DB::delete("DELETE FROM `groups_users` WHERE `username` = '$username' AND `group_id` = {$group['id']}");
dieWithAlert('移除成功!');
}
$add_new_user_form = new UOJForm('add_new_user');
$add_new_user_form->addVInput('new_username', 'text', '用户名', '',
function ($username, &$vdata) {
global $group_id; global $group_id;
if (!validateUInt($x)) { if (!validateUsername($username)) {
return '题单 ID 不合法'; return '用户名不合法';
} }
if (!queryAssignmentByGroupListID($group_id, $x)) {
return '该题单不在作业中'; if (!queryUser($username)) {
return '用户不存在';
} }
if (queryUserInGroup($group_id, $username)) {
return '该用户已经在小组中';
}
$vdata['username'] = $username;
return ''; return '';
}, },
null null
); );
$add_new_user_form->submit_button_config['margin_class'] = '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']}')");
$remove_assignment_form->submit_button_config['align'] = 'compressed'; dieWithJsonData(['status' => 'success', 'message' => '已将用户名为 ' . $vdata['username'] . ' 的用户添加到本小组。']);
$remove_assignment_form->submit_button_config['text'] = '删除作业';
$remove_assignment_form->handle = function() {
global $group_id, $myUser;
$list_id = $_POST['remove_assignment_list_id'];
DB::query("delete from assignments where list_id='{$list_id}' and group_id={$group_id}");
}; };
$remove_assignment_form->runAtServer(); $add_new_user_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert')
.html('用户添加成功!' + (res.message || ''))
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert')
.html('用户添加失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$announcement_form = new UOJForm('announcement_form'); $(window).scrollTop(0);
$announcement_form->addVTextArea('announcement_content', '公告', $group['announcement'], }
function ($x) { EOD);
return ''; $add_new_user_form->runAtServer();
}, }
null
);
$announcement_form->submit_button_config['text'] = '更新公告';
$announcement_form->handle = function() {
global $group_id, $myUser;
$content = $_POST['announcement_content'];
$esc_content = DB::escape($content);
DB::query("update groups set announcement = '{$esc_content}' where id = {$group_id}");
};
$announcement_form->runAtServer();
?> ?>
<?php echoUOJPageHeader('管理 - ' . $group['title']); ?>
<?php echoUOJPageHeader($group['title'] . ' 管理'); ?> <h1 class="h2 d-block d-md-inline-block">
<?= $group['title'] ?>
<h1 class="h2"> <small class="fs-5">(ID: #<?= $group['id'] ?>)</small>
<?= $group['title'] ?>#<?= $group['id'] ?>)管理 管理
</h1> </h1>
<ul class="nav nav-tabs" role="tablist"> <div class="row mt-4">
<li class="nav-item"><a class="nav-link" href="/group/<?= $group['id'] ?>/manage" role="tab">管理</a></li> <!-- left col -->
<li class="nav-item"><a class="nav-link" href="/group/<?= $group['id'] ?>" role="tab">返回</a></li> <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>
</div>
<!-- end left col -->
<!-- right col -->
<div class="col-md-9">
<?php if ($cur_tab == 'profile'): ?>
<div class="card mt-3 mt-md-0">
<div class="card-body">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?= $update_profile_form->printHTML() ?>
</div>
<div class="col">
<h5>注意事项</h5>
<ul class="mb-0">
<li>隐藏的小组无法被普通用户查看,即使该用户属于本小组。</li>
<li>公告支持 Markdown 语法,但不支持添加数学公式。</li>
</ul> </ul>
</div>
</div>
</div>
</div>
<?php elseif ($cur_tab == 'assignments'): ?>
<div class="card mt-3 mt-md-0">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" href="#assignments" data-bs-toggle="tab" data-bs-target="#assignments">作业列表</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#add-assignment" data-bs-toggle="tab" data-bs-target="#add-assignment">添加作业</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="assignments">
<?php
$now = new DateTime();
$hidden_time = new DateTime();
$hidden_time->sub(new DateInterval('P7D'));
echoLongTable(
['*'],
'groups_assignments',
"group_id = {$group['id']}",
'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>
<th style="width:8em">操作</th>
</tr>
EOD,
function($row) use ($group, $now, $hidden_time) {
$list = queryProblemList($row['list_id']);
$end_time = DateTime::createFromFormat('Y-m-d H:i:s', $row['end_time']);
<div class="top-buffer-md"></div> echo '<tr>';
echo '<td class="text-center">', $list['id'], '</td>';
<h5>编辑小组信息</h5> echo '<td>', '<a class="text-decoration-none" href="/group/', $group['id'], '/assignment/', $list['id'],'">', HTML::escape($list['title']), '</a>', '</td>';
<div class="mb-4"> if ($end_time < $hidden_time) {
<?php $group_editor->printHTML(); ?> echo '<td class="text-secondary">已隐藏</td>';
} elseif ($end_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 '<td>';
echo '<a class="text-decoration-none d-inline-block align-middle" href="/problem_list/', $list['id'], '/manage">编辑</a> ';
echo ' <form class="d-inline-block" method="POST" onsubmit=\'return confirm("你真的要移除这份作业吗?移除作业不会删除题单。")\'>'
. '<input type="hidden" name="_token" value="' . crsf_token() . '">'
. '<input type="hidden" name="list_id" value="' . $list['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>';
echo '</tr>';
},
[
'page_len' => 20,
'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle'],
]
);
?>
</div>
<div class="tab-pane" id="add-assignment">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?php $add_new_assignment_form->printHTML() ?>
</div>
<div class="col">
<h5>注意事项</h5>
<ul class="mt-0">
<li>要被添加为作业的题单必须是公开的。</li>
<li>请为学生预留合理的完成作业的时间。</li>
<li>排行榜将在结束后停止更新。</li>
<li>如需延长结束时间请删除后再次添加,排行数据不会丢失。</li>
<li>作业结束七天后将会自动在小组主页中隐藏,但仍可直接通过 URL 访问。</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<?php elseif ($cur_tab == 'users'): ?>
<div class="card mt-3 mt-md-0">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" href="#users" data-bs-toggle="tab" data-bs-target="#users">用户列表</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#add-user" data-bs-toggle="tab" data-bs-target="#add-user">添加用户</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="users">
<?php
echoLongTable(
['*'],
'groups_users',
"group_id = {$group['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>';
echo '<td>';
echo '<form class="d-inline-block" method="POST" onsubmit=\'return confirm("你真的要从小组中移除这个用户吗?")\'>'
. '<input type="hidden" name="_token" value="' . crsf_token() . '">'
. '<input type="hidden" name="remove_username" value="' . $row['username'] . '">'
. '<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>';
},
[
'page_len' => 20,
'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle'],
]
);
?>
</div>
<div class="tab-pane" id="add-user">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?php $add_new_user_form->printHTML() ?>
</div>
<div class="col">
<h5>注意事项</h5>
<ul class="mb-0">
<li>添加用户前请确认用户名是否正确以免带来不必要的麻烦。</li>
<li>用户被添加到小组后将自动被加入组内的所有作业排行中。</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif ?>
</div>
</div> </div>
<h5>编辑小组公告</h5>
<?php $announcement_form->printHTML(); ?>
<h5>添加用户到小组</h5>
<?php $add_new_user_form->printHTML(); ?>
<h5>从小组中删除用户</h5>
<?php $delete_user_form->printHTML(); ?>
<h5>为小组添加作业</h5>
<?php $add_new_assignment_form->printHTML(); ?>
<h5>删除小组的作业</h5>
<?php $remove_assignment_form->printHTML(); ?>
<h5>已被自动隐藏的作业</h5>
<ul>
<?php
$assignments = queryGroupAssignments($group['id']);
foreach ($assignments as $ass) {
$ddl = DateTime::createFromFormat('Y-m-d H:i:s', $ass['deadline']);
$create_time = DateTime::createFromFormat('Y-m-d H:i:s', $ass['create_time']);
$now = new DateTime();
if ($now->getTimestamp() - $ddl->getTimestamp() <= 604800) {
continue;
} // 7d
echo '<li>';
echo '<a ';
if (isset($REQUIRE_LIB['bootstrap5'])) {
echo ' class="text-decoration-none" ';
}
echo ' href="/problem_list/', $ass['list_id'], '">', $ass['title'], ' (题单 #', $ass['list_id'], ')</a>';
if ($ddl < $now) {
echo '<sup style="color:red">&nbsp;overdue</sup>';
} elseif ($ddl->getTimestamp() - $now->getTimestamp() < 86400) { // 1d
echo '<sup style="color:red">&nbsp;soon</sup>';
} elseif ($now->getTimestamp() - $create_time->getTimestamp() < 86400) { // 1d
echo '<sup style="color:red">&nbsp;new</sup>';
}
$ddl_str = $ddl->format('Y-m-d H:i');
echo ' (截止时间: ', $ddl_str, '<a ';
if (isset($REQUIRE_LIB['bootstrap5'])) {
echo ' class="text-decoration-none" ';
}
echo ' href="/group/',$group['id'],'/assignment/', $ass['list_id'], '">查看完成情况</a>)';
echo '</li>';
}
if (count($assignments) == 0) {
echo '<p>暂无作业</p>';
}
?>
</ul>
<?php echoUOJPageFooter() ?> <?php echoUOJPageFooter() ?>

View File

@ -19,7 +19,7 @@
$count = $_result["count(*)"]; $count = $_result["count(*)"];
function throwError($msg) { function throwError($msg) {
returnJSONData(['status' => 'error', 'message' => $msg]); dieWithJsonData(['status' => 'error', 'message' => $msg]);
} }
$allowedTypes = [IMAGETYPE_PNG, IMAGETYPE_JPEG]; $allowedTypes = [IMAGETYPE_PNG, IMAGETYPE_JPEG];
@ -66,7 +66,7 @@
$existing_image = DB::selectFirst("SELECT * FROM users_images WHERE `hash` = '$hash'"); $existing_image = DB::selectFirst("SELECT * FROM users_images WHERE `hash` = '$hash'");
if ($existing_image) { if ($existing_image) {
returnJSONData(['status' => 'success', 'path' => $existing_image['path']]); dieWithJsonData(['status' => 'success', 'path' => $existing_image['path']]);
} }
$image = new Imagick($_FILES["image_upload_file"]["tmp_name"]); $image = new Imagick($_FILES["image_upload_file"]["tmp_name"]);
@ -92,7 +92,7 @@
DB::insert("INSERT INTO users_images (`path`, uploader, width, height, upload_time, size, `hash`) VALUES ('$filename', '{$myUser['username']}', $width, $height, now(), {$_FILES["image_upload_file"]["size"]}, '$hash')"); DB::insert("INSERT INTO users_images (`path`, uploader, width, height, upload_time, size, `hash`) VALUES ('$filename', '{$myUser['username']}', $width, $height, now(), {$_FILES["image_upload_file"]["size"]}, '$hash')");
returnJSONData(['status' => 'success', 'path' => $filename]); dieWithJsonData(['status' => 'success', 'path' => $filename]);
} elseif ($_POST['image_delete_submit'] == 'submit') { } elseif ($_POST['image_delete_submit'] == 'submit') {
crsf_defend(); crsf_defend();

View File

@ -1,6 +1,6 @@
<?php <?php
$blogs = DB::selectAll("select blogs.id, title, poster, post_time from important_blogs, blogs where is_hidden = 0 and important_blogs.blog_id = blogs.id order by level desc, important_blogs.blog_id desc limit 5"); $blogs = DB::selectAll("select blogs.id, title, poster, post_time from important_blogs, blogs where is_hidden = 0 and important_blogs.blog_id = blogs.id order by level desc, important_blogs.blog_id desc limit 5");
$countdowns = DB::selectAll("select * from countdowns order by endtime asc"); $countdowns = DB::selectAll("select * from countdowns order by end_time asc");
$friend_links = DB::selectAll("select * from friend_links order by level desc, id asc"); $friend_links = DB::selectAll("select * from friend_links order by level desc, id asc");
if (!isset($_COOKIE['bootstrap4'])) { if (!isset($_COOKIE['bootstrap4'])) {
@ -134,7 +134,7 @@
<?php endif ?> mb-0"> <?php endif ?> mb-0">
<?php foreach ($countdowns as $countdown): ?> <?php foreach ($countdowns as $countdown): ?>
<?php <?php
$enddate = strtotime($countdown['endtime']); $enddate = strtotime($countdown['end_time']);
$nowdate = time(); $nowdate = time();
$diff = floor(($enddate - $nowdate) / (24 * 60 * 60)); $diff = floor(($enddate - $nowdate) / (24 * 60 * 60));
?> ?>

View File

@ -56,12 +56,12 @@
$blog_id = $_POST['blog_id']; $blog_id = $_POST['blog_id'];
if (!validateUInt($blog_id)) { if (!validateUInt($blog_id)) {
die('<script>alert("移除失败:博客 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('移除失败:博客 ID 无效');
} }
DB::delete("DELETE FROM important_blogs WHERE blog_id = {$blog_id}"); DB::delete("DELETE FROM important_blogs WHERE blog_id = {$blog_id}");
die('<script>alert("移除成功!");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('移除成功!');
} }
$announcements = DB::selectAll("SELECT blogs.id as id, blogs.title as title, blogs.poster as poster, user_info.realname as realname, blogs.post_time as post_time, important_blogs.level as level, blogs.is_hidden as is_hidden FROM important_blogs INNER JOIN blogs ON important_blogs.blog_id = blogs.id INNER JOIN user_info ON blogs.poster = user_info.username ORDER BY level DESC, important_blogs.blog_id DESC"); $announcements = DB::selectAll("SELECT blogs.id as id, blogs.title as title, blogs.poster as poster, user_info.realname as realname, blogs.post_time as post_time, important_blogs.level as level, blogs.is_hidden as is_hidden FROM important_blogs INNER JOIN blogs ON important_blogs.blog_id = blogs.id INNER JOIN user_info ON blogs.poster = user_info.username ORDER BY level DESC, important_blogs.blog_id DESC");
@ -121,15 +121,15 @@
$countdown_id = $_POST['countdown_id']; $countdown_id = $_POST['countdown_id'];
if (!validateUInt($countdown_id)) { if (!validateUInt($countdown_id)) {
die('<script>alert("删除失败:倒计时 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除失败:倒计时 ID 无效');
} }
DB::delete("DELETE FROM countdowns WHERE id = {$countdown_id}"); DB::delete("DELETE FROM countdowns WHERE id = {$countdown_id}");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除成功!');
} }
$countdowns = DB::selectAll("SELECT id, title, endtime FROM countdowns ORDER BY endtime ASC"); $countdowns = DB::selectAll("SELECT id, title, end_time FROM countdowns ORDER BY end_time ASC");
$add_countdown_form = new UOJForm('add_countdown'); $add_countdown_form = new UOJForm('add_countdown');
$add_countdown_form->addInput('countdown_title', 'text', '标题', '', $add_countdown_form->addInput('countdown_title', 'text', '标题', '',
@ -144,10 +144,10 @@
}, },
null null
); );
$add_countdown_form->addInput('countdown_endtime', 'text', '结束时间', date("Y-m-d H:i:s"), $add_countdown_form->addInput('countdown_end_time', 'text', '结束时间', date("Y-m-d H:i:s"),
function($endtime, &$vdata) { function($end_time, &$vdata) {
try { try {
$vdata['endtime'] = new DateTime($endtime); $vdata['end_time'] = new DateTime($end_time);
} catch (Exception $e) { } catch (Exception $e) {
return '无效时间格式'; return '无效时间格式';
} }
@ -158,9 +158,9 @@
); );
$add_countdown_form->handle = function(&$vdata) { $add_countdown_form->handle = function(&$vdata) {
$esc_title = DB::escape($vdata['title']); $esc_title = DB::escape($vdata['title']);
$esc_endtime = DB::escape($vdata['endtime']->format('Y-m-d H:i:s')); $esc_end_time = DB::escape($vdata['end_time']->format('Y-m-d H:i:s'));
DB::insert("INSERT INTO countdowns (title, endtime) VALUES ('{$esc_title}', '{$esc_endtime}')"); DB::insert("INSERT INTO countdowns (title, end_time) VALUES ('{$esc_title}', '{$esc_end_time}')");
}; };
$add_countdown_form->submit_button_config['align'] = 'compressed'; $add_countdown_form->submit_button_config['align'] = 'compressed';
$add_countdown_form->submit_button_config['text'] = '添加'; $add_countdown_form->submit_button_config['text'] = '添加';
@ -174,12 +174,12 @@
$item_id = $_POST['item_id']; $item_id = $_POST['item_id'];
if (!validateUInt($item_id)) { if (!validateUInt($item_id)) {
die('<script>alert("删除失败ID 无效");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除失败ID 无效');
} }
DB::delete("DELETE FROM links WHERE id = {$item_id}"); DB::delete("DELETE FROM links WHERE id = {$item_id}");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除成功!');
} }
$links = DB::selectAll("SELECT `id`, `title`, `url`, `level` FROM `friend_links` ORDER BY `level` DESC, `id` ASC"); $links = DB::selectAll("SELECT `id`, `title`, `url`, `level` FROM `friend_links` ORDER BY `level` DESC, `id` ASC");
@ -315,7 +315,7 @@
DB::query("insert into user_info (username, realname, email, school, password, svn_password, register_time, usergroup) values ('$username', '$realname', '$email', '$school', '$password', '$svn_password', now(), 'U')"); DB::query("insert into user_info (username, realname, email, school, password, svn_password, register_time, usergroup) values ('$username', '$realname', '$email', '$school', '$password', '$svn_password', now(), 'U')");
returnJSONData(['status' => 'success', 'message' => '']); dieWithJsonData(['status' => 'success', 'message' => '']);
}; };
$register_form->setAjaxSubmit(<<<EOD $register_form->setAjaxSubmit(<<<EOD
function(res) { function(res) {
@ -370,7 +370,7 @@ EOD);
DB::query("update user_info set password = '$esc_password' where username = '$esc_username'"); DB::query("update user_info set password = '$esc_password' where username = '$esc_username'");
returnJSONData(['status' => 'success', 'message' => '用户 ' . $vdata['username'] . ' 的密码已经被成功重置。']); dieWithJsonData(['status' => 'success', 'message' => '用户 ' . $vdata['username'] . ' 的密码已经被成功重置。']);
}; };
$change_password_form->submit_button_config['margin_class'] = 'mt-3'; $change_password_form->submit_button_config['margin_class'] = 'mt-3';
$change_password_form->submit_button_config['text'] = '重置'; $change_password_form->submit_button_config['text'] = '重置';
@ -436,7 +436,7 @@ EOD);
break; break;
} }
returnJSONData(['status' => 'success', 'message' => '用户 ' . $username . ' 现在是 ' . $usergroup . '。']); dieWithJsonData(['status' => 'success', 'message' => '用户 ' . $username . ' 现在是 ' . $usergroup . '。']);
}; };
$change_usergroup_form->setAjaxSubmit(<<<EOD $change_usergroup_form->setAjaxSubmit(<<<EOD
function(res) { function(res) {
@ -539,17 +539,17 @@ EOD);
$image_id = $_POST['image_id']; $image_id = $_POST['image_id'];
if (!validateUInt($image_id)) { if (!validateUInt($image_id)) {
die('<script>alert("删除失败:图片 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除失败:图片 ID 无效');
} }
if (!($image = DB::selectFirst("SELECT * from users_images where id = $image_id"))) { if (!($image = DB::selectFirst("SELECT * from users_images where id = $image_id"))) {
die('<script>alert("删除失败:图片不存在");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除失败:图片不存在');
} }
unlink(UOJContext::storagePath().$result['path']); unlink(UOJContext::storagePath().$result['path']);
DB::delete("DELETE FROM users_images WHERE id = $image_id"); DB::delete("DELETE FROM users_images WHERE id = $image_id");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET); dieWithAlert('删除成功!');
} }
@ -710,7 +710,7 @@ EOD);
col_tr += '<tr>'; col_tr += '<tr>';
col_tr += '<td>' + row['title'] + '</td>'; col_tr += '<td>' + row['title'] + '</td>';
col_tr += '<td>' + row['endtime'] + '</td>'; col_tr += '<td>' + row['end_time'] + '</td>';
col_tr += '<td>' + col_tr += '<td>' +
'<form method="POST" onsubmit=\'return confirm("你真的要删除这个倒计时吗?")\'>' + '<form method="POST" onsubmit=\'return confirm("你真的要删除这个倒计时吗?")\'>' +
'<input type="hidden" name="_token" value="<?= crsf_token() ?>">' + '<input type="hidden" name="_token" value="<?= crsf_token() ?>">' +

View File

@ -179,7 +179,7 @@ EOD);
DB::update("UPDATE user_info SET email = '$esc_email', qq = '$esc_qq', sex = '$esc_sex', motto = '$esc_motto', codeforces_handle = '$esc_codeforces_handle', github = '$esc_github', website = '$esc_website', avatar_source = '$esc_avatar_source' WHERE username = '{$user['username']}'"); DB::update("UPDATE user_info SET email = '$esc_email', qq = '$esc_qq', sex = '$esc_sex', motto = '$esc_motto', codeforces_handle = '$esc_codeforces_handle', github = '$esc_github', website = '$esc_website', avatar_source = '$esc_avatar_source' WHERE username = '{$user['username']}'");
returnJSONData(['status' => 'success']); dieWithJsonData(['status' => 'success']);
}; };
$update_profile_form->submit_button_config['margin_class'] = 'mt-3'; $update_profile_form->submit_button_config['margin_class'] = 'mt-3';
$update_profile_form->submit_button_config['text'] = '更新'; $update_profile_form->submit_button_config['text'] = '更新';
@ -209,21 +209,21 @@ EOD);
$new_password = $_POST['new_password']; $new_password = $_POST['new_password'];
if (!validatePassword($old_password) || !checkPassword($user, $old_password)) { if (!validatePassword($old_password) || !checkPassword($user, $old_password)) {
returnJSONData(['status' => 'error', 'message' => '旧密码错误']); dieWithJsonData(['status' => 'error', 'message' => '旧密码错误']);
} }
if (!validatePassword($new_password)) { if (!validatePassword($new_password)) {
returnJSONData(['status' => 'error', 'message' => '新密码不合法']); dieWithJsonData(['status' => 'error', 'message' => '新密码不合法']);
} }
if ($old_password == $new_password) { if ($old_password == $new_password) {
returnJSONData(['status' => 'error', 'message' => '新密码不能与旧密码相同']); dieWithJsonData(['status' => 'error', 'message' => '新密码不能与旧密码相同']);
} }
$password = getPasswordToStore($new_password, $user['username']); $password = getPasswordToStore($new_password, $user['username']);
DB::update("UPDATE `user_info` SET `password` = '$password' where `username` = '{$user['username']}'"); DB::update("UPDATE `user_info` SET `password` = '$password' where `username` = '{$user['username']}'");
returnJSONData(['status' => 'success', 'message' => '密码修改成功']); dieWithJsonData(['status' => 'success', 'message' => '密码修改成功']);
} }
} elseif ($cur_tab == 'privilege') { } elseif ($cur_tab == 'privilege') {
if (isset($_POST['submit-privilege']) && $_POST['submit-privilege'] == 'privilege' && isSuperUser($myUser)) { if (isset($_POST['submit-privilege']) && $_POST['submit-privilege'] == 'privilege' && isSuperUser($myUser)) {
@ -254,7 +254,7 @@ EOD);
DB::update("UPDATE `user_info` SET `usertype` = '{$user['usertype']}' where `username` = '{$user['username']}'"); DB::update("UPDATE `user_info` SET `usertype` = '{$user['usertype']}' where `username` = '{$user['username']}'");
returnJSONData(['status' => 'success', 'message' => '权限修改成功']); dieWithJsonData(['status' => 'success', 'message' => '权限修改成功']);
} }
} }

View File

@ -1,4 +1,6 @@
<?php <?php
define('SCRIPT_REFRESH_AS_GET', '<script>;window.location = window.location.origin + window.location.pathname + (window.location.search.length ? window.location.search + "&" : "?") + "_refresh_" + (+new Date()) + "=" + (+new Date()) + window.location.hash;</script>');
class UOJForm { class UOJForm {
public $form_name; public $form_name;
public $succ_href; public $succ_href;
@ -862,8 +864,12 @@ EOD;
return $form; return $form;
} }
function returnJSONData($data) { function dieWithJsonData($data) {
header('Content-Type: application/json'); header('Content-Type: application/json');
die(json_encode($data)); die(json_encode($data));
} }
function dieWithAlert($str) {
die('<script>alert(decodeURIComponent("'.rawurlencode($str).'"));</script>'.SCRIPT_REFRESH_AS_GET);
}
?> ?>

View File

@ -130,6 +130,9 @@ function queryContestProblems($id) {
function queryGroup($id) { function queryGroup($id) {
return DB::selectFirst("select * from groups where id = $id", MYSQLI_ASSOC); 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) { function queryUserInGroup($group_id, $username) {
return DB::selectFirst("select * from groups_users where username='$username' and group_id='$group_id'", MYSQLI_ASSOC); return DB::selectFirst("select * from groups_users where username='$username' and group_id='$group_id'", MYSQLI_ASSOC);
} }
@ -143,17 +146,13 @@ 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); 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) { function queryGroupAssignments($group_id) {
return DB::selectAll("select a.id as id, a.list_id as list_id, a.create_time as create_time, a.deadline as deadline, b.title from assignments a left join lists b on a.list_id = b.id where a.group_id = $group_id order by a.deadline asc", MYSQLI_ASSOC); return DB::selectAll("select a.id as 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 order by a.end_time asc", MYSQLI_ASSOC);
} }
function queryGroupActiveAssignments($group_id) { function queryGroupActiveAssignments($group_id) {
return DB::selectAll("select a.id as id, a.group_id as group_id, a.list_id as list_id, a.create_time as create_time, a.deadline as deadline, b.title from assignments a left join lists b on a.list_id = b.id where a.group_id = $group_id and a.deadline > addtime(now(), '-168:00:00') order by a.deadline asc", MYSQLI_ASSOC); return DB::selectAll("select a.id as id, 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 queryAssignment($id) {
return DB::selectFirst("select * from assignments where id = $id", MYSQLI_ASSOC);
} }
function queryAssignmentByGroupListID($group_id, $list_id) { function queryAssignmentByGroupListID($group_id, $list_id) {
return DB::selectFirst("select * from assignments where list_id='$list_id' and group_id='$group_id'", MYSQLI_ASSOC); return DB::selectFirst("select * from groups_assignments where list_id='$list_id' and group_id='$group_id'", MYSQLI_ASSOC);
} }
function queryZanVal($id, $type, $user) { function queryZanVal($id, $type, $user) {

View File

@ -15,6 +15,7 @@ return [
'problems lists' => 'Problems Lists', 'problems lists' => 'Problems Lists',
'groups' => 'Groups', 'groups' => 'Groups',
'add new group' => 'Add new group', 'add new group' => 'Add new group',
'group announcement' => 'Group Announcement',
'group announcements' => 'Group Announcements', 'group announcements' => 'Group Announcements',
'users count' => 'Users', 'users count' => 'Users',
'submissions' => 'Submissions', 'submissions' => 'Submissions',

View File

@ -15,6 +15,7 @@ return [
'problems lists' => '题单', 'problems lists' => '题单',
'groups' => '小组', 'groups' => '小组',
'add new group' => '添加新小组', 'add new group' => '添加新小组',
'group announcement' => '小组公告',
'group announcements' => '小组公告', 'group announcements' => '小组公告',
'users count' => '用户数量', 'users count' => '用户数量',
'submissions' => '提交记录', 'submissions' => '提交记录',

View File

@ -49,7 +49,7 @@ Route::group([
Route::any('/groups', '/groups.php'); Route::any('/groups', '/groups.php');
Route::any('/group/{id}', '/group.php'); Route::any('/group/{id}', '/group.php');
Route::any('/group/{id}/manage', '/group_manage.php'); Route::any('/group/{id}/manage(?:/{tab})?', '/group_manage.php');
Route::any('/group/{id}/assignment/{list_id}', '/group_assignment.php'); Route::any('/group/{id}/assignment/{list_id}', '/group_assignment.php');
Route::any('/blogs', '/blogs.php'); Route::any('/blogs', '/blogs.php');

View File

@ -0,0 +1 @@
ref: https://github.com/renbaoshuo/S2OJ/pull/8

View File

@ -0,0 +1,4 @@
RENAME TABLE groups_assignments TO assignments;
ALTER TABLE `assignments` ADD COLUMN `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE `assignments` CHANGE COLUMN `end_time` `deadline` datetime NOT NULL;
ALTER TABLE `countdowns` CHANGE COLUMN `end_time` `endtime` datetime NOT NULL;

View File

@ -0,0 +1,4 @@
RENAME TABLE assignments TO groups_assignments;
ALTER TABLE `groups_assignments` DROP COLUMN `create_time`;
ALTER TABLE `groups_assignments` CHANGE COLUMN `deadline` `end_time` datetime NOT NULL;
ALTER TABLE `countdowns` CHANGE COLUMN `endtime` `end_time` datetime NOT NULL;

View File

@ -21,8 +21,16 @@
</div> </div>
<?php endif ?> <?php endif ?>
<?php if ($standings_data): ?> <?php if ($standings_data): ?>
<div class="tab-pane card-body" id="tab-standings"> <div class="tab-pane" id="tab-standings">
<?php uojIncludeView('contest-standings', array_merge($standings_data, ['no_bs5_card' => ''])) ?> <?php
uojIncludeView('contest-standings', array_merge(
$standings_data,
[
'standings_config' => [
'div_classes' => ['table-responsive', 'mb-3']
]
]
)); ?>
</div> </div>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -1,14 +1,13 @@
<div id="standings" class=" <div id="standings"></div>
<?php if (!isset($no_bs5_card)): ?>
card card-default
<?php endif ?>"></div>
<script type="text/javascript"> <script type="text/javascript">
standings_version=<?=$contest['extra_config']['standings_version']?>; var standings_version=<?=$contest['extra_config']['standings_version']?>;
show_self_reviews=<?=isset($show_self_reviews) && $show_self_reviews ? 'true' : 'false' ?>; var show_self_reviews=<?=isset($show_self_reviews) && $show_self_reviews ? 'true' : 'false' ?>;
contest_id=<?=$contest['id']?>; var contest_id=<?=$contest['id']?>;
standings=<?=json_encode($standings)?>; var standings=<?=json_encode($standings)?>;
score=<?=json_encode($score)?>; var score=<?=json_encode($score)?>;
problems=<?=json_encode($contest_data['problems'])?>; var problems=<?=json_encode($contest_data['problems'])?>;
$(document).ready(showStandings()); var standings_config = <?=json_encode(isset($standings_config) ? $standings_config : ['_config' => true])?>;
$(document).ready(showStandings(standings_config));
</script> </script>

View File

@ -11,6 +11,8 @@
<?php <?php
$group_detail = DB::selectFirst("select * from groups where id = {$group['id']}"); $group_detail = DB::selectFirst("select * from groups where id = {$group['id']}");
$group_announcement = $group_detail['announcement']; $group_announcement = $group_detail['announcement'];
$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']) ?>"> <a class="fw-bold text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>">
@ -18,7 +20,7 @@
</a> </a>
<?php if ($group_announcement): ?> <?php if ($group_announcement): ?>
<div class="text-break"> <div class="text-break">
<?= HTML::purifier_inline()->purify($group_announcement) ?> <?= $purifier->purify($parsedown->line($group_announcement)) ?>
</div> </div>
<?php else: ?> <?php else: ?>
<div class="text-muted"> <div class="text-muted">
@ -38,11 +40,13 @@
} }
usort($assignments, function($a, $b) { usort($assignments, function($a, $b) {
$deadline_a = DateTime::createFromFormat('Y-m-d H:i:s', $a['deadline']); $end_time_a = DateTime::createFromFormat('Y-m-d H:i:s', $a['end_time']);
$deadline_b = DateTime::createFromFormat('Y-m-d H:i:s', $b['deadline']); $end_time_b = DateTime::createFromFormat('Y-m-d H:i:s', $b['end_time']);
return $deadline_b->getTimestamp() - $deadline_a->getTimestamp(); return $end_time_b->getTimestamp() - $end_time_a->getTimestamp();
}); });
$assignments = array_slice($assignments, 0, 5);
?> ?>
<?php if (count($assignments)): ?> <?php if (count($assignments)): ?>
<div class="card card-default mb-2" id="group-assignments"> <div class="card card-default mb-2" id="group-assignments">
@ -53,22 +57,19 @@
<?php foreach ($assignments as $assignment): ?> <?php foreach ($assignments as $assignment): ?>
<li class="list-group-item"> <li class="list-group-item">
<?php <?php
$deadline = DateTime::createFromFormat('Y-m-d H:i:s', $assignment['deadline']); $end_time = DateTime::createFromFormat('Y-m-d H:i:s', $assignment['end_time']);
$create_time = DateTime::createFromFormat('Y-m-d H:i:s', $assignment['create_time']);
$now = new DateTime(); $now = new DateTime();
?> ?>
<a href="<?= HTML::url('/group/'.$assignment['group_id'].'/assignment/'.$assignment['list_id']) ?>" class="fw-bold text-decoration-none"> <a href="<?= HTML::url('/group/'.$assignment['group_id'].'/assignment/'.$assignment['list_id']) ?>" class="fw-bold text-decoration-none">
<?= $assignment['title'] ?> <?= $assignment['title'] ?>
<?php if ($deadline < $now): ?> <?php if ($end_time < $now): ?>
<sup class="fw-normal text-danger">overdue</sup> <sup class="fw-normal text-danger">overdue</sup>
<?php elseif ($deadline->getTimestamp() - $now->getTimestamp() < 86400): ?> <?php elseif ($end_time->getTimestamp() - $now->getTimestamp() < 86400): ?>
<sup class="fw-normal text-danger">soon</sup> <sup class="fw-normal text-danger">soon</sup>
<?php elseif ($now->getTimestamp() - $create_time->getTimestamp() < 86400): ?>
<sup class="fw-normal text-danger">new</sup>
<?php endif ?> <?php endif ?>
</a> </a>
<div class="text-end small text-muted"> <div class="text-end small text-muted">
截止时间: <?= $deadline->format('Y-m-d H:i') ?> 截止时间: <?= $end_time->format('Y-m-d H:i') ?>
</div> </div>
</li> </li>
<?php endforeach ?> <?php endforeach ?>

View File

@ -51,11 +51,13 @@ label {
float: right; float: right;
} }
.uoj-table > tbody > tr:last-child { .uoj-table > tbody > tr:last-child,
.uoj-table > tbody > tr:last-child > td {
border-bottom-color: transparent; border-bottom-color: transparent;
} }
.uoj-table > thead > tr:first-child { .uoj-table > thead > tr:first-child,
.uoj-table > thead > tr:first-child > td {
border-top-color: transparent; border-top-color: transparent;
} }

View File

@ -541,18 +541,16 @@ $.fn.long_table = function(data, cur_page, header_row, get_row_str, config) {
$(table_div).append( $(table_div).append(
$('<div class="' + div_classes.join(' ') + '" />').append( $('<div class="' + div_classes.join(' ') + '" />').append(
(typeof config.print_before_table === 'function' ? config.print_before_table() : ''),
$('<table class="' + table_classes.join(' ') + '" />').append( $('<table class="' + table_classes.join(' ') + '" />').append(
$('<thead>' + header_row + '</thead>') $('<thead>' + header_row + '</thead>')
).append( ).append(
tbody tbody
) ),
(typeof config.print_after_table === 'function' ? config.print_after_table() : '')
) )
); );
if (config.print_after_table != undefined) {
$(table_div).append(config.print_after_table());
}
var get_page_li = function(p, h) { var get_page_li = function(p, h) {
if (p == -1) { if (p == -1) {
return $('<li class="page-item"></li>').addClass('disabled').append($('<a class="page-link"></a>').append(h)); return $('<li class="page-item"></li>').addClass('disabled').append($('<a class="page-link"></a>').append(h));
@ -1129,7 +1127,7 @@ function showCommentReplies(id, replies) {
} }
// standings // standings
function showStandings() { function showStandings(config) {
$("#standings").long_table( $("#standings").long_table(
standings, standings,
1, 1,
@ -1178,9 +1176,9 @@ function showStandings() {
col_tr += '</tr>'; col_tr += '</tr>';
return col_tr; return col_tr;
}, { }, {
div_classes: ['table-responsive'], div_classes: config.div_classes ? config.div_classes : ['table-responsive', 'card', 'my-3'],
table_classes: ['table', 'table-bordered', 'text-center', 'align-middle', 'uoj-table', 'uoj-standings-table', 'mb-0'], table_classes: config.table_classes ? config.table_classes : ['table', 'table-bordered', 'text-center', 'align-middle', 'uoj-table', 'uoj-standings-table', 'mb-0'],
page_len: 100, page_len: config.page_len ? config.page_len : 50,
print_after_table: function() { print_after_table: function() {
return '<div class="card-footer bg-transparent text-end text-muted">' + uojLocale("contests::n participants", standings.length) + '</div><script>if (window.MathJax) window.MathJax.typeset();</scr' + 'ipt>'; return '<div class="card-footer bg-transparent text-end text-muted">' + uojLocale("contests::n participants", standings.length) + '</div><script>if (window.MathJax) window.MathJax.typeset();</scr' + 'ipt>';
} }