From daf0c554854c812e2ad1e5b282a06a6b286304f3 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Thu, 2 Feb 2023 10:29:29 +0800 Subject: [PATCH] feat: dropzone form --- web/app/libs/uoj-form-lib.php | 184 +++++++++++++++++++++--------- web/app/locale/problems/en.php | 2 +- web/app/locale/problems/zh-cn.php | 2 +- web/app/models/DropzoneForm.php | 126 ++++++++++++++++++++ web/app/views/dropzone-form.php | 87 ++++++++++++++ web/css/uoj-bs5.css | 10 ++ 6 files changed, 352 insertions(+), 59 deletions(-) create mode 100644 web/app/models/DropzoneForm.php create mode 100644 web/app/views/dropzone-form.php diff --git a/web/app/libs/uoj-form-lib.php b/web/app/libs/uoj-form-lib.php index 956a391..aef5409 100644 --- a/web/app/libs/uoj-form-lib.php +++ b/web/app/libs/uoj-form-lib.php @@ -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; - $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' => << 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', + '' . implode(', ', array_map(fn ($req) => $req['file_name'], $requirement)) . '' + )); + + $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; } diff --git a/web/app/locale/problems/en.php b/web/app/locale/problems/en.php index 98bdbd2..912c448 100644 --- a/web/app/locale/problems/en.php +++ b/web/app/locale/problems/en.php @@ -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', diff --git a/web/app/locale/problems/zh-cn.php b/web/app/locale/problems/zh-cn.php index 5918395..751f1a0 100644 --- a/web/app/locale/problems/zh-cn.php +++ b/web/app/locale/problems/zh-cn.php @@ -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' => '满分提交', diff --git a/web/app/models/DropzoneForm.php b/web/app/models/DropzoneForm.php new file mode 100644 index 0000000..4d0926c --- /dev/null +++ b/web/app/models/DropzoneForm.php @@ -0,0 +1,126 @@ + [], + '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 = <<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]); + } +} diff --git a/web/app/views/dropzone-form.php b/web/app/views/dropzone-form.php new file mode 100644 index 0000000..97e84b0 --- /dev/null +++ b/web/app/views/dropzone-form.php @@ -0,0 +1,87 @@ + + +
+ introduction ?> +
+
+ +
+
+ 'submit', 'id' => "button-submit-{$form->name}", 'name' => "submit-{$form->name}", + 'value' => $form->name, 'class' => 'btn btn-primary' + ], $form->submit_button_config['text']) ?> +
+
+ +
+
+
+ +
+
+
+
+ 移除 +
+
+ +
+
+
+ + Check + + + + +
+
+ + Error + + + + + + +
+
+
+ diff --git a/web/css/uoj-bs5.css b/web/css/uoj-bs5.css index 80410b7..2520679 100644 --- a/web/css/uoj-bs5.css +++ b/web/css/uoj-bs5.css @@ -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; +}