S2OJ/web/app/controllers/super_manage.php

1171 lines
35 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
requireLib('bootstrap5');
requireLib('md5');
requireLib('jquery.query');
requirePHPLib('form');
requirePHPLib('judger');
define('SCRIPT_REFRESH_AS_GET', '<script>;window.location = window.location.origin + window.location.pathname + (window.location.search.length ? window.location.search + "&" : "?") + "_=" + (+new Date()) + window.location.hash;</script>');
if (!Auth::check()) {
redirectToLogin();
}
if (!isSuperUser($myUser)) {
become403Page();
}
$cur_tab = isset($_GET['tab']) ? $_GET['tab'] : 'index';
$tabs_info = [
'index' => [
'name' => '首页管理',
'url' => "/super_manage/index",
],
'users' => [
'name' => '用户管理',
'url' => "/super_manage/users",
],
'submissions' => [
'name' => '提交记录',
'url' => "/super_manage/submissions",
],
'custom_test' => [
'name' => '自定义测试',
'url' => "/super_manage/custom_test",
],
'judger' => [
'name' => '评测机管理',
'url' => "/super_manage/judger",
],
'image_hosting' => [
'name' => '图床管理',
'url' => "/super_manage/image_hosting",
],
];
if (!isset($tabs_info[$cur_tab])) {
become404Page();
}
if ($cur_tab == 'index') {
// ========== 公告 ==========
if (isset($_POST['submit-delete_announcement']) && $_POST['submit-delete_announcement'] == 'delete_announcement') {
crsf_defend();
$blog_id = $_POST['blog_id'];
if (!validateUInt($blog_id)) {
die('<script>alert("移除失败:博客 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET);
}
DB::delete("DELETE FROM important_blogs WHERE blog_id = {$blog_id}");
die('<script>alert("移除成功!");</script>' . SCRIPT_REFRESH_AS_GET);
}
$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");
$add_announcement_form = new UOJForm('add_announcement');
$add_announcement_form->addInput('blog_id', 'text', '博客 ID', '',
function($id, &$vdata) {
if (!validateUInt($id)) {
return '博客 ID 无效';
}
if (!queryBlog($id)) {
return '博客不存在';
}
$vdata['blog_id'] = $id;
return '';
},
null
);
$add_announcement_form->addInput('blog_level', 'text', '置顶级别', '0',
function ($x, &$vdata) {
if (!validateUInt($x)) {
return '数字不合法';
}
if ($x > 3) {
return '该级别不存在';
}
$vdata['level'] = $x;
return '';
},
null
);
$add_announcement_form->handle = function(&$vdata) {
$blog_id = $vdata['blog_id'];
$blog_level = $vdata['level'];
if (DB::selectFirst("select * from important_blogs where blog_id = {$blog_id}")) {
DB::update("update important_blogs set level = {$blog_level} where blog_id = {$blog_id}");
} else {
DB::insert("insert into important_blogs (blog_id, level) values ({$blog_id}, {$blog_level})");
}
};
$add_announcement_form->submit_button_config['align'] = 'compressed';
$add_announcement_form->submit_button_config['text'] = '提交';
$add_announcement_form->succ_href = '/super_manage/index#announcements';
$add_announcement_form->runAtServer();
// ========== 倒计时 ==========
if (isset($_POST['submit-delete_countdown']) && $_POST['submit-delete_countdown'] == 'delete_countdown') {
crsf_defend();
$countdown_id = $_POST['countdown_id'];
if (!validateUInt($countdown_id)) {
die('<script>alert("删除失败:倒计时 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET);
}
DB::delete("DELETE FROM countdowns WHERE id = {$countdown_id}");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET);
}
$countdowns = DB::selectAll("SELECT id, title, endtime FROM countdowns ORDER BY endtime ASC");
$add_countdown_form = new UOJForm('add_countdown');
$add_countdown_form->addInput('countdown_title', 'text', '标题', '',
function($title, &$vdata) {
if ($title == '') {
return '标题不能为空';
}
$vdata['title'] = $title;
return '';
},
null
);
$add_countdown_form->addInput('countdown_endtime', 'text', '结束时间', date("Y-m-d H:i:s"),
function($endtime, &$vdata) {
try {
$vdata['endtime'] = new DateTime($endtime);
} catch (Exception $e) {
return '无效时间格式';
}
return '';
},
null
);
$add_countdown_form->handle = function(&$vdata) {
$esc_title = DB::escape($vdata['title']);
$esc_endtime = DB::escape($vdata['endtime']->format('Y-m-d H:i:s'));
DB::insert("INSERT INTO countdowns (title, endtime) VALUES ('{$esc_title}', '{$esc_endtime}')");
};
$add_countdown_form->submit_button_config['align'] = 'compressed';
$add_countdown_form->submit_button_config['text'] = '添加';
$add_countdown_form->succ_href = '/super_manage/index#countdowns';
$add_countdown_form->runAtServer();
// ========== 常用链接 ==========
if (isset($_POST['submit-delete_link']) && $_POST['submit-delete_link'] == 'delete_link') {
crsf_defend();
$item_id = $_POST['item_id'];
if (!validateUInt($item_id)) {
die('<script>alert("删除失败ID 无效");</script>' . SCRIPT_REFRESH_AS_GET);
}
DB::delete("DELETE FROM links WHERE id = {$item_id}");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET);
}
$links = DB::selectAll("SELECT `id`, `title`, `url`, `level` FROM `friend_links` ORDER BY `level` DESC, `id` ASC");
$add_link_form = new UOJForm('add_link');
$add_link_form->addInput('link_title', 'text', '标题', '',
function($title, &$vdata) {
if ($title == '') {
return '标题不能为空';
}
$vdata['title'] = $title;
return '';
},
null
);
$add_link_form->addInput('link_url', 'text', '链接', '',
function($url, &$vdata) {
if (!validateURL($url)) {
return '链接不合法';
}
$vdata['url'] = $url;
return '';
},
null
);
$add_link_form->addInput('link_level', 'text', '权重', '10',
function($level, &$vdata) {
if (!validateUInt($level)) {
return '数字不合法';
}
$vdata['level'] = $level;
return '';
},
null
);
$add_link_form->handle = function(&$vdata) {
$esc_title = DB::escape($vdata['title']);
$esc_url = DB::escape($vdata['url']);
$level = $vdata['level'];
DB::insert("INSERT INTO friend_links (title, url, level) VALUES ('{$esc_title}', '{$esc_url}', {$level})");
};
$add_link_form->submit_button_config['align'] = 'compressed';
$add_link_form->submit_button_config['text'] = '添加';
$add_link_form->succ_href = '/super_manage/index#links';
$add_link_form->runAtServer();
} elseif ($cur_tab == 'users') {
$user_list_cond = [];
if (isset($_GET['username']) && $_GET['username'] != "") {
$user_list_cond[] = "username like '%" . DB::escape($_GET['username']) . "%'";
}
if (isset($_GET['usergroup']) && $_GET['usergroup'] != "") {
$user_list_cond[] = "usergroup = '" . DB::escape($_GET['usergroup']) . "'";
}
if (isset($_GET['usertype']) && $_GET['usertype'] != "") {
$user_list_cond[] = "usertype like '%" . DB::escape($_GET['usertype']) . "%'";
}
if ($user_list_cond) {
$user_list_cond = join($user_list_cond, ' and ');
} else {
$user_list_cond = '1';
}
$register_form = new UOJForm('register');
$register_form->addVInput('new_username', 'text', '用户名', '',
function ($username, &$vdata) {
if (!validateUsername($username)) {
return '用户名不合法';
}
if (queryUser($username)) {
return '该用户已存在';
}
$vdata['username'] = $username;
return '';
},
null
);
$register_form->addVInput('new_password', 'password', '密码', '',
function ($password, &$vdata) {
$vdata['password'] = $password;
return '';
},
'validatePassword'
);
$register_form->addVInput('new_email', 'text', '电子邮件(选填)', '',
function ($email, &$vdata) {
if ($email && !validateEmail($email)) {
return '邮件地址不合法';
}
$vdata['email'] = $email;
return '';
},
null
);
$register_form->addVInput('new_realname', 'text', '真实姓名(选填)', '',
function ($realname, &$vdata) {
$vdata['realname'] = $realname;
return '';
},
null
);
$register_form->addVInput('new_school', 'text', '学校名称(选填)', '',
function ($school, &$vdata) {
$vdata['school'] = $school;
return '';
},
null
);
$register_form->handle = function(&$vdata) {
$username = $vdata['username'];
$realname = DB::escape($vdata['realname']);
$school = DB::escape($vdata['school']);
$email = DB::escape($vdata['email']);
$password = hash_hmac('md5', $vdata['password'], getPasswordClientSalt());
$password = getPasswordToStore($password, $username);
$svn_password = uojRandString(10);
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')");
header('Content-Type: application/json');
die(json_encode(['status' => 'success', 'message' => '']));
};
$register_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert-register')
.html('用户新建成功!' + (res.message || ''))
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert-register')
.html('用户新建失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$(window).scrollTop(0);
}
EOD);
$register_form->runAtServer();
$change_password_form = new UOJForm('change_password');
$change_password_form->addVInput('p_username', 'text', '用户名', '',
function ($username, &$vdata) {
if (!validateUsername($username)) {
return '用户名不合法';
}
if (!queryUser($username)) {
return '用户不存在';
}
$vdata['username'] = $username;
return '';
},
null
);
$change_password_form->addVInput('p_password', 'password', '密码', '',
function ($password, &$vdata) {
$vdata['password'] = $password;
return '';
},
'validatePassword'
);
$change_password_form->handle = function(&$vdata) {
$esc_username = DB::escape($vdata['username']);
$password = hash_hmac('md5', $vdata['password'], getPasswordClientSalt());
$esc_password = DB::escape(getPasswordToStore($password, $vdata['username']));
DB::query("update user_info set password = '$esc_password' where username = '$esc_username'");
header('Content-Type: application/json');
die(json_encode(['status' => 'success', 'message' => '用户 ' . $vdata['username'] . ' 的密码已经被成功重置。']));
};
$change_password_form->submit_button_config['margin_class'] = 'mt-3';
$change_password_form->submit_button_config['text'] = '重置';
$change_password_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert-reset-password')
.html('密码重置成功!' + (res.message || ''))
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert-reset-password')
.html('密码重置失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$(window).scrollTop(0);
}
EOD);
$change_password_form->runAtServer();
$change_usergroup_form = new UOJForm('change_usergroup');
$change_usergroup_form->addVInput('username', 'text', '用户名', '',
function ($username, &$vdata) {
if (!validateUsername($username)) {
return '用户名不合法';
}
if (!queryUser($username)) {
return '用户不存在';
}
$vdata['username'] = $username;
return '';
},
null
);
$change_usergroup_form->addVSelect('op_type', [
'banneduser' => '设为封禁用户',
'normaluser' => '设为普通用户',
'superuser' => '设为超级用户',
], '操作类型', '');
$change_usergroup_form->handle = function($vdata) {
$username = $vdata['username'];
$usergroup = '';
switch ($_POST['op_type']) {
case 'banneduser':
DB::update("update user_info set usergroup = 'B', usertype = 'banned' where username = '{$username}'");
$usergroup = '被封禁的用户';
break;
case 'normaluser':
DB::update("update user_info set usergroup = 'U', usertype = 'student' where username = '{$username}'");
$usergroup = '普通用户';
break;
case 'superuser':
DB::update("update user_info set usergroup = 'S', usertype = 'student' where username = '{$username}'");
$usergroup = '超级用户';
break;
}
header('Content-Type: application/json');
die(json_encode(['status' => 'success', 'message' => '用户 ' . $username . ' 现在是 ' . $usergroup . '。']));
};
$change_usergroup_form->setAjaxSubmit(<<<EOD
function(res) {
if (res.status === 'success') {
$('#result-alert-change_usergroup')
.html('修改成功!' + (res.message || ''))
.addClass('alert-success')
.removeClass('alert-danger')
.show();
} else {
$('#result-alert-change_usergroup')
.html('修改失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
}
$(window).scrollTop(0);
}
EOD);
$change_usergroup_form->runAtServer();
} elseif ($cur_tab == 'submissions') {
} elseif ($cur_tab == 'custom_test') {
requireLib('hljs');
$submissions_pag = new Paginator([
'col_names' => ['*'],
'table_name' => 'custom_test_submissions',
'cond' => '1',
'tail' => 'order by id desc',
'page_len' => 10
]);
$custom_test_deleter = new UOJForm('custom_test_deleter');
$custom_test_deleter->addInput('last', 'text', '删除末尾记录', '5',
function ($x, &$vdata) {
if (!validateUInt($x)) {
return '不合法';
}
$vdata['last'] = $x;
return '';
},
null
);
$custom_test_deleter->handle = function(&$vdata) {
$all = DB::selectAll("select * from custom_test_submissions order by id asc limit {$vdata['last']}");
foreach ($all as $submission) {
$content = json_decode($submission['content'], true);
unlink(UOJContext::storagePath().$content['file_name']);
}
DB::delete("delete from custom_test_submissions order by id asc limit {$vdata['last']}");
};
$custom_test_deleter->submit_button_config['align'] = 'compressed';
$custom_test_deleter->runAtServer();
} elseif ($cur_tab == 'judger') {
$judger_adder = new UOJForm('judger_adder');
$judger_adder->addInput('judger_adder_name', 'text', '评测机名称', '',
function ($x, &$vdata) {
if (!validateUsername($x)) {
return '不合法';
}
if (DB::selectCount("select count(*) from judger_info where judger_name='$x'")!=0) {
return '不合法';
}
$vdata['name'] = $x;
return '';
},
null
);
$judger_adder->handle = function(&$vdata) {
$password = uojRandString(32);
DB::insert("insert into judger_info (judger_name,password) values('{$vdata['name']}','{$password}')");
};
$judger_adder->submit_button_config['align'] = 'compressed';
$judger_adder->runAtServer();
$judger_deleter = new UOJForm('judger_deleter');
$judger_deleter->addInput('judger_deleter_name', 'text', '评测机名称', '',
function ($x, &$vdata) {
if (!validateUsername($x)) {
return '不合法';
}
if (DB::selectCount("select count(*) from judger_info where judger_name='$x'")!=1) {
return '不合法';
}
$vdata['name'] = $x;
return '';
},
null
);
$judger_deleter->handle = function(&$vdata) {
DB::delete("delete from judger_info where judger_name='{$vdata['name']}'");
};
$judger_deleter->submit_button_config['align'] = 'compressed';
$judger_deleter->runAtServer();
} elseif ($cur_tab == 'image_hosting') {
if (isset($_POST['submit-delete_image']) && $_POST['submit-delete_image'] == 'delete_image') {
crsf_defend();
$image_id = $_POST['image_id'];
if (!validateUInt($image_id)) {
die('<script>alert("删除失败:图片 ID 无效");</script>' . SCRIPT_REFRESH_AS_GET);
}
if (!($image = DB::selectFirst("SELECT * from users_images where id = $image_id"))) {
die('<script>alert("删除失败:图片不存在");</script>' . SCRIPT_REFRESH_AS_GET);
}
unlink(UOJContext::storagePath().$result['path']);
DB::delete("DELETE FROM users_images WHERE id = $image_id");
die('<script>alert("删除成功!");</script>' . SCRIPT_REFRESH_AS_GET);
}
$change_user_image_total_size_limit_form = new UOJForm('change_user_image_total_size_limit');
$change_user_image_total_size_limit_form->submit_button_config['align'] = 'compressed';
$change_user_image_total_size_limit_form->addInput('change_user_image_total_size_limit_username', 'text', '用户名', '',
function ($x, &$vdata) {
if (!validateUsername($x)) {
return '用户名不合法';
}
if (!queryUser($x)) {
return '用户不存在';
}
$vdata['username'] = $x;
return '';
},
null
);
$change_user_image_total_size_limit_form->addInput('change_user_image_total_size_limit_limit', 'text', '存储限制单位Byte', '104857600',
function ($x, &$vdata) {
if (!validateUInt($x, 10)) {
return '限制不合法';
}
if (intval($x) > 2147483648) {
return '限制不能大于 2 GB';
}
$vdata['limit'] = $x;
return '';
},
null
);
$change_user_image_total_size_limit_form->handle = function(&$vdata) {
DB::update("UPDATE user_info SET images_size_limit = {$vdata['limit']} WHERE username = '{$vdata['username']}'");
};
$change_user_image_total_size_limit_form->runAtServer();
}
?>
<?php echoUOJPageHeader(UOJLocale::get('system manage')) ?>
<h1 class="h2">
<?= UOJLocale::get('system manage') ?>
</h1>
<div class="row mt-4">
<!-- left col -->
<div class="col-md-3">
<div class="list-group">
<?php foreach ($tabs_info as $id => $tab): ?>
<a
role="button"
class="list-group-item list-group-item-action <?= $cur_tab == $id ? 'active' : '' ?>"
href="<?= $tab['url'] ?>">
<?= $tab['name'] ?>
</a>
<?php endforeach ?>
</div>
</div>
<!-- end left col -->
<!-- right col -->
<div class="col-md-9">
<?php if ($cur_tab == 'index'): ?>
<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="#announcements" data-bs-toggle="tab" data-bs-target="#announcements">公告</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#countdowns" data-bs-toggle="tab" data-bs-target="#countdowns">倒计时</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#links" data-bs-toggle="tab" data-bs-target="#links">常用链接</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- 公告 -->
<div class="tab-pane active" id="announcements">
<div id="announcements-list"></div>
<script>
var announcements = <?= json_encode($announcements) ?>;
$('#announcements-list').long_table(
announcements,
1,
'<tr>' +
'<th style="width:3em">ID</th>' +
'<th style="width:14em">标题</th>' +
'<th style="width:8em">发布者</th>' +
'<th style="width:8em">发布时间</th>' +
'<th style="width:6em">置顶等级</th>' +
'<th style="width:8em">操作</th>' +
'</tr>',
function(row) {
var col_tr = '';
col_tr += '<tr>';
col_tr += '<td>' + row['id'] + '</td>';
col_tr += '<td>' +
(row['is_hidden'] ? '<span class="text-danger">[隐藏]</span> ' : '') +
'<a class="text-decoration-none" href="/blogs/' + row['id'] + '">' +
row['title'] +
'</a>' +
'</td>';
col_tr += '<td>' + getUserLink(row['poster'], row['realname']) + '</td>';
col_tr += '<td>' + row['post_time'] + '</td>';
col_tr += '<td>' + row['level'] + '</td>';
col_tr += '<td>' +
'<a class="text-decoration-none d-inline-block align-middle" href="/post/' + row['id'] + '/write">编辑</a>' +
'<form class="d-inline-block ms-2" method="POST" onsubmit=\'return confirm("你真的要移除这条公告吗?")\'>' +
'<input type="hidden" name="_token" value="<?= crsf_token() ?>">' +
'<input type="hidden" name="blog_id" value="' + row['id'] + '">' +
'<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-delete_announcement" value="delete_announcement">移除</button>' +
'</form>' +
'</td>';
col_tr += '</tr>';
return col_tr;
},
{
div_classes: ['table-responsive'],
table_classes: ['table', 'align-middle'],
page_len: 20,
}
);
</script>
<h5>添加/修改公告</h5>
<?php $add_announcement_form->printHTML(); ?>
</div>
<!-- 倒计时 -->
<div class="tab-pane" id="countdowns">
<div id="countdowns-list"></div>
<script>
var countdowns = <?= json_encode($countdowns) ?>;
$('#countdowns-list').long_table(
countdowns,
1,
'<tr>' +
'<th style="width:14em">标题</th>' +
'<th style="width:8em">结束时间</th>' +
'<th style="width:6em">操作</th>' +
'</tr>',
function(row) {
var col_tr = '';
col_tr += '<tr>';
col_tr += '<td>' + row['title'] + '</td>';
col_tr += '<td>' + row['endtime'] + '</td>';
col_tr += '<td>' +
'<form method="POST" onsubmit=\'return confirm("你真的要删除这个倒计时吗?")\'>' +
'<input type="hidden" name="_token" value="<?= crsf_token() ?>">' +
'<input type="hidden" name="countdown_id" value="' + row['id'] + '">' +
'<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-delete_countdown" value="delete_countdown">删除</button>' +
'</form>' +
'</td>';
col_tr += '</tr>';
return col_tr;
},
{
div_classes: ['table-responsive'],
table_classes: ['table', 'align-middle'],
page_len: 20,
}
);
</script>
<h5>添加倒计时</h5>
<?php $add_countdown_form->printHTML(); ?>
</div>
<!-- 常用链接 -->
<div class="tab-pane" id="links">
<div id="links-list"></div>
<script>
var links = <?= json_encode($links) ?>;
$('#links-list').long_table(
links,
1,
'<tr>' +
'<th style="width:18em">标题</th>' +
'<th style="width:26em">链接</th>' +
'<th style="width:14em">操作</th>' +
'</tr>',
function(row) {
var col_tr = '';
col_tr += '<tr>';
col_tr += '<td>' + row['title'] + '</td>';
col_tr += '<td>' + row['url'] + '</td>';
col_tr += '<td>' +
'<form method="POST" onsubmit=\'return confirm("你真的要删除这条链接吗?")\'>' +
'<input type="hidden" name="_token" value="<?= crsf_token() ?>">' +
'<input type="hidden" name="link_id" value="' + row['id'] + '">' +
'<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-delete_link" value="delete_link">删除</button>' +
'</form>' +
'</td>';
col_tr += '</tr>';
return col_tr;
},
{
div_classes: ['table-responsive'],
table_classes: ['table', 'align-middle'],
page_len: 20,
}
);
</script>
<h5>添加常用链接</h5>
<?php $add_link_form->printHTML(); ?>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Javascript to enable link to tab
var hash = location.hash.replace(/^#/, '');
if (hash) {
bootstrap.Tab.jQueryInterface.call($('.nav-tabs a[href="#' + hash + '"]'), 'show').blur();
}
// Change hash for page-reload
$('.nav-tabs a').on('shown.bs.tab', function(e) {
window.location.hash = e.target.hash;
});
});
</script>
<?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">
<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="#new-user" data-bs-toggle="tab" data-bs-target="#new-user">新增用户</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#reset-password" data-bs-toggle="tab" data-bs-target="#reset-password">重置密码</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#user-group" data-bs-toggle="tab" data-bs-target="#user-group">用户类别</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="users">
<form class="row gy-2 gx-3 align-items-end mb-3" target="_self" method="GET">
<div class="col-auto">
<label for="username" class="form-label">用户名</label>
<input type="text" class="form-control" name="username" id="user-query-username" value="" />
</div>
<div class="col-auto">
<label for="user-query-usergroup" class="form-label">用户类别</label>
<select class="form-select" id="user-query-usergroup" name="usergroup">
<?php
$usergroups = [
'' => '*: 所有用户',
'B' => 'B: 封禁用户',
'U' => 'U: 普通用户',
'S' => 'S: 超级用户',
];
?>
<?php foreach ($usergroups as $name => $group): ?>
<option value="<?= $name ?>"
<?php if ($_GET['usergroup'] == $name): ?>
selected
<?php endif ?>
><?= $group ?></option>
<?php endforeach ?>
</select>
</div>
<div class="col-auto">
<label for="user-query-usertype" class="form-label">用户权限</label>
<select class="form-select" id="user-query-usertype" name="usertype">
<?php
$usertypes = [
'' => '*: 所有',
'student' => 'student: 学生',
'teacher' => 'teacher: 老师',
'problem_uploader' => 'problem_uploader: 题目上传者',
'problem_manager' => 'problem_manager: 题目管理员',
'contest_judger' => 'contest_judger: 比赛评测员',
'contest_only' => 'contest_only: 仅比赛参加者',
];
?>
<?php foreach ($usertypes as $name => $type): ?>
<option value="<?= $name ?>"
<?php if ($_GET['usertype'] == $name): ?>
selected
<?php endif ?>
><?= $type ?></option>
<?php endforeach ?>
</select>
</div>
<div class="col-auto">
<button type="submit" id="user-query-submit" class="mt-2 btn btn-secondary">查询</button>
</div>
</form>
<?php
echoLongTable(
['*'],
'user_info',
$user_list_cond,
'order by username asc',
<<<EOD
<tr>
<th>用户名</th>
<th>学校</th>
<th>用户类别</th>
<th>权限</th>
<th>注册时间</th>
<th>操作</th>
</tr>
EOD,
function($row) {
echo '<tr>';
echo '<td>', '<span class="uoj-username" data-realname="', HTML::escape($row['realname']), '">', $row['username'], '</span>', '</td>';
echo '<td>', HTML::escape($row['school']), '</td>';
echo '<td>';
switch ($row['usergroup']) {
case 'S':
echo UOJLocale::get('user::super user');
break;
case 'B':
echo UOJLocale::get('user::banned user');
break;
default:
echo UOJLocale::get('user::normal user');
break;
}
echo '</td>';
echo '<td>';
foreach (explode(',', $row['usertype']) as $idx => $type) {
if ($idx) {
echo ', ';
}
echo UOJLocale::get('user::' . str_replace('_', ' ', $type)) ?: HTML::escape($type);
}
echo '</td>';
echo '<td>', $row['register_time'], '</td>';
echo '<td>', '<a class="text-decoration-none d-inline-block align-middle" href="/user/', $row['username'], '/edit">编辑</a>', '</td>';
echo '</tr>';
},
[
'page_len' => 20,
'div_classes' => ['table-responsive'],
'table_classes' => ['table', 'align-middle'],
],
);
?>
</div>
<div class="tab-pane" id="new-user">
<div id="result-alert-register" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?php $register_form->printHTML() ?>
</div>
<div class="col mt-3 mt-md-0">
<h5>注意事项</h5>
<ul class="mb-0">
<li>用户名推荐格式为年级 + 姓名全拼,如 2022 级的张三同学可以设置为 <code>2022zhangsan</code>。对于外校学生,推荐格式为学校名称缩写 + 姓名拼音首字母,如山大附中的赵锦熙同学可以设置为 <code>sdfzzjx</code>)。</li>
<li>请提醒用户及时修改初始密码,以免账号被盗导致教学资源流出。请勿设置过于简单的初始密码。</li>
<li>我们推荐在创建账号时输入号主的电子邮件地址以便后期发生忘记密码等情况时进行验证。</li>
<li>创建账号后可以在「修改个人信息」页面中的「特权」选项卡为用户分配权限。特别地,如果该用户是外校学生,那么您可能需要将其设置为「仅比赛参加者」以禁止其查看已参与比赛以外的题目。</li>
</ul>
</div>
</div>
</div>
<div class="tab-pane" id="reset-password">
<div id="result-alert-reset-password" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?php $change_password_form->printHTML() ?>
</div>
<div class="col mt-3 mt-md-0">
<h5>注意事项</h5>
<ul class="mb-0">
<li>在为用户重置密码前请核对对方身份以免被骗。</li>
<li>请勿设置过于简单的密码。</li>
<li>请提醒用户在登录后及时修改初始密码。</li>
</ul>
</div>
</div>
</div>
<div class="tab-pane" id="user-group">
<div id="result-alert-change_usergroup" class="alert" role="alert" style="display: none"></div>
<div class="row row-cols-1 row-cols-md-2">
<div class="col">
<?php $change_usergroup_form->printHTML() ?>
</div>
<div class="col mt-3 mt-md-0">
<h5>注意事项</h5>
<ul class="mb-0">
<li>用户被封禁后将不能再次登录系统。</li>
<li>将当前用户移除权限后将无法再次访问本页面。</li>
<li>在修改用户类别前请仔细核对用户名以免产生不必要的麻烦。</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Javascript to enable link to tab
var hash = location.hash.replace(/^#/, '');
if (hash) {
bootstrap.Tab.jQueryInterface.call($('.nav-tabs a[href="#' + hash + '"]'), 'show').blur();
}
// Change hash for page-reload
$('.nav-tabs a').on('shown.bs.tab', function(e) {
window.location.hash = e.target.hash;
});
});
</script>
<?php elseif ($cur_tab === 'submissions'): ?>
<h4>测评失败的提交记录</h4>
<?php
echoSubmissionsList(
"result_error = 'Judgement Failed'",
'order by id desc',
[
'result_hidden' => '',
'table_config' => [
'div_classes' => ['card', 'mb-3', 'table-responsive'],
'table_classes' => ['table', 'uoj-table', 'mb-0', 'text-center']
]
],
$myUser
);
?>
<?php elseif ($cur_tab === 'custom_test'): ?>
<div class="card mb-3 table-responsive">
<table class="table uoj-table mb-0">
<thead>
<tr>
<th class="text-center">ID</th>
<th class="text-center">题目 ID</th>
<th>提交者</th>
<th>提交时间</th>
<th>测评时间</th>
</tr>
</thead>
<tbody>
<?php foreach ($submissions_pag->get() as $submission): ?>
<?php
$problem = queryProblemBrief($submission['problem_id']);
$submission_result = json_decode($submission['result'], true);
?>
<tr style="cursor: pointer" data-bs-toggle="collapse" data-bs-target="#custom_test__<?= $submission['id'] ?>">
<td class="text-center">#<?= $submission['id'] ?></td>
<td class="text-center">#<?= $submission['problem_id'] ?></td>
<td><?= getUserLink($submission['submitter']) ?></td>
<td><?= $submission['submit_time'] ?></td>
<td><?= $submission['judge_time'] ?></td>
</tr>
<tr class="collapse" id="custom_test__<?= $submission['id'] ?>">
<td colspan="233">
<?php echoSubmissionContent($submission, getProblemCustomTestRequirement($problem)) ?>
<?php echoCustomTestSubmissionDetails($submission_result['details'], "submission-{$submission['id']}-details") ?>
</td>
</tr>
<?php endforeach ?>
<?php if ($submissions_pag->isEmpty()): ?>
<tr>
<td class="text-center" colspan="233">
<?= UOJLocale::get('none') ?>
</td>
</tr>
<?php endif ?>
</tbody>
</table>
</div>
<?= $submissions_pag->pagination() ?>
<div class="card mt-3">
<div class="card-body">
<h5 class="card-title">删除末尾的 n 条记录</h5>
<?php $custom_test_deleter->printHTML() ?>
</div>
</div>
<?php elseif ($cur_tab == 'judger'): ?>
<h3>评测机列表</h3>
<?php
echoLongTable(
['*'],
'judger_info',
'1',
'',
<<<EOD
<tr>
<th>评测机名称</th>
<th>密码</th>
<th>IP</th>
</tr>
EOD,
function($row) {
echo <<<EOD
<tr>
<td>{$row['judger_name']}</td>
<td>{$row['password']}</td>
<td>{$row['ip']}</td>
</tr>
EOD;
},
[
'page_len' => 10,
'div_classes' => ['card', 'mb-3', 'table-responsive'],
'table_classes' => ['table', 'uoj-table', 'mb-0'],
]
); ?>
<div class="card">
<div class="card-body">
<h5>添加评测机</h5>
<?php $judger_adder->printHTML(); ?>
<h5>删除评测机</h5>
<?php $judger_deleter->printHTML(); ?>
</div>
</div>
<?php elseif ($cur_tab == 'image_hosting'): ?>
<?php
echoLongTable(
['*'],
'users_images',
'1',
'order by id desc',
<<<EOD
<tr>
<th style="width: 10em">上传者</th>
<th style="width: 14em">预览</th>
<th style="width: 6em">文件大小</th>
<th style="width: 8em">上传时间</th>
<th style="width: 6em">操作</th>
</tr>
EOD,
function($row) {
$user_link = getUserLink($row['uploader']);
if ($row['size'] < 1024 * 512) {
$size = strval(round($row['size'] * 1.0 / 1024, 1)) . ' KB';
} else {
$size = strval(round($row['size'] * 1.0 / 1024 / 1024, 1)) . ' MB';
}
$token = crsf_token();
echo <<<EOD
<tr>
<td>$user_link</td>
<td><img src="{$row['path']}" width="250" loading="lazy"></td>
<td>$size</td>
<td>{$row['upload_time']}</td>
<td>
<form class="d-inline-block" method="POST" onsubmit="return confirm('你真的要删除这张图片吗?')">
<input type="hidden" name="_token" value="$token">
<input type="hidden" name="image_id" value="{$row['id']}">
<button class="btn btn-link text-danger text-decoration-none p-0" type="submit" name="submit-delete_image" value="delete_image">删除</button>
</form>
</td>
</tr>
EOD;
},
[
'page_len' => 20,
'div_classes' => ['card', 'mb-3', 'table-responsive'],
'table_classes' => ['table', 'uoj-table', 'mb-0'],
]
); ?>
<div class="card mt-3">
<div class="card-body">
<h5>修改用户图床空间上限</h5>
<?php $change_user_image_total_size_limit_form->printHTML() ?>
</div>
</div>
<?php endif ?>
</div>
<!-- end right col -->
</div>
<?php echoUOJPageFooter() ?>