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

This commit is contained in:
Baoshuo Ren 2022-10-20 10:56:02 +08:00 committed by GitHub
commit a9583e605e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 724 additions and 714 deletions

View File

@ -816,6 +816,10 @@ CREATE TABLE `user_info` (
`last_login` timestamp NOT NULL DEFAULT 0,
`last_visited` timestamp NOT NULL DEFAULT 0,
`images_size_limit` int(11) UNSIGNED NOT NULL DEFAULT 104857600, /* 100 MiB */
`codeforces_handle` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`github` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`website` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`avatar_source` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'gravatar',
PRIMARY KEY (`username`),
KEY `ac_num` (`ac_num`,`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;

View File

@ -78,6 +78,9 @@
<p class="card-text">
快使用 Gravatar Gravatar 地址:<a href="https://cn.gravatar.com" target="_blank">https://cn.gravatar.com</a>。进去后注册个帐号然后与邮箱绑定并上传头像,就 OK 啦!
</p>
<p class="card-text">
上不去 Gravatar没关系我们现在也支持 QQ 头像了!你只需要前往 “更改个人信息” 页面填写自己的 QQ 号,并将 “头像来源” 选为 “QQ” 就可以让你的 QQ 头像显示在 S2OJ 上啦!
</p>
<h5 class="mt-4">递归 10<sup>7</sup> 层怎么没爆栈啊</h5>
<p class="card-text">

View File

@ -1,4 +1,7 @@
<?php
requireLib('bootstrap5');
requireLib('calendar_heatmap');
if (!Auth::check() && UOJConfig::$data['switch']['force-login']) {
redirectToLogin();
}
@ -13,138 +16,9 @@
become404Page();
}
?>
<?php
if (!isset($_COOKIE['bootstrap4'])) {
$REQUIRE_LIB['bootstrap5'] = '';
$REQUIRE_LIB['calendar_heatmap'] = '';
} else {
$REQUIRE_LIB['github_contribution_graph'] = '';
}
?>
<?php echoUOJPageHeader($user['username'] . ' - ' . UOJLocale::get('user profile')) ?>
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
<?php uojIncludeView('user-info', array('user' => $user, 'myUser' => $myUser)) ?>
<?php else: ?>
<?php
$esc_sex = HTML::escape($user['sex']);
$col_sex="color:blue";
if ($esc_sex == "M") {
$esc_sex="";
$col_sex="color:blue";
} elseif ($esc_sex == "F") {
$esc_sex="";
$col_sex="color:red";
} else {
$esc_sex="";
$col_sex="color:black";
}
?>
<div class="card border-info">
<h5 class="card-header bg-info"><?= UOJLocale::get('user profile') ?></h5>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-4 order-md-9">
<img class="media-object img-thumbnail d-block mx-auto" alt="<?= $user['username'] ?> Avatar" src="<?= HTML::avatar_addr($user, 256) ?>" />
</div>
<div class="col-md-8 order-md-1">
<h2><span class="uoj-honor" data-realname="<?= $user['realname'] ?>"><?= $user['username'] ?></span> <span><strong style="<?= $col_sex ?>"><?= $esc_sex ?></strong></span></h2>
<div class="list-group">
<div class="list-group-item">
<h4 class="list-group-item-heading"><?= UOJLocale::get('email') ?></h4>
<p class="list-group-item-text"><?= HTML::escape($user['email']) ?></p>
</div>
<div class="list-group-item">
<h4 class="list-group-item-heading"><?= UOJLocale::get('QQ') ?></h4>
<p class="list-group-item-text"><?= HTML::escape($user['qq'] != 0 ? $user['qq'] : 'Unfilled') ?></p>
</div>
<div class="list-group-item">
<h4 class="list-group-item-heading"><?= UOJLocale::get('motto') ?></h4>
<div class="list-group-item-text"><?= HTML::purifier_inline()->purify(HTML::parsedown()->line($user['motto'])) ?></div>
</div>
<?php if (isSuperUser($myUser)): ?>
<div class="list-group-item">
<h4 class="list-group-item-heading">register time</h4>
<p class="list-group-item-text"><?= $user['register_time'] ?></p>
</div>
<div class="list-group-item">
<h4 class="list-group-item-heading">remote_addr</h4>
<p class="list-group-item-text"><?= $user['remote_addr'] ?></p>
</div>
<div class="list-group-item">
<h4 class="list-group-item-heading">http_x_forwarded_for</h4>
<p class="list-group-item-text"><?= $user['http_x_forwarded_for'] ?></p>
</div>
<?php endif ?>
</div>
</div>
</div>
<?php if (Auth::check()): ?>
<?php if (Auth::id() != $user['username']): ?>
<a type="button" class="btn btn-info btn-sm" href="/user_msg?enter=<?= $user['username'] ?>"><span class="glyphicon glyphicon-envelope"></span> <?= UOJLocale::get('send private message') ?></a>
<?php else: ?>
<a type="button" class="btn btn-info btn-sm" href="/user/<?= $user['username'] ?>/edit"><span class="glyphicon glyphicon-pencil"></span> <?= UOJLocale::get('modify my profile') ?></a>
<?php endif ?>
<?php endif ?>
<a type="button" class="btn btn-success btn-sm" href="<?= HTML::blog_url($user['username'], '/') ?>"><span class="glyphicon glyphicon-arrow-right"></span> <?= UOJLocale::get('visit his blog', $username) ?></a>
<a type="button" class="btn btn-success btn-sm" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>"><span class="glyphicon glyphicon-arrow-right"></span> 查看 <?= $username ?> 的所有赛后总结</a>
<div class="top-buffer-lg"></div>
<div class="list-group">
<div class="list-group-item">
<?php
$_result = DB::query("select date(submit_time), problem_id from submissions where submitter = '{$username}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
$result = [];
$vis = [];
while ($row = DB::fetch($_result)) {
$id = $row['date(submit_time)'] . ':' . $row['problem_id'];
if (!$vis[$id]) {
$vis[$id] = 1;
$result[strtotime($row['date(submit_time)']) * 1000]++;
}
}
?>
<h4 class="list-group-item-heading"><?= UOJLocale::get('n accepted in last year', count($result)) ?></h4>
<div id="accepted-graph"></div>
<script>
var accepted_graph_data = [
<?php
foreach ($result as $key => $val) {
echo "{ timestamp: {$key}, count: {$val} }, ";
}
?>
];
$(document).ready(function () {
$('#accepted-graph').github_graph({
data: accepted_graph_data,
texts: ['AC', 'AC'],
h_days: ['Tue', 'Thu', 'Sat'],
});
});
</script>
</div>
<div class="list-group-item">
<?php
$ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}';");
?>
<h4 class="list-group-item-heading"><?= UOJLocale::get('accepted problems').''.UOJLocale::get('n problems in total', count($ac_problems))?> </h4>
<ul class="list-group-item-text nav">
<?php
foreach ($ac_problems as $problem) {
echo '<li class="mr-1 mb-1"><a href="/problem/', $problem['problem_id'], '" role="button" class="btn btn-light h-100" style="width: 12rem;">#', $problem['problem_id'], '. ', $problem['title'], '</a></li>';
}
if (empty($ac_problems)) {
echo UOJLocale::get('none');
}
?>
</ul>
</div>
</div>
</div>
</div>
<?php endif ?>
<?php uojIncludeView('user-info', array('user' => $user, 'myUser' => $myUser)) ?>
<?php echoUOJPageFooter() ?>

View File

@ -1,4 +1,8 @@
<?php
requireLib('bootstrap5');
requireLib('md5');
requirePHPLib('form');
if (!Auth::check()) {
redirectToLogin();
}
@ -11,202 +15,475 @@
become403Page();
}
function handlePost() {
global $myUser, $user;
if ($user['username'] == Auth::id()) {
if (!isset($_POST['old_password'])) {
return '无效表单';
}
$old_password = $_POST['old_password'];
if (!validatePassword($old_password) || !checkPassword($user, $old_password)) {
return "失败:密码错误。";
}
}
if ($_POST['ptag']) {
$password = $_POST['password'];
if (!validatePassword($password)) {
return "失败:无效密码。";
}
$password = getPasswordToStore($password, $user['username']);
DB::update("update user_info set password = '$password' where username = '{$user['username']}'");
}
if (isset($_GET['tab'])) {
$cur_tab = $_GET['tab'];
} else {
$cur_tab = 'profile';
}
$tabs_info = [
'profile' => [
'name' => '<i class="bi bi-person-fill"></i> 个人资料',
'url' => "/user/{$user['username']}/edit/profile",
],
'password' => [
'name' => '<i class="bi bi-lock-fill"></i> 修改密码',
'url' => "/user/{$user['username']}/edit/password",
],
'privilege' => [
'name' => '<i class="bi bi-key-fill"></i> 特权',
'url' => "/user/{$user['username']}/edit/privilege",
]
];
if (!isset($tabs_info[$cur_tab])) {
become404Page();
}
$email = $_POST['email'];
if (!validateEmail($email)) {
return "失败:无效电子邮箱。";
}
$esc_email = DB::escape($email);
DB::update("update user_info set email = '$esc_email' where username = '{$user['username']}'");
if ($cur_tab == 'profile') {
$update_profile_form = new UOJForm('update_profile');
$username = UOJLocale::get('username');
$avatar = UOJLocale::get('avatar');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-username" class="form-label">$username</label>
<input type="text" class="form-control" id="input-username" aria-describedby="help-username" value="{$user['username']}" disabled>
<div id="help-username" class="form-text">用户名不能被修改。</div>
</div>
EOD);
$update_profile_form->addVCheckboxes('avatar_source', [
'gravatar' => 'Gravatar',
'qq' => 'QQ',
], UOJLocale::get('user::avatar source'), $user['avatar_source']);
$change_avatar_help = UOJLocale::get('change avatar help');
$update_profile_form->appendHTML(<<<EOD
<div style="margin-top: -1.25rem;" class="mb-3 small text-muted">
$change_avatar_help
</div>
EOD);
$update_profile_form->addVInput('email', 'email', UOJLocale::get('email'), $user['email'],
function($email, &$vdata) {
if (!validateEmail($email)) {
return 'Email 格式不合法。';
}
if ($_POST['Qtag']) {
$qq = $_POST['qq'];
if (!validateQQ($qq)) {
return "失败无效QQ。";
}
$esc_qq = DB::escape($qq);
DB::update("update user_info set qq = '$esc_qq' where username = '{$user['username']}'");
$vdata['email'] = $email;
return '';
}, null);
$update_profile_form->addVInput('qq', 'text', UOJLocale::get('QQ'), $user['qq'] == 0 ? '' : $user['qq'],
function($qq, &$vdata) {
if ($qq && !validateQQ($qq)) {
return 'QQ 格式不合法。';
}
$vdata['qq'] = $qq;
return '';
}, null);
$update_profile_form->addVInput('github', 'text', 'GitHub', $user['github'],
function($github, &$vdata) {
if ($github && !validateGitHubUsername($github)) {
return 'GitHub 用户名不合法。';
}
$vdata['github'] = $github;
return '';
}, null);
if (isSuperUser($myUser)) {
$update_profile_form->addVInput('school', 'text', UOJLocale::get('school'), $user['school'],
function($school, &$vdata) {
$vdata['school'] = $school;
return '';
}, null);
} else {
DB::update("update user_info set QQ = NULL where username = '{$user['username']}'");
$school = UOJLocale::get('school');
$update_profile_form->appendHTML(<<<EOD
<div class="mb-3">
<label for="input-school" class="form-label">$school</label>
<input type="text" class="form-control" id="input-school" aria-describedby="help-school" value="{$user['school']}" disabled>
<div id="help-school" class="form-text">只有管理员才能修改用户所属学校。</div>
</div>
EOD);
}
if ($_POST['sex'] == "U" || $_POST['sex'] == 'M' || $_POST['sex'] == 'F') {
$sex = $_POST['sex'];
$esc_sex = DB::escape($sex);
DB::update("update user_info set sex = '$esc_sex' where username = '{$user['username']}'");
}
if (validateMotto($_POST['motto'])) {
$esc_motto = DB::escape($_POST['motto']);
DB::update("update user_info set motto = '$esc_motto' where username = '{$user['username']}'");
}
return "ok";
$update_profile_form->addVCheckboxes('sex', [
'U' => UOJLocale::get('refuse to answer'),
'M' => UOJLocale::get('male'),
'F' => UOJLocale::get('female'),
], UOJLocale::get('sex'), $user['sex']);
$update_profile_form->addVInput('motto', 'text', UOJLocale::get('motto'), $user['motto'],
function($motto, &$vdata) {
if (!validateMotto($motto)) {
return '格言格式不合法';
}
$vdata['motto'] = $motto;
return '';
}, null);
$update_profile_form->addVInput('codeforces_handle', 'text', UOJLocale::get('codeforces handle'), $user['codeforces_handle'],
function($codeforces_handle, &$vdata) {
if ($codeforces_handle && !validateUsername($codeforces_handle)) {
return 'Codeforces 用户名格式不合法。';
}
$vdata['codeforces_handle'] = $codeforces_handle;
return '';
}, null);
$update_profile_form->addVInput('website', 'text', UOJLocale::get('user::website'), $user['website'],
function($url, &$vdata) {
if ($url && !validateURL($url)) {
return '链接格式不合法。';
}
$vdata['website'] = $url;
return '';
}, null);
$update_profile_form->handle = function(&$vdata) use ($user, $myUser) {
$esc_email = DB::escape($vdata['email']);
$esc_qq = DB::escape($vdata['qq']);
$esc_github = DB::escape($vdata['github']);
$esc_sex = DB::escape($_POST['sex']);
$esc_motto = DB::escape($vdata['motto']);
$esc_codeforces_handle = DB::escape($vdata['codeforces_handle']);
$esc_website = DB::escape($vdata['website']);
$esc_avatar_source = DB::escape($_POST['avatar_source']);
if (isSuperUser($myUser)) {
$esc_school = DB::escape($vdata['school']);
DB::update("UPDATE user_info SET school = '$esc_school' 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']}'");
header('Content-Type: application/json');
die(json_encode(['status' => 'success']));
};
$update_profile_form->submit_button_config['margin_class'] = 'mt-3';
$update_profile_form->submit_button_config['text'] = '更新';
$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();
}
if (isset($_POST['change'])) {
die(handlePost());
$(window).scrollTop(0);
}
EOD);
$update_profile_form->runAtServer();
} elseif ($cur_tab == 'password') {
if (isset($_POST['submit-change_password']) && $_POST['submit-change_password'] == 'change_password') {
header('Content-Type: application/json');
$old_password = $_POST['current_password'];
$new_password = $_POST['new_password'];
if (!validatePassword($old_password) || !checkPassword($user, $old_password)) {
die(json_encode(['status' => 'error', 'message' => '旧密码错误']));
}
if (!validatePassword($new_password)) {
die(json_encode(['status' => 'error', 'message' => '新密码不合法']));
}
if ($old_password == $new_password) {
die(json_encode(['status' => 'error', 'message' => '新密码不能与旧密码相同']));
}
$password = getPasswordToStore($new_password, $user['username']);
DB::update("UPDATE `user_info` SET `password` = '$password' where `username` = '{$user['username']}'");
die(json_encode(['status' => 'success', 'message' => '密码修改成功']));
}
} elseif ($cur_tab == 'privilege') {
if (isset($_POST['submit-privilege']) && $_POST['submit-privilege'] == 'privilege' && isSuperUser($myUser)) {
header('Content-Type: application/json');
$user['usertype'] = 'student';
if ($_POST['user_type'] == 'teacher') {
removeUserType($user, 'student');
addUserType($user, 'teacher');
} else {
addUserType($user, 'student');
}
if ($_POST['problem_uploader'] == 'yes') {
addUserType($user, 'problem_uploader');
}
if ($_POST['problem_manager'] == 'yes') {
addUserType($user, 'problem_manager');
}
if ($_POST['contest_judger'] == 'yes') {
addUserType($user, 'contest_judger');
}
if ($_POST['contest_only'] == 'yes') {
addUserType($user, 'contest_only');
}
DB::update("UPDATE `user_info` SET `usertype` = '{$user['usertype']}' where `username` = '{$user['username']}'");
die(json_encode(['status' => 'success', 'message' => '权限修改成功']));
}
}
$pageTitle = $user['username'] == $myUser['username']
? UOJLocale::get('modify my profile')
: UOJLocale::get('modify his profile', $user['username'])
?>
<?php
$REQUIRE_LIB['dialog'] = '';
$REQUIRE_LIB['md5'] = '';
?>
<?php echoUOJPageHeader(UOJLocale::get('modify my profile')) ?>
<h2 class="page-header">
<?php if ($user['username'] == Auth::id()): ?>
<?= UOJLocale::get('modify my profile') ?>
<?php else: ?>
修改 <?= $user['username'] ?> 的个人信息
<?php endif ?>
</h2>
<?php if (isSuperUser($myUser)): ?>
<p>您正在使用管理特权修改 <?= $user['username'] ?> 的个人信息。</p>
<?php echoUOJPageHeader($pageTitle) ?>
<h1 class="h2">
<?= $pageTitle ?>
</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>
<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("/user/{$user['username']}") ?>">
<i class="bi bi-arrow-left"></i> 返回
</a>
<?php if (isSuperUser($myUser) && $user['username'] != $myUser['username']): ?>
<div class="alert alert-warning mt-3 small" role="alert">
您正在使用管理特权查看并编辑其它用户的资料。
</div>
<?php endif ?>
<form id="form-update" class="form-horizontal">
<?php if ($user['username'] == Auth::id()): ?>
<h4><?= UOJLocale::get('please enter your password for authorization') ?></h4>
<div id="div-old_password" class="form-group">
<label for="input-old_password" class="col-sm-2 control-label"><?= UOJLocale::get('password') ?></label>
<div class="col-sm-3">
<input type="password" class="form-control" name="old_password" id="input-old_password" placeholder="<?= UOJLocale::get('enter your password') ?>" maxlength="20" />
<span class="help-block" id="help-old_password"></span>
</div>
</div>
<?php endif ?>
<h4><?= UOJLocale::get('please enter your new profile') ?></h4>
<div id="div-password" class="form-group">
<label for="input-password" class="col-sm-2 control-label"><?= UOJLocale::get('new password') ?></label>
<div class="col-sm-3">
<input type="password" class="form-control" id="input-password" name="password" placeholder="<?= UOJLocale::get('enter your new password') ?>" maxlength="20" />
<input type="password" class="form-control top-buffer-sm" id="input-confirm_password" placeholder="<?= UOJLocale::get('re-enter your new password') ?>" maxlength="20" />
<span class="help-block" id="help-password"><?= UOJLocale::get('leave it blank if you do not want to change the password') ?></span>
</div>
</div>
<div id="div-email" class="form-group">
<label for="input-email" class="col-sm-2 control-label"><?= UOJLocale::get('email') ?></label>
<div class="col-sm-3">
<input type="email" class="form-control" name="email" id="input-email" value="<?=$user['email']?>" placeholder="<?= UOJLocale::get('enter your email') ?>" maxlength="50" />
<span class="help-block" id="help-email"></span>
</div>
</div>
<div id="div-qq" class="form-group">
<label for="input-qq" class="col-sm-2 control-label"><?= UOJLocale::get('QQ') ?></label>
<div class="col-sm-3">
<input type="text" class="form-control" name="qq" id="input-qq" value="<?= $user['qq'] != 0 ? $user['qq'] : '' ?>" placeholder="<?= UOJLocale::get('enter your QQ') ?>" maxlength="50" />
<span class="help-block" id="help-qq"></span>
</div>
</div>
<div id="div-sex" class="form-group">
<label for="input-sex" class="col-sm-2 control-label"><?= UOJLocale::get('sex') ?></label>
<div class="col-sm-3">
<select class="form-control" id="input-sex" name="sex">
<option value="U"<?= $user['sex'] == 'U' ? ' selected="selected"' : ''?>><?= UOJLocale::get('refuse to answer') ?></option>
<option value="M"<?= $user['sex'] == 'M' ? ' selected="selected"' : ''?>><?= UOJLocale::get('male') ?></option>
<option value="F"<?= $user['sex'] == 'F' ? ' selected="selected"' : ''?>><?= UOJLocale::get('female') ?></option>
</select>
</div>
</div>
<div id="div-motto" class="form-group">
<label for="input-motto" class="col-sm-2 control-label"><?= UOJLocale::get('motto') ?></label>
<div class="col-sm-3">
<textarea class="form-control" id="input-motto" name="motto"><?=HTML::escape($user['motto'])?></textarea>
<span class="help-block" id="help-motto">格言支持 Markdown 语法。</span>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-3">
<p class="form-control-static"><strong><?= UOJLocale::get('change avatar help') ?></strong></p>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-3">
<button type="submit" id="button-submit" class="btn btn-secondary"><?= UOJLocale::get('submit') ?></button>
</div>
</div>
</form>
<script type="text/javascript">
function validateUpdatePost() {
var ok = true;
ok &= getFormErrorAndShowHelp('email', validateEmail);
</div>
<!-- end left col -->
<?php if ($user['username'] == Auth::id()): ?>
ok &= getFormErrorAndShowHelp('old_password', validatePassword);
<?php endif ?>
<!-- right col -->
<div class="col-md-9">
<?php if ($cur_tab == 'profile'): ?>
<div class="card">
<div class="card-body">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<?php $update_profile_form->printHTML() ?>
</div>
</div>
<?php elseif ($cur_tab == 'password'): ?>
<div class="card">
<div class="card-body">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<form method="post" id="form-change_password">
<div class="mb-3">
<label for="input-current_password" class="form-label">
<?= UOJLocale::get('current password') ?>
</label>
<input type="password" class="form-control" id="input-current_password" placeholder="<?= UOJLocale::get('enter your password') ?>" maxlength="20">
<div id="help-current_password" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="input-new_password" class="form-label">
<?= UOJLocale::get('new password') ?>
</label>
<input type="password" class="form-control" id="input-new_password" placeholder="<?= UOJLocale::get('enter your new password') ?>" maxlength="20">
<div id="help-new_password" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="input-confirm_password" class="form-label">
<?= UOJLocale::get('confirm new password') ?>
</label>
<input type="password" class="form-control" id="input-confirm_password" placeholder="<?= UOJLocale::get('re-enter your new password') ?>" maxlength="20">
<div id="help-confirm_password" class="invalid-feedback"></div>
</div>
<?php if (isSuperUser($myUser) && $user['username'] != $myUser['username']): ?>
<div class="alert alert-warning mb-0" role="alert">
如需修改其他用户的密码,请前往 <a href="/super-manage/users" class="alert-link">系统管理</a> 页面操作。
</div>
<?php endif ?>
if ($('#input-password').val().length > 0)
ok &= getFormErrorAndShowHelp('password', validateSettingPassword);
if ($('#input-qq').val().length > 0)
ok &= getFormErrorAndShowHelp('qq', validateQQ);
ok &= getFormErrorAndShowHelp('motto', validateMotto);
return ok;
}
function submitUpdatePost() {
if (!validateUpdatePost())
return;
$.post('', {
change : '',
etag : $('#input-email').val().length,
ptag : $('#input-password').val().length,
Qtag : $('#input-qq').val().length,
email : $('#input-email').val(),
password : md5($('#input-password').val(), "<?= getPasswordClientSalt() ?>"),
<?php if ($user['username'] == Auth::id()): ?>
old_password : md5($('#input-old_password').val(), "<?= getPasswordClientSalt() ?>"),
<?php endif ?>
qq : $('#input-qq').val(),
sex : $('#input-sex').val(),
motto : $('#input-motto').val()
}, function(msg) {
if (msg == 'ok') {
BootstrapDialog.show({
title : '修改成功',
message : '用户信息修改成功',
type : BootstrapDialog.TYPE_SUCCESS,
buttons : [{
label: '好的',
action: function(dialog) {
dialog.close();
<div class="text-center">
<button type="submit" id="button-submit-change_password" name="submit-change_password" value="change_password" class="mt-3 btn btn-secondary">更新</button>
</div>
</form>
</div>
</div>
<script>
$('#form-change_password').submit(function() {
var ok = true;
$('#result-alert').hide();
ok &= getFormErrorAndShowHelp('current_password', validatePassword);
ok &= getFormErrorAndShowHelp('new_password', validateSettingPassword);
if (ok) {
$.ajax({
method: 'POST',
data: {
'submit-change_password': 'change_password',
'current_password': md5($('#input-current_password').val(), "<?= getPasswordClientSalt() ?>"),
'new_password': md5($('#input-new_password').val(), "<?= getPasswordClientSalt() ?>"),
},
success: 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();
}
}],
onhidden : function(dialog) {
window.location.href = '/user/<?=$user['username']?>';
$(window).scrollTop(0);
},
error: function() {
$('#result-alert')
.html('密码修改失败:请求失败。')
.removeClass('alert-success')
.addClass('alert-danger')
.show();
$(window).scrollTop(0);
}
});
} else {
BootstrapDialog.show({
title : '修改失败',
message : msg,
type : BootstrapDialog.TYPE_DANGER,
buttons: [{
label: '好的',
action: function(dialog) {
dialog.close();
}
}],
});
}
return false;
});
}
$(document).ready(function(){$('#form-update').submit(function(e) {submitUpdatePost();e.preventDefault();});
});
</script>
</script>
<?php elseif ($cur_tab == 'privilege'): ?>
<div class="card">
<div class="card-body">
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
<form id="form-privilege" method="post">
<?php if (isSuperUser($myUser)): ?>
<fieldset>
<?php else: ?>
<fieldset disabled>
<?php endif ?>
<div class="input-group mb-3">
<label for="input-user_type" class="form-label">
<?= UOJLocale::get('user::user type') ?>
</label>
<div class="form-check ms-3">
<input class="form-check-input" type="radio" name="user_type" value="student" id="input-user_type" <?= hasUserType($user, 'student') && !hasUserType($user, 'teacher') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-user_type">
<?= UOJLocale::get('user::student') ?>
</label>
</div>
<div class="form-check ms-2">
<input class="form-check-input" type="radio" name="user_type" value="teacher" id="input-user_type_2" <?= hasUserType($user, 'teacher') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-user_type_2">
<?= UOJLocale::get('user::teacher') ?>
</label>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="problem_uploader" id="input-problem_uploader" <?= hasUserType($user, 'problem_uploader') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-problem_uploader">
<?= UOJLocale::get('user::problem uploader') ?>
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="problem_manager" id="input-problem_manager" <?= hasUserType($user, 'problem_manager') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-problem_manager">
<?= UOJLocale::get('user::problem manager') ?>
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="contest_judger" id="input-contest_judger" <?= hasUserType($user, 'contest_judger') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-contest_judger">
<?= UOJLocale::get('user::contest judger') ?>
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="contest_only" id="input-contest_only" <?= hasUserType($user, 'contest_only') ? 'checked' : '' ?>>
<label class="form-check-label" for="input-contest_only">
<?= UOJLocale::get('user::contest only') ?>
</label>
</div>
</fieldset>
<?php if (isSuperUser($myUser)): ?>
<div class="text-center">
<button type="submit" id="button-submit-privilege" name="submit-privilege" value="privilege" class="mt-3 btn btn-secondary">更新</button>
</div>
<?php endif ?>
</form>
<script>
$('#form-privilege').submit(function(e) {
$('#result-alert').hide();
$.post('', {
user_type: $('input[name=user_type]:checked').val(),
problem_uploader: $('input[name=problem_uploader]').prop('checked') ? 'yes' : 'no',
problem_manager: $('input[name=problem_manager]').prop('checked') ? 'yes' : 'no',
contest_judger: $('input[name=contest_judger]').prop('checked') ? 'yes' : 'no',
contest_only: $('input[name=contest_only]').prop('checked') ? 'yes' : 'no',
'submit-privilege': 'privilege',
}, function(res) {
if (res && res.status === 'success') {
$('#result-alert')
.html('权限修改成功!')
.addClass('alert-success')
.removeClass('alert-danger')
.show();
$(window).scrollTop(0);
} else {
$('#result-alert')
.html('权限修改失败。' + (res.message || ''))
.removeClass('alert-success')
.addClass('alert-danger')
.show();
$(window).scrollTop(0);
}
});
return false;
});
</script>
</div>
</div>
<?php endif ?>
<!-- end right col -->
</div>
</div>
<?php echoUOJPageFooter() ?>

View File

@ -159,7 +159,7 @@ EOD;
public function addVSelect($name, $options, $label_text, $default_value) {
$default_value = htmlspecialchars($default_value);
$html = <<<EOD
<div id="div-$name">
<div id="div-$name" class="mb-3">
<label for="input-$name" class="control-label">$label_text</label>
<select class="form-control form-select" id="input-{$name}" name="$name">
@ -225,6 +225,40 @@ EOD;
EOD;
$this->addNoVal($name, $html);
}
public function addVCheckboxes($name, $options, $label_text, $default_value) {
$default_value = htmlspecialchars($default_value);
$html = <<<EOD
<div id="div-$name" class="input-group mb-3">
<label class="form-label me-3">$label_text</label>
EOD;
foreach ($options as $opt_name => $opt_label) {
$html .= <<<EOD
<div class="form-check ms-2">
EOD;
if ($opt_name != $default_value) {
$html .= <<<EOD
<input class="form-check-input" type="radio" id="input-$name-$opt_name" name="$name" value="$opt_name">
EOD;
} else {
$html .= <<<EOD
<input class="form-check-input" type="radio" id="input-$name-$opt_name" name="$name" value="$opt_name" checked="checked">
EOD;
}
$html .= <<<EOD
<label class="form-check-label" for="input-$name-$opt_name">$opt_label</label>
</div>
EOD;
}
$html .= <<<EOD
</div>
EOD;
$this->add($name, $html,
function($opt) use ($options) {
return isset($options[$opt]) ? '' : "无效选项";
},
null
);
}
public function addCKEditor($name, $label_text, $default_value, $validator_php, $validator_js) {
$default_value = htmlspecialchars($default_value);
global $REQUIRE_LIB;
@ -534,14 +568,15 @@ EOD;
if ($field['validator_js'] != 'always_ok') {
echo <<<EOD
if (${field['name']}_err) {
$('#div-${field['name']}').addClass('has-error');
$('#div-${field['name']}').addClass('has-error'); // for bootstrap4
$('#input-${field['name']}').addClass('is-invalid');
$('#help-${field['name']}').text(${field['name']}_err);
ok = false;
} else {
$('#div-${field['name']}').removeClass('has-error');
$('#div-${field['name']}').removeClass('has-error'); // for bootstrap4
$('#input-${field['name']}').removeClass('is-invalid');
$('#help-${field['name']}').text('');
}
EOD;
}
}
@ -562,11 +597,13 @@ EOD;
$(this).find("input[type='file']").each(function() {
for (var i = 0; i < this.files.length; i++) {
if (this.files[i].size > 10 * 1024 * 1024) {
$('#div-' + $(this).attr('name')).addClass('has-error');
$('#div-' + $(this).attr('name')).addClass('has-error'); // for bootstrap4
$('#input-' + $(this).attr('name')).addClass('is-invalid');
$('#help-' + $(this).attr('name')).text('文件大小不能超过10M');
ok = false;
} else {
$('#div-' + $(this).attr('name')).removeClass('has-error');
$('#div-' + $(this).attr('name')).removeClass('has-error'); // for bootstrap4
$('#input-' + $(this).attr('name')).removeClass('is-invalid');
$('#help-' + $(this).attr('name')).text('');
}
}

View File

@ -129,7 +129,7 @@ function blog_name_decode($name) {
return $name;
}
function addUserType($user, $type) {
function addUserType(&$user, $type) {
$usertype = explode(',', $user['usertype']);
if (!in_array($type, $usertype)) {
$usertype[] = $type;
@ -137,7 +137,7 @@ function addUserType($user, $type) {
$user['usertype'] = implode(',', $usertype);
return $user;
}
function removeUserType($user, $type) {
function removeUserType(&$user, $type) {
$usertype = explode(',', $user['usertype']);
if (in_array($type, $usertype)) {
$usertype = array_diff($usertype, array($type));

View File

@ -55,3 +55,7 @@ function validateURL($url) {
function validateString($str) {
return preg_match('/[^0-9a-zA-Z]/', $str) !== true;
}
function validateGitHubUsername($username) {
return is_string($username) && preg_match('/^[a-zA-Z0-9_-]{1,20}$/', $username);
}

View File

@ -9,6 +9,7 @@ return [
'private message' => 'Private Message',
'system message' => 'System Message',
'system manage' => 'System Manage',
'avatar' => 'Avatar',
'contests' => 'Contests',
'problems' => 'Problems',
'problems lists' => 'Problems Lists',
@ -35,11 +36,15 @@ return [
'username' => 'Username',
'password' => 'Password',
'new password' => 'New password',
'current password' => 'Current password',
'confirm new password' => 'Confirm new password',
'verification code' => 'Verification code',
'email' => 'Email',
'QQ' => 'QQ',
'school' => 'School',
'sex' => 'Sex',
'motto' => 'Motto',
'codeforces handle' => 'Codeforces handle',
'view all' => 'View all',
'hidden' => 'Hidden',
'appraisal' => 'Appraisal',
@ -58,6 +63,9 @@ return [
'user profile' => 'User profile',
'send private message' => 'Send private message',
'modify my profile' => 'Modify my profile',
'modify his profile' => function($name) {
return "Modify $name's profile";
},
'visit his blog' => function($name) {
return "Visit $name's blog";
},
@ -68,7 +76,7 @@ return [
'please enter your password for authorization' => 'Please enter your password for authorization',
'please enter your new profile' => 'Please enter your new profile',
'leave it blank if you do not want to change the password' => 'Leave it blank if you do not want to change the password',
'change avatar help' => 'Do you want to change your avatar? Please see <a href="/faq">Help</a>',
'change avatar help' => 'Do you want to change your avatar? Please see <a class="text-decoration-none" href="/faq">Help</a>',
'enter your username' => 'Enter your username',
'enter your email' => 'Enter your email',
'enter your password' => 'Enter your password',
@ -90,16 +98,6 @@ return [
'friend links' => 'Frequently Used Links',
'server time' => 'Server Time',
'opensource project' => 'OpenSource Project, modified by S2OJ',
'admin' => 'Admin',
'student' => 'Student',
'teacher' => 'Teacher',
'problem uploader' => 'Problem Uploader',
'problem manager' => 'Problem Manager',
'contest judger' => 'Contest Judger',
'contest only' => 'Contest Only',
'last active at' => 'Last active at',
'online' => 'Online',
'offline' => 'Offline',
'apps' => 'Apps',
'image hosting' => 'Image Hosting',
'html to markdown' => 'HTML to Markdown',

View File

@ -9,6 +9,7 @@ return [
'private message' => '私信',
'system message' => '系统消息',
'system manage' => '系统管理',
'avatar' => '头像',
'contests' => '比赛',
'problems' => '题库',
'problems lists' => '题单',
@ -35,11 +36,15 @@ return [
'username' => '用户名',
'password' => '密码',
'new password' => '新密码',
'current password' => '当前密码',
'confirm new password' => '确认新密码',
'verification code' => '验证码',
'email' => 'Email',
'QQ' => 'QQ',
'school' => '学校',
'sex' => '性别',
'motto' => '格言',
'codeforces handle' => 'Codeforces 用户名',
'view all' => '查看全部',
'hidden' => '隐藏',
'appraisal' => '评价',
@ -58,6 +63,9 @@ return [
'user profile' => '用户信息',
'send private message' => '发送私信',
'modify my profile' => '更改个人信息',
'modify his profile' => function($name) {
return "更改 $name 的个人信息";
},
'visit his blog' => function($name) {
return "访问 $name 的博客";
},
@ -68,7 +76,7 @@ return [
'please enter your password for authorization' => '请输入您的密码进行身份验证',
'please enter your new profile' => '请输入新的个人信息',
'leave it blank if you do not want to change the password' => '如果不想修改密码请留空',
'change avatar help' => '想改头像?见<a href="/faq">帮助</a>',
'change avatar help' => '想改头像?见 <a class="text-decoration-none" href="/faq">帮助</a>',
'enter your username' => '输入用户名',
'enter your email' => '输入 Email',
'enter your password' => '输入密码',
@ -90,16 +98,6 @@ return [
'friend links' => '常用链接',
'server time' => '服务器时间',
'opensource project' => '开源项目S2OJ 魔改版',
'admin' => '管理员',
'student' => '学生',
'teacher' => '老师',
'problem uploader' => '题目上传者',
'problem manager' => '题目管理员',
'contest judger' => '比赛评测员',
'contest only' => '仅比赛用户',
'online' => '在线',
'offline' => '离线',
'last active at' => '最后活动于',
'apps' => '应用',
'image hosting' => '图床',
'html to markdown' => 'HTML 转 Markdown',

View File

@ -0,0 +1,17 @@
<?php
return [
'belongs to these groups' => 'Belongs to these groups:',
'avatar source' => 'Avatar source',
'website' => 'Website',
'user type' => 'User type',
'admin' => 'Admin',
'student' => 'Student',
'teacher' => 'Teacher',
'problem uploader' => 'Problem Uploader',
'problem manager' => 'Problem Manager',
'contest judger' => 'Contest Judger',
'contest only' => 'Contest Only',
'last active at' => 'Last active at',
'online' => 'Online',
'offline' => 'Offline',
];

View File

@ -0,0 +1,17 @@
<?php
return [
'belongs to these groups' => '属于这些小组:',
'avatar source' => '头像来源',
'website' => '网址',
'user type' => '用户类型',
'admin' => '管理员',
'student' => '学生',
'teacher' => '老师',
'problem uploader' => '题目上传者',
'problem manager' => '题目管理员',
'contest judger' => '比赛评测员',
'contest only' => '仅比赛用户',
'online' => '在线',
'offline' => '离线',
'last active at' => '最后活动于',
];

View File

@ -8,6 +8,20 @@ class HTML {
return strip_tags($str);
}
public static function avatar_addr($user, $size) {
if ($user['avatar_source'] == 'qq' && $user['qq']) {
$s = '5';
if ($size <= 40) {
$s = '2';
} elseif ($size <= 100) {
$s = '3';
} elseif ($size <= 140) {
$s = '4';
}
return "https://q1.qlogo.cn/g?b=qq&nk={$user['qq']}&s=$s";
}
return '//gravatar.loli.net/avatar/' . md5(strtolower(trim($user['email']))) . "?d=mm&amp;s=$size";
}
@ -24,14 +38,14 @@ class HTML {
return '<input type="hidden" name="_token" value="'.crsf_token().'" />';
}
public static function div_vinput($name, $type, $label_text, $default_value) {
return '<div id="'."div-$name".'">'
. '<label for="'."input-$name".'" class="control-label">'.$label_text.'</label>'
return '<div id="'."div-$name".'" class="mb-3">'
. '<label for="'."input-$name".'" class="control-label form-label">'.$label_text.'</label>'
. '<input type="'.$type.'" class="form-control" name="'.$name.'" id="'."input-$name".'" value="'.HTML::escape($default_value).'" />'
. '<span class="help-block" id="'."help-$name".'"></span>'
. '<span class="help-block invalid-feedback" id="'."help-$name".'"></span>'
. '</div>';
}
public static function div_vtextarea($name, $label_text, $default_value) {
return '<div id="'."div-$name".'">'
return '<div id="'."div-$name".'" class="mb-3">'
. '<label for="'."input-$name".'" class="control-label">'.$label_text.'</label>'
. '<textarea class="form-control" name="'.$name.'" id="'."input-$name".'">'.HTML::escape($default_value).'</textarea>'
. '<span class="help-block" id="'."help-$name".'"></span>'

View File

@ -2,7 +2,7 @@
class UOJLocale {
public static $supported_locales = array('zh-cn', 'en');
public static $supported_modules = array('basic', 'contests', 'problems', 'time');
public static $supported_modules = array('basic', 'contests', 'problems', 'time', 'user');
public static $data = array();
public static $required = array();

View File

@ -105,7 +105,7 @@ class Upgrader {
public static function upgradeToLatest() {
$names = array_filter(scandir(self::upgraderRoot()), function ($name) {
return is_dir(self::upgraderRoot().'/'.$name) && preg_match('/^\d+_[a-zA-Z_]+$/', $name);
return is_dir(self::upgraderRoot().'/'.$name) && preg_match('/^\d+_[0-9a-zA-Z_]+$/', $name);
});
natsort($names);

View File

@ -72,7 +72,7 @@ Route::group([
Route::any('/reset-password', '/reset_pw.php');
Route::any('/user/{username}', '/user_info.php');
Route::any('/user/{username}/edit', '/user_info_edit.php');
Route::any('/user/{username}/edit(?:/{tab})?', '/user_info_edit.php');
Route::any('/user_msg', '/user_msg.php');
Route::any('/user/{username}/system_msg', '/user_system_msg.php');

View File

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

View File

@ -0,0 +1,4 @@
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `codeforces_handle`;
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `github`;
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `website`;
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `avatar_source`;

View File

@ -0,0 +1,4 @@
ALTER TABLE `user_info` ADD COLUMN `codeforces_handle` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
ALTER TABLE `user_info` ADD COLUMN `github` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
ALTER TABLE `user_info` ADD COLUMN `website` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
ALTER TABLE `user_info` ADD COLUMN `avatar_source` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'gravatar';

View File

@ -219,12 +219,6 @@
<?= HTML::js_src('/js/ckeditor/ckeditor.js') ?>
<?php endif ?>
<?php if (isset($REQUIRE_LIB['github_contribution_graph'])): ?>
<!-- github contribution graph -->
<?= HTML::css_link('/css/github_contribution_graph.css') ?>
<?= HTML::js_src('/js/jquery.github_contribution_graph.js') ?>
<?php endif ?>
<?php if (isset($REQUIRE_LIB['calendar_heatmap'])): ?>
<!-- jquery-calendar-heatmap -->
<?= HTML::css_link('/css/jquery.calendar_heatmap.min.css') ?>

View File

@ -68,7 +68,7 @@ function fTime($time, $gran = -1) {
<div class="card-body">
<?php if ($user['usergroup'] == 'S'): ?>
<span class="badge bg-secondary">
<?= UOJLocale::get('admin') ?>
<?= UOJLocale::get('user::admin') ?>
</span>
<?php endif ?>
<h3>
@ -102,35 +102,107 @@ function fTime($time, $gran = -1) {
<li class="list-group-item">
<i class="bi bi-key-fill me-1"></i>
<?php foreach (explode(',', $user['usertype']) as $idx => $type): ?>
<?php if ($idx): ?>, <?php endif ?>
<?php if ($type == 'teacher'): ?>
<?= UOJLocale::get('teacher') ?>
<?php elseif ($type == 'student'): ?>
<?= UOJLocale::get('student') ?>
<?php elseif ($type == 'problem_uploader'): ?>
<?= UOJLocale::get('problem uploader') ?>
<?php elseif ($type == 'problem_manager'): ?>
<?= UOJLocale::get('problem manager') ?>
<?php elseif ($type == 'contest_judger'): ?>
<?= UOJLocale::get('contest judger') ?>
<?php elseif ($type == 'contest_only'): ?>
<?= UOJLocale::get('contest only') ?>
<?php else: ?>
<?= HTML::escape($type) ?>
<?php endif ?>
<?php if ($idx): ?>,<?php endif ?>
<span><?= UOJLocale::get('user::' . str_replace('_', ' ', $type)) ?: HTML::escape($type) ?></span>
<?php endforeach ?>
</li>
<?php endif ?>
<?php if ($user['email']): ?>
<li class="list-group-item">
<i class="bi bi-envelope-fill me-1"></i>
<a class="text-decoration-none text-body" href="mailto:<?= HTML::escape($user['email']) ?>">
<?= HTML::escape($user['email']) ?>
</a>
</li>
<?php endif ?>
<?php if ($user['qq']): ?>
<li class="list-group-item">
<i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path d="M433.754 420.445c-11.526 1.393-44.86-52.741-44.86-52.741 0 31.345-16.136 72.247-51.051 101.786 16.842 5.192 54.843 19.167 45.803 34.421-7.316 12.343-125.51 7.881-159.632 4.037-34.122 3.844-152.316 8.306-159.632-4.037-9.045-15.25 28.918-29.214 45.783-34.415-34.92-29.539-51.059-70.445-51.059-101.792 0 0-33.334 54.134-44.859 52.741-5.37-.65-12.424-29.644 9.347-99.704 10.261-33.024 21.995-60.478 40.144-105.779C60.683 98.063 108.982.006 224 0c113.737.006 163.156 96.133 160.264 214.963 18.118 45.223 29.912 72.85 40.144 105.778 21.768 70.06 14.716 99.053 9.346 99.704z" fill="currentColor"/></svg></i>
<?= HTML::escape($user['qq']) ?>
<a class="text-decoration-none text-body" href="http://wpa.qq.com/msgrd?v=3&uin=<?= HTML::escape($user['qq']) ?>&site=qq&menu=yes" target="_blank">
<?= HTML::escape($user['qq']) ?>
</a>
</li>
<?php endif ?>
<?php if ($user['github']): ?>
<li class="list-group-item">
<i class="bi bi-github me-1"></i>
<a class="text-decoration-none text-body" href="https://github.com/<?= HTML::escape($user['github']) ?>" target="_blank">
<?= HTML::escape($user['github']) ?>
</a>
</li>
<?php endif ?>
<?php if ($user['codeforces_handle']): ?>
<li class="list-group-item d-flex align-items-center">
<div class="flex-shrink-0"><i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" width="16" height="16"><title>Codeforces</title><path d="M4.5 7.5C5.328 7.5 6 8.172 6 9v10.5c0 .828-.672 1.5-1.5 1.5h-3C.673 21 0 20.328 0 19.5V9c0-.828.673-1.5 1.5-1.5h3zm9-4.5c.828 0 1.5.672 1.5 1.5v15c0 .828-.672 1.5-1.5 1.5h-3c-.827 0-1.5-.672-1.5-1.5v-15c0-.828.673-1.5 1.5-1.5h3zm9 7.5c.828 0 1.5.672 1.5 1.5v7.5c0 .828-.672 1.5-1.5 1.5h-3c-.828 0-1.5-.672-1.5-1.5V12c0-.828.672-1.5 1.5-1.5h3z"/></svg></i>&nbsp;</div>
<div>
<a id="codeforces-profile-link" class="text-decoration-none" href="https://codeforces.com/profile/<?= $user['codeforces_handle'] ?>" target="_blank" style="color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;">
<?= $user['codeforces_handle'] ?>
</a>
<div id="codeforces-rating" style="font-family: verdana, arial, sans-serif; line-height: 1.2em; text-transform: capitalize;"></div>
</div>
<script>
function getRatingColor(rating) {
if (rating >= 2400) return 'ff0000';
if (rating >= 2100) return 'ff8c00';
if (rating >= 1900) return 'aa00aa';
if (rating >= 1600) return '0000ff';
if (rating >= 1400) return '03a89e';
if (rating >= 1200) return '008000';
return '808080';
}
function showCodeforcesRating(handle, rating, text) {
var color = '#' + getRatingColor(rating);
$('#codeforces-profile-link')
.html(rating >= 3000 ? ('<span style="color:#000!important">' + handle[0] + '</span>' + handle.substring(1)) : handle)
.css('color', color)
.css('font-family', 'Helvetica Neue, Helvetica, Arial, sans-serif')
.css('font-size', '1.1em')
.css('font-weight', 'bold');
$('#codeforces-rating')
.html(text + ', ' + rating)
.css('color', color);
}
function processCodeforcesInfoData(data) {
if (!data || data.status !== 'OK' || !data.result || !data.result.length) return;
var result = data.result[0];
if (result.rating) {
showCodeforcesRating(result.handle, result.rating, result.rank);
} else {
showCodeforcesRating(result.handle, 0, 'Unrated');
}
}
$(document).ready(function() {
if (('Promise' in window) && ('any' in Promise)) {
// race
Promise.any([
fetch('https://codeforces.com/api/user.info?handles=<?= $user['codeforces_handle'] ?>'),
fetch('https://codeforc.es/api/user.info?handles=<?= $user['codeforces_handle'] ?>')
]).then(function(res) {
return res.json();
}).then(function(data) {
processCodeforcesInfoData(data);
});
} else {
$.get('https://codeforces.com/api/user.info?handles=<?= $user['codeforces_handle'] ?>', function(data) {
processCodeforcesInfoData(data);
});
}
});
</script>
</li>
<?php endif ?>
<?php if ($user['website']): ?>
<li class="list-group-item">
<i class="bi bi-link-45deg me-1"></i>
<a class="text-decoration-none text-body text-break" href="<?= HTML::escape($user['website']) ?>" target="_blank">
<?= HTML::escape($user['website']) ?>
</a>
</li>
<?php endif ?>
</ul>
@ -139,16 +211,16 @@ function fTime($time, $gran = -1) {
<?php if (time() - $last_visited < 60 * 15): // 15 mins ?>
<span class="text-success">
<i class="bi bi-circle-fill me-1"></i>
<?= UOJLocale::get('online') ?>
<?= UOJLocale::get('user::online') ?>
</span>
<?php else: ?>
<span class="text-danger">
<i class="bi bi-circle-fill me-1"></i>
<?= UOJLocale::get('offline') ?>
<?= UOJLocale::get('user::offline') ?>
</span>
<?php if ($last_visited > 0): ?>
<span class="text-muted small">
, <?= UOJLocale::get('last active at') ?>
, <?= UOJLocale::get('user::last active at') ?>
<?= fTime($last_visited, 0) ?>
</span>
<?php endif ?>
@ -182,10 +254,34 @@ function fTime($time, $gran = -1) {
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>">
<i class="bi bi-arrow-right-square"></i>
赛后总结
<?= UOJLocale::get('contests::contest self reviews') ?>
</a>
</nav>
<div class="card card-default mb-2">
<?php if (!isset($is_blog_aboutme)): ?>
<?php $groups = queryGroupsOfUser($user['username']) ?>
<div class="card mb-2">
<div class="card-body">
<h4 class="card-title h5">
<?= UOJLocale::get('user::belongs to these groups') ?>
</h4>
<ul class="mb-0">
<?php foreach ($groups as $group): ?>
<li>
<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>">
<?= $group['title'] ?>
</a>
</li>
<?php endforeach ?>
<?php if (!count($groups)): ?>
<?= UOJLocale::get('none') ?>
<?php endif ?>
</ul>
</div>
</div>
<?php endif ?>
<div class="card mb-2">
<div class="card-body">
<?php
$_result = DB::query("select date_format(submit_time, '%Y-%m-%d'), problem_id from submissions where submitter = '{$user['username']}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
@ -214,8 +310,8 @@ while ($row = DB::fetch($_result)) {
</script>
</div>
</div>
<div class="card card-default mb-2">
<div class="card-body">
<div class="card mb-2">
<div class="card-body">
<?php $ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}' order by id") ?>
<h4 class="card-title h5">
<?= UOJLocale::get('accepted problems').': '.UOJLocale::get('n problems in total', count($ac_problems))?>
@ -233,7 +329,7 @@ while ($row = DB::fetch($_result)) {
<?= UOJLocale::get('none'); ?>
<?php endif ?>
</ul>
</div>
</div>
</div>
<?php if (isSuperUser($myUser)): ?>

View File

@ -1,24 +0,0 @@
.svg-tip {
padding: 10px;
background: rgba(0,0,0,0.8);
color: #bbb;
font-size: 12px;
position: absolute;
z-index: 99999;
text-align: center;
border-radius: 3px;
}
.svg-tip:after {
-moz-box-sizing: border-box;
box-sizing: border-box;
position: absolute;
left: 50%;
height: 5px;
width: 5px;
bottom: -10px;
margin: 0 0 0 -5px;
content: " ";
border: 5px solid transparent;
border-top-color: rgba(0,0,0,0.8);
}

View File

@ -1,308 +0,0 @@
/**
* Jquery plugin to render like contribution graph on Github.
*
* @see {@link https://github.com/bachvtuan/Github-Contribution-Graph}
* @author bachvtuan@gmail.com
* @license MIT License
* @since 0.1.0
*/
//Format string
if (!String.prototype.formatString) {
String.prototype.formatString = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != "undefined" ? args[number] : match;
});
};
}
(function ($) {
$.fn.github_graph = function (options) {
//If the number less than 10, add Zero before it
var prettyNumber = function (number) {
return number < 10
? "0" + number.toString()
: (number = number.toString());
};
/*
Count the number on each day and store the object
*/
var processListTimeStamp = function (list_timestamp) {
//The result will store into this varriable
obj_timestamp = {};
for (var i = 0; i < list_timestamp.length; i++) {
var _type = typeof list_timestamp[i];
var _d =
_type == "number"
? new Date(list_timestamp[i])
: new Date(list_timestamp[i].timestamp);
var display_date = getDisplayDate(_d);
var increase = _type == "number" ? 1 : list_timestamp[i].count;
if (!obj_timestamp[display_date]) {
obj_timestamp[display_date] = increase;
} else {
obj_timestamp[display_date] += increase;
}
}
};
var getDisplayDate = function (date_obj) {
var pretty_month = prettyNumber(date_obj.getMonth() + 1);
var pretty_date = prettyNumber(date_obj.getDate());
return "{0}-{1}-{2}".formatString(
date_obj.getFullYear(),
pretty_month,
pretty_date
);
};
var getCount = function (display_date) {
if (obj_timestamp[display_date]) {
return obj_timestamp[display_date];
}
return 0;
};
var getColor = function (count) {
if (typeof settings.colors[0] == "string") {
return count > settings.colors.length - 1
? settings.colors[settings.colors.length - 1]
: settings.colors[count];
}
const isLargeNumber = (element) => element.count > count;
i = settings.colors.findIndex(isLargeNumber);
return i == -1
? settings.colors[settings.colors.length - 1].color
: settings.colors[i - 1].color;
};
var start = function () {
processListTimeStamp(settings.data);
var wrap_chart = _this;
settings.colors_length = settings.colors.length;
var radius = settings.border.radius;
var hoverColor = settings.border.hover_color;
var clickCallback = settings.click;
var start_date;
if (settings.start_date == null) {
// if set null, will get from 365 days from now
start_date = new Date();
start_date.setMonth(start_date.getMonth() - 12);
start_date.setDate(start_date.getDate() + 1);
} else {
// formats:
// - YYYY-MM-DD
// - YYYY/MM/DD
start_date = new Date(settings.start_date);
}
end_date = new Date(start_date);
end_date.setMonth(end_date.getMonth() + 12);
end_date.setDate(end_date.getDate() - 1);
var loop_html = "";
var step = 13;
var month_position = [];
month_position.push({ month_index: start_date.getMonth(), x: 0 });
var using_month = start_date.getMonth();
var week = 0;
var g_x = week * step;
var item_html =
'<g transform="translate(' + g_x.toString() + ', 0)">';
for (
;
start_date.getTime() <= end_date.getTime();
start_date.setDate(start_date.getDate() + 1)
) {
if (start_date.getDay() == 0) {
var item_html =
'<g transform="translate(' + g_x.toString() + ', 0)">';
}
var month_in_day = start_date.getMonth();
var data_date = getDisplayDate(start_date);
if (start_date.getDay() == 0 && month_in_day != using_month) {
using_month = month_in_day;
month_position.push({ month_index: using_month, x: g_x });
}
var count = getCount(data_date);
var color = getColor(count);
var y = start_date.getDay() * step;
item_html +=
'<rect class="day" width="11" height="11" y="' +
y +
'" fill="' +
color +
'" data-count="' +
count +
'" data-date="' +
data_date +
'" rx="' +
radius +
'" ry="' +
radius +
'"/>';
if (start_date.getDay() == 6) {
item_html += "</g>";
loop_html += item_html;
item_html = null;
week++;
g_x = week * step;
}
}
if (item_html != null) {
item_html += "</g>";
loop_html += item_html;
}
//trick
if (month_position[1].x - month_position[0].x < 40) {
//Fix ugly graph by remove first item
month_position.shift(0);
}
for (var i = 0; i < month_position.length; i++) {
var item = month_position[i];
var month_name = settings.month_names[item.month_index];
loop_html +=
'<text x="' +
item.x +
'" y="-5" class="month">' +
month_name +
"</text>";
}
//Add Monday, Wenesday, Friday label
loop_html +=
'<text text-anchor="middle" class="wday" dx="-10" dy="22">{0}</text>'.formatString(
settings.h_days[0]
) +
'<text text-anchor="middle" class="wday" dx="-10" dy="48">{0}</text>'.formatString(
settings.h_days[1]
) +
'<text text-anchor="middle" class="wday" dx="-10" dy="74">{0}</text>'.formatString(
settings.h_days[2]
);
//Fixed size for now with width= 721 and height = 110
var wire_html =
'<svg width="721" height="110" viewBox="0 0 721 110" class="js-calendar-graph-svg">' +
'<g transform="translate(20, 20)">' +
loop_html +
"</g>" +
"</svg>";
wrap_chart.html(wire_html);
$(_this)
.find(".day")
.on("click", function () {
if (clickCallback) {
clickCallback(
$(this).attr("data-date"),
parseInt($(this).attr("data-count"))
);
}
});
$(_this)
.find(".day")
.hover(
function () {
$(this).attr(
"style",
"stroke-width: 1; stroke: " + hoverColor
);
},
function () {
$(this).attr("style", "stroke-width:0");
}
);
_this.find("rect").on("mouseenter", mouseEnter);
_this.find("rect").on("mouseleave", mouseLeave);
appendTooltip();
};
var mouseLeave = function (evt) {
$(".svg-tip").hide();
};
//handle event mouseenter when enter into rect element
var mouseEnter = function (evt) {
var target_offset = $(evt.target).offset();
var count = $(evt.target).attr("data-count");
var date = $(evt.target).attr("data-date");
var count_text = count > 1 ? settings.texts[1] : settings.texts[0];
var text = "{0} {1} on {2}".formatString(count, count_text, date);
var svg_tip = $(".svg-tip").show();
svg_tip.html(text);
var svg_width = Math.round(svg_tip.width() / 2 + 5);
var svg_height = svg_tip.height() * 2 + 10;
svg_tip.css({ top: target_offset.top - svg_height - 5 });
svg_tip.css({ left: target_offset.left - svg_width });
};
//Append tooltip to display when mouse enter the rect element
//Default is display:none
var appendTooltip = function () {
if ($(".svg-tip").length == 0) {
$("body").append(
'<div class="svg-tip svg-tip-one-line" style="display:none" ></div>'
);
}
};
var settings = $.extend(
{
colors: ["#eeeeee", "#d6e685", "#8cc665", "#44a340", "#44a340"],
border: {
radius: 2,
hover_color: "#999",
},
click: null,
start_date: null,
//List of name months
month_names: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
h_days: ["M", "W", "F"],
//Default is empty, it can be overrided
data: [],
},
options
);
var _this = $(this);
start();
};
})(jQuery);