mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-10 07:58:45 +00:00
440 lines
11 KiB
PHP
440 lines
11 KiB
PHP
<?php
|
|
|
|
class UOJForm {
|
|
public ?string $form_name;
|
|
public ?string $succ_href;
|
|
public $extra_validator = null;
|
|
public $handle;
|
|
private $ajax_submit_js = null;
|
|
private $run_at_server_handler = [];
|
|
private $data = [];
|
|
private $vdata = [];
|
|
private $main_html = '';
|
|
|
|
public $config = [
|
|
'is_big' => false,
|
|
'has_file' => false,
|
|
'ctrl_enter_submit' => false,
|
|
'max_post_size' => 15728640, // 15M
|
|
'max_file_size_mb' => 10, // 10M
|
|
'form' => [
|
|
'class' => '',
|
|
],
|
|
'submit_container' => [
|
|
'class' => 'mt-3 text-center',
|
|
],
|
|
'submit_button' => [
|
|
'class' => 'btn btn-primary',
|
|
'text' => '提交',
|
|
],
|
|
'back_button' => [
|
|
'href' => null,
|
|
'class' => 'btn btn-secondary',
|
|
],
|
|
'confirm' => [
|
|
'smart' => false,
|
|
'text' => null,
|
|
]
|
|
];
|
|
|
|
public function __construct($form_name) {
|
|
$this->form_name = $form_name;
|
|
$this->succ_href = UOJContext::requestURI();
|
|
$this->handle = function (&$vdata) {
|
|
};
|
|
|
|
$this->run_at_server_handler["check-{$this->form_name}"] = function () {
|
|
die(json_encode($this->validateAtServer()));
|
|
};
|
|
$this->run_at_server_handler["submit-{$this->form_name}"] = function () {
|
|
if ($this->config['no_submit']) {
|
|
UOJResponse::page404();
|
|
}
|
|
foreach ($this->data as $field) {
|
|
if (!isset($field['no_val']) && !isset($_POST[$field['name']])) {
|
|
UOJResponse::message('The form is incomplete.');
|
|
}
|
|
}
|
|
|
|
if (UOJContext::requestMethod() == 'POST') {
|
|
$len = UOJContext::contentLength();
|
|
if ($len === null) {
|
|
UOJResponse::page403();
|
|
} elseif ($len > $this->config['max_post_size']) {
|
|
UOJResponse::message('The form is too large.');
|
|
}
|
|
}
|
|
|
|
crsf_defend();
|
|
$errors = $this->validateAtServer();
|
|
if ($errors) {
|
|
$err_str = '';
|
|
foreach ($errors as $name => $err) {
|
|
$esc_err = htmlspecialchars($err);
|
|
$err_str .= "$name: $esc_err<br />";
|
|
}
|
|
UOJResponse::message($err_str);
|
|
}
|
|
$fun = $this->handle;
|
|
$fun($this->vdata);
|
|
|
|
if ($this->succ_href !== 'none') {
|
|
redirectTo($this->succ_href);
|
|
}
|
|
die();
|
|
};
|
|
}
|
|
|
|
public function setAjaxSubmit($js) {
|
|
$GLOBALS['REQUIRE_LIB']['jquery.form'] = '';
|
|
$this->ajax_submit_js = $js;
|
|
}
|
|
|
|
public function add($name, $html, $validator_php, $validator_js) {
|
|
$this->main_html .= $html;
|
|
$this->data[] = [
|
|
'name' => $name,
|
|
'validator_php' => $validator_php,
|
|
'validator_js' => $validator_js
|
|
];
|
|
}
|
|
|
|
public function addNoVal($name, $html) {
|
|
$this->main_html .= $html;
|
|
$this->data[] = array(
|
|
'name' => $name,
|
|
'validator_js' => 'always_ok',
|
|
'no_val' => ''
|
|
);
|
|
}
|
|
|
|
public function appendHTML($html) {
|
|
$this->main_html .= $html;
|
|
}
|
|
|
|
public function addHidden($name, $default_value, $validator_php, $validator_js) {
|
|
$default_value = HTML::escape($default_value);
|
|
$html = <<<EOD
|
|
<input type="hidden" name="$name" id="input-$name" value="$default_value" />
|
|
EOD;
|
|
$this->add($name, $html, $validator_php, $validator_js);
|
|
}
|
|
|
|
public function addCheckbox($name, $config) {
|
|
$config += [
|
|
'checked' => false,
|
|
'div_class' => 'form-check',
|
|
'role' => 'checkbox',
|
|
'input_class' => 'form-check-input',
|
|
'label' => '',
|
|
'label_class' => 'form-check-label',
|
|
'help' => '',
|
|
'help_class' => 'form-text',
|
|
'disabled' => false,
|
|
];
|
|
|
|
$html = '';
|
|
$html .= HTML::tag_begin('div', ['class' => $config['div_class']]);
|
|
$html .= HTML::empty_tag('input', [
|
|
'class' => $config['input_class'],
|
|
'type' => 'checkbox',
|
|
'name' => $name,
|
|
'id' => "input-$name",
|
|
'checked' => $config['checked'] ? 'checked' : null,
|
|
'value' => '1',
|
|
'disabled' => $config['disabled'] ? 'disabled' : null,
|
|
]);
|
|
$html .= HTML::tag('label', [
|
|
'class' => $config['label_class'],
|
|
'for' => "input-$name",
|
|
], $config['label']);
|
|
|
|
if ($config['help']) {
|
|
$html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']);
|
|
}
|
|
|
|
$html .= HTML::tag_end('div');
|
|
|
|
$this->addNoVal($name, $html);
|
|
}
|
|
|
|
public function addSelect($name, $config) {
|
|
$config += [
|
|
'div_class' => '',
|
|
'select_class' => 'form-select',
|
|
'options' => [],
|
|
'default_value' => '',
|
|
'label' => '',
|
|
'label_class' => 'form-label',
|
|
'help' => '',
|
|
'help_class' => 'form-text',
|
|
'disabled' => false,
|
|
];
|
|
|
|
$html = '';
|
|
$html .= HTML::tag_begin('div', ['id' => "div-$name", 'class' => $config['div_class']]);
|
|
|
|
// Label
|
|
if ($config['label']) {
|
|
$html .= HTML::tag('label', [
|
|
'class' => $config['label_class'],
|
|
'for' => "input-$name",
|
|
], $config['label']);
|
|
}
|
|
|
|
// Select
|
|
$html .= HTML::tag_begin('select', ['id' => "input-$name", 'name' => $name, 'class' => $config['select_class']]);
|
|
|
|
foreach ($config['options'] as $opt_name => $opt_label) {
|
|
if ($opt_name == $config['default_value']) {
|
|
$html .= HTML::tag('option', ['value' => $opt_name, 'selected' => 'selected'], $opt_label);
|
|
} else {
|
|
$html .= HTML::tag('option', ['value' => $opt_name], $opt_label);
|
|
}
|
|
}
|
|
|
|
$html .= HTML::tag_end('select');
|
|
|
|
// Help text
|
|
if ($config['help']) {
|
|
$html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']);
|
|
}
|
|
|
|
$html .= HTML::tag_end('div');
|
|
|
|
$this->add(
|
|
$name,
|
|
$html,
|
|
function ($opt) use ($config) {
|
|
return isset($config['options'][$opt]) ? '' : "无效选项";
|
|
},
|
|
null
|
|
);
|
|
}
|
|
|
|
public function printHTML() {
|
|
echo HTML::tag_begin('form', [
|
|
'action' => UOJContext::requestURI(),
|
|
'method' => 'POST',
|
|
'class' => $this->config['form']['class'],
|
|
'id' => "form-{$this->form_name}",
|
|
'enctype' => $this->config['is_big'] ? 'multipart/form-data' : 'application/x-www-form-urlencoded',
|
|
]);
|
|
|
|
echo HTML::hiddenToken();
|
|
|
|
echo $this->main_html;
|
|
|
|
if (!$this->config['no_submit']) {
|
|
echo HTML::tag_begin('div', ['class' => $this->config['submit_container']['class']]);
|
|
|
|
echo HTML::tag('button', [
|
|
'type' => 'submit',
|
|
'id' => "button-submit-{$this->form_name}",
|
|
'name' => "submit-{$this->form_name}",
|
|
'value' => $this->form_name,
|
|
'class' => $this->config['submit_button']['class']
|
|
], $this->config['submit_button']['text']);
|
|
|
|
if ($this->config['back_button']['href'] !== null) {
|
|
echo HTML::tag('a', [
|
|
'class' => $this->config['back_button']['class'],
|
|
'href' => $this->config['back_button']['href']
|
|
], '返回');
|
|
}
|
|
|
|
echo HTML::tag_end('div');
|
|
}
|
|
|
|
echo HTML::tag_end('form');
|
|
|
|
if ($this->config['no_submit']) {
|
|
return;
|
|
}
|
|
|
|
echo <<<EOD
|
|
<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
EOD;
|
|
if ($this->config['ctrl_enter_submit']) {
|
|
echo <<<EOD
|
|
$('#form-{$this->form_name}').keydown(function(e) {
|
|
if (e.keyCode == 13 && e.ctrlKey) {
|
|
$('#button-submit-{$this->form_name}').click();
|
|
}
|
|
});
|
|
EOD;
|
|
}
|
|
echo <<<EOD
|
|
$('#form-{$this->form_name}').submit(function(e) {
|
|
var ok = true;
|
|
|
|
EOD;
|
|
$need_ajax = false;
|
|
if ($this->extra_validator) {
|
|
$need_ajax = true;
|
|
}
|
|
foreach ($this->data as $field) {
|
|
if ($field['validator_js'] != null) {
|
|
if ($field['validator_js'] != 'always_ok') {
|
|
echo <<<EOD
|
|
var {$field['name']}_err = ({$field['validator_js']})($('#input-{$field['name']}').val());
|
|
EOD;
|
|
}
|
|
} else {
|
|
$need_ajax = true;
|
|
}
|
|
}
|
|
|
|
if ($need_ajax) {
|
|
echo <<<EOD
|
|
var post_data = {};
|
|
EOD;
|
|
foreach ($this->data as $field) {
|
|
if ($field['validator_js'] == null) {
|
|
echo <<<EOD
|
|
var {$field['name']}_err = 'Unknown error';
|
|
post_data.{$field['name']} = $('#input-{$field['name']}').val();
|
|
EOD;
|
|
}
|
|
}
|
|
echo <<<EOD
|
|
post_data['check-{$this->form_name}'] = "";
|
|
$.ajax({
|
|
url: '{$_SERVER['REQUEST_URI']}',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
async: false,
|
|
|
|
data: post_data,
|
|
success: function(data) {
|
|
EOD;
|
|
foreach ($this->data as $field) {
|
|
if ($field['validator_js'] == null) {
|
|
echo <<<EOD
|
|
{$field['name']}_err = data.${field['name']};
|
|
EOD;
|
|
}
|
|
}
|
|
echo <<<EOD
|
|
if (data.extra != undefined) {
|
|
alert(data.extra);
|
|
ok = false;
|
|
}
|
|
}
|
|
});
|
|
EOD;
|
|
}
|
|
|
|
foreach ($this->data as $field) {
|
|
if ($field['validator_js'] != 'always_ok') {
|
|
echo <<<EOD
|
|
if (${field['name']}_err) {
|
|
$('#div-${field['name']}').addClass('has-validation has-error');
|
|
$('#div-${field['name']}').addClass('is-invalid');
|
|
$('#input-${field['name']}').addClass('is-invalid');
|
|
$('#help-${field['name']}').text(${field['name']}_err);
|
|
ok = false;
|
|
} else {
|
|
$('#div-${field['name']}').removeClass('has-validation has-error');
|
|
$('#div-${field['name']}').removeClass('is-invalid');
|
|
$('#input-${field['name']}').removeClass('is-invalid');
|
|
$('#help-${field['name']}').text('');
|
|
}
|
|
EOD;
|
|
}
|
|
}
|
|
|
|
if ($this->config['confirm']['smart']) {
|
|
$this->config['confirm']['text'] = '你真的要' . $this->config['submit']['text'] . '吗?';
|
|
}
|
|
if ($this->config['confirm']['text']) {
|
|
echo <<<EOD
|
|
if (!confirm('{$this->config['confirm']['text']}')) {
|
|
ok = false;
|
|
}
|
|
EOD;
|
|
}
|
|
if ($this->config['has_file']) {
|
|
echo <<<EOD
|
|
$(this).find("input[type='file']").each(function() {
|
|
for (var i = 0; i < this.files.length; i++) {
|
|
if (this.files[i].size > {$this->config['max_file_size_mb']} * 1024 * 1024) {
|
|
$('#div-' + $(this).attr('name')).addClass('has-validation has-error');
|
|
$('#div-' + $(this).attr('name')).addClass('is-invalid');
|
|
$('#input-' + $(this).attr('name')).addClass('is-invalid');
|
|
$('#help-' + $(this).attr('name')).text('文件大小不能超过 {$this->config['max_file_size_mb']} MB');
|
|
ok = false;
|
|
} else {
|
|
$('#div-' + $(this).attr('name')).removeClass('has-validation has-error');
|
|
$('#div-' + $(this).attr('name')).removeClass('is-invalid');
|
|
$('#input-' + $(this).attr('name')).removeClass('is-invalid');
|
|
$('#help-' + $(this).attr('name')).text('');
|
|
}
|
|
}
|
|
});
|
|
EOD;
|
|
}
|
|
|
|
if ($this->ajax_submit_js !== null) {
|
|
echo <<<EOD
|
|
e.preventDefault();
|
|
if (ok) {
|
|
$(this).ajaxSubmit({
|
|
beforeSubmit: function(formData) {
|
|
formData.push({name: 'submit-{$this->form_name}', value: '{$this->form_name}', type: 'submit'});
|
|
},
|
|
success: {$this->ajax_submit_js}
|
|
});
|
|
}
|
|
EOD;
|
|
} else {
|
|
echo <<<EOD
|
|
return ok;
|
|
EOD;
|
|
}
|
|
echo <<<EOD
|
|
});
|
|
});
|
|
</script>
|
|
EOD;
|
|
}
|
|
|
|
private function validateAtServer() {
|
|
$errors = array();
|
|
if ($this->extra_validator) {
|
|
$fun = $this->extra_validator;
|
|
$err = $fun();
|
|
if ($err) {
|
|
$errors['extra'] = $err;
|
|
}
|
|
}
|
|
foreach ($this->data as $field) {
|
|
if (!isset($field['no_val']) && isset($_POST[$field['name']])) {
|
|
$fun = $field['validator_php'];
|
|
$ret = $fun($_POST[$field['name']], $this->vdata, $field['name']);
|
|
if (is_array($ret) && isset($ret['error'])) {
|
|
$err = $ret['error'];
|
|
} else {
|
|
$err = $ret;
|
|
}
|
|
if ($err) {
|
|
$errors[$field['name']] = $err;
|
|
}
|
|
if (is_array($ret) && isset($ret['store'])) {
|
|
$this->vdata[$field['name']] = $ret['store'];
|
|
}
|
|
}
|
|
}
|
|
return $errors;
|
|
}
|
|
|
|
public function runAtServer() {
|
|
foreach ($this->run_at_server_handler as $type => $handler) {
|
|
if (isset($_POST[$type])) {
|
|
$handler();
|
|
}
|
|
}
|
|
}
|
|
}
|