diff --git a/amd/build/submissionform.min.js b/amd/build/submissionform.min.js
new file mode 100644
index 00000000..5e7ae076
--- /dev/null
+++ b/amd/build/submissionform.min.js
@@ -0,0 +1,8 @@
+/**
+ * Submission form utility.
+ * @copyright 2024 Astor Bizard
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define("mod_vpl/submissionform",["jquery"],(function($){return{setup:function(){var updateHeaders=function(submitType){var archive="archive"==submitType;$("#id_headersubmitarchive").toggle(archive).toggleClass("collapsed",!archive),$("#id_headersubmitfiles").toggle(!archive).toggleClass("collapsed",archive)},$select=$('select[name="submitmethod"]');$select.change((function(){updateHeaders($select.val())})),updateHeaders($select.val())}}}));
+
+//# sourceMappingURL=submissionform.min.js.map
\ No newline at end of file
diff --git a/amd/build/submissionform.min.js.map b/amd/build/submissionform.min.js.map
new file mode 100644
index 00000000..a6197915
--- /dev/null
+++ b/amd/build/submissionform.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"submissionform.min.js","sources":["../src/submissionform.js"],"sourcesContent":["// This file is part of VPL for Moodle - http://vpl.dis.ulpgc.es/\n//\n// VPL for Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// VPL for Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with VPL for Moodle. If not, see .\n\n/**\n * Submission form utility.\n * @copyright 2024 Astor Bizard\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n return {\n setup: function() {\n var updateHeaders = function(submitType) {\n var archive = submitType == 'archive';\n $('#id_headersubmitarchive').toggle(archive).toggleClass('collapsed', !archive);\n $('#id_headersubmitfiles').toggle(!archive).toggleClass('collapsed', archive);\n };\n var $select = $('select[name=\"submitmethod\"]');\n $select.change(function() {\n updateHeaders($select.val());\n });\n updateHeaders($select.val());\n }\n };\n});\n"],"names":["define","$","setup","updateHeaders","submitType","archive","toggle","toggleClass","$select","change","val"],"mappings":";;;;;AAqBAA,gCAAO,CAAC,WAAW,SAASC,GACxB,MAAO,CACHC,MAAO,WACH,IAAIC,cAAgB,SAASC,YACzB,IAAIC,QAAwB,WAAdD,WACdH,EAAE,2BAA2BK,OAAOD,SAASE,YAAY,aAAcF,SACvEJ,EAAE,yBAAyBK,QAAQD,SAASE,YAAY,YAAaF,UAErEG,QAAUP,EAAE,+BAChBO,QAAQC,QAAO,WACXN,cAAcK,QAAQE,MAC1B,IACAP,cAAcK,QAAQE,MAC1B,EAER"}
\ No newline at end of file
diff --git a/amd/src/submissionform.js b/amd/src/submissionform.js
new file mode 100644
index 00000000..b5cfe180
--- /dev/null
+++ b/amd/src/submissionform.js
@@ -0,0 +1,37 @@
+// This file is part of VPL for Moodle - http://vpl.dis.ulpgc.es/
+//
+// VPL for Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// VPL for Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with VPL for Moodle. If not, see .
+
+/**
+ * Submission form utility.
+ * @copyright 2024 Astor Bizard
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery'], function($) {
+ return {
+ setup: function() {
+ var updateHeaders = function(submitType) {
+ var archive = submitType == 'archive';
+ $('#id_headersubmitarchive').toggle(archive).toggleClass('collapsed', !archive);
+ $('#id_headersubmitfiles').toggle(!archive).toggleClass('collapsed', archive);
+ };
+ var $select = $('select[name="submitmethod"]');
+ $select.change(function() {
+ updateHeaders($select.val());
+ });
+ updateHeaders($select.val());
+ }
+ };
+});
diff --git a/forms/submission.php b/forms/submission.php
index bf27374f..88acc9e2 100644
--- a/forms/submission.php
+++ b/forms/submission.php
@@ -62,7 +62,7 @@
$instance = $vpl->get_instance();
$vpl->print_header( get_string( 'submission', VPL ) );
$vpl->print_view_tabs( basename( __FILE__ ) );
-$mform = new mod_vpl_submission_form( 'submission.php', $vpl );
+$mform = new mod_vpl_submission_form( 'submission.php', $vpl, $userid );
if ($mform->is_cancelled()) {
vpl_inmediate_redirect( vpl_mod_href( 'view.php', 'id', $id ) );
die();
@@ -76,20 +76,94 @@
die();
}
\mod_vpl\util\phpconfig::increase_memory_limit();
+ $rfn = $vpl->get_required_fgm();
+ $reqfiles = $rfn->getFileList();
$files = [];
- for ($i = 0; $i < $instance->maxfiles; $i ++) {
- $attribute = 'file' . $i;
- $name = $mform->get_new_filename( $attribute );
- $data = $mform->get_file_content( $attribute );
- if ($data !== false && $name !== false) {
+ $prevsub = $vpl->last_user_submission( $userid );
+ $firstsub = ($prevsub === false);
+ if (!$firstsub) {
+ $prevsubfiles = (new mod_vpl_submission( $vpl, $prevsub ))->get_submitted_fgm()->getAllFiles();
+ }
+ if ($fromform->submitmethod == 'archive') {
+ if (!$firstsub && $fromform->archiveaction == 'replace') {
+ // Use files of previous submission.
+ foreach ($prevsubfiles as $subfilename => $subfilecontent) {
+ $files[$subfilename] = $subfilecontent;
+ }
+ }
+ // Open archive.
+ $zipname = $mform->save_temp_file( 'archive' );
+ $zip = new ZipArchive();
+ $zip->open($zipname);
+ $subreqfiles = [];
+ $subotherfiles = [];
+ // Read archive and split between required / additional files.
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $filename = $zip->statIndex($i)['name'];
+ if (substr($filename, -1) == '/') { // Directory.
+ continue;
+ }
+ $filecontent = file_get_contents('zip://' . $zipname . '#' . $filename);
// Autodetect text file encode if not binary.
- if (! vpl_is_binary( $name )) {
- $encode = mb_detect_encoding( $data, 'UNICODE, UTF-16, UTF-8, ISO-8859-1', true );
- if ($encode > '') { // If code detected.
- $data = iconv( $encode, 'UTF-8', $data );
+ if (! vpl_is_binary( $filename )) {
+ $encoding = mb_detect_encoding( $filecontent, 'UNICODE, UTF-16, UTF-8, ISO-8859-1', true );
+ if ($encoding > '') { // If code detected.
+ $filecontent = iconv( $encoding, 'UTF-8', $filecontent );
+ }
+ } else {
+ if (in_array($filename . '.b64', $reqfiles)) {
+ $filename = $filename . '.b64';
+ $filecontent = base64_encode($filecontent);
+ }
+ }
+ if (in_array($filename, $reqfiles)) {
+ $subreqfiles[$filename] = $filecontent;
+ } else {
+ $subotherfiles[$filename] = $filecontent;
+ }
+ }
+ foreach ($reqfiles as $reqfile) {
+ if (isset($subreqfiles[$reqfile])) {
+ $files[$reqfile] = $subreqfiles[$reqfile];
+ }
+ }
+ foreach ($subotherfiles as $filename => $filecontent) {
+ $files[$filename] = $filecontent;
+ }
+ // Close archive.
+ $zip->close();
+ unlink($zipname);
+ } else {
+ for ($i = 0; $i < $instance->maxfiles; $i ++) {
+ $field = 'file' . $i;
+ if (!$firstsub && isset($fromform->{$field . 'action'}) && $fromform->{$field . 'action'} == 'keep') {
+ $filename = $fromform->{$field . 'name'};
+ $files[$filename] = $prevsubfiles[$filename];
+ } else {
+ if (isset($fromform->{$field . 'action'}) && $fromform->{$field . 'action'} == 'replace'
+ || !empty($fromform->{$field . 'rename'}) && !empty($fromform->{$field . 'name'})) {
+ $name = $fromform->{$field . 'name'};
+ } else {
+ $name = $mform->get_new_filename( $field );
+ }
+ $data = $mform->get_file_content( $field );
+ if ($data !== false && $name !== false) {
+ // Autodetect text file encode if not binary.
+ if (! vpl_is_binary( $name )) {
+ $encode = mb_detect_encoding( $data, 'UNICODE, UTF-16, UTF-8, ISO-8859-1', true );
+ if ($encode > '') { // If code detected.
+ $data = iconv( $encode, 'UTF-8', $data );
+ }
+ $files[$name] = $data;
+ } else {
+ if (in_array($name . '.b64', $reqfiles)) {
+ $files[$name . '.b64'] = base64_encode($data);
+ } else {
+ $files[$name] = $data;
+ }
+ }
}
}
- $files[$name] = $data;
}
}
$errormessage = '';
@@ -102,15 +176,14 @@
// If evaluate on submission.
if ($instance->evaluate && $instance->evaluateonsubmission) {
- vpl_redirect( vpl_mod_href( 'forms/evaluation.php', 'id', $id, 'userid', $userid ),
- get_string( 'saved', VPL ));
+ $redirecturl = vpl_mod_href( 'forms/evaluation.php', 'id', $id, 'userid', $userid );
+ } else {
+ $redirecturl = vpl_mod_href( 'forms/submissionview.php', 'id', $id, 'userid', $userid );
}
- vpl_redirect( vpl_mod_href( 'forms/submissionview.php', 'id', $id, 'userid', $userid ),
- get_string( 'saved', VPL ));
+ vpl_redirect( $redirecturl, get_string( 'saved', VPL ));
} else {
- vpl_notice( get_string( 'notsaved', VPL ) );
vpl_redirect( vpl_mod_href( 'forms/submission.php', 'id', $id, 'userid', $userid ),
- $errormessage);
+ get_string( 'notsaved', VPL ) . '
' . $errormessage, 'error');
}
}
diff --git a/forms/submission_form.php b/forms/submission_form.php
index aee50cfe..4d14bfb2 100644
--- a/forms/submission_form.php
+++ b/forms/submission_form.php
@@ -31,17 +31,18 @@
class mod_vpl_submission_form extends moodleform {
protected $vpl;
+ protected $userid;
protected function getinternalform() {
return $this->_form;
}
- public function __construct($page, $vpl) {
+ public function __construct($page, $vpl, $userid) {
$this->vpl = $vpl;
+ $this->userid = $userid;
parent::__construct( $page );
}
protected function definition() {
- global $CFG;
+ global $CFG, $OUTPUT, $PAGE;
$mform = & $this->_form;
- $mform->addElement( 'header', 'headersubmission', get_string( 'submission', VPL ) );
// Identification info.
$mform->addElement( 'hidden', 'id' );
$mform->setType( 'id', PARAM_INT );
@@ -54,18 +55,97 @@ protected function definition() {
] );
$mform->setType( 'comments', PARAM_TEXT );
- // Files upload.
+ $submission = $this->vpl->last_user_submission( $this->userid );
+ $firstsub = ($submission === false);
$instance = $this->vpl->get_instance();
- $files = $this->vpl->get_required_files();
- $nfiles = count( $files );
- for ($i = 0; $i < $instance->maxfiles; $i ++) {
+ $reqfiles = $this->vpl->get_required_files();
+
+ $mform->addElement( 'select', 'submitmethod', get_string( 'submitmethod', VPL ),
+ [ 'archive' => get_string( 'archive', VPL ), 'files' => get_string( 'files' ) ] );
+ $mform->setDefault( 'submitmethod', count($reqfiles) == 1 ? 'files' : 'archive' );
+
+ $mform->addElement( 'header', 'headersubmitarchive', get_string( 'submitarchive', VPL ) );
+
+ $filepickertitle = get_string( 'submitarchive', VPL );
+ if (!$firstsub) {
+ $mform->addElement( 'radio', 'archiveaction', $filepickertitle,
+ get_string( 'archivereplacedelete', VPL ), 'replacedelete');
+ $mform->addElement( 'radio', 'archiveaction', '',
+ get_string( 'archivereplace', VPL ), 'replace' );
+ $mform->disabledIf( 'archiveaction', 'submitmethod', 'neq', 'archive' );
+ $filepickertitle = null;
+ }
+ $mform->addElement( 'filepicker', 'archive', $filepickertitle, null, [ 'accepted_types' => '.zip' ] );
+ $mform->disabledIf( 'archive', 'submitmethod', 'neq', 'archive' );
+
+ $mform->addElement( 'header', 'headersubmitfiles', get_string( 'submitfiles', VPL ) );
+
+ // Files upload.
+ $i = 0;
+ $requiredicon = $OUTPUT->pix_icon('requestedfiles', get_string('required'), 'mod_vpl', [ 'class' => 'text-info' ]);
+ foreach ($reqfiles as $reqfile) {
$field = 'file' . $i;
- if ($i < $nfiles) {
- $mform->addElement( 'filepicker', $field, $files[$i] );
- } else {
- $mform->addElement( 'filepicker', $field, get_string( 'anyfile', VPL ) );
+ $filepickertitle = $requiredicon . $reqfile;
+ if (!$firstsub) {
+ $mform->addElement( 'radio', $field . 'action', $filepickertitle,
+ get_string( 'keepcurrentfile', VPL ), 'keep');
+ $mform->addElement( 'radio', $field . 'action', '',
+ get_string( 'replacefile', VPL ), 'replace' );
+ $mform->disabledIf( $field . 'action', 'submitmethod', 'neq', 'files' );
+ $mform->addElement( 'hidden', $field . 'name', $reqfile );
+ $mform->setType( $field . 'name', PARAM_RAW );
+ $filepickertitle = null;
+ }
+ $mform->addElement( 'filepicker', $field, $filepickertitle );
+ $mform->disabledIf( $field, 'submitmethod', 'neq', 'files' );
+ if (!$firstsub) {
+ $mform->disabledIf( $field, $field . 'action', 'neq', 'replace' );
}
+ $i++;
+ }
+ if (!$firstsub) {
+ $subfiles = (new mod_vpl_submission( $this->vpl, $submission ))->get_submitted_fgm()->getFileList();
+ foreach ($subfiles as $subfile) {
+ if (!in_array($subfile, $reqfiles)) {
+ $field = 'file' . $i;
+ $mform->addElement( 'radio', $field . 'action', $subfile, get_string( 'keepcurrentfile', VPL ), 'keep');
+ $mform->addElement( 'radio', $field . 'action', '', get_string( 'deletefile', VPL ), 'delete' );
+ $mform->addElement( 'radio', $field . 'action', '', get_string( 'replacefile', VPL ), 'replace' );
+ $mform->disabledIf( $field . 'action', 'submitmethod', 'neq', 'files' );
+ $mform->addElement( 'hidden', $field . 'name', $subfile );
+ $mform->setType( $field . 'name', PARAM_PATH );
+ $mform->addElement( 'filepicker', $field );
+ $mform->disabledIf( $field, 'submitmethod', 'neq', 'files' );
+ $mform->disabledIf( $field, $field . 'action', 'neq', 'replace' );
+ $i++;
+ }
+ }
+ }
+
+ while ($i < $instance->maxfiles) {
+ $field = 'file' . $i;
+ $mform->addElement( 'filepicker', $field, get_string( 'anyfile', VPL ) );
+ $mform->disabledIf( $field, 'submitmethod', 'neq', 'files' );
+ $mform->addGroup([
+ $mform->createElement('advcheckbox', $field . 'rename', get_string('renameuploadedfile', VPL)),
+ $mform->createElement('text', $field . 'name', get_string('new_file_name', VPL),
+ [ 'size' => 32, 'placeholder' => get_string('new_file_name', VPL) ]),
+ ]);
+ $mform->setType( $field . 'name', PARAM_PATH );
+ $mform->setDefault( $field . 'rename', 0 );
+ $mform->disabledIf( $field . 'name', $field . 'rename' );
+ $mform->disabledIf( $field . 'name', $field . 'name', 'neq', 'files' );
+ $i++;
}
$this->add_action_buttons( true, get_string( 'submit' ) );
+
+ $PAGE->requires->js_call_amd('mod_vpl/submissionform', 'setup');
+ }
+ public function set_data($data) {
+ for ($i = 0; $i < $this->vpl->get_instance()->maxfiles; $i++) {
+ $data->{'file'.$i.'action'} = 'keep';
+ $data->{'archiveaction'} = 'replacedelete';
+ }
+ parent::set_data($data);
}
}
diff --git a/lang/en/vpl.php b/lang/en/vpl.php
index a1034e91..2c6829a1 100644
--- a/lang/en/vpl.php
+++ b/lang/en/vpl.php
@@ -30,6 +30,9 @@
$string['allfiles'] = 'All files';
$string['allsubmissions'] = 'All submissions';
$string['anyfile'] = 'Any file';
+$string['archive'] = 'Archive';
+$string['archivereplace'] = 'Replace only files present in archive';
+$string['archivereplacedelete'] = 'Replace all files and delete files not present in archive';
$string['attemptnumber'] = 'Attempt number {$a}';
$string['autodetect'] = 'Autodetect';
$string['automaticevaluation'] = 'Automatic evaluation';
@@ -89,6 +92,7 @@
$string['delete_file_fq'] = "delete '{\$a}' file?";
$string['delete_file_q'] = 'Delete file?';
$string['deleteallsubmissions'] = 'Delete all submissions';
+$string['deletefile'] = 'Delete file';
$string['description'] = 'Description';
$string['diff'] = 'diff';
$string['directory_not_renamed'] = 'Directory \'{$a}\' has not been renamed';
@@ -175,6 +179,7 @@
$string['jail_servers_config'] = 'Execution servers config';
$string['jail_servers_description'] = 'Write a line for each server';
$string['joinedfiles'] = 'Joined selected files';
+$string['keepcurrentfile'] = 'Keep current file';
$string['keepfiles'] = 'Files to keep when running';
$string['keyboard'] = 'Keyboard';
$string['lasterror'] = 'Last error info';
@@ -294,7 +299,9 @@
$string['rename'] = 'Rename';
$string['rename_file'] = 'Rename file';
$string['rename_directory'] = 'Rename directory';
+$string['renameuploadedfile'] = 'Rename uploaded file';
$string['replace_find'] = 'Replace/Find';
+$string['replacefile'] = 'Replace contents';
$string['replacenewer'] = "A newer version was already saved.\nDo you want to replace the newer version with this one?";
$string['requestedfiles'] = 'Requested files';
$string['requirednet'] = 'Require network address';
@@ -347,6 +354,9 @@
$string['submissionselection'] = 'Submission selection';
$string['submissionslist'] = 'Submissions list';
$string['submissionview'] = 'Submission view';
+$string['submitarchive'] = 'Submit archive';
+$string['submitfiles'] = 'Submit files';
+$string['submitmethod'] = 'Submit method';
$string['submittedby'] = 'Submitted by {$a}';
$string['submittedon'] = 'Submitted on';
$string['submittedonp'] = 'Submitted on {$a}';
diff --git a/locallib.php b/locallib.php
index c88ae1f9..be43df88 100644
--- a/locallib.php
+++ b/locallib.php
@@ -1065,7 +1065,7 @@ function vpl_get_overrides($vplid) {
* @param array $parms Parameters to pass to the function
* @return mixed Value returned by the function or throw exception
*/
-function vpl_call_with_lock(string $locktype, string $resource, string $function, array $parms) {
+function vpl_call_with_lock(string $locktype, string $resource, string $function, array & $parms) {
$lockfactory = \core\lock\lock_config::get_lock_factory($locktype);
if ($lock = $lockfactory->get_lock($resource, VPL_LOCK_TIMEOUT)) {
try {
diff --git a/vpl.class.php b/vpl.class.php
index a250742c..30b32afa 100644
--- a/vpl.class.php
+++ b/vpl.class.php
@@ -809,7 +809,6 @@ public function pass_submission_restriction(& $alldata, & $error) {
public static function internal_add_submission($vpl, $userid, & $files, $comments, & $error) {
global $USER, $DB;
if (! $vpl->pass_submission_restriction( $files, $error )) {
- $error = get_string('notavailable');
return false;
}
$group = false;