From bd053ee693a279a255896f5ae224bd7dbb229b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Gal=C4=8D=C3=ADk?= Date: Fri, 16 Sep 2016 17:19:32 +0200 Subject: [PATCH 1/5] Add output files and checklists --- classes/event/vpl_outputlist_updated.php | 38 ++ classes/event/vpl_outputlist_viewed.php | 38 ++ filegroup.class.php | 63 ++++ forms/gradesubmission.php | 8 +- forms/outputfiles.php | 150 ++++++++ lang/en/vpl.php | 18 + lib.php | 3 + view.php | 16 + views/downloadoutputfile.php | 96 +++++ vpl.class.php | 25 ++ vpl_submission.class.php | 424 +++++++++++++++++++---- vpl_submission_CE.class.php | 13 +- 12 files changed, 820 insertions(+), 72 deletions(-) create mode 100644 classes/event/vpl_outputlist_updated.php create mode 100644 classes/event/vpl_outputlist_viewed.php create mode 100644 forms/outputfiles.php create mode 100644 views/downloadoutputfile.php diff --git a/classes/event/vpl_outputlist_updated.php b/classes/event/vpl_outputlist_updated.php new file mode 100644 index 00000000..f106d936 --- /dev/null +++ b/classes/event/vpl_outputlist_updated.php @@ -0,0 +1,38 @@ +. + +/** + * Class for logging of execution options update events + * + * @package mod_vpl + * @copyright 2016 onwards Frantisek Galcik + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Frantisek Galcik + */ +namespace mod_vpl\event; + +require_once(dirname( __FILE__ ) . '/../../locallib.php'); +defined( 'MOODLE_INTERNAL' ) || die(); +class vpl_outputlist_updated extends vpl_base { + protected function init() { + parent::init(); + $this->data ['crud'] = 'u'; + $this->legacyaction = 'set outputlist'; + } + public function get_description() { + return $this->get_description_mod('view outputlist'); + } +} diff --git a/classes/event/vpl_outputlist_viewed.php b/classes/event/vpl_outputlist_viewed.php new file mode 100644 index 00000000..57342bd3 --- /dev/null +++ b/classes/event/vpl_outputlist_viewed.php @@ -0,0 +1,38 @@ +. + +/** + * Class for logging of execution options update events + * + * @package mod_vpl + * @copyright 2016 onwards Frantisek Galcik + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Frantisek Galcik + */ +namespace mod_vpl\event; + +require_once(dirname( __FILE__ ) . '/../../locallib.php'); +defined( 'MOODLE_INTERNAL' ) || die(); +class vpl_outputlist_viewed extends vpl_base { + protected function init() { + parent::init(); + $this->data ['crud'] = 'r'; + $this->legacyaction = 'view outputlist'; + } + public function get_description() { + return $this->get_description_mod('view outputlist'); + } +} diff --git a/filegroup.class.php b/filegroup.class.php index 61cb0f6f..4a7c4e52 100644 --- a/filegroup.class.php +++ b/filegroup.class.php @@ -328,6 +328,40 @@ public function getfiledata($mix) { return ''; } + /** + * Get file size by number or name + * + * @param int/string $mix + * @return int + */ + function get_file_size($mix) { + if (is_int( $mix )) { + $num = $mix; + $filelist = $this->getFileList(); + if ($num >=0 && $num < count($filelist)) { + $filename = $this->dir.self::encodeFileName( $filelist[$num] ); + if (file_exists( $filename )) { + return filesize( $filename ); + } else { + return -1; + } + } + } else if (is_string( $mix )) { + $filelist = $this->getFileList(); + if (array_search( $mix, $filelist ) !== false) { + $fullfilename = $this->dir.self::encodeFileName( $mix ); + if (file_exists( $fullfilename )) { + return filesize( $fullfilename ); + } else { + return -1; + } + } + } + + debugging( "File not found $mix" , DEBUG_DEVELOPER ); + return ''; + } + /** * Return is there is some file with data * @@ -409,5 +443,34 @@ public function download_files($name, $watermark = false) { die(); } } + + /** + * Download a file + * @param $filename filename + * @return boolean true, if the file has been sent, false otherwise. + */ + function download_file($filename) { + $filelist = $this->getFileList(); + + if (array_search( $filename, $filelist ) !== false) { + $fullfilename = $this->dir.self::encodeFileName( $filename ); + if (!file_exists( $fullfilename )) { + return false; + } + } else { + return false; + } + + @header( 'Content-Description: File Transfer' ); + @header( 'Content-Type: application/octet-stream' ); + @header( 'Content-Disposition: attachment; filename="'.basename($filename).'"' ); + @header( 'Expires: 0' ); + @header( 'Cache-Control: must-revalidate' ); + @header( 'Pragma: public' ); + @header( 'Content-Length: ' . filesize($fullfilename) ); + readfile( $fullfilename ); + return true; + } + } diff --git a/forms/gradesubmission.php b/forms/gradesubmission.php index 56cdb12f..2a5395eb 100644 --- a/forms/gradesubmission.php +++ b/forms/gradesubmission.php @@ -179,13 +179,15 @@ function vpl_grade_header($vpl, $inpopup) { } else { $res = $submission->getCE(); if ($res ['executed']) { - $graderaw = $submission->proposedGrade($res['execution']); - if ( $graderaw > '' ) { + $parsed_execution = $submission->parse_execution($res['execution']); + $graderaw = $parsed_execution->grade; + + if( $graderaw > '' ) { $data->grade = format_float($graderaw, 5, true, true); } else { $data->grade = ''; } - $data->comments = $submission->proposedComment( $res ['execution'] ); + $data->comments = $parsed_execution->comments; } } if (! empty( $CFG->enableoutcomes )) { diff --git a/forms/outputfiles.php b/forms/outputfiles.php new file mode 100644 index 00000000..63f41207 --- /dev/null +++ b/forms/outputfiles.php @@ -0,0 +1,150 @@ +. + +/** + * @version $Id: outputfiles.php,v 1.0 2016-09-07 20:19:00 fero Exp $ + * @package mod_vpl + * @copyright 2016 Frantisek Galcik + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Frantisek Galcik + */ + +require_once dirname(__FILE__) . '/../../../config.php'; +require_once dirname(__FILE__) . '/../locallib.php'; +require_once dirname(__FILE__) . '/../vpl.class.php'; +require_once $CFG->libdir . '/formslib.php'; + +class mod_vpl_outputfiles_form extends moodleform { + public $clear_selection = false; + public $clear_newfile = false; + + protected $filenames; + + function __construct($page, $filenames) { + $this->filenames = $filenames; + parent::__construct($page); + } + + protected function definition() { + $mform = &$this->_form; + $mform->addElement('hidden', 'id', required_param('id', PARAM_INT)); + $mform->setType('id', PARAM_INT); + $mform->addElement('header', 'header_outputfiles', get_string('outputfiles', VPL)); + + $mform->addElement('text', 'newfile', get_string('newoutputfile', VPL)); + $mform->setType('newfile', PARAM_NOTAGS); + $mform->addHelpButton('newfile', 'newoutputfile', VPL); + $mform->addElement('submit', 'addoutputfile', get_string('addoutputfile', VPL)); + + if (count($this->filenames) > 0) { + $num = 0; + foreach ($this->filenames as $filename) { + $mform->addElement('checkbox', 'outputfile' . $num, s($filename)); + $mform->setDefault('outputfile' . $num, false); + $num++; + } + $mform->addElement('submit', 'removeoutputfiles', get_string('removeoutputfiles', VPL)); + } + } + + public function validation($data, $files) { + $errors = array(); + if (!empty($data['addoutputfile'])) { + $filename = trim($data['newfile']); + if (($filename == '') || !vpl_is_valid_path_name($filename)) { + $errors['newfile'] = get_string('invalidfilename', VPL); + } + + if (in_array($filename, $this->filenames)) { + $errors['newfile'] = get_string('duplicatedfilename', VPL); + } + } + + return $errors; + } + + function definition_after_data() { + parent::definition_after_data(); + $mform = &$this->_form; + + if ((count($this->filenames) > 0) && ($this->clear_selection)) { + $num = 0; + foreach ($this->filenames as $filename) { + $mform->getElement('outputfile' . $num)->setValue(false); + $num++; + } + } + + if ($this->clear_newfile) { + $mform->getElement('newfile')->setValue(''); + } + } +} + +require_login(); + +$id = required_param('id', PARAM_INT); +$vpl = new mod_vpl($id); +$vpl->prepare_page('forms/outputfiles.php', array( + 'id' => $id +)); +vpl_include_jsfile('hideshow.js'); +$vpl->require_capability(VPL_MANAGE_CAPABILITY); + +//Display page +$vpl->print_header(get_string('outputfiles', VPL)); +$vpl->print_heading_with_help('outputfiles'); + +$filenames = $vpl->get_output_files(); +$mform = new mod_vpl_outputfiles_form('outputfiles.php', $filenames); +if ($fromform = $mform->get_data()) { + $list_changed = false; + $clear_newfile = false; + $clear_selection = false; + if (!empty($fromform->removeoutputfiles)) { + $nlist = count($filenames); + $new_filenames = array(); + for ($i = 0; $i < $nlist; $i++) { + $name = 'outputfile' . $i; + if (empty($fromform->$name)) { + $new_filenames[] = $filenames[$i]; + } + } + $filenames = $new_filenames; + $list_changed = true; + $clear_selection = true; + } + + if (!empty($fromform->addoutputfile)) { + array_unshift($filenames, trim($fromform->newfile)); + $list_changed = true; + $clear_newfile = true; + $clear_selection = true; + } + + if ($list_changed) { + $vpl->set_output_files($filenames); + \mod_vpl\event\vpl_outputlist_updated::log($vpl); + vpl_notice(get_string('outputlistupdated', VPL)); + $mform = new mod_vpl_outputfiles_form('outputfiles.php', $filenames); + $mform->clear_newfile = $clear_newfile; + $mform->clear_selection = $clear_selection; + } +} + +\mod_vpl\event\vpl_outputlist_viewed::log($vpl); +$mform->display(); +$vpl->print_footer(); \ No newline at end of file diff --git a/lang/en/vpl.php b/lang/en/vpl.php index 306c811f..90ae57bf 100644 --- a/lang/en/vpl.php +++ b/lang/en/vpl.php @@ -413,3 +413,21 @@

Each variation has an identification code and a description. The identification code is used by the vpl_enviroment.sh file to pass the variation assigned to each student to the script files. The description, formatted in HTML, is shown to the students that have assigned the corresponding variation.

'; + +$string['menuoutputfiles'] = 'Output files'; +$string['outputfiles'] = 'Output files'; +$string['outputfiles_help'] = 'Some help about output files'; +$string['newoutputfile'] = 'Filename'; +$string['newoutputfile_help'] = 'Some help about file name'; +$string['addoutputfile'] = 'Add'; +$string['removeoutputfiles'] = 'Remove selected'; +$string['duplicatedfilename'] = 'Duplicated filename'; +$string['invalidfilename'] = 'Invalid filename'; +$string['outputlistupdated'] = 'List of output files has been updated.'; +$string['checklist'] = 'Checklist'; +$string['message_error'] = 'Error:'; +$string['message_warning'] = 'Warning:'; +$string['message_note'] = 'Note:'; +$string['message_internal'] = 'Internal:'; +$string['test_ok'] = 'OK'; +$string['test_failed'] = 'FAILED'; \ No newline at end of file diff --git a/lib.php b/lib.php index f7c3938c..e2262574 100644 --- a/lib.php +++ b/lib.php @@ -510,6 +510,7 @@ function vpl_extend_settings_navigation(settings_navigation $settings, navigatio $strcheckjails = get_string( 'check_jail_servers', VPL ); $strsetjails = get_string( 'local_jail_servers', VPL ); $menustrexecutionkeepfiles = get_string( 'menukeepfiles', VPL ); + $menustroutputfiles = get_string('menuoutputfiles',VPL); $menustrcheckjails = get_string( 'menucheck_jail_servers', VPL ); $menustrsetjails = get_string( 'menulocal_jail_servers', VPL ); $advance->add( $strexecutionfiles, new moodle_url( '/mod/vpl/forms/executionfiles.php', $parms ) @@ -518,6 +519,8 @@ function vpl_extend_settings_navigation(settings_navigation $settings, navigatio , navigation_node::TYPE_SETTING ); $advance->add( $strexecutionkeepfiles, new moodle_url( '/mod/vpl/forms/executionkeepfiles.php', $parms ) , navigation_node::TYPE_SETTING ); + $advance->add( $menustroutputfiles, new moodle_url( '/mod/vpl/forms/outputfiles.php', $parms ) + , navigation_node::TYPE_SETTING ); $advance->add( $strvariations, new moodle_url( '/mod/vpl/forms/variations.php', $parms ) , navigation_node::TYPE_SETTING ); $advance->add( $strcheckjails, new moodle_url( '/mod/vpl/views/checkjailservers.php', $parms ) diff --git a/view.php b/view.php index f1ddf314..6036aef8 100644 --- a/view.php +++ b/view.php @@ -72,6 +72,22 @@ echo '

' . get_string( 'executionfiles', VPL ) . "

\n"; $fe->print_files( false ); } + + // print names of output files defined for this lab + $output_files = $vpl->get_output_files(); + if (count($output_files) > 0) { + echo '

'.get_string('outputfiles', VPL)."

\n"; + echo '
    '; + foreach ($output_files as $filename) { + // distinguish between hidden and normal files + if (substr(basename($filename), 0, 1) == '.') { + echo '
  • '.s($filename).'
  • '; + } else { + echo '
  • '.s($filename).'
  • '; + } + } + echo '
'; + } } // Finish the page. if (vpl_get_webservice_available()) { diff --git a/views/downloadoutputfile.php b/views/downloadoutputfile.php new file mode 100644 index 00000000..86e643b9 --- /dev/null +++ b/views/downloadoutputfile.php @@ -0,0 +1,96 @@ +. + +/** + * Download an output file + * + * @package mod_vpl + * @copyright 2016 Frantisek Galcik + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Frantisek Galcik + */ +require_once dirname(__FILE__) . '/../../../config.php'; +global $CFG, $USER; +require_once dirname(__FILE__) . '/../locallib.php'; +require_once dirname(__FILE__) . '/../vpl.class.php'; +require_once dirname(__FILE__) . '/../vpl_submission.class.php'; +try{ + require_login(); + $id = required_param('id',PARAM_INT); + $file = required_param('file', PARAM_FILE); + + $vpl = new mod_vpl($id); + $userid = optional_param('userid',FALSE,PARAM_INT); + $submissionid = optional_param('submissionid',FALSE,PARAM_INT); + + if(!$vpl->has_capability(VPL_GRADE_CAPABILITY)){ + $userid = FALSE; + $submissionid = FALSE; + } + //Read record + if($userid && $userid != $USER->id){ + //Grader + $vpl->require_capability(VPL_GRADE_CAPABILITY); + $grader =TRUE; + if($submissionid){ + $subinstance = $DB->get_record('vpl_submissions',array('id' => $submissionid)); + }else{ + $subinstance = $vpl->last_user_submission($userid); + } + } + else{ + //Download own submission + $vpl->require_capability(VPL_VIEW_CAPABILITY); + $userid = $USER->id; + $grader = FALSE; + if($submissionid && $vpl->has_capability(VPL_GRADE_CAPABILITY)){ + $subinstance = $DB->get_record('vpl_submissions',array('id' => $submissionid)); + }else{ + $subinstance = $vpl->last_user_submission($userid); + } + $vpl->password_check(); + } + + //Check consistence + if(!$subinstance){ + throw new Exception(get_string('nosubmission',VPL)); + } + if($subinstance->vpl != $vpl->get_instance()->id){ + throw new Exception(get_string('invalidcourseid')); + } + $submissionid = $subinstance->id; + + if($vpl->is_inconsistent_user($subinstance->userid,$userid)){ + throw new Exception('vpl submission user inconsistence'); + } + if($vpl->get_instance()->id != $subinstance->vpl){ + throw new Exception('vpl submission vpl inconsistence'); + } + $submission = new mod_vpl_submission($vpl,$subinstance); + $fgm = $submission->get_output_files_fgm(); + $is_hidden = (substr(basename($file), 0, 1) == '.'); + if (!$vpl->has_capability(VPL_GRADE_CAPABILITY) && $is_hidden) { + throw new Exception('file does not exists'); + } + if (!$fgm->download_file($file)) { + throw new Exception('file does not exists'); + } +}catch (Exception $e){ + $vpl->prepare_page('views/downloadoutputfile.php', array('id' => $id, 'file'=>$file)); + $vpl->print_header(get_string('download',VPL)); + echo $OUTPUT->box($e->getMessage()); + $vpl->print_footer(); +} diff --git a/vpl.class.php b/vpl.class.php index ac8b310d..2b87e79a 100644 --- a/vpl.class.php +++ b/vpl.class.php @@ -351,6 +351,31 @@ public function get_execution_fgm() { } return $this->executionfgm; } + + /** + * Returns name of file storing list of output files. + * @return string filename to store list of output files + **/ + function get_output_files_filename(){ + return $this->get_data_directory().'/output_files.lst'; + } + + /** + * Returns names of output files. + * @return array names of output files + **/ + function get_output_files(){ + return vpl_read_list_from_file($this->get_output_files_filename()); + } + + /** + * Sets names of output files. + * @param $files array list of filenames of output files. + */ + function set_output_files($files) { + vpl_write_list_to_file($this->get_output_files_filename(), $files); + } + // FIXME check and remove function. public function set_initial_file($name, $files) { $filelist = ''; diff --git a/vpl_submission.class.php b/vpl_submission.class.php index de98b9d8..ed472283 100644 --- a/vpl_submission.class.php +++ b/vpl_submission.class.php @@ -53,6 +53,12 @@ class mod_vpl_submission { */ protected $submittedfgm; + /** + * Internal var object to output file group manager + * @var object of file group manager + */ + protected $output_files_fgm; + /** * Constructor * @@ -156,6 +162,33 @@ public function set_submitted_file($files) { $fg = $this->get_submitted_fgm(); $fg->addallfiles($files); } + + /** + * get path to directory with output files + * @return string directory with output files + */ + function get_output_files_directory() { + return $this->get_data_directory().'outputfiles/'; + } + + /** + * get absolute path to name of file with list of output files + * @return string file name + **/ + function get_outputfileslistname() { + return $this->get_data_directory().'outputfiles.lst'; + } + + /** + * @return object file group manager for output files + **/ + function get_output_files_fgm(){ + if (!$this->output_files_fgm) { + $this->output_files_fgm = new file_group_process( $this->get_outputfileslistname(), $this->get_output_files_directory() ); + } + return $this->output_files_fgm; + } + public function is_equal_to(&$files, $comment = '') { if ($this->instance->comments != $comment) { return false; @@ -635,30 +668,82 @@ public function print_info($autolink = false) { */ public function print_ce() { global $OUTPUT; - $ce = $this->getce(); - if ($ce ['compilation'] === 0) { + $ce = $this->getCE(); + if($ce['compilation'] === 0){ return; } - $this->get_ce_html( $ce, $compilation, $execution, $grade, true, true ); - if (strlen( $compilation ) + strlen( $execution ) + strlen( $grade ) > 0) { - $div = new vpl_hide_show_div( ! $this->is_graded() || ! $this->vpl->get_visiblegrade() ); - echo '

' . get_string( 'automaticevaluation', VPL ) . $div->generate( true ) . '

'; + + $ce_html = $this->get_ce_html($ce, true, true); + $outputfiles = $this->get_ce_output_files_html(); + + $show_evaluation = false; + $show_evaluation |= strlen($ce_html->compilation) > 0; + $show_evaluation |= strlen($ce_html->execution) > 0; + $show_evaluation |= strlen($ce_html->grade) > 0; + $show_evaluation |= strlen($ce_html->checklist) > 0; + $show_evaluation |= strlen($outputfiles) > 0; + + if($show_evaluation){ + $div = new vpl_hide_show_div(!$this->is_graded() || !$this->vpl->get_visiblegrade()); + echo '

'.get_string('automaticevaluation',VPL).$div->generate(true).'

'; $div->begin_div(); echo $OUTPUT->box_start(); - if (strlen( $grade ) > 0) { - echo '' . $grade . '
'; + if(strlen($ce_html->grade)>0){ + echo ''.$ce_html->grade.'
'; } - if (strlen( $compilation ) > 0) { - echo $compilation; + if(strlen($ce_html->compilation)>0){ + echo $ce_html->compilation; } - if (strlen( $execution ) > 0) { - echo $execution; + if(strlen($ce_html->execution)>0){ + echo $ce_html->execution; } + if (strlen($ce_html->checklist)>0) { + echo $ce_html->checklist; + } + if (strlen($outputfiles)>0){ + echo $outputfiles; + } + echo $OUTPUT->box_end(); $div->end_div(); } } + private function get_ce_output_files_html() { + $fgm = $this->get_output_files_fgm(); + $output_files = $fgm->getFileList(); + $downloadable_files = array(); + $is_grader = $this->vpl->has_capability(VPL_GRADE_CAPABILITY); + foreach ($output_files as $output_file) { + $is_hidden = (substr(basename($output_file), 0, 1) == '.'); + if ($is_grader || !$is_hidden) { + $downloadable_files[] = $output_file; + } + } + + if (count($downloadable_files) == 0) { + return ''; + } + + $id = $this->vpl->get_course_module()->id; + $user_id = $this->instance->userid; + $submission_id = $this->instance->id; + + $ret = ''; + $ret .= '

'.get_string('outputfiles', VPL).'

'."\n"; + $ret .= '
    '; + foreach ($downloadable_files as $file) { + $url = vpl_mod_href('views/downloadoutputfile.php', 'id', $id, 'userid', $user_id, 'submissionid', $submission_id, 'file', $file); + $fs = $fgm->get_file_size($file); + if ($fs >= 0) { + $ret .= '
  • '.s($file).' ('.$fs.' B)
  • '; + } + } + $ret .= '
'; + + return $ret; + } + /** * Print sudmission */ @@ -674,6 +759,8 @@ public function print_submission() { const COMMENTTAG = 'Comment :=>>'; const BEGINCOMMENTTAG = '<|--'; const ENDCOMMENTTAG = '--|>'; + const CHECKLISTTAG = 'Checklist :=>>'; + public function proposedgrade($text) { $ret = ''; $nl = vpl_detect_newline( $text ); @@ -686,6 +773,9 @@ public function proposedgrade($text) { return $ret; } public function proposedcomment($text) { + $parsed_execution = $this->parse_execution($text); + return $parsed_execution->comments; + /* $incomment = false; $ret = ''; $nl = vpl_detect_newline( $text ); @@ -708,6 +798,70 @@ public function proposedcomment($text) { } } return $ret; + */ + } + + /** + * Parses raw execution. + * @param $text string the raw execution result + * @return string execution result without lines with/in tags. + */ + function parse_execution($text) { + $ret = new stdClass(); + $ret->grade = ''; + $ret->comments = ''; + $ret->checklist = array(); + $ret->execution = ''; + + $nl = vpl_detect_newline($text); + $lines = explode($nl,$text); + + $closing_tag = false; + $tagPairs = array(self::GRADETAG => false, self::COMMENTTAG => false, self::CHECKLISTTAG => false, self::BEGINCOMMENTTAG => self::ENDCOMMENTTAG); + foreach($lines as $line){ + $line = rtrim($line); + $tline = trim($line); + if($closing_tag !== false) { + // we are in a block tag + if ($tline === $closing_tag) { + $closing_tag = false; + } else { + // handle line in a block tag + if ($closing_tag === self::ENDCOMMENTTAG) { + $ret->comments .= $line."\n"; + } + } + }else{ + // detect presence of opening tag + $opening_tag = false; + foreach ($tagPairs as $opening => $closing) { + if (substr($line, 0, strlen($opening)) === $opening) { + $opening_tag = $opening; + $closing_tag = $closing; + break; + } + } + + // remove opening tag from line when found + if ($opening_tag !== false) { + $line = substr($line, strlen($opening_tag)); + } + + // handle line according to found tag + if ($opening_tag === false) { + $ret->execution .= $line."\n"; + } elseif ($opening_tag === self::COMMENTTAG) { + $ret->comments .= $line."\n"; + } elseif ($opening_tag === self::CHECKLISTTAG) { + $ret->checklist[] = $line; + } elseif ($opening_tag === self::GRADETAG) { + if ($ret->grade == '') { + $ret->grade = $line; + } + } + } + } + return $ret; } /** @@ -919,6 +1073,17 @@ public function savece($result) { if ($result ['executed'] > 0) { file_put_contents( $execfn, $result ['execution'] ); } + + if(isset($result['outputfiles'])) { + $fgm = $this->get_output_files_fgm(); + foreach ($result['outputfiles'] as $filename => $content) { + if (is_string($content)) { + $fgm->addFile($filename, $content); + } else if (is_object($content)) { + $fgm->addFile($filename, $content->scalar); + } + } + } } /** @@ -944,58 +1109,61 @@ public function getce() { return $ret; } - /** - * Get compilation, execution and proposed grade from array - * - * @param $response array - * response from server - * @param - * $compilation - * @param - * $execution - * @param - * $grade - * @return void - */ - public function get_ce_html($response, &$compilation, &$execution, &$grade, $dropdown, $returnrawexecution = false) { - $compilation = ''; - $execution = ''; - $grade = ''; - if ($response ['compilation']) { - $compilation = $this->result_to_html( $response ['compilation'], $dropdown ); - if (strlen( $compilation )) { - $compilation = '' . get_string( 'compilation', VPL ) . '
' . $compilation; + public function get_ce_html($response, $dropdown, $returnrawexecution=false){ + $ret = new stdClass(); + $ret->compilation = ''; + $ret->execution = ''; + $ret->grade = ''; + $ret->checklist = ''; + + if($response['compilation']){ + $ret->compilation = $this->result_to_html($response['compilation'],$dropdown); + if(strlen($ret->compilation)>0){ + $ret->compilation =''.get_string('compilation',VPL).'
'.$ret->compilation; } } - if ($response ['executed'] > 0) { - $rawexecution = $response ['execution']; - $proposedcomments = $this->proposedcomment( $rawexecution ); - $proposedgrade = $this->proposedgrade( $rawexecution ); - $execution = $this->result_to_html( $proposedcomments, $dropdown ); - if (strlen( $execution )) { - $execution = '' . get_string( 'comments', VPL ) . "
\n" . $execution; + + if($response['executed']>0){ + $raw_execution = $response['execution']; + $parsed_execution = $this->parse_execution($raw_execution); + $proposed_comments = $parsed_execution->comments; + $proposed_grade = $parsed_execution->grade; + $execution=$this->result_to_HTML($proposed_comments,$dropdown); + if(strlen($execution)>0){ + $execution = ''.get_string('comments',VPL)."
\n".$execution; } - if (strlen( $proposedgrade )) { - $sgrade = $this->print_grade_core( $proposedgrade ); - $grade = get_string( 'proposedgrade', VPL, $sgrade ); + if(strlen($proposed_grade)>0){ + $sgrade = $this->print_grade_core($proposed_grade); + $ret->grade = get_string('proposedgrade',VPL,$sgrade); } - // Show raw ejecution if no grade or comments. - if (strlen( $rawexecution ) > 0 && (strlen( $execution ) + strlen( $proposedgrade ) == 0)) { - $execution .= "
\n"; - $execution .= '' . get_string( 'execution', VPL ) . "
\n"; - $execution .= '
' . s( $rawexecution ) . '
'; - } else if ($returnrawexecution && strlen( $rawexecution ) > 0 - && ($this->vpl->has_capability( VPL_MANAGE_CAPABILITY ))) { - // Show raw ejecution if manager and $returnrawexecution. + + if (count($parsed_execution->checklist)>0) { + $ret->checklist = $this->get_checklist_html($parsed_execution->checklist); + } + + // show raw ejecution if no grade or comments + if(strlen($raw_execution)>0 && + (strlen($execution)+strlen($proposed_grade)==0) ){ + $execution .="
\n"; + $execution .=''.get_string('execution',VPL)."
\n"; + $execution .= '
'.s($parsed_execution->execution).'
'; + } // show raw ejecution if manager and $returnrawexecution + elseif($returnrawexecution && strlen($raw_execution)>0 && + ($this->vpl->has_capability(VPL_MANAGE_CAPABILITY))){ $div = new vpl_hide_show_div(); - $execution .= "
\n"; - $execution .= '' . get_string( 'execution', VPL ) . $div->generate( true ) . "
\n"; - $execution .= $div->begin_div( true ); - $execution .= '
' . s( $rawexecution ) . '
'; - $execution .= $div->end_div( true ); + $execution .="
\n"; + $execution .=''.get_string('execution',VPL).$div->generate(true)."
\n"; + $execution .=$div->begin_div(true); + $execution .= '
'.s($raw_execution).'
'; + $execution .=$div->end_div(true); } + + $ret->execution = $execution; } + + return $ret; } + public function get_ce_for_editor($response = null) { $ce = new stdClass(); $ce->compilation = ''; @@ -1010,8 +1178,10 @@ public function get_ce_for_editor($response = null) { } if ($response ['executed'] > 0) { $rawexecution = $response ['execution']; - $evaluation = $this->proposedcomment( $rawexecution ); - $proposedgrade = $this->proposedgrade( $rawexecution ); + $parsed_execution = $this->parse_execution($rawexecution); + $evaluation = $parsed_execution->comments; + $proposedgrade = $parsed_execution->grade; + $ce->evaluation = $evaluation; // TODO Important what to show to students about grade. if (strlen( $proposedgrade ) && $this->vpl->get_instance()->grade) { @@ -1020,7 +1190,11 @@ public function get_ce_for_editor($response = null) { } // Show raw ejecution if no grade or comments. $manager = $this->vpl->has_capability( VPL_MANAGE_CAPABILITY ); - if ((strlen( $rawexecution ) > 0 && (strlen( $evaluation ) + strlen( $proposedgrade ) == 0)) || $manager) { + if ((strlen( $rawexecution ) > 0 && (strlen( $evaluation ) + strlen( $proposedgrade ) == 0)) && !$manager) { + $ce->execution = $parsed_execution->execution; + } + + if ($manager) { $ce->execution = $rawexecution; } } @@ -1043,17 +1217,135 @@ public function get_detail() { } public function get_ce_parms() { $response = $this->getce(); - $this->get_ce_html( $response, $compilation, $execution, $grade, false ); + $ce_html = $this->get_ce_html($response, false); $params = ''; - if (strlen( $compilation )) { - $params .= vpl_param_tag( 'compilation', $compilation ); + if (strlen( $ce_html->compilation )) { + $params .= vpl_param_tag( 'compilation', $ce_html->compilation ); } - if (strlen( $execution )) { - $params .= vpl_param_tag( 'evaluation', $execution ); + if (strlen( $ce_html->execution )) { + $params .= vpl_param_tag( 'evaluation', $ce_html->execution ); } - if (strlen( $grade )) { - $params .= vpl_param_tag( 'grade', $grade ); + if (strlen( $ce_html->grade )) { + $params .= vpl_param_tag( 'grade', $ce_html->grade ); } return $params; } + + /** + * Returns checklist transformed to html. + * @param $checklist array items of generated checklist + * @return string checklist formatted as html + */ + private function get_checklist_html($checklist) { + // each checklist line has format: type > details + // types: group (named group of checklist items - test), test (a test), + // error (error message related to the last test) + // warning (warning message related to the last test) + // note (information message related to the last test) + // internal (information message related to the last test which is available only for graders) + // OK (indicates that the last test passed) + // FAILED (indicates that the last test failed) + + $is_grader = $this->vpl->has_capability(VPL_GRADE_CAPABILITY); + + // build checklist tree + $message_types = array('error', 'warning', 'note', 'internal'); + $groups = array(); + $current_group = null; + $current_test = null; + foreach ($checklist as $line) { + $details = ''; + $separator = strpos($line, '>'); + if ($separator === false) { + $type = trim($line); + } else { + $type = trim(substr($line, 0, $separator)); + $details = trim(substr($line, $separator+1)); + } + + // ensure group + if (($type === 'group') || is_null($current_group)) { + $current_group = new stdClass(); + $current_group->title = ''; + $current_group->tests = array(); + $groups[] = $current_group; + $current_test = null; + } + + if ($type === 'group') { + $current_group->title = $details; + continue; + } + + // ensure test + if (($type === 'test') || is_null($current_test)) { + $current_test = new stdClass(); + $current_test->title = ''; + $current_test->messages = array(); + $current_test->status = false; + $current_group->tests[] = $current_test; + } + + if ($type == 'test') { + $current_test->title = $details; + continue; + } + + if ($type === 'OK') { + $current_test->status = true; + continue; + } + + if ($type === 'FAILED') { + $current_test->status = false; + continue; + } + + if (in_array($type, $message_types)) { + $message = new stdClass(); + $message->type = $type; + $message->content = $details; + $current_test->messages[] = $message; + } + } + + // generate html + $ret = ''; + foreach ($groups as $group) { + if (count($group->tests) == 0) { + continue; + } + + $table = new html_table(); + $table->caption = s($group->title); + $table->align = array ('left', 'right'); + $table->data = array(); + + foreach ($group->tests as $test) { + $messages = ''; + foreach ($test->messages as $message) { + if (($message->type !== 'internal') || (($message->type === 'internal') && $is_grader)) { + $messages .= '
  • ' . get_string('message_' . $message->type, VPL) . ' ' . s($message->content) . '
  • '; + } + } + + if (strlen($messages) > 0) { + $messages = '
      '.$messages.'
    '; + } + + if ($test->status) { + $status_html = ''.get_string('test_ok', VPL).''; + } else { + $status_html = ''.get_string('test_failed', VPL).''; + } + + $table->data[] = array(s($test->title).$messages, $status_html); + } + + $ret .= html_writer::table($table); + } + + return $ret; + } + } diff --git a/vpl_submission_CE.class.php b/vpl_submission_CE.class.php index 3017eb40..c088bb32 100644 --- a/vpl_submission_CE.class.php +++ b/vpl_submission_CE.class.php @@ -313,10 +313,16 @@ public function prepare_execution($type, &$already = array(), $vpl = null) { $data->files ['vpl_environment.sh'] = $info; $data->files ['common_script.sh'] = file_get_contents( dirname( __FILE__ ) . '/jail/default_scripts/common_script.sh' ); - // TODO change jail server to avoid this patch. + // TODO change jail server to avoid this patch (NOTE: can be removed as jail server fixes it) if (count( $data->filestodelete ) == 0) { // If keeping all files => add dummy. $data->filestodelete ['__vpl_to_delete__'] = 1; } + + // Add names of output files + foreach ($vpl->get_output_files() as $filename) { + $data->outputfiles [$filename] = 1; + } + // Info to log who/what. $data->userid = $this->instance->userid; $data->activityid = $this->vpl->get_instance()->id; @@ -445,8 +451,9 @@ public function retrieveresult() { // If automatic grading. if ($this->vpl->get_instance()->automaticgrading) { $data = new StdClass(); - $data->grade = $this->proposedGrade( $response ['execution'] ); - $data->comments = $this->proposedComment( $response ['execution'] ); + $parsed_execution = $this->parse_execution( $response['execution'] ); + $data->grade = $parsed_execution->grade; + $data->comments = $parsed_execution->comments; $this->set_grade( $data, true ); } } From 51b38bc7bd6c27bb07e3e2af050dbdc7042a1a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Gal=C4=8D=C3=ADk?= Date: Fri, 16 Sep 2016 22:42:31 +0200 Subject: [PATCH 2/5] Add help for output files. --- lang/en/vpl.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/en/vpl.php b/lang/en/vpl.php index 90ae57bf..3c96ef13 100644 --- a/lang/en/vpl.php +++ b/lang/en/vpl.php @@ -416,9 +416,9 @@ $string['menuoutputfiles'] = 'Output files'; $string['outputfiles'] = 'Output files'; -$string['outputfiles_help'] = 'Some help about output files'; +$string['outputfiles_help'] = 'If the evaluation produces supplementary files, you can specify their filenames and these files will be included to the result of evaluation.'; $string['newoutputfile'] = 'Filename'; -$string['newoutputfile_help'] = 'Some help about file name'; +$string['newoutputfile_help'] = 'Dot prefixed filenames determine hidden files which are available only to graders.'; $string['addoutputfile'] = 'Add'; $string['removeoutputfiles'] = 'Remove selected'; $string['duplicatedfilename'] = 'Duplicated filename'; From 5a3b00dc68246243759497a2d76eef2cf27c270b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Gal=C4=8D=C3=ADk?= Date: Wed, 21 Sep 2016 20:03:56 +0200 Subject: [PATCH 3/5] Fix bug - saving of required files. --- forms/requiredfiles.json.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/requiredfiles.json.php b/forms/requiredfiles.json.php index 7c5a2f38..c5aba2c3 100644 --- a/forms/requiredfiles.json.php +++ b/forms/requiredfiles.json.php @@ -49,7 +49,7 @@ 'action' => $action ) ) ); echo $OUTPUT->header(); // Send headers. - $data = json_decode( file_get_contents( 'php://input' ) ); + $actiondata = json_decode( file_get_contents( 'php://input' ) ); switch ($action) { case 'save' : $postfiles = mod_vpl_edit::filesfromide($actiondata->files); From d13173445576f1d77bbce0fb315164922e397884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Gal=C4=8D=C3=ADk?= Date: Sat, 24 Sep 2016 08:40:39 +0200 Subject: [PATCH 4/5] Fix bug with submission of files and evaluation. --- forms/evaluation.php | 2 +- forms/submission.php | 26 ++++++++++++++------------ lang/en/vpl.php | 36 +++++++++++++++++++----------------- vpl.class.php | 4 ++++ 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/forms/evaluation.php b/forms/evaluation.php index fdd10578..e3e939dd 100644 --- a/forms/evaluation.php +++ b/forms/evaluation.php @@ -64,5 +64,5 @@ } else { $nexturl = "../forms/submissionview.php?id={$id}&userid={$userid}"; } -vpl_editor_util::generateEvaluateScript( $ajaxurl, $nexturl ); +vpl_editor_util::generate_evaluate_script( $ajaxurl, $nexturl ); $vpl->print_footer(); diff --git a/forms/submission.php b/forms/submission.php index 5cac5965..72d6422b 100644 --- a/forms/submission.php +++ b/forms/submission.php @@ -74,8 +74,11 @@ die(); } $rfn = $vpl->get_required_fgm(); - $minfiles = count( $rfn->getFilelist() ); + $required_files = $rfn->getFilelist(); + $minfiles = count( $required_files ); $files = array (); + + $errormessage = ''; for ($i = 0; $i < $instance->maxfiles; $i ++) { $attribute = 'file' . $i; $name = $mform->get_new_filename( $attribute ); @@ -88,21 +91,20 @@ $data = iconv( $encode, 'UTF-8', $data ); } } - $files [] = array ( - 'name' => $name, - 'data' => $data - ); + // check file duplicates as indication of wrong submission + if (array_key_exists($name, $files) && !is_null($files [$name])) { + $errormessage .= s(get_string('duplicate_file', VPL, $name)) . "
    "; + break; + } + $files [$name] = $data; } else { - if ($i < $minfiles) { // Add empty file if required. - $files [] = array ( - 'name' => '', - 'data' => '' - ); + // if required file is missing, add the file with undefined content. + if ($i < $minfiles) { + $files [$required_files[$i]] = null; } } } - $errormessage = ''; - if ($subid = $vpl->add_submission( $userid, $files, $fromform->comments, $errormessage )) { + if ((strlen($errormessage) == 0) && ($subid = $vpl->add_submission( $userid, $files, $fromform->comments, $errormessage ))) { \mod_vpl\event\submission_uploaded::log( array ( 'objectid' => $subid, 'context' => $vpl->get_context(), diff --git a/lang/en/vpl.php b/lang/en/vpl.php index 3c96ef13..58eccfa6 100644 --- a/lang/en/vpl.php +++ b/lang/en/vpl.php @@ -414,20 +414,22 @@ the variation assigned to each student to the script files. The description, formatted in HTML, is shown to the students that have assigned the corresponding variation.

    '; -$string['menuoutputfiles'] = 'Output files'; -$string['outputfiles'] = 'Output files'; -$string['outputfiles_help'] = 'If the evaluation produces supplementary files, you can specify their filenames and these files will be included to the result of evaluation.'; -$string['newoutputfile'] = 'Filename'; -$string['newoutputfile_help'] = 'Dot prefixed filenames determine hidden files which are available only to graders.'; -$string['addoutputfile'] = 'Add'; -$string['removeoutputfiles'] = 'Remove selected'; -$string['duplicatedfilename'] = 'Duplicated filename'; -$string['invalidfilename'] = 'Invalid filename'; -$string['outputlistupdated'] = 'List of output files has been updated.'; -$string['checklist'] = 'Checklist'; -$string['message_error'] = 'Error:'; -$string['message_warning'] = 'Warning:'; -$string['message_note'] = 'Note:'; -$string['message_internal'] = 'Internal:'; -$string['test_ok'] = 'OK'; -$string['test_failed'] = 'FAILED'; \ No newline at end of file +$string ['menuoutputfiles'] = 'Output files'; +$string ['outputfiles'] = 'Output files'; +$string ['outputfiles_help'] = 'If the evaluation produces supplementary files, you can specify their filenames and these files will be included to the result of evaluation.'; +$string ['newoutputfile'] = 'Filename'; +$string ['newoutputfile_help'] = 'Dot prefixed filenames determine hidden files which are available only to graders.'; +$string ['addoutputfile'] = 'Add'; +$string ['removeoutputfiles'] = 'Remove selected'; +$string ['duplicatedfilename'] = 'Duplicated filename'; +$string ['invalidfilename'] = 'Invalid filename'; +$string ['outputlistupdated'] = 'List of output files has been updated.'; +$string ['checklist'] = 'Checklist'; +$string ['message_error'] = 'Error:'; +$string ['message_warning'] = 'Warning:'; +$string ['message_note'] = 'Note:'; +$string ['message_internal'] = 'Internal:'; +$string ['test_ok'] = 'OK'; +$string ['test_failed'] = 'FAILED'; +$string ['missing_requested_file'] = 'Missing requested file: {$a}'; +$string ['duplicate_file'] = 'Duplicate upload of file \'{$a}\'.'; \ No newline at end of file diff --git a/vpl.class.php b/vpl.class.php index 2b87e79a..07c244be 100644 --- a/vpl.class.php +++ b/vpl.class.php @@ -633,6 +633,10 @@ public function pass_submission_restriction(& $alldata, & $error) { $a->found = $name; $error .= s( get_string( 'unexpected_file_name', VPL, $a ) ) . "
    "; } + if ($i < $lr && is_null($data)) { + $error .= s( get_string( 'missing_requested_file', VPL, $list[ $i ] ) ) . "
    "; + } + $i++; } return strlen( $error ) == 0; From c825e21de739e0a77f832aa222ae01c5abaa742b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Gal=C4=8D=C3=ADk?= Date: Sat, 24 Sep 2016 11:36:15 +0200 Subject: [PATCH 5/5] Add faster js text sanitization, fix problem with accordion refresh. --- editor/VPLIDE.js | 3 ++- editor/VPLUtil.js | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/editor/VPLIDE.js b/editor/VPLIDE.js index bcaeeb3c..f403f8e0 100644 --- a/editor/VPLIDE.js +++ b/editor/VPLIDE.js @@ -665,7 +665,8 @@ result_container.show(); result_container.width(menu.width() / 3); } - result.accordion('refresh'); + // causes exception (not fully loaded?); refresh is invoked later during autoResizeTab + //result.accordion('refresh'); if (grade > '') { result.accordion('option', 'active', 1); } else { diff --git a/editor/VPLUtil.js b/editor/VPLUtil.js index 8d74f758..7cc2c33c 100644 --- a/editor/VPLUtil.js +++ b/editor/VPLUtil.js @@ -38,7 +38,15 @@ return $JQVPL('
    ' + t + '
    ').html(); }; VPL_Util.sanitizeText = function(s) { - return s.replace(/&/g, "&").replace(//g, ">"); + var map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + return s.replace(/[&<>"']/g, function(m) { return map[m]; }); }; VPL_Util.setProtocol = function(coninfo) {