mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-26 03:08:41 +00:00
Merge branch 'master' into uoj_form_v2
This commit is contained in:
commit
e2c3a3eb4e
45
.config.php
Normal file
45
.config.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
return [
|
||||
'profile' => [
|
||||
'oj-name' => '石家庄二中信息学在线评测系统',
|
||||
'oj-name-short' => 'S2OJ',
|
||||
'administrator' => 'root',
|
||||
'admin-email' => 'admin@sjzezoj.com',
|
||||
'QQ-group' => '',
|
||||
'ICP-license' => '冀ICP备2020028886号',
|
||||
],
|
||||
'database' => [
|
||||
'database' => 'app_uoj233',
|
||||
'username' => 'root',
|
||||
'password' => 'root',
|
||||
'host' => 'uoj-db',
|
||||
'port' => '3306',
|
||||
],
|
||||
'security' => [
|
||||
'user' => [
|
||||
'client_salt' => 'salt_0',
|
||||
],
|
||||
'cookie' => [
|
||||
'checksum_salt' => ['salt_1', 'salt_2', 'salt_3'],
|
||||
],
|
||||
],
|
||||
'mail' => [
|
||||
'noreply' => [
|
||||
'username' => 'noreply@local_uoj.ac',
|
||||
'password' => '_mail_noreply_password_',
|
||||
'host' => 'smtp.local_uoj.ac',
|
||||
'secure' => 'tls',
|
||||
'port' => 587,
|
||||
]
|
||||
],
|
||||
'judger' => [
|
||||
'socket' => [
|
||||
'port' => '2333',
|
||||
'password' => '_judger_socket_password_'
|
||||
],
|
||||
],
|
||||
'switch' => [
|
||||
'blog-domain-mode' => 3,
|
||||
'open-register' => false,
|
||||
],
|
||||
];
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,3 +3,6 @@ uoj_data_1/
|
||||
uoj_data_2/
|
||||
.php-cs-fixer.cache
|
||||
docker-compose.local.yml
|
||||
.config.php
|
||||
.config.development.php
|
||||
.config.local.php
|
||||
|
@ -979,6 +979,17 @@ CREATE TABLE `upgrades` (
|
||||
|
||||
LOCK TABLES `upgrades` WRITE;
|
||||
/*!40000 ALTER TABLE `upgrades` DISABLE KEYS */;
|
||||
INSERT INTO `upgrades` (`name`, `status`, `updated_at`) VALUES
|
||||
('3_parsedown', 'up', now()),
|
||||
('4_image_hosting', 'up', now()),
|
||||
('6_user_info_v2', 'up', now()),
|
||||
('8_group_v2', 'up', now()),
|
||||
('9_list_v2', 'up', now()),
|
||||
('14_sync_from_uoj.ac', 'up', now()),
|
||||
('16_list_v3', 'up', now()),
|
||||
('18_user_permissions', 'up', now()),
|
||||
('20_problem_difficulty', 'up', now()),
|
||||
('21_problem_difficulty', 'up', now());
|
||||
/*!40000 ALTER TABLE `upgrades` ENABLE KEYS */;
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
@ -60,15 +60,6 @@ services:
|
||||
volumes:
|
||||
- ./uoj_data/web/data:/var/uoj_data
|
||||
- ./uoj_data/web/storage:/opt/uoj/web/app/storage
|
||||
- ./.config.development.php:/opt/uoj/web/app/.config.php
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- UOJ_PROTOCOL=http
|
||||
- DATABASE_HOST=uoj-db
|
||||
- DATABASE_PASSWORD=root
|
||||
- JUDGER_SOCKET_PORT=2333
|
||||
- JUDGER_SOCKET_PASSWORD=_judger_socket_password_
|
||||
- SALT_0=salt_0
|
||||
- SALT_1=salt_1
|
||||
- SALT_2=salt_2
|
||||
- SALT_3=salt_3
|
||||
|
@ -43,15 +43,6 @@ services:
|
||||
volumes:
|
||||
- ./uoj_data/web/data:/var/uoj_data
|
||||
- ./uoj_data/web/storage:/opt/uoj/web/app/storage
|
||||
- ./.config.php:/opt/uoj/web/app/.config.php
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- UOJ_PROTOCOL=https
|
||||
- DATABASE_HOST=uoj-db
|
||||
- DATABASE_PASSWORD=root
|
||||
- JUDGER_SOCKET_PORT=2333
|
||||
- JUDGER_SOCKET_PASSWORD=_judger_socket_password_
|
||||
- SALT_0=salt_0
|
||||
- SALT_1=salt_1
|
||||
- SALT_2=salt_2
|
||||
- SALT_3=salt_3
|
||||
|
@ -20,21 +20,21 @@ return [
|
||||
'domain' => null,
|
||||
'main' => [
|
||||
'protocol' => 'http',
|
||||
'host' => '_httpHost_',
|
||||
'port' => '80/443'
|
||||
'host' => UOJContext::requestDomain(),
|
||||
'port' => '80/443',
|
||||
],
|
||||
'blog' => [
|
||||
'protocol' => 'http',
|
||||
'host' => '_httpHost_',
|
||||
'port' => '80/443'
|
||||
'host' => UOJContext::requestDomain(),
|
||||
'port' => '80/443',
|
||||
]
|
||||
],
|
||||
'security' => [
|
||||
'user' => [
|
||||
'client_salt' => 'salt0'
|
||||
'client_salt' => 'salt0',
|
||||
],
|
||||
'cookie' => [
|
||||
'checksum_salt' => ['salt1', 'salt2', 'salt3']
|
||||
'checksum_salt' => ['salt1', 'salt2', 'salt3'],
|
||||
],
|
||||
],
|
||||
'mail' => [
|
||||
@ -43,17 +43,17 @@ return [
|
||||
'password' => '_mail_noreply_password_',
|
||||
'host' => 'smtp.local_uoj.ac',
|
||||
'secure' => 'tls',
|
||||
'port' => 587
|
||||
'port' => 587,
|
||||
]
|
||||
],
|
||||
'judger' => [
|
||||
'socket' => [
|
||||
'port' => '233',
|
||||
'password' => '_judger_socket_password_'
|
||||
'password' => '_judger_socket_password_',
|
||||
]
|
||||
],
|
||||
'switch' => [
|
||||
'blog-domain-mode' => 3,
|
||||
'open-register' => false
|
||||
]
|
||||
'open-register' => false,
|
||||
],
|
||||
];
|
||||
|
@ -79,19 +79,20 @@ $time_form->succ_href = "/contests";
|
||||
$time_form->runAtServer();
|
||||
?>
|
||||
|
||||
<?php echoUOJPageHeader('添加比赛') ?>
|
||||
<?php echoUOJPageHeader(UOJLocale::get('contests::add new contest')) ?>
|
||||
|
||||
<div class="row">
|
||||
<!-- left col -->
|
||||
<div class="col-lg-9">
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">添加比赛</h1>
|
||||
<h1 class="card-title">
|
||||
<?= UOJLocale::get('contests::add new contest') ?>
|
||||
</h1>
|
||||
|
||||
<div class="w-full" style="max-width: 400px">
|
||||
<?php $time_form->printHTML(); ?>
|
||||
<?php $time_form->printHTML() ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,6 +102,7 @@ $time_form->runAtServer();
|
||||
<aside class="col-lg-3 mt-3 mt-lg-0">
|
||||
<?php uojIncludeView('sidebar') ?>
|
||||
</aside>
|
||||
<!-- end right col -->
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
105
web/app/controllers/app/html2markdown.php
Normal file
105
web/app/controllers/app/html2markdown.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php requireLib('bootstrap5') ?>
|
||||
|
||||
<?php echoUOJPageHeader(UOJLocale::get('html to markdown')) ?>
|
||||
|
||||
<h1>
|
||||
<?= UOJLocale::get('html to markdown') ?>
|
||||
</h1>
|
||||
|
||||
<style>
|
||||
#html,
|
||||
#markdown {
|
||||
font-family: Cascadia Mono, Ubuntu Mono, Roboto Mono, Jetbrains Mono, Fira Code, Consolas, '思源黑体 Regular', '思源宋体 Light', '宋体', 'Courier New', monospace;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
<div class="col">
|
||||
<textarea class="form-control" id="html" placeholder="input html here"></textarea>
|
||||
</div>
|
||||
<div class="col">
|
||||
<textarea data-no-autosize readonly class="form-control" id="markdown" placeholder="output markdown here" style="height: 100%"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent text-end">
|
||||
<a href="https://s2oj.github.io/#/user/apps/html2markdown" target="_blank">使用教程</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= HTML::js_src('/js/turndown.js') ?>
|
||||
<?= HTML::js_src('/js/turndown-plugin-gfm.js') ?>
|
||||
|
||||
<script>
|
||||
function mathjaxScriptBlockType(node) {
|
||||
if (node.nodeName !== 'SCRIPT') return null;
|
||||
|
||||
const a = node.getAttribute('type');
|
||||
if (!a || a.indexOf('math/tex') < 0) return null;
|
||||
|
||||
return a.indexOf('display') >= 0 ? 'block' : 'inline';
|
||||
}
|
||||
|
||||
var turndownService = new TurndownService({
|
||||
headingStyle: 'atx',
|
||||
hr: '---',
|
||||
bulletListMarker: '-',
|
||||
codeBlockStyle: 'fenced',
|
||||
fence: '```',
|
||||
emDelimiter: '_',
|
||||
strongDelimiter: '**',
|
||||
linkStyle: 'inlined',
|
||||
linkReferenceStyle: 'full',
|
||||
preformattedCode: false,
|
||||
});
|
||||
|
||||
turndownService.use(turndownPluginGfm.gfm);
|
||||
turndownService.addRule('mathjaxRendered', {
|
||||
filter: function(node) {
|
||||
return node.nodeName === 'SPAN' && node.getAttribute('class') === 'MathJax';
|
||||
},
|
||||
replacement: function(content) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
turndownService.addRule('mathjaxScriptInline', {
|
||||
filter: function(node) {
|
||||
return mathjaxScriptBlockType(node) === 'inline';
|
||||
},
|
||||
|
||||
escapeContent: function() {
|
||||
// We want the raw unescaped content since this is what Katex will need to render
|
||||
// If we escape, it will double the \\ in particular.
|
||||
return false;
|
||||
},
|
||||
|
||||
replacement: function(content, node, options) {
|
||||
return '$' + content + '$';
|
||||
}
|
||||
});
|
||||
turndownService.addRule('mathjaxScriptBlock', {
|
||||
filter: function(node) {
|
||||
return mathjaxScriptBlockType(node) === 'block';
|
||||
},
|
||||
|
||||
escapeContent: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
replacement: function(content, node, options) {
|
||||
return '$$\n' + content + '\n$$';
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#html').on('input', function() {
|
||||
$('#markdown').val(turndownService.turndown($('#html').val()));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
@ -466,6 +466,10 @@ $pag = new Paginator($pag_config);
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
});
|
||||
|
||||
var copy_url_toast = new bootstrap.Toast('#copy-url-toast', {
|
||||
delay: 2000
|
||||
});
|
@ -1,59 +1,123 @@
|
||||
<?php
|
||||
requirePHPLib('form');
|
||||
requirePHPLib('form');
|
||||
|
||||
$forgot_form = new UOJBs4Form('forgot');
|
||||
$forgot_form->addInput('username', 'text', '用户名', '',
|
||||
function($username, &$vdata) {
|
||||
if (!validateUsername($username)) {
|
||||
return '用户名不合法';
|
||||
}
|
||||
$vdata['user'] = UOJUser::query($username);
|
||||
if (!$vdata['user']) {
|
||||
return '该用户不存在';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
null
|
||||
);
|
||||
$forgot_form->handle = function(&$vdata) {
|
||||
$user = $vdata['user'];
|
||||
$password = $user["password"];
|
||||
use Gregwar\Captcha\PhraseBuilder;
|
||||
|
||||
$oj_name = UOJConfig::$data['profile']['oj-name'];
|
||||
$oj_name_short = UOJConfig::$data['profile']['oj-name-short'];
|
||||
$sufs = base64url_encode($user['username'] . "." . md5($user['username'] . "+" . $password));
|
||||
$url = HTML::url("/reset-password", array('params' => array('p' => $sufs)));
|
||||
$html = <<<EOD
|
||||
$forgot_form = new UOJBs4Form('forgot');
|
||||
$forgot_form->addInput(
|
||||
'username',
|
||||
'text',
|
||||
'用户名',
|
||||
'',
|
||||
function ($username, &$vdata) {
|
||||
if (!validateUsername($username)) {
|
||||
return '用户名不合法';
|
||||
}
|
||||
$vdata['user'] = UOJUser::query($username);
|
||||
if (!$vdata['user']) {
|
||||
return '该用户不存在';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
null
|
||||
);
|
||||
$forgot_form->appendHTML(<<<EOD
|
||||
<div id="div-captcha" class="form-group">
|
||||
<label for="input-captcha" class="col-sm-2 control-label">验证码</label>
|
||||
<div class="col-sm-3" style="max-width: 60%">
|
||||
<input type="text" class="form-control" id="input-captcha" name="captcha" placeholder="请输入验证码" maxlength="20" style="display: inline-block; width: 12em;" />
|
||||
<div style="display: inline-block; margin-left: 8px; position: relative; top: -2px; cursor: pointer;">
|
||||
<img id="captcha" src="" />
|
||||
</div>
|
||||
<span class="help-block" id="help-captcha" style="display: block"></span>
|
||||
</div>
|
||||
</div>
|
||||
EOD);
|
||||
$forgot_form->handle = function (&$vdata) {
|
||||
$user = $vdata['user'];
|
||||
$password = $user["password"];
|
||||
|
||||
if (!isset($_SESSION['phrase']) || !PhraseBuilder::comparePhrases($_SESSION['phrase'], $_POST['captcha'])) {
|
||||
becomeMsgPage('验证码错误!');
|
||||
}
|
||||
|
||||
if (!$user['email']) {
|
||||
becomeMsgPage('用户未填写邮件地址,请联系管理员重置!');
|
||||
}
|
||||
|
||||
$oj_name = UOJConfig::$data['profile']['oj-name'];
|
||||
$oj_name_short = UOJConfig::$data['profile']['oj-name-short'];
|
||||
$check_code = md5($user['username'] . "+" . $password . '+' . UOJTime::$time_now_str);
|
||||
$sufs = base64url_encode($user['username'] . "." . $check_code);
|
||||
$url = HTML::url("/reset-password", ['params' => ['p' => $sufs]]);
|
||||
$oj_url = HTML::url('/');
|
||||
$name = $user['username'];
|
||||
$remote_addr = UOJContext::remoteAddr();
|
||||
$http_x_forwarded_for = UOJContext::httpXForwardedFor();
|
||||
$user_agent = UOJContext::httpUserAgent();
|
||||
|
||||
if ($user['realname']) {
|
||||
$name .= ' (' . $user['realname'] . ')';
|
||||
}
|
||||
|
||||
$html = <<<EOD
|
||||
<base target="_blank" />
|
||||
|
||||
<p>{$user['username']}您好,</p>
|
||||
<p>您刚刚启用了{$oj_name_short}密码找回功能,请进入下面的链接重设您的密码:</p>
|
||||
<p><a href="$url">$url</a></p>
|
||||
<p>{$oj_name}</p>
|
||||
<p>{$name} 您好,</p>
|
||||
|
||||
<style type="text/css">
|
||||
body{font-size:14px;font-family:arial,verdana,sans-serif;line-height:1.666;padding:0;margin:0;overflow:auto;white-space:normal;word-wrap:break-word;min-height:100px}
|
||||
pre {white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}
|
||||
</style>
|
||||
<p>您最近告知我们需要重置您在 {$oj_name_short} 上账号的密码。请访问以下链接:<a href="{$url}">{$url}</a> (如果无法点击链接,请试着复制链接并粘贴至浏览器中打开。)</p>
|
||||
<p>如果您没有请求重置密码,则忽略此信息。该链接将在 72 小时后自动过期失效。</p>
|
||||
|
||||
<ul>
|
||||
<li><small>请求 IP: {$remote_addr} (转发来源: {$http_x_forwarded_for})</small></li>
|
||||
<li><small>用户代理: {$user_agent}</small></li>
|
||||
</ul>
|
||||
|
||||
<p>{$oj_name}</p>
|
||||
<p><a href="{$oj_url}">{$oj_url}</a></p>
|
||||
EOD;
|
||||
|
||||
$mailer = UOJMail::noreply();
|
||||
$mailer->addAddress($user['email'], $user['username']);
|
||||
$mailer->Subject = $oj_name_short."密码找回";
|
||||
$mailer->msgHTML($html);
|
||||
if (!$mailer->send()) {
|
||||
error_log($mailer->ErrorInfo);
|
||||
becomeMsgPage('<div class="text-center"><h2>邮件发送失败,请重试 <span class="glyphicon glyphicon-remove"></span></h2></div>');
|
||||
} else {
|
||||
becomeMsgPage('<div class="text-center"><h2>邮件发送成功 <span class="glyphicon glyphicon-ok"></span></h2></div>');
|
||||
}
|
||||
};
|
||||
$forgot_form->submit_button_config['align'] = 'offset';
|
||||
$mailer = UOJMail::noreply();
|
||||
$mailer->addAddress($user['email'], $user['username']);
|
||||
$mailer->Subject = $oj_name_short . " 密码找回";
|
||||
$mailer->msgHTML($html);
|
||||
if (!$mailer->send()) {
|
||||
error_log($mailer->ErrorInfo);
|
||||
becomeMsgPage('<div class="text-center"><h2>邮件发送失败,请重试!</h2></div>');
|
||||
} else {
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
'extra' => DB::json_set('extra', '$.reset_password_check_code', $check_code, '$.reset_password_time', UOJTime::$time_now_str),
|
||||
],
|
||||
"where", [
|
||||
"username" => $user['username'],
|
||||
],
|
||||
]);
|
||||
|
||||
$forgot_form->runAtServer();
|
||||
?>
|
||||
becomeMsgPage('<div class="text-center"><h2>邮件发送成功,请检查收件箱!</h2><span>如果邮件未出现在收件箱中,请检查垃圾箱。</span></div>');
|
||||
}
|
||||
};
|
||||
$forgot_form->submit_button_config['align'] = 'offset';
|
||||
|
||||
$forgot_form->runAtServer();
|
||||
?>
|
||||
<?php echoUOJPageHeader('找回密码') ?>
|
||||
<h2 class="page-header">找回密码</h2>
|
||||
<h4>请输入需要找回密码的用户名:</h4>
|
||||
<?php $forgot_form->printHTML(); ?>
|
||||
<script>
|
||||
function refreshCaptcha() {
|
||||
var timestamp = new Date().getTime();
|
||||
$("#captcha").attr("src", "/captcha" + '?' + timestamp);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
refreshCaptcha();
|
||||
|
||||
$("#captcha").click(function(e) {
|
||||
refreshCaptcha();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -49,7 +49,7 @@ UOJGroup::cur()->userCanView(Auth::user(), ['ensure' => true]);
|
||||
</h2>
|
||||
<?php if (UOJGroup::info('announcement')) : ?>
|
||||
<div class="text-break">
|
||||
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line(UOJGroup::info('announcement'))) ?>
|
||||
<?= HTML::purifier_inline()->purify(HTML::parsedown(['username_with_color' => true])->line(UOJGroup::info('announcement'))) ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="text-muted">
|
||||
|
@ -1,46 +0,0 @@
|
||||
<?php requireLib('bootstrap5') ?>
|
||||
|
||||
<?php echoUOJPageHeader(UOJLocale::get('html to markdown')) ?>
|
||||
|
||||
<h1>
|
||||
<?= UOJLocale::get('html to markdown') ?>
|
||||
</h1>
|
||||
|
||||
<style>
|
||||
#html,
|
||||
#markdown {
|
||||
font-family: Cascadia Mono, Ubuntu Mono, Roboto Mono, Jetbrains Mono, Fira Code, Consolas, '思源黑体 Regular', '思源宋体 Light', '宋体', 'Courier New', monospace;
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 row-cols-md-2">
|
||||
<div class="col">
|
||||
<textarea class="form-control" id="html" placeholder="input html here"></textarea>
|
||||
</div>
|
||||
<div class="col">
|
||||
<textarea data-no-autosize readonly class="form-control" id="markdown" placeholder="output markdown here" style="height: 100%"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent text-end">
|
||||
<a href="https://s2oj.github.io/#/user/apps/html2markdown" target="_blank">使用教程</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= HTML::js_src('/js/h2m.js') ?>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#html').on('input', function() {
|
||||
$('#markdown').val(h2m($('#html').val(), {
|
||||
converter: 'Gfm'
|
||||
}));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
@ -318,7 +318,7 @@ EOD);
|
||||
echo HTML::tag_begin('tr');
|
||||
echo HTML::tag('td', ['class' => 'text-center'], $problem->info['id']);
|
||||
echo HTML::tag_begin('td');
|
||||
echo $problem->getLink();
|
||||
echo $problem->getLink(['with' => 'none']);
|
||||
if ($problem->info['is_hidden']) {
|
||||
echo ' <span class="badge text-bg-danger"><i class="bi bi-eye-slash-fill"></i> ', UOJLocale::get('hidden'), '</span> ';
|
||||
}
|
||||
|
@ -9,16 +9,28 @@ Auth::check() || redirectToLogin();
|
||||
UOJUser::checkPermission(Auth::user(), 'lists.view') || UOJResponse::page403();
|
||||
|
||||
if (UOJList::userCanCreateList(Auth::user())) {
|
||||
$new_list_form = new UOJBs4Form('new_list');
|
||||
$new_list_form = new UOJForm('new_list');
|
||||
$new_list_form->handle = function () {
|
||||
DB::insert("insert into lists (title, is_hidden) values ('未命名题单', 1)");
|
||||
$id = DB::insert_id();
|
||||
DB::insert("insert into lists_contents (id, content, content_md) values ($id, '', '')");
|
||||
DB::insert([
|
||||
"insert into lists",
|
||||
DB::bracketed_fields(['title', 'is_hidden']),
|
||||
"values",
|
||||
DB::tuple(['未命名题单', 1]),
|
||||
]);
|
||||
$list_id = DB::insert_id();
|
||||
DB::insert([
|
||||
"insert into lists_contents",
|
||||
DB::bracketed_fields(['id', 'content', 'content_md']),
|
||||
"values",
|
||||
DB::tuple([$list_id, '', '']),
|
||||
]);
|
||||
redirectTo("/list/{$list_id}");
|
||||
die();
|
||||
};
|
||||
$new_list_form->submit_button_config['align'] = 'right';
|
||||
$new_list_form->submit_button_config['class_str'] = 'btn btn-primary';
|
||||
$new_list_form->submit_button_config['text'] = UOJLocale::get('problems::add new list');
|
||||
$new_list_form->submit_button_config['smart_confirm'] = '';
|
||||
$new_list_form->config['submit_container']['class'] = 'text-end';
|
||||
$new_list_form->config['submit_button']['class'] = 'btn btn-primary';
|
||||
$new_list_form->config['submit_button']['text'] = UOJLocale::get('problems::add new list');
|
||||
$new_list_form->config['confirm']['smart'] = true;
|
||||
$new_list_form->runAtServer();
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ if (UOJContest::cur()) {
|
||||
|
||||
$submission_requirement = UOJProblem::cur()->getSubmissionRequirement();
|
||||
$custom_test_requirement = UOJProblem::cur()->getCustomTestRequirement();
|
||||
$custom_test_enabled = false; // $custom_test_requirement && $pre_submit_check_ret === true;
|
||||
$custom_test_enabled = $custom_test_requirement && $pre_submit_check_ret === true;
|
||||
|
||||
function handleUpload($zip_file_name, $content, $tot_size) {
|
||||
global $is_participating;
|
||||
@ -193,7 +193,7 @@ if ($pre_submit_check_ret === true && !$no_more_submission) {
|
||||
'FS::randomAvailableSubmissionFileName',
|
||||
'handleUpload'
|
||||
);
|
||||
$zip_answer_form->extra_validators[] = $submission_extra_validator;
|
||||
$zip_answer_form->extra_validator = $submission_extra_validator;
|
||||
$zip_answer_form->succ_href = $is_participating ? '/contest/' . UOJContest::info('id') . '/submissions' : '/submissions';
|
||||
$zip_answer_form->runAtServer();
|
||||
}
|
||||
@ -385,46 +385,64 @@ if (UOJContest::cur()) {
|
||||
<div class="card mb-2">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">上传者</span>
|
||||
<span><?= UOJProblem::cur()->getUploaderLink() ?></span>
|
||||
<span class="flex-shrink-0">
|
||||
<?= UOJLocale::get('problems::uploader') ?>
|
||||
</span>
|
||||
<span>
|
||||
<?= UOJProblem::cur()->getUploaderLink() ?>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">难度</span>
|
||||
<span><?= UOJProblem::cur()->getDifficultyHTML() ?></span>
|
||||
</li>
|
||||
<?php if (Auth::check()) : ?>
|
||||
<?php if (!UOJContest::cur() || UOJContest::cur()->progress() >= CONTEST_FINISHED) : ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">历史分数</span>
|
||||
<?php $his_score = DB::selectSingle(["select max(score)", "from submissions", "where", ["problem_id" => UOJProblem::info('id'), "submitter" => Auth::id()]]) ?>
|
||||
<span class="flex-shrink-0">
|
||||
<?= UOJLocale::get('problems::difficulty') ?>
|
||||
</span>
|
||||
<span>
|
||||
<?= UOJProblem::cur()->getDifficultyHTML() ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php if (Auth::check()) : ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">
|
||||
<?= UOJLocale::get('problems::historical score') ?>
|
||||
</span>
|
||||
<?php $his_score = DB::selectSingle(["select max(score)", "from submissions", "where", ["problem_id" => UOJProblem::info('id'), "submitter" => Auth::id()]]) ?>
|
||||
|
||||
<a class="<?= is_null($his_score) ? '' : 'uoj-score' ?>" href="<?= HTML::url('/submissions', ['params' => ['problem_id' => UOJProblem::info('id'), 'submitter' => Auth::id()]]) ?>">
|
||||
<?= is_null($his_score) ? '无' : $his_score ?>
|
||||
</a>
|
||||
<a class="<?= is_null($his_score) ? '' : 'uoj-score' ?>" href="<?= HTML::url('/submissions', ['params' => ['problem_id' => UOJProblem::info('id'), 'submitter' => Auth::id()]]) ?>">
|
||||
<?= is_null($his_score) ? '无' : $his_score ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">
|
||||
<?= UOJLocale::get('problems::tags') ?>
|
||||
</span>
|
||||
<span>
|
||||
<?php if (UOJProblem::info('is_hidden')) : ?>
|
||||
<a href="<?= HTML::url('/problems', ['params' => ['is_hidden' => 'on']]) ?>">
|
||||
<span class="badge text-bg-danger">
|
||||
<i class="bi bi-eye-slash-fill"></i>
|
||||
<?= UOJLocale::get('hidden') ?>
|
||||
</span>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<?php foreach (UOJProblem::cur()->queryTags() as $tag) : ?>
|
||||
<?= HTML::tag(
|
||||
'a',
|
||||
['class' => 'uoj-problem-tag'],
|
||||
HTML::tag('span', ['class' => 'badge bg-secondary'], HTML::escape($tag))
|
||||
) ?>
|
||||
<?php endforeach ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">标签</span>
|
||||
<span>
|
||||
<?php if (UOJProblem::info('is_hidden')) : ?>
|
||||
<a href="<?= HTML::url('/problems', ['params' => ['is_hidden' => 'on']]) ?>">
|
||||
<span class="badge text-bg-danger">
|
||||
<i class="bi bi-eye-slash-fill"></i>
|
||||
<?= UOJLocale::get('hidden') ?>
|
||||
</span>
|
||||
</a>
|
||||
<?php endif ?>
|
||||
<?php foreach (UOJProblem::cur()->queryTags() as $tag) : ?>
|
||||
<?= HTML::tag(
|
||||
'a',
|
||||
['class' => 'uoj-problem-tag'],
|
||||
HTML::tag('span', ['class' => 'badge bg-secondary'], HTML::escape($tag))
|
||||
) ?>
|
||||
<?php endforeach ?>
|
||||
<span class="flex-shrink-0">
|
||||
<?= UOJLocale::get('appraisal') ?>
|
||||
</span>
|
||||
<span>
|
||||
<?= UOJProblem::cur()->getZanBlock() ?>
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<span class="flex-shrink-0">评价</span>
|
||||
<span><?= UOJProblem::cur()->getZanBlock() ?></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -73,6 +73,9 @@ EOD;
|
||||
])
|
||||
]);
|
||||
dataNewProblem($id);
|
||||
|
||||
redirectTo("/problem/{$id}/manage/statement");
|
||||
die();
|
||||
};
|
||||
$new_problem_form->submit_button_config['align'] = 'right';
|
||||
$new_problem_form->submit_button_config['class_str'] = 'btn btn-primary';
|
||||
|
@ -101,6 +101,29 @@ if (UOJProblem::cur()->userCanManage(Auth::user()) || UOJProblem::cur()->userPer
|
||||
]);
|
||||
};
|
||||
$add_new_solution_form->runAtServer();
|
||||
|
||||
if (UOJUser::checkPermission(Auth::user(), 'blogs.create')) {
|
||||
$quick_add_new_solution_form = new UOJForm('quick_add_new_solution');
|
||||
$quick_add_new_solution_form->config['submit_container']['class'] = '';
|
||||
$quick_add_new_solution_form->config['submit_button']['class'] = 'btn btn-link text-decoration-none p-0';
|
||||
$quick_add_new_solution_form->config['submit_button']['text'] = '快速新建文章';
|
||||
$quick_add_new_solution_form->handle = function () {
|
||||
DB::insert([
|
||||
"insert into blogs",
|
||||
"(title, content, content_md, poster, is_hidden, post_time, active_time)",
|
||||
"values", DB::tuple([
|
||||
'【题解】' . UOJProblem::cur()->getTitle(), '', '',
|
||||
Auth::id(), false, DB::now(), DB::now()
|
||||
])
|
||||
]);
|
||||
|
||||
$blog_id = DB::insert_id();
|
||||
|
||||
redirectTo(HTML::blog_url(Auth::id(), "/post/{$blog_id}/write"));
|
||||
die();
|
||||
};
|
||||
$quick_add_new_solution_form->runAtServer();
|
||||
}
|
||||
}
|
||||
|
||||
$pag_config = [
|
||||
@ -262,18 +285,16 @@ $pag = new Paginator($pag_config);
|
||||
您当前无法为本题新增题解。
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<a target="_blank" class="text-decoration-none" href="<?= HTML::blog_url(Auth::id(), '/post/new/write?title=' . urlencode('【题解】#' . UOJProblem::info('id') . '. ' . UOJProblem::info('title')) . '&is_hidden=0') ?>">
|
||||
快速新建文章
|
||||
</a>
|
||||
<div class="small text-muted mt-1">发布文章后,请返回本页输入博客 ID</div>
|
||||
</div>
|
||||
<?php if (isset($quick_add_new_solution_form)) : ?>
|
||||
<div class="card-footer bg-transparent">
|
||||
<?php $quick_add_new_solution_form->printHTML() ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php uojIncludeView('sidebar'); ?>
|
||||
<!-- End right col -->
|
||||
<?php uojIncludeView('sidebar') ?>
|
||||
</aside>
|
||||
|
||||
<!-- end right col -->
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -174,6 +174,85 @@ $difficulty_form->runAtServer();
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header fw-bold">
|
||||
标签填充
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<script>
|
||||
function fillTag(tags) {
|
||||
if (typeof tags === 'string') tags = [tags];
|
||||
|
||||
tags = tags.map(tag => tag.trim()).filter(Boolean);
|
||||
|
||||
var originalTags = $('#input-problem_tags')
|
||||
.val()
|
||||
.replace(/,/g, ',')
|
||||
.split(',')
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean);
|
||||
var newTagsSet = new Set(originalTags.concat(tags));
|
||||
|
||||
$('#input-problem_tags').val(Array.from(newTagsSet.values()).join(', '));
|
||||
$('#input-problem_tags').trigger('input');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="row row-cols-4 row-cols-lg-2 g-2">
|
||||
<?php foreach (UOJProblem::$categories as $category => $tags) : ?>
|
||||
<?php $category_id = uniqid('category-'); ?>
|
||||
|
||||
<div class="d-inline-block" id="category-container-<?= $category_id ?>">
|
||||
<button id="category-button-<?= $category_id ?>" class="btn btn-sm btn-light w-100" type="button"><?= $category ?></button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
bootstrap.Popover.jQueryInterface.call($('#category-button-<?= $category_id ?>'), {
|
||||
container: $('#category-container-<?= $category_id ?>'),
|
||||
html: true,
|
||||
placement: 'left',
|
||||
animation: false,
|
||||
trigger: 'manual',
|
||||
fallbackPlacements: ['bottom', 'right'],
|
||||
content: [
|
||||
<?php foreach ($tags as $tag) : ?> '<?= $tag ?>', <?php endforeach ?>
|
||||
].map(tag => ('<button class="btn btn-sm btn-light d-inline-block mr-1 mb-1" onclick="fillTag([\'<?= $category ?>\', \'' + tag + '\'])">' + tag + '</button>')).join(' '),
|
||||
sanitizeFn(content) {
|
||||
return content;
|
||||
},
|
||||
}).on("mouseenter", function() {
|
||||
var _this = this;
|
||||
|
||||
$(this).popover("show");
|
||||
$(this).siblings(".popover").on("mouseleave", function() {
|
||||
$(_this).popover('hide');
|
||||
});
|
||||
}).on("mouseleave", function() {
|
||||
var _this = this;
|
||||
|
||||
var check_popover_status = function() {
|
||||
setTimeout(function() {
|
||||
if (!$(".popover:hover").length) {
|
||||
$(_this).popover("hide")
|
||||
} else {
|
||||
check_popover_status();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
check_popover_status();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-muted small bg-transparent">
|
||||
将鼠标悬浮至主分类上,点击弹出框中的对应标签即可将其填充至题目标签中。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header fw-bold">
|
||||
题目难度
|
||||
@ -183,7 +262,6 @@ $difficulty_form->runAtServer();
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -1,39 +1,55 @@
|
||||
<?php
|
||||
if (!isset($_GET['p'])) {
|
||||
become404Page();
|
||||
}
|
||||
function resetPassword() {
|
||||
list($username, $check_code) = explode('.', base64url_decode($_GET['p']));
|
||||
if (!isset($_POST['newPW']) || !validatePassword($_POST['newPW'])) {
|
||||
return '操作失败,无效密码';
|
||||
}
|
||||
if (!isset($username) || !validateUsername($username)) {
|
||||
return '不明错误';
|
||||
}
|
||||
if (!isset($check_code)) {
|
||||
return '不明错误';
|
||||
}
|
||||
if (!isset($_GET['p'])) {
|
||||
become404Page();
|
||||
}
|
||||
|
||||
$newPW = $_POST['newPW'];
|
||||
$user = UOJUser::query($username);
|
||||
if ($user == null) {
|
||||
return '不明错误';
|
||||
}
|
||||
if ($check_code !== md5($user['username'] . '+' . $user['password'])) {
|
||||
return '不明错误';
|
||||
}
|
||||
$newPW = getPasswordToStore($newPW, $user['username']);
|
||||
DB::update("update user_info set password = '$newPW' where username = '{$user['username']}'");
|
||||
return 'ok';
|
||||
list($username, $check_code) = explode('.', base64url_decode($_GET['p']));
|
||||
$user = UOJUser::query($username);
|
||||
|
||||
if (!$user) become404Page();
|
||||
if (!isset($check_code) || strlen($check_code) != 32) become404Page();
|
||||
|
||||
$extra = UOJUser::getExtra($user);
|
||||
|
||||
if ($check_code !== $extra['reset_password_check_code']) {
|
||||
become404Page();
|
||||
}
|
||||
|
||||
if (UOJTime::str2time($extra['reset_password_time'])->add(new DateInterval('P3D')) < UOJTime::$time_now) {
|
||||
becomeMsgPage('链接已过期');
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
global $user;
|
||||
|
||||
if (!isset($_POST['newPW']) || !validatePassword($_POST['newPW'])) {
|
||||
return '操作失败,无效密码';
|
||||
}
|
||||
if (isset($_POST['reset'])) {
|
||||
die(resetPassword());
|
||||
}
|
||||
?>
|
||||
|
||||
$newPW = $_POST['newPW'];
|
||||
$newPW = getPasswordToStore($newPW, $user['username']);
|
||||
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"password" => $newPW,
|
||||
"extra" => DB::json_remove('extra', '$.reset_password_check_code', '$.reset_password_time'),
|
||||
],
|
||||
"where", [
|
||||
"username" => $user['username'],
|
||||
],
|
||||
]);
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
if (isset($_POST['reset'])) {
|
||||
die(resetPassword());
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
$REQUIRE_LIB['dialog'] = '';
|
||||
$REQUIRE_LIB['md5'] = '';
|
||||
?>
|
||||
$REQUIRE_LIB['dialog'] = '';
|
||||
$REQUIRE_LIB['md5'] = '';
|
||||
?>
|
||||
<?php echoUOJPageHeader('更改密码') ?>
|
||||
<h2 class="page-header">更改密码</h2>
|
||||
<form id="form-reset" class="form-horizontal">
|
||||
@ -44,60 +60,60 @@
|
||||
<input type="password" class="form-control top-buffer-sm" id="input-confirm_password" placeholder="再次输入新密码" maxlength="20" />
|
||||
<span class="help-block" id="help-password"></span>
|
||||
</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">提交</button>
|
||||
</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">提交</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
function validateResetPwPost() {
|
||||
var ok = true;
|
||||
ok &= getFormErrorAndShowHelp('password', validateSettingPassword);
|
||||
return ok;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('#form-reset').submit(function(e) {
|
||||
if (!validateResetPwPost()) {
|
||||
return false;
|
||||
}
|
||||
$.post(<?= json_encode($_SERVER['REQUEST_URI']) ?>, {
|
||||
reset : '',
|
||||
newPW : md5($('#input-password').val(), "<?= getPasswordClientSalt() ?>")
|
||||
}, function(res) {
|
||||
if (res == 'ok') {
|
||||
BootstrapDialog.show({
|
||||
title : '提示',
|
||||
message : '密码更改成功',
|
||||
type : BootstrapDialog.TYPE_SUCCESS,
|
||||
buttons: [{
|
||||
label: '好的',
|
||||
action: function(dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}],
|
||||
onhidden : function(dialog) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
BootstrapDialog.show({
|
||||
title : '提示',
|
||||
message : res,
|
||||
type : BootstrapDialog.TYPE_DANGER,
|
||||
buttons: [{
|
||||
function validateResetPwPost() {
|
||||
var ok = true;
|
||||
ok &= getFormErrorAndShowHelp('password', validateSettingPassword);
|
||||
return ok;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('#form-reset').submit(function(e) {
|
||||
if (!validateResetPwPost()) {
|
||||
return false;
|
||||
}
|
||||
$.post(<?= json_encode($_SERVER['REQUEST_URI']) ?>, {
|
||||
reset: '',
|
||||
newPW: md5($('#input-password').val(), "<?= getPasswordClientSalt() ?>")
|
||||
}, function(res) {
|
||||
if (res == 'ok') {
|
||||
BootstrapDialog.show({
|
||||
title: '提示',
|
||||
message: '密码更改成功',
|
||||
type: BootstrapDialog.TYPE_SUCCESS,
|
||||
buttons: [{
|
||||
label: '好的',
|
||||
action: function(dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
}],
|
||||
onhidden: function(dialog) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
BootstrapDialog.show({
|
||||
title: '提示',
|
||||
message: res,
|
||||
type: BootstrapDialog.TYPE_DANGER,
|
||||
buttons: [{
|
||||
label: '好的',
|
||||
action: function(dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}]
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -201,7 +201,9 @@ $comments_pag = new Paginator([
|
||||
"order by id"
|
||||
]);
|
||||
foreach ($replies as $idx => $reply) {
|
||||
$replies[$idx]['poster_realname'] = UOJUser::query($reply['poster'])['realname'];
|
||||
$reply_user = UOJUser::query($reply['poster']);
|
||||
$replies[$idx]['poster_realname'] = $reply_user['realname'];
|
||||
$replies[$idx]['poster_username_color'] = UOJUser::getUserColor($reply_user);
|
||||
$replies[$idx]['content'] = getCommentContentToDisplay($reply);
|
||||
}
|
||||
$replies_json = json_encode($replies);
|
||||
|
@ -32,7 +32,7 @@ $header_row .= '<th style="width:35em">' . UOJLocale::get('contests::problem sel
|
||||
$header_row .= '<th style="width:35em">' . UOJLocale::get('contests::contest self review') . '</th>';
|
||||
$header_row .= '</tr>';
|
||||
|
||||
$parsedown = HTML::parsedown();
|
||||
$parsedown = HTML::parsedown(['username_with_color' => true]);
|
||||
$purifier = HTML::purifier_inline();
|
||||
|
||||
$print_row = function ($row) use ($parsedown, $purifier) {
|
||||
|
@ -168,7 +168,9 @@ if ($perm['manager_view']) {
|
||||
?>
|
||||
|
||||
<?php if ($perm['content'] || $perm['manager_view']) : ?>
|
||||
<?php UOJSubmission::cur()->echoContent() ?>
|
||||
<div class="copy-button-container">
|
||||
<?php UOJSubmission::cur()->echoContent() ?>
|
||||
</div>
|
||||
|
||||
<?php if (isset($hack_form)) : ?>
|
||||
<p class="text-center">
|
||||
@ -221,17 +223,17 @@ if (UOJSubmission::cur()->hasJudged()) {
|
||||
?>
|
||||
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
<?php if (isset($minor_rejudge_form)) : ?>
|
||||
<?php $minor_rejudge_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
<?php if (isset($minor_rejudge_form)) : ?>
|
||||
<?php $minor_rejudge_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($rejudge_form)) : ?>
|
||||
<?php $rejudge_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
<?php if (isset($rejudge_form)) : ?>
|
||||
<?php $rejudge_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($delete_form)) : ?>
|
||||
<?php $delete_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
<?php if (isset($delete_form)) : ?>
|
||||
<?php $delete_form->printHTML() ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -516,7 +516,7 @@ EOD);
|
||||
'new_tmp_expiration_time',
|
||||
'text',
|
||||
'过期时间',
|
||||
(new DateTime())->add(new DateInterval('P7D'))->format('Y-m-d H:i:s'),
|
||||
UOJTime::time2str((new DateTime())->add(new DateInterval('P7D'))->setTime(0, 0, 0)),
|
||||
function ($str, &$vdata) {
|
||||
try {
|
||||
$vdata['expiration_time'] = new DateTime($str);
|
||||
@ -657,6 +657,7 @@ EOD);
|
||||
);
|
||||
$change_usergroup_form->addVSelect('op_type', [
|
||||
'banneduser' => '设为封禁用户',
|
||||
'tempuser' => '设为临时用户',
|
||||
'normaluser' => '设为普通用户',
|
||||
'superuser' => '设为超级用户',
|
||||
], '操作类型', '');
|
||||
@ -666,15 +667,59 @@ EOD);
|
||||
|
||||
switch ($_POST['op_type']) {
|
||||
case 'banneduser':
|
||||
DB::update("update user_info set usergroup = 'B', usertype = 'banned' where username = '{$username}'");
|
||||
$usergroup = '被封禁的用户';
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"usergroup" => "B",
|
||||
"usertype" => "banned",
|
||||
"expiration_time" => null,
|
||||
],
|
||||
"where", [
|
||||
"username" => $username,
|
||||
],
|
||||
]);
|
||||
$usergroup = '被封禁的用户,该用户将无法再次登录系统';
|
||||
break;
|
||||
case 'tempuser':
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"usergroup" => "T",
|
||||
"usertype" => "student",
|
||||
"expiration_time" => DB::now(),
|
||||
],
|
||||
"where", [
|
||||
"username" => $username,
|
||||
],
|
||||
]);
|
||||
$usergroup = '临时用户,请前往个人信息编辑页面修改过期时间';
|
||||
break;
|
||||
case 'normaluser':
|
||||
DB::update("update user_info set usergroup = 'U', usertype = 'student' where username = '{$username}'");
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"usergroup" => "U",
|
||||
"usertype" => "student",
|
||||
"expiration_time" => null,
|
||||
],
|
||||
"where", [
|
||||
"username" => $username,
|
||||
],
|
||||
]);
|
||||
$usergroup = '普通用户';
|
||||
break;
|
||||
case 'superuser':
|
||||
DB::update("update user_info set usergroup = 'S', usertype = 'student' where username = '{$username}'");
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"usergroup" => "S",
|
||||
"usertype" => "student",
|
||||
"expiration_time" => null,
|
||||
],
|
||||
"where", [
|
||||
"username" => $username,
|
||||
],
|
||||
]);
|
||||
$usergroup = '超级用户';
|
||||
break;
|
||||
}
|
||||
@ -1336,7 +1381,7 @@ EOD);
|
||||
EOD,
|
||||
function ($row) {
|
||||
echo '<tr>';
|
||||
echo '<td>', '<span class="uoj-username" data-realname="', HTML::escape($row['realname']), '">', $row['username'], '</span>', '</td>';
|
||||
echo '<td>', UOJUser::getLink($row), '</td>';
|
||||
echo '<td>', HTML::escape($row['school']), '</td>';
|
||||
echo '<td>';
|
||||
switch ($row['usergroup']) {
|
||||
@ -1429,7 +1474,8 @@ EOD);
|
||||
<h5>注意事项</h5>
|
||||
<ul class="mb-0">
|
||||
<li>用户被封禁后将不能再次登录系统。</li>
|
||||
<li>将当前用户移除权限后将无法再次访问本页面。</li>
|
||||
<li>将用户修改为临时用户后,请前往个人信息编辑页面修改过期时间。</li>
|
||||
<li>将当前用户移除管理权限后将无法再次访问本页面。</li>
|
||||
<li>在修改用户类别前请仔细核对用户名以免产生不必要的麻烦。</li>
|
||||
<li>如需为用户设置题目上传者、题目管理员等权限,请前往对应用户的个人资料编辑页面,点击「特权」选项卡修改。</li>
|
||||
</ul>
|
||||
|
@ -266,6 +266,43 @@ EOD);
|
||||
},
|
||||
]
|
||||
);
|
||||
if ($user['usergroup'] == 'B') {
|
||||
$update_profile_form->appendHTML(<<<EOD
|
||||
<div class="mb-3">
|
||||
<label for="input-username_color" class="form-label">用户名颜色</label>
|
||||
<input type="text" class="form-control" id="input-username_color" aria-describedby="help-username_color" value="棕色 - #996600" disabled>
|
||||
<div id="help-username_color" class="form-text">被封禁的用户无法修改用户名颜色。</div>
|
||||
</div>
|
||||
EOD);
|
||||
} else if ($user['usergroup'] == 'T') {
|
||||
$update_profile_form->appendHTML(<<<EOD
|
||||
<div class="mb-3">
|
||||
<label for="input-username_color" class="form-label">用户名颜色</label>
|
||||
<input type="text" class="form-control" id="input-username_color" aria-describedby="help-username_color" value="灰色 - #707070" disabled>
|
||||
<div id="help-username_color" class="form-text">临时用户无法修改用户名颜色。</div>
|
||||
</div>
|
||||
EOD);
|
||||
} else {
|
||||
$additional_colors = [];
|
||||
|
||||
if (isSuperUser($user)) {
|
||||
$additional_colors['#9d3dcf'] = '紫色 - #9d3dcf';
|
||||
}
|
||||
|
||||
$update_profile_form->addSelect('username_color', [
|
||||
'div_class' => 'mb-3',
|
||||
'label' => '用户名颜色',
|
||||
'default_value' => $extra['username_color'],
|
||||
'options' => $additional_colors + [
|
||||
'#0d6efd' => '蓝色 - #0d6efd',
|
||||
'#2da44e' => '绿色 - #2da44e',
|
||||
'#e85aad' => '粉色 - #e85aad',
|
||||
'#f32a38' => '红色 - #f32a38',
|
||||
'#f57c00' => '橙色 - #f57c00',
|
||||
'#00acc1' => '青色 - #00acc1',
|
||||
],
|
||||
]);
|
||||
}
|
||||
$update_profile_form->handle = function (&$vdata) use ($user) {
|
||||
$data = [
|
||||
'email' => $vdata['email'],
|
||||
@ -301,7 +338,9 @@ EOD);
|
||||
'$.social.codeforces',
|
||||
$vdata['codeforces'],
|
||||
'$.social.website',
|
||||
$vdata['website']
|
||||
$vdata['website'],
|
||||
'$.username_color',
|
||||
$_POST['username_color']
|
||||
),
|
||||
],
|
||||
"where", ["username" => $user['username']]
|
||||
@ -376,6 +415,17 @@ EOD);
|
||||
}
|
||||
$update_user_permissions_form->appendHTML(HTML::tag('span', [], UOJLocale::get('user::user group')));
|
||||
$update_user_permissions_form->appendHTML(HTML::tag('span', ['class' => 'd-inline-block ms-3'], $type_text));
|
||||
$update_user_permissions_form->addSelect('user_type', [
|
||||
'label' => '账号类型',
|
||||
'options' => [
|
||||
'student' => '学生',
|
||||
'teacher' => '老师',
|
||||
'system' => '系统',
|
||||
],
|
||||
'div_class' => 'my-3 row gy-2 gx-3 align-items-center',
|
||||
'label_class' => 'form-label col-auto',
|
||||
'select_class' => 'form-select w-auto col-auto',
|
||||
]);
|
||||
$update_user_permissions_form->appendHTML(HTML::tag('h3', ['class' => 'h5 mt-3'], '题目'));
|
||||
$update_user_permissions_form->addCheckbox('problems__view', [
|
||||
'checked' => $extra['permissions']['problems']['view'],
|
||||
@ -707,6 +757,7 @@ EOD);
|
||||
DB::update([
|
||||
"update user_info",
|
||||
"set", [
|
||||
"usertype" => $_POST['user_type'],
|
||||
"extra" => json_encode($extra),
|
||||
],
|
||||
"where", [
|
||||
|
@ -87,14 +87,23 @@ function queryContestData($contest, $config = []) {
|
||||
$people = [];
|
||||
|
||||
if ($contest['extra_config']['individual_or_team'] == 'individual') {
|
||||
$people = DB::selectAll([
|
||||
"select contests_registrants.username, user_info.realname from contests_registrants",
|
||||
$res = DB::selectAll([
|
||||
"select contests_registrants.username, user_info.realname, user_info.extra, user_info.usergroup from contests_registrants",
|
||||
"inner join user_info on contests_registrants.username = user_info.username",
|
||||
"where", [
|
||||
"contest_id" => $contest['id'],
|
||||
"has_participated" => 1
|
||||
]
|
||||
], DB::NUM);
|
||||
foreach ($res as $row) {
|
||||
$extra = json_decode($row[2], true);
|
||||
$people[] = [
|
||||
$row[0],
|
||||
trim(HTML::escape($row[1])),
|
||||
null,
|
||||
UOJUser::getUserColor2($row[3], $extra['username_color']),
|
||||
];
|
||||
}
|
||||
} elseif ($contest['extra_config']['individual_or_team'] == 'team') {
|
||||
$res = DB::selectAll([
|
||||
"select user_info.username, null, user_info.extra from contests_registrants, user_info",
|
||||
@ -106,11 +115,15 @@ function queryContestData($contest, $config = []) {
|
||||
], DB::NUM);
|
||||
foreach ($res as $row) {
|
||||
$extra = json_decode($row[2], true);
|
||||
$row[2] = [
|
||||
'team_name' => $extra['acm']['team_name'],
|
||||
'members' => $extra['acm']['members']
|
||||
$people[] = [
|
||||
$row[0],
|
||||
null,
|
||||
[
|
||||
'team_name' => $extra['acm']['team_name'],
|
||||
'members' => $extra['acm']['members'],
|
||||
],
|
||||
null,
|
||||
];
|
||||
$people[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,7 +386,7 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $cfg = [])
|
||||
}
|
||||
}
|
||||
|
||||
// standings: rank => score, penalty, [username, realname], virtual_rank, ?review
|
||||
// standings: rank => score, penalty, [username, realname, null|array, null|color], virtual_rank, ?review
|
||||
$standings = [];
|
||||
foreach ($contest_data['people'] as $person) {
|
||||
$cur = array(0, 0, $person);
|
||||
|
@ -735,7 +735,7 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
|
||||
$stat = $zip_file->statName($req['file_name']);
|
||||
|
||||
if ($req['type'] == 'source code') {
|
||||
$max_size = isset($req['size']) ? (int)$req['size'] : 50;
|
||||
$max_size = isset($req['size']) ? (int)$req['size'] : 100;
|
||||
if ($stat['size'] > $max_size * 1024) {
|
||||
$zip_file->close();
|
||||
unlink(UOJContext::storagePath() . $zip_file_name);
|
||||
|
@ -1009,62 +1009,6 @@ function echoUOJPageFooter($config = array()) {
|
||||
uojIncludeView('page-footer', $config);
|
||||
}
|
||||
|
||||
function echoRanklist($config = []) {
|
||||
$header_row = '';
|
||||
$header_row .= '<tr>';
|
||||
$header_row .= '<th style="width: 5em;">#</th>';
|
||||
$header_row .= '<th style="width: 14em;">' . UOJLocale::get('username') . '</th>';
|
||||
$header_row .= '<th style="width: 50em;">' . UOJLocale::get('motto') . '</th>';
|
||||
$header_row .= '<th style="width: 5em;">' . UOJLocale::get('solved') . '</th>';
|
||||
$header_row .= '</tr>';
|
||||
|
||||
$parsedown = HTML::parsedown();
|
||||
$purifier = HTML::purifier_inline();
|
||||
$users = [];
|
||||
$print_row = function ($user, $now_cnt) use (&$users, $config, $purifier, $parsedown) {
|
||||
if (!$users) {
|
||||
if ($now_cnt == 1) {
|
||||
$rank = 1;
|
||||
} else {
|
||||
$rank = DB::selectCount("select count(*) from (select b.username as username, count(*) as accepted from best_ac_submissions a inner join user_info b on a.submitter = b.username group by username) as derived where accepted > {$user['ac_num']}") + 1;
|
||||
}
|
||||
} else {
|
||||
$rank = $now_cnt;
|
||||
}
|
||||
|
||||
$user['rank'] = $rank;
|
||||
|
||||
echo '<tr>';
|
||||
echo '<td>' . $user['rank'] . '</td>';
|
||||
echo '<td>' . UOJUser::getLink($user['username']) . '</td>';
|
||||
echo "<td>";
|
||||
echo $purifier->purify($parsedown->line($user['motto']));
|
||||
echo "</td>";
|
||||
echo '<td>' . $user['ac_num'] . '</td>';
|
||||
echo '</tr>';
|
||||
|
||||
$users[] = $user;
|
||||
};
|
||||
|
||||
$from = 'user_info';
|
||||
$col_names = ['user_info.username as username', 'ac_num', 'motto'];
|
||||
$cond = '1';
|
||||
$tail = 'group by user_info.username order by ac_num desc, user_info.username asc';
|
||||
|
||||
if (isset($config['group_id'])) {
|
||||
$group_id = $config['group_id'];
|
||||
$from = "user_info inner join groups_users on (user_info.username = groups_users.username and groups_users.group_id = {$group_id})";
|
||||
$config['pagination_cond'] = "group_id = {$group_id}";
|
||||
}
|
||||
|
||||
if (isset($config['top10'])) {
|
||||
$tail .= ' limit 10';
|
||||
}
|
||||
|
||||
$config['get_row_index'] = '';
|
||||
echoLongTable($col_names, $from, $cond, $tail, $header_row, $print_row, $config);
|
||||
}
|
||||
|
||||
// ===== uoj.ac =====
|
||||
|
||||
function echoJudgmentDetails($raw_details, $styler, $name) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'new contest' => 'New Contest',
|
||||
'current or upcoming contests' => 'Current or upcoming contests',
|
||||
'ended contests' => 'Ended contests',
|
||||
'back to the contest' => 'Back to the contest',
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'new contest' => '新建比赛',
|
||||
'current or upcoming contests' => '正在进行或即将到来的比赛',
|
||||
'ended contests' => '已结束的比赛',
|
||||
'back to the contest' => '返回比赛',
|
||||
|
@ -52,4 +52,7 @@ return [
|
||||
'hacks to me' => 'Hacks to me',
|
||||
'difficulty' => 'Difficulty',
|
||||
'show difficulty' => 'Show difficulty',
|
||||
'tags' => 'Tags',
|
||||
'historical score' => 'Historical Score',
|
||||
'uploader' => 'Uploader',
|
||||
];
|
||||
|
@ -52,4 +52,7 @@ return [
|
||||
'hacks to me' => '我的被Hack记录',
|
||||
'difficulty' => '难度',
|
||||
'show difficulty' => '显示难度',
|
||||
'tags' => '标签',
|
||||
'historical score' => '历史分数',
|
||||
'uploader' => '上传者',
|
||||
];
|
||||
|
@ -436,7 +436,11 @@ class HTML {
|
||||
$def->addElement('footer', 'Block', 'Flow', 'Common');
|
||||
|
||||
$extra_allowed_html = [
|
||||
'span' => ['data-realname' => 'Text', 'data-uoj-username' => 'Number'],
|
||||
'span' => [
|
||||
'class' => 'Enum#uoj-username',
|
||||
'data-realname' => 'Text',
|
||||
'data-color' => 'Color',
|
||||
],
|
||||
'img' => ['width' => 'Text'],
|
||||
];
|
||||
|
||||
@ -463,7 +467,11 @@ class HTML {
|
||||
'small' => [],
|
||||
'del' => [],
|
||||
'br' => [],
|
||||
'span' => ['data-realname' => 'Text', 'data-uoj-username' => 'Number'],
|
||||
'span' => [
|
||||
'class' => 'Enum#uoj-username',
|
||||
'data-realname' => 'Text',
|
||||
'data-color' => 'Color',
|
||||
],
|
||||
];
|
||||
|
||||
$allowed_elements = [];
|
||||
@ -490,8 +498,8 @@ class HTML {
|
||||
return new HTMLPurifier($config);
|
||||
}
|
||||
|
||||
public static function parsedown() {
|
||||
return new UOJMarkdown([
|
||||
public static function parsedown($config = []) {
|
||||
return new UOJMarkdown($config + [
|
||||
'math' => [
|
||||
'enabled' => true,
|
||||
'matchSingleDollar' => true
|
||||
|
@ -258,7 +258,7 @@ class UOJForm {
|
||||
'options' => [],
|
||||
'default_value' => '',
|
||||
'label' => '',
|
||||
'label_class' => 'form-check-label',
|
||||
'label_class' => 'form-label',
|
||||
'help' => '',
|
||||
'help_class' => 'form-text',
|
||||
'disabled' => false,
|
||||
|
@ -1,10 +1,12 @@
|
||||
<?php
|
||||
class UOJMarkdown extends ParsedownMath {
|
||||
public function __construct($options = '') {
|
||||
if (method_exists(get_parent_class(),"__construct")) {
|
||||
if (method_exists(get_parent_class(), "__construct")) {
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
$this->options['username_with_color'] = $options['username_with_color'] ?: false;
|
||||
|
||||
// https://gist.github.com/ShNURoK42/b5ce8baa570975db487c
|
||||
$this->InlineTypes['@'][] = 'UserMention';
|
||||
$this->inlineMarkerList .= '@';
|
||||
@ -19,18 +21,18 @@ class UOJMarkdown extends ParsedownMath {
|
||||
}
|
||||
|
||||
// https://github.com/taufik-nurrohman/parsedown-extra-plugin/blob/1653418c5a9cf5277cd28b0b23ba2d95d18e9bc4/ParsedownExtraPlugin.php#L347-L358
|
||||
protected function doGetContent($Element) {
|
||||
if (isset($Element['text'])) {
|
||||
return $Element['text'];
|
||||
}
|
||||
if (isset($Element['rawHtml'])) {
|
||||
return $Element['rawHtml'];
|
||||
}
|
||||
if (isset($Element['handler']['argument'])) {
|
||||
return implode("\n", (array) $Element['handler']['argument']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected function doGetContent($Element) {
|
||||
if (isset($Element['text'])) {
|
||||
return $Element['text'];
|
||||
}
|
||||
if (isset($Element['rawHtml'])) {
|
||||
return $Element['rawHtml'];
|
||||
}
|
||||
if (isset($Element['handler']['argument'])) {
|
||||
return implode("\n", (array) $Element['handler']['argument']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// https://github.com/taufik-nurrohman/parsedown-extra-plugin/blob/1653418c5a9cf5277cd28b0b23ba2d95d18e9bc4/ParsedownExtraPlugin.php#L369-L378
|
||||
protected function doSetAttributes(&$Element, $From, $Args = array()) {
|
||||
@ -52,27 +54,35 @@ class UOJMarkdown extends ParsedownMath {
|
||||
}
|
||||
|
||||
// https://gist.github.com/ShNURoK42/b5ce8baa570975db487c
|
||||
protected function inlineUserMention($Excerpt) {
|
||||
if (preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) {
|
||||
if (($user = UOJUser::query($matches[1])) && $user['usergroup'] != 'B') {
|
||||
return [
|
||||
'extent' => strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'span',
|
||||
'text' => '@' . $user['username'],
|
||||
'attributes' => [
|
||||
'class' => 'uoj-username',
|
||||
'data-realname' => $user['realname'],
|
||||
'data-uoj-username' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
protected function inlineUserMention($Excerpt) {
|
||||
if (preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) {
|
||||
$mentioned_user = UOJUser::query($matches[1]);
|
||||
|
||||
return [
|
||||
'extent' => strlen($matches[0]),
|
||||
'markup' => $matches[0],
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($mentioned_user) {
|
||||
$color = '#0d6efd';
|
||||
|
||||
if ($this->options['username_with_color']) {
|
||||
$color = UOJUser::getUserColor($mentioned_user);
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'span',
|
||||
'text' => '@' . $mentioned_user['username'],
|
||||
'attributes' => [
|
||||
'class' => 'uoj-username',
|
||||
'data-realname' => UOJUser::getRealname($mentioned_user),
|
||||
'data-color' => $color,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => strlen($matches[0]),
|
||||
'markup' => $matches[0],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class UOJProblem {
|
||||
2300,
|
||||
2400,
|
||||
2500,
|
||||
2600,
|
||||
2700,
|
||||
2900,
|
||||
3100,
|
||||
@ -43,13 +44,254 @@ class UOJProblem {
|
||||
2300 => '#ff8000',
|
||||
2400 => '#ff8000',
|
||||
2500 => '#ff8000',
|
||||
2600 => '#ff0000',
|
||||
2700 => '#ff0000',
|
||||
2900 => '#ff0000',
|
||||
3100 => '#ff0000',
|
||||
3100 => '#aa0000',
|
||||
3300 => '#aa0000',
|
||||
3500 => '#aa0000',
|
||||
];
|
||||
|
||||
public static array $categories = [
|
||||
'算法基础' => [
|
||||
'暴力',
|
||||
'枚举',
|
||||
'模拟',
|
||||
'递归与分治',
|
||||
'贪心',
|
||||
'排序',
|
||||
'前缀和与差分',
|
||||
'二分',
|
||||
'倍增',
|
||||
'构造',
|
||||
'打表',
|
||||
],
|
||||
'搜索' => [
|
||||
'深度优先搜索',
|
||||
'广度优先搜索',
|
||||
'双向搜索',
|
||||
'启发式搜索',
|
||||
'A*',
|
||||
'IDA*',
|
||||
'迭代加深',
|
||||
'回溯法',
|
||||
'Dancing Links',
|
||||
],
|
||||
'动态规划' => [
|
||||
'记忆化搜索',
|
||||
'线性 DP',
|
||||
'背包 DP',
|
||||
'区间 DP',
|
||||
'树形 DP',
|
||||
'状压 DP',
|
||||
'数位 DP',
|
||||
'DAG 上 DP',
|
||||
'插头 DP',
|
||||
'概率 DP',
|
||||
'单调队列优化 DP',
|
||||
'斜率优化 DP',
|
||||
'四边形不等式优化 DP',
|
||||
],
|
||||
'计算几何' => [
|
||||
'Pick 定理',
|
||||
'三角剖分',
|
||||
'凸包',
|
||||
'扫描线',
|
||||
'旋转卡壳',
|
||||
'半平面交',
|
||||
'平面最近点对',
|
||||
'随机增量法',
|
||||
'反演变换',
|
||||
],
|
||||
'数学' => [
|
||||
'位运算',
|
||||
'快速幂',
|
||||
'高精度',
|
||||
'生成函数',
|
||||
'指数生成函数',
|
||||
'向量',
|
||||
'矩阵',
|
||||
'高斯消元',
|
||||
'线性基',
|
||||
'线性规划',
|
||||
'容斥',
|
||||
'组合计数',
|
||||
'离散对数',
|
||||
'单纯形算法',
|
||||
'概率',
|
||||
'置换群',
|
||||
'斐波那契数列',
|
||||
'牛顿迭代法',
|
||||
'数值积分',
|
||||
'分块打表',
|
||||
],
|
||||
'数论' => [
|
||||
'最大公约数',
|
||||
'分解质因数',
|
||||
'欧拉函数',
|
||||
'筛法',
|
||||
'欧拉定理',
|
||||
'费马小定理',
|
||||
'类欧几里得算法',
|
||||
'翡蜀定理',
|
||||
'乘法逆元',
|
||||
'线性同余方程',
|
||||
'Meissel-Lehmer 算法',
|
||||
'二次剩余',
|
||||
'BSGS',
|
||||
'原根',
|
||||
'卢卡斯定理',
|
||||
'莫比乌斯反演',
|
||||
'拉格朗日反演',
|
||||
'杜教筛',
|
||||
'Powerful Number 筛',
|
||||
'Min_25 筛',
|
||||
'洲阁筛',
|
||||
'连分数',
|
||||
'Stern-Brocot 数与 Farey 序列',
|
||||
'Pell 方程',
|
||||
],
|
||||
'字符串' => [
|
||||
'字符串哈希',
|
||||
'字典树',
|
||||
'KMP',
|
||||
'Boyer-Moore',
|
||||
'Z 函数(扩展 KMP)',
|
||||
'AC 自动机',
|
||||
'后缀数组',
|
||||
'后缀自动机',
|
||||
'后缀平衡树',
|
||||
'广义后缀自动机',
|
||||
'Manacher',
|
||||
'回文树',
|
||||
'序列自动机',
|
||||
'最小表示法',
|
||||
'Lyndon 分解',
|
||||
],
|
||||
'图论' => [
|
||||
'拓扑排序',
|
||||
'最短路',
|
||||
'K 短路',
|
||||
'同余最短路',
|
||||
'虚树',
|
||||
'树分治',
|
||||
'动态树分治',
|
||||
'树哈希',
|
||||
'树上启发式合并',
|
||||
'AHU 算法',
|
||||
'矩阵树定理',
|
||||
'最小生成树',
|
||||
'最小树形图',
|
||||
'最小直径生成树',
|
||||
'斯坦纳树',
|
||||
'拆点',
|
||||
'差分约束',
|
||||
'强连通分量',
|
||||
'双连通分量',
|
||||
'割点与桥',
|
||||
'圆方树',
|
||||
'2-SAT',
|
||||
'欧拉图',
|
||||
'哈密顿图',
|
||||
'最小环',
|
||||
'平面图',
|
||||
'网络流',
|
||||
'最大流',
|
||||
'最小割',
|
||||
'费用流',
|
||||
'上下界网络流',
|
||||
'Stoer-Wagner 算法',
|
||||
'二分图',
|
||||
'二分图最大匹配',
|
||||
'二分图最大权匹配',
|
||||
'一般图最大匹配',
|
||||
'一般图最大权匹配',
|
||||
'Prufer 序列',
|
||||
'LGV 引理',
|
||||
'弦图',
|
||||
],
|
||||
'组合数学' => [
|
||||
'排列组合',
|
||||
'卡特兰数',
|
||||
'斯特林数',
|
||||
'贝尔数',
|
||||
'伯努利数',
|
||||
'康托展开',
|
||||
'容斥原理',
|
||||
'抽屉原理',
|
||||
'欧拉数',
|
||||
],
|
||||
'数据结构' => [
|
||||
'栈',
|
||||
'队列',
|
||||
'链表',
|
||||
'哈希表',
|
||||
'并查集',
|
||||
'二叉堆',
|
||||
'配对堆',
|
||||
'树状数组',
|
||||
'线段树',
|
||||
'平衡树',
|
||||
'左偏树',
|
||||
'块状数组',
|
||||
'块状链表',
|
||||
'树分块',
|
||||
'Sqrt Tree',
|
||||
'可持久化数据结构',
|
||||
'单调栈',
|
||||
'单调队列',
|
||||
'ST 表',
|
||||
'树套树',
|
||||
'李超线段树',
|
||||
'区间最值操作与区间历史最值',
|
||||
'划分树',
|
||||
'跳表',
|
||||
'K-D Tree',
|
||||
'珂朵莉树',
|
||||
'动态树',
|
||||
'析合树',
|
||||
],
|
||||
'多项式' => [
|
||||
'拉格朗日插值',
|
||||
'快速傅里叶变换',
|
||||
'快速数论变换',
|
||||
'快速沃尔什变换',
|
||||
'多项式求逆',
|
||||
'多项式开方',
|
||||
'多项式除法与取模',
|
||||
'多项式对数函数与指数函数',
|
||||
'多项式牛顿迭代',
|
||||
'多项式多点求值与快速插值',
|
||||
'多项式三角函数',
|
||||
'多项式反三角函数',
|
||||
'常系数齐次线性递推',
|
||||
],
|
||||
'博弈论' => [
|
||||
'不平等博弈',
|
||||
'SG 函数',
|
||||
'Nim 游戏',
|
||||
'Anti-Nim',
|
||||
'纳什均衡',
|
||||
],
|
||||
'杂项' => [
|
||||
'构造',
|
||||
'离散化',
|
||||
'CDQ 分治',
|
||||
'整体二分',
|
||||
'分块',
|
||||
'莫队',
|
||||
'分数规划',
|
||||
'随机化',
|
||||
'模拟退火',
|
||||
'爬山法',
|
||||
'悬线法',
|
||||
'编译原理',
|
||||
'复杂度分析',
|
||||
'语义分析',
|
||||
'底层优化',
|
||||
],
|
||||
];
|
||||
|
||||
public static function query($id) {
|
||||
if (!isset($id) || !validateUInt($id)) {
|
||||
return null;
|
||||
|
@ -22,7 +22,7 @@ class UOJRanklist {
|
||||
}
|
||||
|
||||
$last_user = null;
|
||||
$parsedown = HTML::parsedown();
|
||||
$parsedown = HTML::parsedown(['username_with_color' => true]);
|
||||
$purifier = HTML::purifier_inline();
|
||||
$print_row = function ($user, $now_cnt) use (&$last_user, &$conds, &$parsedown, &$purifier) {
|
||||
if ($last_user === null) {
|
||||
@ -138,7 +138,7 @@ class UOJRanklist {
|
||||
$header_row .= '</tr>';
|
||||
|
||||
$last_user = null;
|
||||
$parsedown = HTML::parsedown();
|
||||
$parsedown = HTML::parsedown(['username_with_color' => true]);
|
||||
$purifier = HTML::purifier_inline();
|
||||
$print_row = function ($user, $now_cnt) use (&$last_user, &$conds, &$parsedown, &$purifier) {
|
||||
if ($last_user === null) {
|
||||
|
@ -210,6 +210,43 @@ class UOJUser {
|
||||
}
|
||||
}
|
||||
|
||||
public static function getRealname($user) {
|
||||
$realname = $user['realname'];
|
||||
|
||||
if ($user['usertype'] == 'teacher') {
|
||||
$realname .= '老师';
|
||||
}
|
||||
|
||||
return $realname;
|
||||
}
|
||||
|
||||
public static function getUserColor($user) {
|
||||
$extra = UOJUser::getExtra($user);
|
||||
|
||||
return UOJUser::getUserColor2($user['usergroup'], $extra['username_color']);
|
||||
}
|
||||
|
||||
public static function getUserColor2($usergroup, $custom_color = null) {
|
||||
if ($usergroup == 'B') {
|
||||
return '#996600';
|
||||
}
|
||||
|
||||
if ($usergroup == 'T') {
|
||||
return '#707070';
|
||||
}
|
||||
|
||||
if ($usergroup == 'S') {
|
||||
return $custom_color ?: '#9d3dcf';
|
||||
}
|
||||
|
||||
// 前管理员设置颜色为紫色的,颜色改为蓝色
|
||||
if ($custom_color == '#9d3dcf') {
|
||||
return '#0d6efd';
|
||||
}
|
||||
|
||||
return $custom_color ?: '#0d6efd';
|
||||
}
|
||||
|
||||
public static function getLink($user) {
|
||||
if (is_string($user)) {
|
||||
$info = UOJUser::query($user);
|
||||
@ -221,14 +258,18 @@ class UOJUser {
|
||||
}
|
||||
}
|
||||
|
||||
if ($user['usergroup'] == 'B') {
|
||||
return HTML::tag('a', ['class' => 'text-danger fw-bold', 'href' => "/user/{$user['username']}"], $user['username']);
|
||||
}
|
||||
$realname = UOJUser::getRealname($user);
|
||||
|
||||
// 未登录不可查看真实姓名
|
||||
$realname = Auth::check() ? $user['realname'] : '';
|
||||
if (!Auth::check()) {
|
||||
$realname = '';
|
||||
}
|
||||
|
||||
return HTML::tag('span', ['class' => 'uoj-username', 'data-realname' => trim(HTML::escape($realname))], $user['username']);
|
||||
return HTML::tag('span', [
|
||||
'class' => 'uoj-username',
|
||||
'data-color' => UOJUser::getUserColor($user),
|
||||
'data-realname' => trim(HTML::escape($realname)),
|
||||
], $user['username']);
|
||||
}
|
||||
|
||||
public static function getUpdatedExtraVisitHistory($history, $cur) {
|
||||
@ -290,6 +331,7 @@ class UOJUser {
|
||||
'show_email' => 'all',
|
||||
'show_qq' => 'all',
|
||||
'avatar_source' => 'gravatar',
|
||||
'username_color' => isSuperUser($user) ? '#9d3dcf' : '#0d6efd',
|
||||
]);
|
||||
return $extra;
|
||||
}
|
||||
@ -340,6 +382,7 @@ class UOJUser {
|
||||
$extra = UOJUser::getExtra($user);
|
||||
$cur = [
|
||||
'addr' => $info['remote_addr'],
|
||||
'forwarded_addr' => $info['http_x_forwarded_for'],
|
||||
'ua' => substr($info['http_user_agent'], 0, UOJUser::MAX_UA_LEN),
|
||||
'last' => UOJTime::$time_now_str
|
||||
];
|
||||
|
@ -92,9 +92,9 @@ Route::group(
|
||||
Route::any('/click-zan', '/click_zan.php');
|
||||
|
||||
// Apps
|
||||
Route::any('/image_hosting', '/image_hosting/index.php');
|
||||
Route::get('/image_hosting/{image_name}.png', '/image_hosting/get_image.php');
|
||||
Route::any('/html2markdown', '/html2markdown.php');
|
||||
Route::any('/image_hosting', '/app/image_hosting/index.php');
|
||||
Route::get('/image_hosting/{image_name}.png', '/app/image_hosting/get_image.php');
|
||||
Route::any('/html2markdown', '/app/html2markdown.php');
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -19,6 +19,8 @@ class Parsedown
|
||||
|
||||
const version = '1.7.4';
|
||||
|
||||
protected $options = [];
|
||||
|
||||
# ~
|
||||
|
||||
function text($text)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
$reviews = [];
|
||||
|
||||
$parsedown = HTML::parsedown();
|
||||
$parsedown = HTML::parsedown(['username_with_color' => true]);
|
||||
$purifier = HTML::purifier_inline();
|
||||
|
||||
foreach ($contest_data['people'] as $person) {
|
||||
|
@ -35,7 +35,7 @@
|
||||
function(row) {
|
||||
var col_tr = '<tr>';
|
||||
col_tr += '<td>' + row[3] + '</td>';
|
||||
col_tr += '<td>' + getUserLink(row[2][0], row[2][1]) + '</td>';
|
||||
col_tr += '<td>' + getUserLink(row[2][0], row[2][1], row[2][3]) + '</td>';
|
||||
col_tr += '<td>' + '<div><span class="uoj-score" data-max="' + problems.length * 100 + '" style="color:' + getColOfScore(row[0] / problems.length) + '">' + row[0] + '</span></div>' + '<div>' + getPenaltyTimeStr(row[1]) + '</div></td>';
|
||||
for (var i = 0; i < problems.length; i++) {
|
||||
col = score[row[2][0]][i];
|
||||
|
@ -4,11 +4,6 @@ if (!isset($ShowPageFooter)) {
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
[...document.querySelectorAll('[data-bs-toggle="tooltip"]')].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
});
|
||||
</script>
|
||||
<?php if ($ShowPageFooter) : ?>
|
||||
<?php if (UOJNotice::shouldConstantlyCheckNotice()) : ?>
|
||||
<script type="text/javascript">
|
||||
|
@ -102,6 +102,9 @@ if (!isset($ShowPageHeader)) {
|
||||
<!-- Color converter -->
|
||||
<?= HTML::js_src('/js/color-converter.min.js') ?>
|
||||
|
||||
<!-- Clipboard Polyfill -->
|
||||
<?= HTML::js_src('/js/clipboard-polyfill.overwrite-globals.es5.min.js') ?>
|
||||
|
||||
<!-- uoj -->
|
||||
<?= HTML::js_src('/js/uoj.js?v=' . UOJConfig::$data['profile']['s2oj-version']) ?>
|
||||
|
||||
@ -175,7 +178,7 @@ if (!isset($ShowPageHeader)) {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script id="MathJax-script" src="<?= HTML::url('/lib/MathJax/tex-mml-chtml.js') ?>"></script>
|
||||
<script id="MathJax-script" src="<?= HTML::url('/js/mathjax3/tex-mml-chtml.js') ?>"></script>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['jquery.form'])) : ?>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
$purifier = HTML::purifier_inline();
|
||||
$parsedown = HTML::parsedown();
|
||||
$parsedown = HTML::parsedown(['username_with_color' => true]);
|
||||
?>
|
||||
|
||||
<?php if (Auth::check()) : ?>
|
||||
|
@ -21,7 +21,7 @@
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-text">
|
||||
<?= HTML::purifier_inline()->purify(HTML::parsedown()->line($user['motto'])) ?>
|
||||
<?= HTML::purifier_inline()->purify(HTML::parsedown(['username_with_color' => true])->line($user['motto'])) ?>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
|
@ -25,6 +25,11 @@ a {
|
||||
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.uoj-realname {
|
||||
font-size: 90%;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
/* font-size: 2.5rem; */
|
||||
@ -164,7 +169,7 @@ h6,
|
||||
|
||||
.card-uoj-tle > .card-header:hover,
|
||||
.card-uoj-tle > div.card-header > div > .uoj-status-text {
|
||||
color: sandybrown;
|
||||
color: #f4a460;
|
||||
}
|
||||
|
||||
.card-uoj-wrong > .card-header:hover,
|
||||
|
@ -60,14 +60,6 @@ setWebConf(){
|
||||
# Set webroot path
|
||||
ln -sf /opt/uoj/web /var/www/uoj
|
||||
chown -R www-data /var/www/uoj/app/storage
|
||||
# Set web config file
|
||||
php7.4 -a <<UOJEOF
|
||||
\$config = include '/var/www/uoj/app/.default-config.php';
|
||||
\$config['database']['host']='$_database_host_';
|
||||
\$config['database']['password']='$_database_password_';
|
||||
\$config['judger']['socket']['port']='$_judger_socket_port_';
|
||||
file_put_contents('/var/www/uoj/app/.config.php', "<?php\nreturn ".str_replace('\'_httpHost_\'','UOJContext::requestDomain()',var_export(\$config, true)).";\n");
|
||||
UOJEOF
|
||||
# Prepare local sandbox
|
||||
cd /opt/uoj/judger/uoj_judger
|
||||
cat >include/uoj_work_path.h <<UOJEOF
|
||||
@ -84,9 +76,6 @@ initProgress(){
|
||||
#Set uoj_data path
|
||||
mkdir -p /var/uoj_data/upload
|
||||
chown -R www-data:www-data /var/uoj_data
|
||||
#Replace password placeholders
|
||||
sed -i -e "s/salt0/$_salt0_/g" -e "s/salt1/$_salt1_/g" -e "s/salt2/$_salt2_/g" -e "s/salt3/$_salt3_/g" -e "s/_judger_socket_password_/$_judger_socket_password_/g" /var/www/uoj/app/.config.php
|
||||
sed -i -e "s/'protocol' => 'http'/'protocol' => '$_uoj_protocol_'/g" /var/www/uoj/app/.config.php
|
||||
#Start services
|
||||
service ntp restart
|
||||
service apache2 restart
|
||||
|
2
web/js/clipboard-polyfill.overwrite-globals.es5.min.js
vendored
Normal file
2
web/js/clipboard-polyfill.overwrite-globals.es5.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! clipboard-polyfill v4.0.0-rc8 | MIT License | github.com/zenorocha/clipboard.js */
|
||||
"use strict";!function(){var e="text/plain";(function(){(console.warn||console.log).apply(console,arguments)}).bind("[clipboard-polyfill]");var n,t,r,o="undefined"==typeof window?void 0:window,i="undefined"==typeof globalThis?void 0:globalThis,a=null!=(r=null==(n=o)?void 0:n.Promise)?r:null==(t=i)?void 0:t.Promise;var u,l,c,d,f,v="undefined"==typeof navigator?void 0:navigator,s=null==v?void 0:v.clipboard,p=null==(u=null==s?void 0:s.read)?void 0:u.bind(s),b=null==(l=null==s?void 0:s.readText)?void 0:l.bind(s),m=null==(c=null==s?void 0:s.write)?void 0:c.bind(s),y=null==(d=null==s?void 0:s.writeText)?void 0:d.bind(s),w=null==(f=o)?void 0:f.ClipboardItem,h=function(){if(!a)throw new Error("No `Promise` implementation available for `clipboard-polyfill`. Consider using: https://github.com/lgarron/clipboard-polyfill#flat-file-version-with-promise-included");return a}(),g=o;function x(){return"undefined"==typeof ClipboardEvent&&void 0!==(null==g?void 0:g.clipboardData)&&void 0!==(null==g?void 0:g.clipboardData.setData)}function E(n,t,r){for(var o in n.success=!0,t){var i=t[o],a=r.clipboardData;a.setData(o,i),o===e&&a.getData(o)!==i&&(n.success=!1)}r.preventDefault()}function C(e){var n={success:!1},t=E.bind(this,n,e);document.addEventListener("copy",t);try{document.execCommand("copy")}finally{document.removeEventListener("copy",t)}return n.success}function T(e,n){D(e);var t=C(n);return S(),t}function D(e){var n=document.getSelection();if(n){var t=document.createRange();t.selectNodeContents(e),n.removeAllRanges(),n.addRange(t)}}function S(){var e=document.getSelection();e&&e.removeAllRanges()}function A(n){var t,r=e in n;if(x()){if(!r)throw new Error("No `text/plain` value was specified.");if(t=n[e],g.clipboardData.setData("Text",t))return!0;throw new Error("Copying failed, possibly because the user rejected it.")}return!!C(n)||(navigator.userAgent.indexOf("Edge")>-1||(!!T(document.body,n)||(!!function(e){var n=document.createElement("div");n.setAttribute("style","-webkit-user-select: text !important"),n.textContent="temporary element",document.body.appendChild(n);var t=T(n,e);return document.body.removeChild(n),t}(n)||!!function(e){var n=document.createElement("div");n.setAttribute("style","-webkit-user-select: text !important");var t=n;n.attachShadow&&(t=n.attachShadow({mode:"open"}));var r=document.createElement("span");r.innerText=e,t.appendChild(r),document.body.appendChild(n),D(r);var o=document.execCommand("copy");return S(),document.body.removeChild(n),o}(n[e]))))}function R(e,n){var t=[];for(var r in e){var o=e[r];t.push(n(o))}return h.all(t).then((function(n){for(var t={},r=0;r<e.length;r++)t[e[r]]=n[r];return t}))}var k=h.resolve(),L=function(){return h.resolve(!0)},N=h.resolve(!1);function O(e){return new h((function(n,t){try{n(e())}catch(e){t(e)}}))}function P(n){if(!A(function(n){var t={};return t[e]=n,t}(n)))throw new Error("writeText() failed")}function j(){return O((function(){if(b)return b();if(x()){var e=function(){var e=g.clipboardData.getData("Text");if(""===e)throw new Error("Empty clipboard or could not read plain text from clipboard");return e}();return h.resolve(e)}throw new Error("Read is not supported in your browser.")}))}function I(e,n){for(var t in e){if(-1!==e[t].types.indexOf(n))return!0}return!1}var B=function(e,n){var t,r=Object.keys(e),o={};for(var i in e){var a=e[i];o[i]="string"==typeof a?F(i,a):a}return{types:r,presentationStyle:null!=(t=null==n?void 0:n.presentationStyle)?t:"unspecified",getType:function(e){return h.resolve(o[e])}}};function F(e,n){return new Blob([n],{type:e})}function q(e){return R(e.types,(function(n){return e.getType(n)})).then((function(n){var t={};return e.presentationStyle&&(t.presentationStyle=e.presentationStyle),new w(n,t)}))}function z(n){var t={};return t[e]=F(n,e),new B(t)}function G(e,n){return e.getType(n).then((function(e){return n=e,new h((function(e,t){var r=new FileReader;r.addEventListener("load",(function(){var n=r.result;"string"==typeof n?e(n):t("could not convert blob to string")})),r.readAsText(n)}));var n}))}navigator.clipboard||(navigator.clipboard={}),navigator.clipboard.read=function(){return O((function(){return p?p():j().then((function(e){return[z(e)]}))}))},navigator.clipboard.readText=j,navigator.clipboard.write=function(n){return O((function(){if(m&&w){var t=m;return h.all(n.map(q)).then((function(r){return t(r).then(L).catch((function(t){if(!I(n,e)&&!I(n,"text/html"))throw t;return N}))}))}return N})).then((function(t){if(t)return k;I(n,e);return function(e){return R(e.types,(function(n){return G(e,n)}))}(n[0]).then((function(e){if(!A(e))throw new Error("write() failed")}))}))},navigator.clipboard.writeText=function(e){return O((function(){return y?y(e).catch(P):h.resolve(P(e))}))},window.ClipboardItem=B}();
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user