mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-12-22 21:51:52 +00:00
feat: dropzone form
This commit is contained in:
parent
ec084058ba
commit
daf0c55485
@ -61,11 +61,7 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
|
||||
}
|
||||
|
||||
$form->handle = function (&$vdata) use ($form_name, $requirement, $zip_file_name_gen, $handle) {
|
||||
global $myUser;
|
||||
|
||||
if ($myUser == null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
Auth::check() || UOJResponse::page406('请登录后再提交');
|
||||
|
||||
$tot_size = 0;
|
||||
$zip_file_name = $zip_file_name_gen();
|
||||
@ -117,76 +113,150 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
|
||||
}
|
||||
|
||||
function newZipSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle) {
|
||||
$form = new UOJForm($form_name);
|
||||
$name = "zip_ans_{$form_name}";
|
||||
$text = UOJLocale::get('problems::zip file upload introduction', implode(', ', array_map(fn ($req) => $req['file_name'], $requirement)));
|
||||
$html = <<<EOD
|
||||
<div id="div-{$name}">
|
||||
<label class="form-label" for="input-{$name}">$text</label>
|
||||
<input class="form-control" type="file" id="input-{$name}" name="{$name}" />
|
||||
<span class="help-block invalid-feedback" id="help-{$name}"></span>
|
||||
</div>
|
||||
EOD;
|
||||
$form->addNoVal($name, $html);
|
||||
$form->config['is_big'] = true;
|
||||
$form->config['has_file'] = true;
|
||||
$form->handle = function () use ($name, $requirement, $zip_file_name_gen, $handle) {
|
||||
global $myUser;
|
||||
|
||||
if ($myUser == null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
if (!isset($_FILES[$name])) {
|
||||
becomeMsgPage('你在干啥……怎么什么都没交过来……?');
|
||||
} elseif (!is_uploaded_file($_FILES[$name]['tmp_name'])) {
|
||||
becomeMsgPage('上传出错,貌似你什么都没交过来……?');
|
||||
}
|
||||
|
||||
$up_zip_file = new ZipArchive();
|
||||
if ($up_zip_file->open($_FILES[$name]['tmp_name']) !== true) {
|
||||
becomeMsgPage('不是合法的zip压缩文件');
|
||||
}
|
||||
|
||||
$tot_size = 0;
|
||||
$zip_content = array();
|
||||
foreach ($requirement as $req) {
|
||||
$stat = $up_zip_file->statName($req['file_name']);
|
||||
if ($stat === false) {
|
||||
$zip_content[$req['name']] = '';
|
||||
} else {
|
||||
$tot_size += $stat['size'];
|
||||
if ($stat['size'] > 20 * 1024 * 1024) {
|
||||
becomeMsgPage("文件 {$req['file_name']} 实际大小过大。");
|
||||
$form = new DropzoneForm(
|
||||
$form_name,
|
||||
[],
|
||||
[
|
||||
'accept' => <<<EOD
|
||||
function(file, done) {
|
||||
if (file.size > 0) {
|
||||
done();
|
||||
} else {
|
||||
done('请不要上传空文件!');
|
||||
}
|
||||
}
|
||||
$ret = $up_zip_file->getFromName($req['file_name']);
|
||||
if ($ret === false) {
|
||||
$zip_content[$req['name']] = '';
|
||||
} else {
|
||||
$zip_content[$req['name']] = $ret;
|
||||
EOD,
|
||||
]
|
||||
);
|
||||
$form->introduction = HTML::tag('p', [], UOJLocale::get(
|
||||
'problems::zip file upload introduction',
|
||||
'<b>' . implode(', ', array_map(fn ($req) => $req['file_name'], $requirement)) . '</b>'
|
||||
));
|
||||
|
||||
$form->handler = function ($form) use ($requirement, $zip_file_name_gen, $handle) {
|
||||
Auth::check() || UOJResponse::page406('请登录后再提交');
|
||||
|
||||
$files = $form->getFiles();
|
||||
if (count($files) == 0) {
|
||||
UOJResponse::page406('上传出错:请提交至少一个文件');
|
||||
}
|
||||
|
||||
$reqset = [];
|
||||
foreach ($requirement as $req) {
|
||||
$file_name = strtolower($req['file_name']);
|
||||
$reqset[$file_name] = true;
|
||||
}
|
||||
|
||||
$fdict = [];
|
||||
$single_file_size_limit = 20 * 1024 * 1024;
|
||||
|
||||
$invalid_zip_msg = '不是合法的 zip 压缩文件(压缩包里的文件名是否包含特殊字符?或者换个压缩软件试试?)';
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
if (strEndWith(strtolower($name), '.zip')) {
|
||||
$up_zip_file = new ZipArchive();
|
||||
if ($up_zip_file->open($files[$name]['tmp_name']) !== true) {
|
||||
UOJResponse::page406("{$name} {$invalid_zip_msg}");
|
||||
}
|
||||
for ($i = 0; $i < $up_zip_file->numFiles; $i++) {
|
||||
$stat = $up_zip_file->statIndex($i);
|
||||
if ($stat === false) {
|
||||
UOJResponse::page406("{$name} {$invalid_zip_msg}");
|
||||
}
|
||||
$file_name = strtolower(basename($stat['name']));
|
||||
if ($stat['size'] > $single_file_size_limit) {
|
||||
UOJResponse::page406("压缩包内文件 {$file_name} 实际大小过大。");
|
||||
}
|
||||
if ($stat['size'] == 0) { // skip empty files and directories
|
||||
continue;
|
||||
}
|
||||
if (empty($reqset[$file_name])) {
|
||||
UOJResponse::page406("压缩包内包含了题目不需要的文件:{$file_name}");
|
||||
}
|
||||
if (isset($fdict[$file_name])) {
|
||||
UOJResponse::page406("压缩包内的文件出现了重复的文件名:{$file_name}");
|
||||
}
|
||||
$fdict[$file_name] = [
|
||||
'zip' => $up_zip_file,
|
||||
'zip_name' => $name,
|
||||
'size' => $stat['size'],
|
||||
'index' => $i
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$up_zip_file->close();
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
if (!strEndWith(strtolower($name), '.zip')) {
|
||||
$file_name = strtolower($name);
|
||||
if ($file['size'] > $single_file_size_limit) {
|
||||
UOJResponse::page406("文件 {$file_name} 大小过大。");
|
||||
}
|
||||
if ($file['size'] == 0) { // skip empty files
|
||||
continue;
|
||||
}
|
||||
if (empty($reqset[$name])) {
|
||||
UOJResponse::page406("上传了题目不需要的文件:{$file_name}");
|
||||
}
|
||||
if (isset($fdict[$file_name])) {
|
||||
UOJResponse::page406("压缩包内的文件和直接上传的文件中出现了重复的文件名:{$file_name}");
|
||||
}
|
||||
$fdict[$file_name] = [
|
||||
'zip' => false,
|
||||
'size' => $file['size'],
|
||||
'name' => $name
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$tot_size = 0;
|
||||
$up_content = [];
|
||||
$is_empty = true;
|
||||
foreach ($requirement as $req) {
|
||||
$file_name = strtolower($req['file_name']);
|
||||
if (empty($fdict[$file_name])) {
|
||||
$up_content[$req['name']] = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_empty = false;
|
||||
$tot_size += $fdict[$file_name]['size'];
|
||||
|
||||
if ($fdict[$file_name]['zip']) {
|
||||
$ret = $fdict[$file_name]['zip']->getFromIndex($fdict[$file_name]['index']);
|
||||
if ($ret === false) {
|
||||
UOJResponse::page406("{$fdict[$file_name]['zip_name']} {$invalid_zip_msg}");
|
||||
}
|
||||
$up_content[$req['name']] = $ret;
|
||||
} else {
|
||||
$up_content[$req['name']] = file_get_contents($files[$fdict[$file_name]['name']]['tmp_name']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_empty) {
|
||||
UOJResponse::page406('未上传任何题目要求的文件');
|
||||
}
|
||||
|
||||
$zip_file_name = $zip_file_name_gen();
|
||||
|
||||
$zip_file = new ZipArchive();
|
||||
if ($zip_file->open(UOJContext::storagePath() . $zip_file_name, ZipArchive::CREATE) !== true) {
|
||||
becomeMsgPage('提交失败');
|
||||
UOJResponse::page406('提交失败:可能是服务器空间不足导致的');
|
||||
}
|
||||
|
||||
foreach ($requirement as $req) {
|
||||
$zip_file->addFromString($req['file_name'], $zip_content[$req['name']]);
|
||||
$zip_file->addFromString($req['file_name'], $up_content[$req['name']]);
|
||||
}
|
||||
$zip_file->close();
|
||||
|
||||
$content = array();
|
||||
$content['file_name'] = $zip_file_name;
|
||||
$content['config'] = array();
|
||||
$content = [
|
||||
'file_name' => $zip_file_name,
|
||||
'config' => []
|
||||
];
|
||||
|
||||
$handle($zip_file_name, $content, $tot_size);
|
||||
};
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ return [
|
||||
'source code' => 'Source code',
|
||||
'text file' => 'Text file',
|
||||
'zip file upload introduction' => function($str) {
|
||||
return "Upload a .zip file containing $str:";
|
||||
return "Please upload the file(s) named as $str. We will unzip any ZIP file that you upload.";
|
||||
},
|
||||
'or upload files one by one' => 'or upload files one by one:',
|
||||
'accepted submissions' => 'Accepted Submissions',
|
||||
|
@ -25,7 +25,7 @@ return [
|
||||
'source code' => '源代码',
|
||||
'text file' => '文本文件',
|
||||
'zip file upload introduction' => function($str) {
|
||||
return "上传一个 zip 压缩文件,包含 {$str}:";
|
||||
return "请上传你要提交的文件,并命名为 {$str}。如果你提交了 zip 压缩包,我们会为你自动解压。";
|
||||
},
|
||||
'or upload files one by one' => '或者逐个上传:',
|
||||
'accepted submissions' => '满分提交',
|
||||
|
126
web/app/models/DropzoneForm.php
Normal file
126
web/app/models/DropzoneForm.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
class DropzoneForm {
|
||||
public static $default_dropzone_config = [
|
||||
'params' => [],
|
||||
'paramName' => 'file',
|
||||
'maxFiles' => 100,
|
||||
'dictDefaultMessage' => '想要上传文件?请把文件拖到这里',
|
||||
'addRemoveLinks' => false,
|
||||
'autoProcessQueue' => false,
|
||||
'parallelUploads' => 100,
|
||||
'uploadMultiple' => true
|
||||
];
|
||||
|
||||
public string $name;
|
||||
public string $url;
|
||||
public ?string $succ_href;
|
||||
public array $hidden_data = [];
|
||||
public array $dropzone_config = [];
|
||||
public array $dropzone_config_direct = [];
|
||||
public array $submit_button_config = [];
|
||||
public string $submit_condition = <<<EOD
|
||||
function(dz) {
|
||||
let files = dz.getQueuedFiles();
|
||||
if (files.length < 1) {
|
||||
dz.emit('errormultiple', files, '请提交至少一个文件');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
EOD;
|
||||
public string $introduction = '';
|
||||
public $handler;
|
||||
|
||||
public $extra_validators = [];
|
||||
|
||||
function __construct(string $name, array $cfg = [], array $cfg_direct = []) {
|
||||
requireLib('dropzone');
|
||||
|
||||
$this->name = $name;
|
||||
$this->url = UOJContext::requestURI();
|
||||
$this->succ_href = $this->url;
|
||||
$this->dropzone_config = $cfg + self::$default_dropzone_config;
|
||||
$this->dropzone_config['params'] += [
|
||||
"submit-{$this->name}" => $this->name,
|
||||
'_token' => crsf_token()
|
||||
];
|
||||
$this->dropzone_config_direct = $cfg_direct;
|
||||
$this->submit_button_config += [
|
||||
'text' => UOJLocale::get('submit')
|
||||
];
|
||||
$this->extra_validators[] = function () {
|
||||
if ($this->dropzone_config['maxFiles'] !== null && count($_FILES) > $this->dropzone_config['maxFiles']) {
|
||||
return '上传出错:你上传了太多文件了';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
}
|
||||
|
||||
public function formID(): string {
|
||||
return "form-{$this->name}";
|
||||
}
|
||||
|
||||
public function divDropzoneID() {
|
||||
return "{$this->formID()}-div-dropzone";
|
||||
}
|
||||
|
||||
public function helpBlockID() {
|
||||
return "{$this->formID()}-help-block";
|
||||
}
|
||||
|
||||
public function getFile() {
|
||||
assert(!$this->dropzone_config['uploadMultiple']);
|
||||
return $_FILES[$this->dropzone_config['paramName']];
|
||||
}
|
||||
|
||||
public function getFiles() {
|
||||
assert($this->dropzone_config['uploadMultiple']);
|
||||
|
||||
if (!is_array($_FILES[$this->dropzone_config['paramName']]['name'])) {
|
||||
UOJResponse::page406('好像上传出了点问题,再试试?');
|
||||
}
|
||||
|
||||
$n = count($_FILES[$this->dropzone_config['paramName']]['name']);
|
||||
$fields = ['name', 'type', 'tmp_name', 'error', 'size'];
|
||||
$files = [];
|
||||
|
||||
for ($i = 0; $i < $n; $i++) {
|
||||
$file = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (!isset($_FILES[$this->dropzone_config['paramName']][$field][$i])) {
|
||||
UOJResponse::page406('好像上传出了点问题,再试试?');
|
||||
}
|
||||
$file[$field] = $_FILES[$this->dropzone_config['paramName']][$field][$i];
|
||||
}
|
||||
|
||||
if (isset($files[$file['name']])) {
|
||||
UOJResponse::page406('上传的文件中出现了重复的文件名!');
|
||||
}
|
||||
|
||||
if (!is_uploaded_file($file['tmp_name'])) {
|
||||
UOJResponse::page406('好像文件没有成功传过来,再试试?');
|
||||
}
|
||||
|
||||
$files[$file['name']] = $file;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function runAtServer() {
|
||||
if (isset($_POST["submit-{$this->name}"]) && !empty($_FILES)) {
|
||||
foreach ($this->extra_validators as $val) {
|
||||
$err = $val();
|
||||
$err === '' || UOJResponse::message($err);
|
||||
}
|
||||
|
||||
($this->handler)($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function printHTML() {
|
||||
uojIncludeView('dropzone-form', ['form' => $this]);
|
||||
}
|
||||
}
|
87
web/app/views/dropzone-form.php
Normal file
87
web/app/views/dropzone-form.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php assert($form instanceof DropzoneForm) ?>
|
||||
|
||||
<form action="<?= $form->url ?>" id="<?= $form->formID() ?>">
|
||||
<?= $form->introduction ?>
|
||||
<div id="<?= $form->divDropzoneID() ?>" class="dropzone border border-2 text-muted"></div>
|
||||
<div id="<?= $form->helpBlockID() ?>">
|
||||
<span class="help-block"></span>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<?= HTML::tag('button', [
|
||||
'type' => 'submit', 'id' => "button-submit-{$form->name}", 'name' => "submit-{$form->name}",
|
||||
'value' => $form->name, 'class' => 'btn btn-primary'
|
||||
], $form->submit_button_config['text']) ?>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="d-none" id="<?= $form->formID() ?>-template">
|
||||
<div class="dz-preview dz-file-preview">
|
||||
<div class="dz-image">
|
||||
<img data-dz-thumbnail />
|
||||
</div>
|
||||
<div class="dz-details">
|
||||
<div class="dz-size"><span data-dz-size></span></div>
|
||||
<div class="dz-filename"><span data-dz-name></span></div>
|
||||
<a class="text-danger dz-remove" href="javascript:;" data-dz-remove>移除</a>
|
||||
</div>
|
||||
<div class="dz-progress">
|
||||
<span class="dz-upload" data-dz-uploadprogress></span>
|
||||
</div>
|
||||
<div class="dz-error-message"><span data-dz-errormessage></span></div>
|
||||
<div class="dz-success-mark">
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Check</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" stroke-opacity="0.198794158" stroke="#198754" fill-opacity="0.816519475" fill="#198754"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="dz-error-mark">
|
||||
<svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Error</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g stroke="#dc3545" stroke-opacity="0.198794158" fill="#dc3545" fill-opacity="0.816519475">
|
||||
<path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
Dropzone.options.<?= lcfirst(camelize($form->divDropzoneID())) ?> = false;
|
||||
|
||||
$(function() {
|
||||
var cfg = <?= json_encode($form->dropzone_config + ['url' => $form->url]) ?>;
|
||||
cfg.previewTemplate = document.querySelector('#<?= $form->formID() ?>-template').innerHTML;
|
||||
<?php foreach ($form->dropzone_config_direct as $key => $func) : ?>
|
||||
cfg[<?= json_encode($key) ?>] = <?= $func ?>;
|
||||
<?php endforeach ?>
|
||||
<?php if (!isset($form->dropzone_config_direct['successmultiple']) && $form->succ_href !== null) : ?>
|
||||
cfg['successmultiple'] = function(files) {
|
||||
window.location.href = <?= json_encode($form->succ_href) ?>;
|
||||
}
|
||||
<?php endif ?>
|
||||
<?php if (!isset($form->dropzone_config_direct['errormultiple'])) : ?>
|
||||
cfg['errormultiple'] = function(files, message) {
|
||||
$('#<?= $form->helpBlockID() ?>').addClass('invalid-feedback');
|
||||
$('#<?= $form->helpBlockID() ?> span').text(message);
|
||||
}
|
||||
<?php endif ?>
|
||||
var myDropzone = new Dropzone('div#<?= $form->divDropzoneID() ?>', cfg);
|
||||
myDropzone.on('addedfile', function(file) {
|
||||
$('#<?= $form->helpBlockID() ?>').removeClass('invalid-feedback');
|
||||
$('#<?= $form->helpBlockID() ?> span').text('');
|
||||
});
|
||||
myDropzone.on('removedfile', function(file) {
|
||||
$('#<?= $form->helpBlockID() ?>').removeClass('invalid-feedback');
|
||||
$('#<?= $form->helpBlockID() ?> span').text('');
|
||||
});
|
||||
$('form#<?= $form->formID() ?>').submit(function(e) {
|
||||
e.preventDefault();
|
||||
if ((<?= $form->submit_condition ?>)(myDropzone)) {
|
||||
myDropzone.processQueue();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
@ -368,3 +368,13 @@ form.form-horizontal {
|
||||
display: block;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* Dropzone Form */
|
||||
|
||||
.dropzone .dz-preview.dz-file-preview .dz-image {
|
||||
background: #e9ecef !important;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-progress {
|
||||
height: 6px !important;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user