From 064105e357fb5dc12e2b8f4ac3389bef2edbbfe3 Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Wed, 18 Jul 2007 21:13:53 +0000 Subject: [PATCH] Automatic grading Google Summer of Code project (Epaile assignment module) by Arkaitz Garo, First commit. --- lang/en_utf8/assignment.php | 27 +- mod/assignment/config.html | 41 + .../type/program/assignment.class.php | 1127 +++++++++++++++++ mod/assignment/type/program/db/mysql.sql | 56 + mod/assignment/type/program/js/tests.js | 105 ++ mod/assignment/type/program/languages/bash.sh | 44 + mod/assignment/type/program/languages/c.sh | 14 + mod/assignment/type/program/languages/cpp.sh | 13 + .../type/program/languages/haskell.sh | 19 + mod/assignment/type/program/languages/java.sh | 55 + .../type/program/languages/pascal.sh | 19 + mod/assignment/type/program/languages/perl.sh | 42 + mod/assignment/type/program/mod.html | 62 + mod/assignment/type/program/source.html | 25 + mod/assignment/type/program/source.php | 95 ++ .../syntaxhighlighter/Scripts/clipboard.swf | Bin 0 -> 109 bytes .../Scripts/shBrushCSharp.js | 32 + .../syntaxhighlighter/Scripts/shBrushCpp.js | 73 ++ .../syntaxhighlighter/Scripts/shBrushCss.js | 52 + .../Scripts/shBrushDelphi.js | 34 + .../Scripts/shBrushJScript.js | 22 + .../syntaxhighlighter/Scripts/shBrushJava.js | 28 + .../syntaxhighlighter/Scripts/shBrushPhp.js | 60 + .../Scripts/shBrushPython.js | 30 + .../syntaxhighlighter/Scripts/shBrushRuby.js | 28 + .../syntaxhighlighter/Scripts/shBrushSql.js | 42 + .../syntaxhighlighter/Scripts/shBrushVb.js | 29 + .../syntaxhighlighter/Scripts/shBrushXml.js | 70 + .../syntaxhighlighter/Scripts/shCore.js | 414 ++++++ .../Scripts/shCore.uncompressed.js | 674 ++++++++++ .../Styles/SyntaxHighlighter.css | 158 +++ .../syntaxhighlighter/Styles/TestPages.css | 63 + .../syntaxhighlighter/Templates/Test.dwt | 80 ++ 33 files changed, 3632 insertions(+), 1 deletion(-) create mode 100644 mod/assignment/type/program/assignment.class.php create mode 100644 mod/assignment/type/program/db/mysql.sql create mode 100644 mod/assignment/type/program/js/tests.js create mode 100644 mod/assignment/type/program/languages/bash.sh create mode 100644 mod/assignment/type/program/languages/c.sh create mode 100644 mod/assignment/type/program/languages/cpp.sh create mode 100644 mod/assignment/type/program/languages/haskell.sh create mode 100644 mod/assignment/type/program/languages/java.sh create mode 100644 mod/assignment/type/program/languages/pascal.sh create mode 100644 mod/assignment/type/program/languages/perl.sh create mode 100644 mod/assignment/type/program/mod.html create mode 100644 mod/assignment/type/program/source.html create mode 100644 mod/assignment/type/program/source.php create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/clipboard.swf create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCSharp.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCpp.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCss.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushDelphi.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushJScript.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushJava.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushPhp.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushPython.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushRuby.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushSql.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushVb.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushXml.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.uncompressed.js create mode 100644 mod/assignment/type/program/syntaxhighlighter/Styles/SyntaxHighlighter.css create mode 100644 mod/assignment/type/program/syntaxhighlighter/Styles/TestPages.css create mode 100644 mod/assignment/type/program/syntaxhighlighter/Templates/Test.dwt diff --git a/lang/en_utf8/assignment.php b/lang/en_utf8/assignment.php index 8af0a8c405..c68d79217a 100644 --- a/lang/en_utf8/assignment.php +++ b/lang/en_utf8/assignment.php @@ -1,7 +1,7 @@ teacher has posted some feedback on your assignment submission for \'$a->assignment\' @@ -24,9 +25,13 @@ $string['assignmenttype'] = 'Assignment type'; $string['availabledate'] = 'Available from'; $string['comment'] = 'Comment'; $string['commentinline'] = 'Comment inline'; +$string['compileerrors'] = 'Compile errors'; $string['configitemstocount'] = 'Nature of items to be counted for student submissions in online assignments.'; $string['configmaxbytes'] = 'Default maximum assignment size for all assignments on the site (subject to course limits and other local settings)'; +$string['configmaxcpu'] = 'Default maximum assignment cpu time for all assignments on the site (subject to other local settings)'; +$string['configmaxmem'] = 'Default maximum assignment memory usage for all assignments on the site (subject to other local settings)'; $string['confirmdeletefile'] = 'Are you absolutely sure you want to delete this file?
$a'; +$string['crondate'] = 'Cron date'; $string['deletefilefailed'] = 'Deleting of file failed.'; $string['description'] = 'Description'; $string['draft'] = 'Draft'; @@ -46,6 +51,7 @@ for \'$a->assignment\'

It is url\">available on the web site.'; $string['emailteachers'] = 'Email alerts to teachers'; $string['emptysubmission'] = 'You have not submitted anything yet'; +$string['exactouput'] = 'Exact output'; $string['existingfiledeleted'] = 'Existing file has been deleted: $a'; $string['failedupdatefeedback'] = 'Failed to update submission feedback for user $a'; $string['feedback'] = 'Feedback'; @@ -73,9 +79,23 @@ $string['helpuploadsingle'] = '

This type of assignment allows each participan single file, of any type.

This might be a Word processor document, an image, a zipped web site, or anything you ask them to submit.

'; $string['hideintro'] = 'Hide description before available date'; +$string['ignorespace'] = 'Ignore spaces'; +$string['ignorecase'] = 'Ignore Upcase / Lowcase'; +$string['input'] = 'Input'; +$string['langbash'] = 'Bash'; +$string['langc'] = 'C'; +$string['langcpp'] = 'C++'; +$string['langhaskell'] = 'Haskell'; +$string['langjava'] = 'Java'; +$string['langpascal'] = 'Pascal'; +$string['langperl'] = 'Perl'; $string['late'] = '$a late'; +$string['loading'] = 'Grade process in progress... please wait'; +$string['maximumcpu'] = 'Maximum CPU time (seconds)'; $string['maximumgrade'] = 'Maximum grade'; +$string['maximummem'] = 'Maximum memory usage'; $string['maximumsize'] = 'Maximum size'; +$string['maximumfilesize'] = 'Maximum source file size'; $string['modulename'] = 'Assignment'; $string['modulenameplural'] = 'Assignments'; $string['newsubmissions'] = 'Assignments submitted'; @@ -90,12 +110,15 @@ $string['notesupdateerror'] = 'Error when updating notes'; $string['notgradedyet'] = 'Not graded yet'; $string['notsubmittedyet'] = 'Not submitted yet'; $string['onceassignmentsent'] = 'Once the assignment is sent for marking, you will no longer be able to delete or attach file(s). Do you want to continue?'; +$string['output'] = 'Output'; +$string['outputfilter'] = 'Output filter'; $string['overwritewarning'] = 'Warning: uploading again will REPLACE your current submission'; $string['pagesize'] = 'Submissions shown per page'; $string['preventlate'] = 'Prevent late submissions'; $string['quickgrade'] = 'Allow quick grading'; $string['responsefiles'] = 'Response files'; $string['reviewed'] = 'Reviewed'; +$string['runtimeerrors'] = 'Runtime errors'; $string['saveallfeedback'] = 'Save all my feedback'; $string['sendformarking'] = 'Send for marking'; $string['submission'] = 'Submission'; @@ -109,8 +132,10 @@ $string['submitedformarking'] = 'Assignment was already submitted for marking an $string['submitformarking'] = 'Final submission for assignment marking'; $string['submitted'] = 'Submitted'; $string['submittedfiles'] = 'Submitted files'; +$string['tests'] = 'Program tests'; $string['typeoffline'] = 'Offline activity'; $string['typeonline'] = 'Online text'; +$string['typeprogram'] = 'Epaile'; $string['typeupload'] = 'Advanced uploading of files'; $string['typeuploadsingle'] = 'Upload a single file'; $string['unfinalize'] = 'Revert to draft'; diff --git a/mod/assignment/config.html b/mod/assignment/config.html index 6eedcdff2d..0f55ba3b7f 100644 --- a/mod/assignment/config.html +++ b/mod/assignment/config.html @@ -29,6 +29,47 @@ + + assignment_maxcpu: + + assignment_program_get_max_cpu_times + */ + require_once $CFG->dirroot.'/mod/assignment/type/program/assignment.class.php'; + DEFINE('MAX_CPU',60); + + unset($choices); + $choices = assignment_program_get_max_cpu_times(MAX_CPU); + choose_from_menu($choices, "assignment_maxcpu", $CFG->assignment_maxcpu, ""); + ?> + + + + + + + + assignment_maxmem: + + assignment_program_get_max_mem_usages + */ + DEFINE('MAX_MEM',10485760); + + unset($choices); + $choices = assignment_program_get_max_memory_usages(MAX_MEM); + choose_from_menu($choices, "assignment_maxmem", $CFG->assignment_maxmem, ""); + ?> + + + + + + " /> diff --git a/mod/assignment/type/program/assignment.class.php b/mod/assignment/type/program/assignment.class.php new file mode 100644 index 0000000000..1682fec400 --- /dev/null +++ b/mod/assignment/type/program/assignment.class.php @@ -0,0 +1,1127 @@ + get_string('no'), 1 => get_string('yes')); + + // Programming languages + $choices = assignment_program_languages(); + $mform->addElement('select', 'lang', get_string("assignmentlangs", "assignment"), $choices); + $mform->setHelpButton('lang', array('lang',get_string('assignmentlangs','assignment'),'assignment')); + $mform->setDefault('lang', 'java'); + + // Cron date + $mform->addElement('date_time_selector', 'var1', get_string('crondate', 'assignment'), array('optional' => true)); + $mform->setHelpButton('var1', array('timecron',get_string('crondate','assignment'), 'assignment')); + $mform->disabledIf('var1', 'var1', 'eq', 0); + $mform->setDefault('var1', time() + 7 * 24 * 3600); + + // Max. CPU time + unset($choices); + $choices = $this->get_max_cpu_times($CFG->assignment_maxcpu); + $mform->addElement('select', 'var2', get_string('maximumcpu', 'assignment'), $choices); + $mform->setHelpButton('var2', array('maximumcpu',get_string('maximumcpu','assignment'), 'assignment')); + $mform->setDefault('var2', $CFG->assignment_maxcpu); + + // Max. memory usage + unset($choices); + $choices = $this->get_max_memory_usages($CFG->assignment_maxmem); + $mform->addElement('select', 'var3', get_string('maximummem', 'assignment'), $choices); + $mform->setHelpButton('var3', array('maximummem',get_string('maximummem','assignment'), 'assignment')); + $mform->setDefault('var3', $CFG->assignment_maxmem); + + // Allow resubmit + $mform->addElement('select', 'resubmit', get_string("allowresubmit", "assignment"), $ynoptions); + $mform->setHelpButton('resubmit', array('resubmit',get_string('allowresubmit','assignment'), 'assignment')); + $mform->setDefault('resubmit', 0); + + // Email teachers + $mform->addElement('select', 'emailteachers', get_string("emailteachers", "assignment"), $ynoptions); + $mform->setHelpButton('emailteachers', array('emailteachers',get_string('emailteachers','assignment'), 'assignment')); + $mform->setDefault('emailteachers', 0); + + // Submission max bytes + $choices = get_max_upload_sizes($CFG->maxbytes, $COURSE->maxbytes); + $choices[1] = get_string('uploadnotallowed'); + $choices[0] = get_string('courseuploadlimit') . ' (' . display_size($COURSE->maxbytes) . ')'; + $mform->addElement('select', 'maxbytes', get_string('maximumfilesize', 'assignment'), $choices); + $mform->setDefault('maxbytes', $CFG->assignment_maxbytes); + + // Tests form + $mform->addElement('header', 'tests', get_string('tests', 'assignment')); + + // Output filter + unset ($choices); + $choices[1] = get_string('ignorespace', 'assignment'); + $choices[2] = get_string('ignorecase', 'assignment'); + $choices[3] = get_string('exactouput', 'assignment'); + $mform->addElement('select', 'var4', get_string('outputfilter', 'assignment'), $choices); + $mform->setHelpButton('var4', array('outputfilter',get_string('outputfilter','assignment'), 'assignment')); + + // Get course module instance + $cm = new Object(); + if (!empty($update)) { + $cm = get_record("course_modules", "id", $update); + } + + // Get tests data + $tests = array (); + $numtests = $this->get_tests($cm, $tests); + if ($tests) { + // Tests allready defined (update assignment) + $i = 1; + foreach ($tests as $tstObj => $tstValue) { + $mform->addElement('text', "input[$i]", get_string('input', 'assignment') . $i); + $mform->setDefault("input[$i]",$tstValue->input); + $mform->addElement('text', "output[$i]", get_string('output', 'assignment') . $i); + $mform->setDefault("output[$i]",$tstValue->output); + + $i++; + } + } else { + // New assignment + for ($i = 1; $i <= $numtests; $i++) { + $mform->addElement('text', "input[$i]", get_string('input', 'assignment') . $i); + $mform->addElement('text', "output[$i]", get_string('output', 'assignment') . $i); + } + } + } + + /** + * Create a new program type assignment activity + * + * Given an object containing all the necessary data, + * (defined by the form in mod.html) this function + * will create a new instance and return the id number + * of the new instance. + * The due data is added to the calendar + * Tests are added to assignment_epaile_tests table + * + * @param $assignment object The data from the form on mod.html + * @return int The id of the assignment + */ + function add_instance($assignment) { + // Add assignment instance + $assignment->id = parent::add_instance($assignment); + if ($assignment->id) { + $this->after_add_update($assignment); + } + + return $assignment->id; + } + + /** + * Updates a program assignment activity + * + * Given an object containing all the necessary data, + * (defined by the form in mod.html) this function + * will update the assignment instance and return the id number + * The due date is updated in the calendar + * + * @param $assignment object The data from the form on mod.html + * @return int The assignment id + */ + + function update_instance($assignment) { + // Add assignment instance + $returnid = parent::update_instance($assignment); + if ($returnid) { + $this->after_add_update($assignment); + } + + return $returnid; + } + + /** + * Deletes a program assignment activity + * + * Deletes all database records, files and calendar events for this assignment. + * @param $assignment object The assignment to be deleted + * @return boolean False indicates error + */ + function delete_instance($assignment) { + global $CFG; + + // DELETE submissions results + $sql = 'test IN (SELECT id FROM '.$CFG->prefix.'assignment_epaile_tests WHERE assignment='.$assignment->id.')'; + if (!delete_records_select('assignment_epaile_results', $sql)) { + return false; + } + + // DELETE submissions + $sql = 'submission IN (SELECT id FROM '.$CFG->prefix.'assignment_submissions WHERE assignment='.$assignment->id.')'; + if (!delete_records_select('assignment_epaile_submissions', $sql)) { + return false; + } + + // DELETE tests + if (!delete_records('assignment_epaile_tests', 'assignment', $assignment->id)) { + return false; + } + + $result = parent::delete_instance($assignment); + + return $result; + } + + /** + * This function is called at the end of add_instance + * and update_instance, to add or update tests + * + * @param object $assignment the epaile object. + */ + function after_add_update($assignment) { + // Count real input/output (not empty tests) + $assignment->numtests = count($assignment->input); + + // Delete actual tests + delete_records('assignment_epaile_tests', 'assignment', $assignment->id); + + // Insert new tests + for ($i = 0; $i < $assignment->numtests; $i++) { + // Check if tests is not empty + if(!empty($assignment->input[$i+1]) && !empty($assignment->output[$i+1])) { + $test = new Object(); + $test->assignment = $assignment->id; + $test->input = $assignment->input[$i+1]; + $test->output = $assignment->output[$i+1]; + + if (!insert_record('assignment_epaile_tests', $test)) { + return get_string('notestinsert', 'assignment'); + } + + unset ($test); + } + } + } + + /** + * Get tests data for current assignment + * + * @param $instanceid int Instance ID + * @param $tests object The object used to fill the tests + * + * @return $numtests Number of tests + */ + function get_tests($cm, & $tests) { + if (isset ($cm->instance)) + $tests = get_records('assignment_epaile_tests', 'assignment', $cm->instance, 'id ASC'); + + if (empty ($tests)) { + $numtests = NUMTESTS; + } else { + $numtests = count($tests); + } + return $numtests; + } + + /** + * Get compile time / errors + * + * @param $submissionid int Submission id + * + * @return $comp Array + */ + function get_compile($submissionid) { + $comp = array (); + if ($submissionid) + $comp = get_record('assignment_epaile_submissions', 'submission', $submissionid); + + return $comp; + } + + /** + * Get tests results for current submission + * + * @param $submissionid int Submission id + * + * @return $res Array + */ + function get_results($submissionid) { + $res = array (); + if ($submissionid) + $res = get_records('assignment_epaile_results', 'submission', $submissionid); + + return $res; + } + + /** + * Get number of errors in tests + * + * @param $submissionid int Submission id + * + * @return $num int + */ + function get_errors_number($submissionid) { + global $CFG; + + $num = 0; + if($submissionid) { + $sql = "SELECT COUNT(id) AS num FROM ".$CFG->prefix."assignment_epaile_results"; + $sql .= " WHERE submission=$submissionid AND error<>''"; + + if (($res = get_record_sql($sql)) !== false) { + $num = $res->num; + } + } + return $num; + } + + /** + * View assignment details. + */ + function view() { + global $USER; + + $context = get_context_instance(CONTEXT_MODULE, $this->cm->id); + + require_capability('mod/assignment:view', $context); + add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}", $this->assignment->id, $this->cm->id); + + $this->view_header(); + $this->view_intro(); + $this->view_lang(); + $this->view_dates(); + $filecount = $this->count_user_files($USER->id); + + if ($submission = $this->get_submission()) { + if ($submission->timemarked) { + $this->view_feedback(); + } + if ($filecount) { + print_simple_box($this->print_user_files($USER->id, true), 'center'); + } + } + if (has_capability('mod/assignment:submit', $context) && $this->isopen() && (!$filecount || $this->assignment->resubmit || !$submission->timemarked)) { + $this->view_upload_form(); + } + $this->view_footer(); + } + + /** + * Upload file + */ + function upload() { + global $CFG, $USER; + require_capability('mod/assignment:submit', get_context_instance(CONTEXT_MODULE, $this->cm->id)); + $this->view_header(get_string('upload')); + $filecount = $this->count_user_files($USER->id); + $submission = $this->get_submission($USER->id); + if ($this->isopen() && (!$filecount || $this->assignment->resubmit || !$submission->timemarked)) { + if ($submission = $this->get_submission($USER->id)) { + if (($submission->grade >= 0) and !$this->assignment->resubmit) { + notify(get_string('alreadygraded', 'assignment')); + } + } + $dir = $this->file_area_name($USER->id); + require_once ($CFG->dirroot . '/lib/uploadlib.php'); + $um = new upload_manager('newfile', true, false, $this->course, false, $this->assignment->maxbytes); + if ($um->process_file_uploads($dir)) { + $newfile_name = $um->get_new_filename(); + if ($submission) { + $submission->timemodified = time(); + $submission->numfiles = 1; + $submission->submissioncomment = addslashes($submission->submissioncomment); + unset ($submission->data1); // Don't need to update this. + unset ($submission->data2); // Don't need to update this. + if (update_record("assignment_submissions", $submission)) { + add_to_log($this->course->id, 'assignment', 'upload', 'view.php?a=' + . $this->assignment->id, $this->assignment->id, $this->cm->id); + $this->email_teachers($submission); + print_heading(get_string('uploadedfile')); + } else { + notify(get_string("uploadfailnoupdate", "assignment")); + } + print_continue('view.php?id=' . $this->cm->id); + } else { + $newsubmission = $this->prepare_new_submission($USER->id); + $newsubmission->timemodified = time(); + $newsubmission->numfiles = 1; + if (insert_record('assignment_submissions', $newsubmission)) { + add_to_log($this->course->id, 'assignment', 'upload', 'view.php?a=' + . $this->assignment->id, $this->assignment->id, $this->cm->id); + $this->email_teachers($newsubmission); + print_heading(get_string('uploadedfile')); + /******************************************************* + ** Automated grade test ** + ********************************************************/ + $this->print_loading(); + $submission = $this->get_submission($USER->id); + chdir($CFG->dataroot . '/' . $dir); + $cmd = "javac " . $um->get_new_filename() . " > cerrors 2>&1"; + exec($cmd, $exit, $retval); + // Compile errors + if ($retval) { + // Read compile errors + $gestor = fopen($CFG->dataroot . '/' . $dir . '/cerrors', 'r'); + $errors = fread($gestor, filesize($CFG->dataroot . '/' . $dir . '/cerrors')); + fclose($gestor); + // Delete compile errors file + unlink($CFG->dataroot . '/' . $dir . '/cerrors'); + // Update submission + $submission = $this->get_submission($USER->id); + $submission->compileerrors = addslashes($errors); + } + $submission->submission = $submission->id; + $submission->runtime = 10; + if (!insert_record("assignment_epaile_submissions", $submission)) + error(get_string('gradeerror', 'assignment')); + #print_continue('view.php?id='.$this->cm->id); + } else { + notify(get_string("uploadnotregistered", "assignment", $newfile_name)); + print_continue('view.php?id=' . $this->cm->id); + } + } + } + } else { + notify(get_string("uploaderror", "assignment")); //submitting not allowed! + print_continue('view.php?id=' . $this->cm->id); + } + $this->view_footer(); + } + + function view_upload_form() { + global $CFG; + $struploadafile = get_string("uploadafile"); + $strmaxsize = get_string("maxsize", "", display_size($this->assignment->maxbytes)); + echo '
'; + echo '
wwwroot/mod/assignment/upload.php\">"; + echo "

$struploadafile ($strmaxsize)

"; + echo ''; + require_once ($CFG->libdir . '/uploadlib.php'); + upload_print_form_fragment(1, array ( + 'newfile' + ), false, null, 0, $this->assignment->maxbytes, false); + echo ''; + echo '
'; + echo '
'; + } + + function print_student_answer($userid, $return = false) { + global $CFG, $USER; + $filearea = $this->file_area_name($userid); + $output = ''; + if ($basedir = $this->file_area($userid)) { + if ($files = get_directory_list($basedir)) { + foreach ($files as $key => $file) { + require_once ($CFG->libdir . '/filelib.php'); + $icon = mimeinfo('icon', $file); + if ($CFG->slasharguments) { + $ffurl = "$CFG->wwwroot/file.php/$filearea/$file"; + } else { + $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file"; + } + //died right here + //require_once($ffurl); + #$output = ''.$icon.''. + # ''.$file.'
'; + $output = link_to_popup_window('/mod/assignment/type/program/source.php?id=' . $this->cm->id . '&userid=' . $userid . '&file=' . $file, $file . ' source code', $file, 710, 780, $file, 'none', true, 'button' . $userid); + } + } + } + $output = '
' . $output . '
'; + return $output; + } + + /** + * Produces a list of links to the files uploaded by a user + * + * @param $userid int optional id of the user. If 0 then $USER->id is used. + * @param $return boolean optional defaults to false. If true the list is returned rather than printed + * @return string optional + */ + function print_user_files($userid = 0, $return = false) { + global $CFG, $USER; + if (!$userid) { + if (!isloggedin()) { + return ''; + } + $userid = $USER->id; + } + $filearea = $this->file_area_name($userid); + $output = ''; + if ($basedir = $this->file_area($userid)) { + if ($files = get_directory_list($basedir)) { + require_once ($CFG->libdir . '/filelib.php'); + foreach ($files as $key => $file) { + $icon = mimeinfo('icon', $file); + if ($CFG->slasharguments) { + $ffurl = "$CFG->wwwroot/file.php/$filearea/$file"; + } else { + $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file"; + } + #$output .= ''.$icon.''. + # ''.$file.'
'; + // Syntax Highlighert source code + $output = link_to_popup_window('/mod/assignment/type/program/source.php?id=' . $this->cm->id . '&userid=' . $userid . '&file=' . $file, $file . ' source code', $file, 710, 780, $file, 'none', true, 'button' . $userid); + } + } + } + $output = '
' . $output . '
'; + if ($return) { + return $output; + } + echo $output; + } + + /** + * Return the programming language of this instance + * + * @return string Programming language + */ + function get_language() { + $lang = ''; + if ($ass = get_record('assignment', 'id', $this->cm->instance)) + $lang = $ass->lang; + return $lang; + } + + /** + * Display programming language for this assignment + */ + function view_lang() { + $lang = $this->get_language(); + if (!empty ($lang)) { + print_simple_box_start('center', '', '', 0, 'generalbox', 'dates'); + $formatoptions = new stdClass; + $formatoptions->noclean = true; + echo format_text('Programing language: ' . get_string('lang' . $lang, 'assignment'), $this->assignment->format, $formatoptions); + print_simple_box_end(); + } + } + + /** + * Print grade progress + */ + function print_loading() { + global $CFG; + print_simple_box_start('center', '30%', '', '', 'generalbox', 'intro'); + echo '

' . get_string('loading', 'assignment') . '

'; + echo '

Loading

'; + print_simple_box_end(); + } + + /** + * Display all the submissions ready for grading + */ + function display_submissions() { + global $CFG, $db, $USER; + + /* first we check to see if the form has just been submitted + * to request user_preference updates + */ + if (isset ($_POST['updatepref'])) { + $perpage = optional_param('perpage', 10, PARAM_INT); + $perpage = ($perpage <= 0) ? 10 : $perpage; + set_user_preference('assignment_perpage', $perpage); + set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL)); + } + + /* next we get perpage and quickgrade (allow quick grade) params + * from database + */ + $perpage = get_user_preferences('assignment_perpage', 10); + $quickgrade = get_user_preferences('assignment_quickgrade', 0); + $teacherattempts = true; /// Temporary measure + $page = optional_param('page', 0, PARAM_INT); + $strsaveallfeedback = get_string('saveallfeedback', 'assignment'); + + // Some shortcuts to make the code read better + $course = $this->course; + $assignment = $this->assignment; + $cm = $this->cm; + $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet + + add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id=' . $this->assignment->id, $this->assignment->id, $this->cm->id); + + print_header_simple(format_string($this->assignment->name, true), "", '' . $this->strassignments . ' -> ' . format_string($this->assignment->name, true) . ' -> ' . $this->strsubmissions, '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm)); + + // Position swapped + if ($groupmode = groupmode($course, $cm)) { // Groups are being used + $currentgroup = setup_and_print_groups($course, $groupmode, 'submissions.php?id=' . $this->cm->id); + } else { + $currentgroup = false; + } + + // Get all teachers and students + if ($currentgroup) { + $users = get_group_users($currentgroup); + } else { + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + $users = get_users_by_capability($context, 'mod/assignment:submit'); // everyone with this capability set to non-prohibit + } + + $tablecolumns = array ( + 'picture', + 'fullname', + 'grade', + 'compileerrors', + 'runtimeerrors', + 'submissioncomment', + 'timemodified', + 'timemarked', + 'status' + ); + + $tableheaders = array ( + '', + get_string('fullname' + ), get_string('grade'), get_string('compileerrors', 'assignment'), get_string('runtimeerrors', 'assignment'), get_string('comment', 'assignment'), get_string('lastmodified') . ' (' . $course->student . ')', get_string('lastmodified') . ' (' . $course->teacher . ')', get_string('status')); + + require_once ($CFG->libdir . '/tablelib.php'); + $table = new flexible_table('mod-assignment-submissions'); + $table->define_columns($tablecolumns); + $table->define_headers($tableheaders); + $table->define_baseurl($CFG->wwwroot . '/mod/assignment/submissions.php?id=' . $this->cm->id . '&currentgroup=' . $currentgroup); + $table->sortable(true, 'lastname'); //sorted by lastname by default + $table->collapsible(true); + $table->initialbars(true); + $table->column_suppress('picture'); + $table->column_suppress('fullname'); + $table->column_class('picture', 'picture'); + $table->column_class('fullname', 'fullname'); + $table->column_class('grade', 'grade'); + $table->column_class('compileerrors', 'comment'); + $table->column_class('runtimeerrors', 'comment'); + $table->column_class('submissioncomment', 'comment'); + $table->column_class('timemodified', 'timemodified'); + $table->column_class('timemarked', 'timemarked'); + $table->column_class('status', 'status'); + $table->set_attribute('cellspacing', '0'); + $table->set_attribute('id', 'attempts'); + $table->set_attribute('class', 'submissions'); + $table->set_attribute('width', '90%'); + $table->set_attribute('align', 'center'); + + // Start working -- this is necessary as soon as the niceties are over + $table->setup(); + + /// Check to see if groups are being used in this assignment + if (!$teacherattempts) { + $teachers = get_course_teachers($course->id); + if (!empty ($teachers)) { + $keys = array_keys($teachers); + } + foreach ($keys as $key) { + unset ($users[$key]); + } + } + + if (empty ($users)) { + print_heading(get_string('noattempts', 'assignment')); + return true; + } + + /// Construct the SQL + if ($where = $table->get_sql_where()) { + $where .= ' AND '; + } + + if ($sort = $table->get_sql_sort()) { + $sort = ' ORDER BY ' . $sort; + } + + $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, + s.id AS submissionid, s.grade, s.submissioncomment, se.compileerrors, + s.timemodified, s.timemarked '; + $sql = 'FROM ' . $CFG->prefix . 'user u ' . 'LEFT JOIN ' . $CFG->prefix . 'assignment_submissions s ON u.id = s.userid + AND s.assignment = ' . $this->assignment->id . ' ' . 'LEFT JOIN ' . $CFG->prefix . 'assignment_epaile_submissions se ON s.id = se.submission ' . 'WHERE ' . $where . 'u.id IN (' . implode(',', array_keys($users)) . ') '; + $table->pagesize($perpage, count($users)); + + ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next + $offset = $page * $perpage; + $strupdate = get_string('update'); + $strgrade = get_string('grade'); + $grademenu = make_grades_menu($this->assignment->grade); + + if (($ausers = get_records_sql($select . $sql . $sort, $table->get_page_start(), $table->get_page_size())) !== false) { + foreach ($ausers as $auser) { + /// Calculate user status + $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified); + $picture = print_user_picture($auser->id, $course->id, $auser->picture, false, true); + + if (empty ($auser->submissionid)) { + $auser->grade = -1; //no submission yet + } + + if (!empty ($auser->submissionid)) { + ///Prints student answer and student modified date + ///attach file or print link to student answer, depending on the type of the assignment. + ///Refer to print_student_answer in inherited classes. + if ($auser->timemodified > 0) { + $studentmodified = '
' . $this->print_student_answer($auser->id) . userdate($auser->timemodified) . '
'; + } else { + $studentmodified = '
 
'; + } + + ///Print grade, dropdown or text + if ($auser->timemarked > 0) { + $teachermodified = '
' . userdate($auser->timemarked) . '
'; + if ($quickgrade) { + $grade = '
' . choose_from_menu(make_grades_menu($this->assignment->grade), 'menu[' . $auser->id . ']', $auser->grade, get_string('nograde'), '', -1, true, false, $tabindex++) . '
'; + } else { + $grade = '
' . $this->display_grade($auser->grade) . '
'; + } + } else { + $teachermodified = '
 
'; + if ($quickgrade) { + $grade = '
' . choose_from_menu(make_grades_menu($this->assignment->grade), 'menu[' . $auser->id . ']', $auser->grade, get_string('nograde'), '', -1, true, false, $tabindex++) . '
'; + } else { + $grade = '
' . $this->display_grade($auser->grade) . '
'; + } + } + + ///Print Comment + if ($quickgrade) { + $comment = '
'; + } else { + $comment = '
' . shorten_text(strip_tags($auser->submissioncomment), 15) . '
'; + } + } else { + $studentmodified = '
 
'; + $teachermodified = '
 
'; + $status = '
 
'; + + if ($quickgrade) { // allow editing + $grade = '
' . choose_from_menu(make_grades_menu($this->assignment->grade), 'menu[' . $auser->id . ']', $auser->grade, get_string('nograde'), '', -1, true, false, $tabindex++) . '
'; + } else { + $grade = '
-
'; + } + + if ($quickgrade) { + $comment = '
'; + } else { + $comment = '
 
'; + } + } + + ///Print Compile errors + $compileerrors = ($auser->compileerrors) ? 'Yes' : 'No'; + $compileerrors = '
' . $compileerrors . '
'; + + ///Print Runtime errors + $runtimeerrors = ($this->get_errors_number($auser->submissionid)>0) ? 'Yes' : 'No'; + $runtimeerrors = '
' . $runtimeerrors . '
'; + + if (empty ($auser->status)) { /// Confirm we have exclusively 0 or 1 + $auser->status = 0; + } else { + $auser->status = 1; + } + + $buttontext = ($auser->status == 1) ? $strupdate : $strgrade; + + ///No more buttons, we use popups ;-). + $button = link_to_popup_window('/mod/assignment/submissions.php?id=' . $this->cm->id . '&userid=' . $auser->id . '&mode=single' . '&offset=' . $offset++, 'grade' . $auser->id, $buttontext, 500, 780, $buttontext, 'none', true, 'button' . $auser->id); + $status = '
' . $button . '
'; + $row = array ( + $picture, + fullname($auser + ), $grade, $compileerrors, $runtimeerrors, $comment, $studentmodified, $teachermodified, $status); + $table->add_data($row); + } + } + + /// Print quickgrade form around the table + if ($quickgrade) { + echo '
'; + echo ''; + echo ''; + echo ''; + echo '

'; + } + + $table->print_html(); /// Print the whole table + + if ($quickgrade) { + echo '

'; + echo '
'; + } + + /// End of fast grading form + /// Mini form for setting user preference + echo '
'; + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
'; + echo ''; + echo ':'; + echo ''; + helpbutton('pagesize', get_string('pagesize', 'assignment'), 'assignment'); + echo '
'; + print_string('quickgrade', 'assignment'); + echo ':'; + + if ($quickgrade) { + echo ''; + } else { + echo ''; + + } + + helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment') . '

'; + echo '
'; + echo ''; + echo '
'; + echo '
'; + + ///End of mini form + print_footer($this->course); + } + + /** + * Display a single submission, ready for grading on a popup window + * + * This default method prints the teacher info and submissioncomment box at the top and + * the student info and submission at the bottom. + * This method also fetches the necessary data in order to be able to + * provide a "Next submission" button. + * Calls preprocess_submission() to give assignment type plug-ins a chance + * to process submissions before they are graded + * This method gets its arguments from the page parameters userid and offset + */ + function display_submission($extra_javascript = '') { + global $CFG; + + $userid = required_param('userid', PARAM_INT); + $offset = required_param('offset', PARAM_INT); //offset for where to start looking for student. + + if (!$user = get_record('user', 'id', $userid)) { + error('No such user!'); + } + + if (!$submission = $this->get_submission($user->id)) { + $submission = $this->prepare_new_submission($userid); + } + + if (isset($submission->id)) { + $comp = $this->get_compile($submission->id); + } + + if ($submission->timemodified > $submission->timemarked) { + $subtype = 'assignmentnew'; + } else { + $subtype = 'assignmentold'; + } + + /// construct SQL, using current offset to find the data of the next student + $course = $this->course; + $assignment = $this->assignment; + $cm = $this->cm; + + /// Get all teachers and students + $currentgroup = get_current_group($course->id); + if ($currentgroup) { + $users = get_group_users($currentgroup); + } else { + $users = get_course_users($course->id); + } + + $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, + s.id AS submissionid, s.grade, s.submissioncomment, se.compileerrors, + s.timemodified, s.timemarked '; + $sql = 'FROM ' . $CFG->prefix . 'user u ' . 'LEFT JOIN ' . $CFG->prefix . 'assignment_submissions s ON u.id = s.userid + AND s.assignment = ' . $this->assignment->id . ' ' . 'LEFT JOIN ' . $CFG->prefix . 'assignment_epaile_submissions se ON s.id = se.submission ' . 'WHERE u.id IN (' . implode(',', array_keys($users)) . ') '; + + require_once ($CFG->libdir . '/tablelib.php'); + if ($sort = flexible_table :: get_sql_sort('mod-assignment-submissions')) { + $sort = 'ORDER BY ' . $sort . ' '; + } + + $nextid = 0; + if (($auser = get_records_sql($select . $sql . $sort, $offset +1, 1)) !== false) { + $nextuser = array_shift($auser); + /// Calculate user status + $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified); + $nextid = $nextuser->id; + } + + print_header(get_string('feedback', 'assignment') . ':' . fullname($user, true) . ':' . format_string($this->assignment->name)); + + /// Print any extra javascript needed for saveandnext + echo $extra_javascript; + + ///Some javascript to help with setting up >.> + echo '' . "\n"; + echo ''; + + ///Start of student info row + echo ''; + echo ''; + echo ''; + echo ''; + ///End of student info row + + ///Start of teacher info row + echo ''; + echo ''; + echo '
'; + print_user_picture($user->id, $this->course->id, $user->picture); + echo ''; + echo '
'; + echo '
' . fullname($user, true) . '
'; + + if ($submission->timemodified) { + echo '
' . userdate($submission->timemodified) . $this->display_lateness($submission->timemodified) . '
'; + } + echo '
'; + + $this->print_user_files($user->id); + if(isset($comp)) + $this->print_user_errors($user->id,$comp->compileerrors, ''); + echo '
'; + if ($submission->teacher) { + $teacher = get_record('user', 'id', $submission->teacher); + } else { + global $USER; + $teacher = $USER; + } + print_user_picture($teacher->id, $this->course->id, $teacher->picture); + echo ''; + echo '
'; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; //selected menu index + //new hidden field, initialized to -1. + echo ''; + if ($submission->timemarked) { + echo '
'; + echo '
' . fullname($teacher, true) . '
'; + echo '
' . userdate($submission->timemarked) . '
'; + echo '
'; + } + echo '
' . get_string('grade') . ':'; + choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1); + echo '
'; + echo '
'; + $this->preprocess_submission($submission); + echo '
'; + print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id); + if ($this->usehtmleditor) { + echo ''; + } else { + echo '
'; + choose_from_menu(format_text_menu(), "format", $submission->format, ""); + helpbutton("textformat", get_string("helpformatting")); + echo '
'; + } + ///End of teacher info row + ///Print Buttons in Single View + echo '
'; + echo ''; + echo ''; + //if there are more to be graded. + if ($nextid) { + echo ''; + echo ''; + } + echo '
'; + echo '
'; + $customfeedback = $this->custom_feedbackform($submission, true); + if (!empty ($customfeedback)) { + echo $customfeedback; + } + echo '
'; + if ($this->usehtmleditor) { + use_html_editor(); + } + print_footer('none'); + } + + /** + * Show compile or runtime errors + * + * @param $userid int optional id of the user. If 0 then $USER->id is used. + * @param $return boolean optional defaults to false. If true the list is returned rather than printed + * @return string optional + */ + function print_user_errors($userid = 0, $compileerrors = '', $runtimeerrors = '', $return = false) { + global $CFG, $USER; + + if (!$userid) { + if (!isloggedin()) { + return ''; + } + $userid = $USER->id; + } + + $output = ''; + $clear = '
'; + if ($compileerrors) { + $output .= '
Compile errors:
'; + $output .= '
' . $compileerrors . '
'; + } + + if ($runtimeerrors) { + $output .= '
'; + $output .= 'Runtime output:
'; + $output .= '
' . $runtimeerrors . '
'; + } + + $output = '
' . $output . '
'; + + if ($return) { + return $clear . $output; + } + + echo $clear . $output; + } + + /** + * This function returns an + * array of possible memory sizes in an array, translated to the + * local language. + * + * @uses SORT_NUMERIC + * @param int $sizebytes Moodle site $CGF->assignment_maxmem + * @return array + */ + function get_max_memory_usages($sitebytes=0) { + global $CFG; + + // Get max size + $maxsize = $sitebytes; + + $memusage[$maxsize] = display_size($maxsize); + + $sizelist = array(10240, 51200, 102400, 512000, 1048576, 2097152, + 5242880, 10485760); + + // Allow maxbytes to be selected if it falls outside the above boundaries + if( isset($CFG->maxmem) && !in_array($CFG->maxmem, $sizelist) ){ + $sizelist[] = $CFG->maxbytes; + } + + foreach ($sizelist as $sizebytes) { + if ($sizebytes < $maxsize) { + $memusage[$sizebytes] = display_size($sizebytes); + } + } + + krsort($memusage, SORT_NUMERIC); + + return $memusage; + } + + /** + * This function returns an + * array of possible CPU time (in seconds) in an array + * + * @uses SORT_NUMERIC + * @param int $time Moodle site $CGF->assignment_maxcpu + * @return array + */ + function get_max_cpu_times($time=0) { + global $CFG; + + // Get max size + $maxtime = $time; + + $cputime[$maxtime] = $maxtime.' secs'; + + $timelist = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 25, 30, 40, 50, 60); + + // Allow maxtime to be selected if it falls outside the above boundaries + if( isset($CFG->maxcpu) && !in_array($CFG->maxcpu, $timelist) ){ + $cputime[] = $CFG->maxbytes; + } + + foreach ($timelist as $timesecs) { + if ($timesecs < $maxtime) { + $cputime[$timesecs] = $timesecs.' secs'; + } + } + + krsort($cputime, SORT_NUMERIC); + + return $cputime; + } +} + +/** + * OTHER GENERAL FUNCTIONS FOR PROGRAM ASSIGNMENTS + */ + +/** + * Returns an array of installed programming languages indexed and sorted by name + * + * @return array The index is the name of the assignment type, the value its full name from the language strings + */ +function assignment_program_languages() { + global $CFG; + $lang = array (); + $dir = $CFG->dirroot . '/mod/assignment/type/program/languages/'; + $files = get_directory_list($dir); + $names = preg_replace('/\.(\w+)/', '', $files); // Replace file extension with nothing + foreach ($names as $name) { + $lang[$name] = get_string('lang' . $name, 'assignment'); + } + asort($lang); + return $lang; +} + +/** + * This function returns an + * array of possible CPU time (in seconds) in an array + * + * This is done by calling the get_max_cpu_times() method of the assignment type class + */ +function assignment_program_get_max_cpu_times($time=0) { + $ass = new assignment_program(); + + return $ass->get_max_cpu_times($time); +} + +/** + * This function returns an + * array of possible memory sizes in an array, translated to the + * local language. + * + * This is done by calling the get_max_memory_usages() method of the assignment type class + */ +function assignment_program_get_max_memory_usages($sitebytes=0) { + $ass = new assignment_program(); + + return $ass->get_max_memory_usages($sitebytes); +} +?> diff --git a/mod/assignment/type/program/db/mysql.sql b/mod/assignment/type/program/db/mysql.sql new file mode 100644 index 0000000000..f24ae7cae7 --- /dev/null +++ b/mod/assignment/type/program/db/mysql.sql @@ -0,0 +1,56 @@ +-- +-- Table structure for table `mdl_assignment_epaile_results` +-- + +CREATE TABLE `prefix_assignment_epaile_results` ( + `id` bigint(10) NOT NULL auto_increment, + `submission` bigint(10) NOT NULL, + `test` bigint(10) NOT NULL, + `runtime` bigint(10) NOT NULL, + `status` varchar(50) collate utf8_unicode_ci NOT NULL COMMENT 'Possible values: Accepted, Wrong answer, Internal error', + `output` varchar(255) collate utf8_unicode_ci default NULL COMMENT 'Program output', + `error` text collate utf8_unicode_ci COMMENT 'Runtime error', + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Test results' AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `mdl_assignment_epaile_submissions` +-- + +CREATE TABLE `prefix_assignment_epaile_submissions` ( + `id` bigint(10) unsigned NOT NULL auto_increment, + `submission` bigint(10) unsigned NOT NULL default '0', + `compileerrors` text collate utf8_unicode_ci, + `compiletime` bigint(10) NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `prefix_epaisubm_epa_ix` (`submission`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Info about submitted assignments' AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `mdl_assignment_epaile_tests` +-- + +CREATE TABLE `prefix_assignment_epaile_tests ` ( + `id` bigint(10) unsigned NOT NULL auto_increment, + `assignment` bigint(10) unsigned NOT NULL default '0', + `input` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT 'Program input', + `output` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT 'Expected output', + PRIMARY KEY (`id`), + KEY `prefix_epaitest_epa_ix` (`assignment`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Info about assignment tests' AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Add new field to `assignment` table +-- +ALTER TABLE `prefix_assignment` ADD `lang` VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL AFTER `var5` ; + +-- +-- Some values for `config` table +-- + INSERT INTO `prefix_config` (`name`,`value`) VALUES ('assignment_maxmem', '512000'),('assignment_maxcpu', '15'); \ No newline at end of file diff --git a/mod/assignment/type/program/js/tests.js b/mod/assignment/type/program/js/tests.js new file mode 100644 index 0000000000..5ffe5ffbd7 --- /dev/null +++ b/mod/assignment/type/program/js/tests.js @@ -0,0 +1,105 @@ + \ No newline at end of file diff --git a/mod/assignment/type/program/languages/bash.sh b/mod/assignment/type/program/languages/bash.sh new file mode 100644 index 0000000000..e7b6a13e95 --- /dev/null +++ b/mod/assignment/type/program/languages/bash.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +# Bash compile wrapper-script for 'test_solution.sh'. +# See that script for syntax and more info. +# +# This script does not actually "compile" the source, but writes a +# shell script that will function as the executable: when called, it +# will execute the source with the correct interpreter syntax, thus +# allowing this interpreted source to be used transparantly as if it +# was compiled to a standalone binary. +# +# NOTICE: this compiler script cannot be used with the USE_CHROOT +# configuration option turned on! (Unless proper preconfiguration of +# the chroot environment has been taken care of.) + +SOURCE="$1" +DEST="$2" + +RUNOPTIONS="--noprofile --norc -r -p" + +# Check for '#!' interpreter line: don't allow it to prevent teams +# from passing options to the interpreter. +if grep '^#!' $SOURCE &>/dev/null ; then + echo "Error: interpreter statement(s) found:" + grep -n '^#!' $SOURCE + exit 1 +fi + +# Check bash syntax: +bash $RUNOPTIONS -n $SOURCE +EXITCODE=$? +[ "$EXITCODE" -ne 0 ] && exit $EXITCODE + +# Write executing script: +cat > $DEST < $DEST </dev/null ; then + echo "Error: interpreter statement(s) found:" + grep -n '^#!' $SOURCE + exit 1 +fi + +# Check perl syntax: +perl -c -W $SOURCE +EXITCODE=$? +[ "$EXITCODE" -ne 0 ] && exit $EXITCODE + +# Write executing script: +cat > $DEST < + + + + + + + $tstValue) { +?> + + + + + + + + + + + + + + + + + + + + + +
+ + Delete Test +
+ : + + : +
Delete Test
: :
+ + + + +
Add
+ diff --git a/mod/assignment/type/program/source.html b/mod/assignment/type/program/source.html new file mode 100644 index 0000000000..6353977a33 --- /dev/null +++ b/mod/assignment/type/program/source.html @@ -0,0 +1,25 @@ + + + + + <?php echo $file ?> + + + +
+

+ +

+ + + + + + + \ No newline at end of file diff --git a/mod/assignment/type/program/source.php b/mod/assignment/type/program/source.php new file mode 100644 index 0000000000..47995905cf --- /dev/null +++ b/mod/assignment/type/program/source.php @@ -0,0 +1,95 @@ +instance)) { + error(get_string('error_invalidepaileid','assignment')); + } + + if (!$course = get_record('course', 'id', $assignment->course)) { + error(get_string('error_misconfiguredcourse','assignment')); + } + } else { + if (!$assignment = get_record('assignment', 'id', $e)) { + error(get_string('error_incorrectmodule','assignment')); + } + if (!$course = get_record('course', 'id', $assignment->course)) { + error(get_string('error_misconfiguredcourse','assignment')); + } + if (!$cm = get_coursemodule_from_instance('assignment', $assignment->id, $course->id)) { + error(get_string('error_invalidcoursemodule','assignment')); + } + } + + if(!$userid) + error(get_string('nouser','assignment')); + + if(!$file) + error(get_string('nosuchfile','assignment')); + + require_login($course->id, false, $cm); + + // Load up the required assignment code + require('assignment.class.php'); + $assignmentinstance = new assignment_program($cm->id, $assignment, $cm, $course); + + $filearea = $assignmentinstance->file_area_name($userid); + + if ($basedir = $assignmentinstance->file_area($userid)) { + require_once($CFG->libdir.'/filelib.php'); + + if ($CFG->slasharguments) { + $ffurl = "$CFG->dataroot/$filearea/$file"; + } else { + $ffurl = "$CFG->dataroot/$filearea/$file"; + } + } + + if($gestor = fopen($ffurl,'r')) { + $code = fread($gestor, filesize($ffurl)); + fclose($gestor); + } else { + error(get_string('filereaderror','assignment')); + } + + $lang = $assignmentinstance->get_language(); + + include('source.html'); +?> \ No newline at end of file diff --git a/mod/assignment/type/program/syntaxhighlighter/Scripts/clipboard.swf b/mod/assignment/type/program/syntaxhighlighter/Scripts/clipboard.swf new file mode 100644 index 0000000000000000000000000000000000000000..2cfe37185b27a7abf2db96f1ed2d6d15d7f601f8 GIT binary patch literal 109 zcmV-z0FwVhS5pUh0001ZoU349jb~usU%=9Emx)0FCc@6Z&ImM#g9$9j P93;-*0OSGyce)t=rY|(Z literal 0 HcmV?d00001 diff --git a/mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCSharp.js b/mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCSharp.js new file mode 100644 index 0000000000..b8beffb561 --- /dev/null +++ b/mod/assignment/type/program/syntaxhighlighter/Scripts/shBrushCSharp.js @@ -0,0 +1,32 @@ +dp.sh.Brushes.CSharp = function() +{ + var keywords = 'abstract as base bool break byte case catch char checked class const ' + + 'continue decimal default delegate do double else enum event explicit ' + + 'extern false finally fixed float for foreach get goto if implicit in int ' + + 'interface internal is lock long namespace new null object operator out ' + + 'override params private protected public readonly ref return sbyte sealed set ' + + 'short sizeof stackalloc static string struct switch this throw true try ' + + 'typeof uint ulong unchecked unsafe ushort using virtual void while'; + + this.regexList = [ + // There's a slight problem with matching single line comments and figuring out + // a difference between // and ///. Using lookahead and lookbehind solves the + // problem, unfortunately JavaScript doesn't support lookbehind. So I'm at a + // loss how to translate that regular expression to JavaScript compatible one. +// { regex: new RegExp('(?) + | () + | (<)*(\w+)*\s*(\w+)\s*=\s*(".*?"|'.*?'|\w+)(/*>)* + | () + */ + var index = 0; + var match = null; + var regex = null; + + // Match CDATA in the following format + // (\<|<)\!\[[\w\s]*?\[(.|\s)*?\]\](\>|>) + this.GetMatches(new RegExp('(\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\>|>)', 'gm'), 'cdata'); + + // Match comments + // (\<|<)!--\s*.*\s*?--(\>|>) + this.GetMatches(new RegExp('(\<|<)!--\\s*.*\\s*?--(\>|>)', 'gm'), 'comments'); + + // Match attributes and their values + // (:|\w+)\s*=\s*(".*?"|\'.*?\'|\w+)* + regex = new RegExp('([:\\w-\.]+)\\s*=\\s*(".*?"|\'.*?\'|\\w+)*|(\\w+)', 'gm'); // Thanks to Tomi Blinnikka of Yahoo! for fixing namespaces in attributes + while((match = regex.exec(this.code)) != null) + { + if(match[1] == null) + { + continue; + } + + push(this.matches, new dp.sh.Match(match[1], match.index, 'attribute')); + + // if xml is invalid and attribute has no property value, ignore it + if(match[2] != undefined) + { + push(this.matches, new dp.sh.Match(match[2], match.index + match[0].indexOf(match[2]), 'attribute-value')); + } + } + + // Match opening and closing tag brackets + // (\<|<)/*\?*(?!\!)|/*\?*(\>|>) + this.GetMatches(new RegExp('(\<|<)/*\\?*(?!\\!)|/*\\?*(\>|>)', 'gm'), 'tag'); + + // Match tag names + // (\<|<)/*\?*\s*(\w+) + regex = new RegExp('(?:\<|<)/*\\?*\\s*([:\\w-\.]+)', 'gm'); + while((match = regex.exec(this.code)) != null) + { + push(this.matches, new dp.sh.Match(match[1], match.index + match[0].indexOf(match[1]), 'tag-name')); + } +} diff --git a/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.js b/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.js new file mode 100644 index 0000000000..e3ee84be2d --- /dev/null +++ b/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.js @@ -0,0 +1,414 @@ +var dp={sh:{Toolbar:{},Utils:{},RegexLib:{},Brushes:{},Strings:{AboutDialog:"About...

dp.SyntaxHighlighter

Version: {V}

http://www.dreamprojections.com/syntaxhighlighter

©2004-2007 Alex Gorbatchev.
"},ClipboardSwf:null,Version:"1.5"}}; +dp.SyntaxHighlighter=dp.sh; +dp.sh.Toolbar.Commands={ExpandSource:{label:"+ expand source",check:function(_1){ +return _1.collapse; +},func:function(_2,_3){ +_2.parentNode.removeChild(_2); +_3.div.className=_3.div.className.replace("collapsed",""); +}},ViewSource:{label:"view plain",func:function(_4,_5){ +var _6=_5.originalCode.replace(/"+_6+""); +_7.document.close(); +}},CopyToClipboard:{label:"copy to clipboard",check:function(){ +return window.clipboardData!=null||dp.sh.ClipboardSwf!=null; +},func:function(_8,_9){ +var _a=_9.originalCode; +if(window.clipboardData){ +window.clipboardData.setData("text",_a); +}else{ +if(dp.sh.ClipboardSwf!=null){ +var _b=_9.flashCopier; +if(_b==null){ +_b=document.createElement("div"); +_9.flashCopier=_b; +_9.div.appendChild(_b); +} +_b.innerHTML=""; +} +} +alert("The code is in your clipboard now"); +}},PrintSource:{label:"print",func:function(_c,_d){ +var _e=document.createElement("IFRAME"); +var _f=null; +_e.style.cssText="position:absolute;width:0px;height:0px;left:-500px;top:-500px;"; +document.body.appendChild(_e); +_f=_e.contentWindow.document; +dp.sh.Utils.CopyStyles(_f,window.document); +_f.write("
"+_d.div.innerHTML+"
"); +_f.close(); +_e.contentWindow.focus(); +_e.contentWindow.print(); +alert("Printing..."); +document.body.removeChild(_e); +}},About:{label:"?",func:function(_10){ +var wnd=window.open("","_blank","dialog,width=300,height=150,scrollbars=0"); +var doc=wnd.document; +dp.sh.Utils.CopyStyles(doc,window.document); +doc.write(dp.sh.Strings.AboutDialog.replace("{V}",dp.sh.Version)); +doc.close(); +wnd.focus(); +}}}; +dp.sh.Toolbar.Create=function(_13){ +var div=document.createElement("DIV"); +div.className="tools"; +for(var _15 in dp.sh.Toolbar.Commands){ +var cmd=dp.sh.Toolbar.Commands[_15]; +if(cmd.check!=null&&!cmd.check(_13)){ +continue; +} +div.innerHTML+=""+cmd.label+""; +} +return div; +}; +dp.sh.Toolbar.Command=function(_17,_18){ +var n=_18; +while(n!=null&&n.className.indexOf("dp-highlighter")==-1){ +n=n.parentNode; +} +if(n!=null){ +dp.sh.Toolbar.Commands[_17].func(_18,n.highlighter); +} +}; +dp.sh.Utils.CopyStyles=function(_1a,_1b){ +var _1c=_1b.getElementsByTagName("link"); +for(var i=0;i<_1c.length;i++){ +if(_1c[i].rel.toLowerCase()=="stylesheet"){ +_1a.write(""); +} +} +}; +dp.sh.RegexLib={MultiLineCComments:new RegExp("/\\*[\\s\\S]*?\\*/","gm"),SingleLineCComments:new RegExp("//.*$","gm"),SingleLinePerlComments:new RegExp("#.*$","gm"),DoubleQuotedString:new RegExp("\"(?:\\.|(\\\\\\\")|[^\\\"\"])*\"","g"),SingleQuotedString:new RegExp("'(?:\\.|(\\\\\\')|[^\\''])*'","g")}; +dp.sh.Match=function(_1e,_1f,css){ +this.value=_1e; +this.index=_1f; +this.length=_1e.length; +this.css=css; +}; +dp.sh.Highlighter=function(){ +this.noGutter=false; +this.addControls=true; +this.collapse=false; +this.tabsToSpaces=true; +this.wrapColumn=80; +this.showColumns=true; +}; +dp.sh.Highlighter.SortCallback=function(m1,m2){ +if(m1.indexm2.index){ +return 1; +}else{ +if(m1.lengthm2.length){ +return 1; +} +} +} +} +return 0; +}; +dp.sh.Highlighter.prototype.CreateElement=function(_23){ +var _24=document.createElement(_23); +_24.highlighter=this; +return _24; +}; +dp.sh.Highlighter.prototype.GetMatches=function(_25,css){ +var _27=0; +var _28=null; +while((_28=_25.exec(this.code))!=null){ +this.matches[this.matches.length]=new dp.sh.Match(_28[0],_28.index,css); +} +}; +dp.sh.Highlighter.prototype.AddBit=function(str,css){ +if(str==null||str.length==0){ +return; +} +var _2b=this.CreateElement("SPAN"); +str=str.replace(/ /g," "); +str=str.replace(/"); +if(css!=null){ +if((/br/gi).test(str)){ +var _2c=str.split(" 
"); +for(var i=0;i<_2c.length;i++){ +_2b=this.CreateElement("SPAN"); +_2b.className=css; +_2b.innerHTML=_2c[i]; +this.div.appendChild(_2b); +if(i+1<_2c.length){ +this.div.appendChild(this.CreateElement("BR")); +} +} +}else{ +_2b.className=css; +_2b.innerHTML=str; +this.div.appendChild(_2b); +} +}else{ +_2b.innerHTML=str; +this.div.appendChild(_2b); +} +}; +dp.sh.Highlighter.prototype.IsInside=function(_2e){ +if(_2e==null||_2e.length==0){ +return false; +} +for(var i=0;ic.index)&&(_2e.index/gi,"\n"); +var _44=_43.split("\n"); +if(this.addControls==true){ +this.bar.appendChild(dp.sh.Toolbar.Create(this)); +} +if(this.showColumns){ +var div=this.CreateElement("div"); +var _46=this.CreateElement("div"); +var _47=10; +var i=1; +while(i<=150){ +if(i%_47==0){ +div.innerHTML+=i; +i+=(i+"").length; +}else{ +div.innerHTML+="·"; +i++; +} +} +_46.className="columns"; +_46.appendChild(div); +this.bar.appendChild(_46); +} +for(var i=0,lineIndex=this.firstLine;i<_44.length-1;i++,lineIndex++){ +var li=this.CreateElement("LI"); +var _4b=this.CreateElement("SPAN"); +li.className=(i%2==0)?"alt":""; +_4b.innerHTML=_44[i]+" "; +li.appendChild(_4b); +this.ol.appendChild(li); +} +this.div.innerHTML=""; +}; +dp.sh.Highlighter.prototype.Highlight=function(_4c){ +function Trim(str){ +return str.replace(/^\s*(.*?)[\s\n]*$/g,"$1"); +} +function Chop(str){ +return str.replace(/\n*$/,"").replace(/^\n*/,""); +} +function Unindent(str){ +var _50=str.split("\n"); +var _51=new Array(); +var _52=new RegExp("^\\s*","g"); +var min=1000; +for(var i=0;i<_50.length&&min>0;i++){ +if(Trim(_50[i]).length==0){ +continue; +} +var _55=_52.exec(_50[i]); +if(_55!=null&&_55.length>0){ +min=Math.min(_55[0].length,min); +} +} +if(min>0){ +for(var i=0;i<_50.length;i++){ +_50[i]=_50[i].substr(min); +} +} +return _50.join("\n"); +} +function Copy(_57,_58,_59){ +return _57.substr(_58,_59-_58); +} +var pos=0; +if(_4c==null){ +_4c=""; +} +this.originalCode=_4c; +this.code=Chop(Unindent(_4c)); +this.div=this.CreateElement("DIV"); +this.bar=this.CreateElement("DIV"); +this.ol=this.CreateElement("OL"); +this.matches=new Array(); +this.div.className="dp-highlighter"; +this.div.highlighter=this; +this.bar.className="bar"; +this.ol.start=this.firstLine; +if(this.CssClass!=null){ +this.ol.className=this.CssClass; +} +if(this.collapse){ +this.div.className+=" collapsed"; +} +if(this.noGutter){ +this.div.className+=" nogutter"; +} +if(this.tabsToSpaces==true){ +this.code=this.ProcessSmartTabs(this.code); +} +this.ProcessRegexList(); +if(this.matches.length==0){ +this.AddBit(this.code,null); +this.SwitchToList(); +this.div.appendChild(this.ol); +return; +} +this.matches=this.matches.sort(dp.sh.Highlighter.SortCallback); +for(var i=0;i"+_76.Style+""); +} +_76.firstLine=(_63==null)?parseInt(GetOptionValue("firstline",_7e,1)):_63; +_76.Highlight(_7d[_78]); +_76.source=_7d; +_7d.parentNode.insertBefore(_76.div,_7d); +} +}; + diff --git a/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.uncompressed.js b/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.uncompressed.js new file mode 100644 index 0000000000..d79c0099c7 --- /dev/null +++ b/mod/assignment/type/program/syntaxhighlighter/Scripts/shCore.uncompressed.js @@ -0,0 +1,674 @@ +/** + * Code Syntax Highlighter. + * Version 1.5 + * Copyright (C) 2004-2007 Alex Gorbatchev. + * http://www.dreamprojections.com/syntaxhighlighter/ + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// create namespaces +// +var dp = { + sh : + { + Toolbar : {}, + Utils : {}, + RegexLib: {}, + Brushes : {}, + Strings : { + AboutDialog : 'About...

dp.SyntaxHighlighter

Version: {V}

http://www.dreamprojections.com/syntaxhighlighter

©2004-2007 Alex Gorbatchev.
' + }, + ClipboardSwf : null, + Version : '1.5' + } +}; + +// make an alias +dp.SyntaxHighlighter = dp.sh; + +// +// Toolbar functions +// + +dp.sh.Toolbar.Commands = { + ExpandSource: { + label: '+ expand source', + check: function(highlighter) { return highlighter.collapse; }, + func: function(sender, highlighter) + { + sender.parentNode.removeChild(sender); + highlighter.div.className = highlighter.div.className.replace('collapsed', ''); + } + }, + + // opens a new windows and puts the original unformatted source code inside. + ViewSource: { + label: 'view plain', + func: function(sender, highlighter) + { + var code = highlighter.originalCode.replace(/' + code + ''); + wnd.document.close(); + } + }, + + // Copies the original source code in to the clipboard. Uses either IE only method or Flash object if ClipboardSwf is set + CopyToClipboard: { + label: 'copy to clipboard', + check: function() { return window.clipboardData != null || dp.sh.ClipboardSwf != null; }, + func: function(sender, highlighter) + { + var code = highlighter.originalCode; + + if(window.clipboardData) + { + window.clipboardData.setData('text', code); + } + else if(dp.sh.ClipboardSwf != null) + { + var flashcopier = highlighter.flashCopier; + + if(flashcopier == null) + { + flashcopier = document.createElement('div'); + highlighter.flashCopier = flashcopier; + highlighter.div.appendChild(flashcopier); + } + + flashcopier.innerHTML = ''; + } + + alert('The code is in your clipboard now'); + } + }, + + // creates an invisible iframe, puts the original source code inside and prints it + PrintSource: { + label: 'print', + func: function(sender, highlighter) + { + var iframe = document.createElement('IFRAME'); + var doc = null; + + // this hides the iframe + iframe.style.cssText = 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;'; + + document.body.appendChild(iframe); + doc = iframe.contentWindow.document; + + dp.sh.Utils.CopyStyles(doc, window.document); + doc.write('
' + highlighter.div.innerHTML + '
'); + doc.close(); + + iframe.contentWindow.focus(); + iframe.contentWindow.print(); + + alert('Printing...'); + + document.body.removeChild(iframe); + } + }, + + About: { + label: '?', + func: function(highlighter) + { + var wnd = window.open('', '_blank', 'dialog,width=300,height=150,scrollbars=0'); + var doc = wnd.document; + + dp.sh.Utils.CopyStyles(doc, window.document); + + doc.write(dp.sh.Strings.AboutDialog.replace('{V}', dp.sh.Version)); + doc.close(); + wnd.focus(); + } + } +}; + +// creates a
with all toolbar links +dp.sh.Toolbar.Create = function(highlighter) +{ + var div = document.createElement('DIV'); + + div.className = 'tools'; + + for(var name in dp.sh.Toolbar.Commands) + { + var cmd = dp.sh.Toolbar.Commands[name]; + + if(cmd.check != null && !cmd.check(highlighter)) + continue; + + div.innerHTML += '' + cmd.label + ''; + } + + return div; +} + +// executes toolbar command by name +dp.sh.Toolbar.Command = function(name, sender) +{ + var n = sender; + + while(n != null && n.className.indexOf('dp-highlighter') == -1) + n = n.parentNode; + + if(n != null) + dp.sh.Toolbar.Commands[name].func(sender, n.highlighter); +} + +// copies all from 'target' window to 'dest' +dp.sh.Utils.CopyStyles = function(destDoc, sourceDoc) +{ + var links = sourceDoc.getElementsByTagName('link'); + + for(var i = 0; i < links.length; i++) + if(links[i].rel.toLowerCase() == 'stylesheet') + destDoc.write(''); +} + +// +// Common reusable regular expressions +// +dp.sh.RegexLib = { + MultiLineCComments : new RegExp('/\\*[\\s\\S]*?\\*/', 'gm'), + SingleLineCComments : new RegExp('//.*$', 'gm'), + SingleLinePerlComments : new RegExp('#.*$', 'gm'), + DoubleQuotedString : new RegExp('"(?:\\.|(\\\\\\")|[^\\""])*"','g'), + SingleQuotedString : new RegExp("'(?:\\.|(\\\\\\')|[^\\''])*'", 'g') +}; + +// +// Match object +// +dp.sh.Match = function(value, index, css) +{ + this.value = value; + this.index = index; + this.length = value.length; + this.css = css; +} + +// +// Highlighter object +// +dp.sh.Highlighter = function() +{ + this.noGutter = false; + this.addControls = true; + this.collapse = false; + this.tabsToSpaces = true; + this.wrapColumn = 80; + this.showColumns = true; +} + +// static callback for the match sorting +dp.sh.Highlighter.SortCallback = function(m1, m2) +{ + // sort matches by index first + if(m1.index < m2.index) + return -1; + else if(m1.index > m2.index) + return 1; + else + { + // if index is the same, sort by length + if(m1.length < m2.length) + return -1; + else if(m1.length > m2.length) + return 1; + } + return 0; +} + +dp.sh.Highlighter.prototype.CreateElement = function(name) +{ + var result = document.createElement(name); + result.highlighter = this; + return result; +} + +// gets a list of all matches for a given regular expression +dp.sh.Highlighter.prototype.GetMatches = function(regex, css) +{ + var index = 0; + var match = null; + + while((match = regex.exec(this.code)) != null) + this.matches[this.matches.length] = new dp.sh.Match(match[0], match.index, css); +} + +dp.sh.Highlighter.prototype.AddBit = function(str, css) +{ + if(str == null || str.length == 0) + return; + + var span = this.CreateElement('SPAN'); + +// str = str.replace(/&/g, '&'); + str = str.replace(/ /g, ' '); + str = str.replace(//g, '>'); + str = str.replace(/\n/gm, ' 
'); + + // when adding a piece of code, check to see if it has line breaks in it + // and if it does, wrap individual line breaks with span tags + if(css != null) + { + if((/br/gi).test(str)) + { + var lines = str.split(' 
'); + + for(var i = 0; i < lines.length; i++) + { + span = this.CreateElement('SPAN'); + span.className = css; + span.innerHTML = lines[i]; + + this.div.appendChild(span); + + // don't add a
for the last line + if(i + 1 < lines.length) + this.div.appendChild(this.CreateElement('BR')); + } + } + else + { + span.className = css; + span.innerHTML = str; + this.div.appendChild(span); + } + } + else + { + span.innerHTML = str; + this.div.appendChild(span); + } +} + +// checks if one match is inside any other match +dp.sh.Highlighter.prototype.IsInside = function(match) +{ + if(match == null || match.length == 0) + return false; + + for(var i = 0; i < this.matches.length; i++) + { + var c = this.matches[i]; + + if(c == null) + continue; + + if((match.index > c.index) && (match.index < c.index + c.length)) + return true; + } + + return false; +} + +dp.sh.Highlighter.prototype.ProcessRegexList = function() +{ + for(var i = 0; i < this.regexList.length; i++) + this.GetMatches(this.regexList[i].regex, this.regexList[i].css); +} + +dp.sh.Highlighter.prototype.ProcessSmartTabs = function(code) +{ + var lines = code.split('\n'); + var result = ''; + var tabSize = 4; + var tab = '\t'; + + // This function inserts specified amount of spaces in the string + // where a tab is while removing that given tab. + function InsertSpaces(line, pos, count) + { + var left = line.substr(0, pos); + var right = line.substr(pos + 1, line.length); // pos + 1 will get rid of the tab + var spaces = ''; + + for(var i = 0; i < count; i++) + spaces += ' '; + + return left + spaces + right; + } + + // This function process one line for 'smart tabs' + function ProcessLine(line, tabSize) + { + if(line.indexOf(tab) == -1) + return line; + + var pos = 0; + + while((pos = line.indexOf(tab)) != -1) + { + // This is pretty much all there is to the 'smart tabs' logic. + // Based on the position within the line and size of a tab, + // calculate the amount of spaces we need to insert. + var spaces = tabSize - pos % tabSize; + + line = InsertSpaces(line, pos, spaces); + } + + return line; + } + + // Go through all the lines and do the 'smart tabs' magic. + for(var i = 0; i < lines.length; i++) + result += ProcessLine(lines[i], tabSize) + '\n'; + + return result; +} + +dp.sh.Highlighter.prototype.SwitchToList = function() +{ + // thanks to Lachlan Donald from SitePoint.com for this
tag fix. + var html = this.div.innerHTML.replace(/<(br)\/?>/gi, '\n'); + var lines = html.split('\n'); + + if(this.addControls == true) + this.bar.appendChild(dp.sh.Toolbar.Create(this)); + + // add columns ruler + if(this.showColumns) + { + var div = this.CreateElement('div'); + var columns = this.CreateElement('div'); + var showEvery = 10; + var i = 1; + + while(i <= 150) + { + if(i % showEvery == 0) + { + div.innerHTML += i; + i += (i + '').length; + } + else + { + div.innerHTML += '·'; + i++; + } + } + + columns.className = 'columns'; + columns.appendChild(div); + this.bar.appendChild(columns); + } + + for(var i = 0, lineIndex = this.firstLine; i < lines.length - 1; i++, lineIndex++) + { + var li = this.CreateElement('LI'); + var span = this.CreateElement('SPAN'); + + // uses .line1 and .line2 css styles for alternating lines + li.className = (i % 2 == 0) ? 'alt' : ''; + span.innerHTML = lines[i] + ' '; + + li.appendChild(span); + this.ol.appendChild(li); + } + + this.div.innerHTML = ''; +} + +dp.sh.Highlighter.prototype.Highlight = function(code) +{ + function Trim(str) + { + return str.replace(/^\s*(.*?)[\s\n]*$/g, '$1'); + } + + function Chop(str) + { + return str.replace(/\n*$/, '').replace(/^\n*/, ''); + } + + function Unindent(str) + { + var lines = str.split('\n'); + var indents = new Array(); + var regex = new RegExp('^\\s*', 'g'); + var min = 1000; + + // go through every line and check for common number of indents + for(var i = 0; i < lines.length && min > 0; i++) + { + if(Trim(lines[i]).length == 0) + continue; + + var matches = regex.exec(lines[i]); + + if(matches != null && matches.length > 0) + min = Math.min(matches[0].length, min); + } + + // trim minimum common number of white space from the begining of every line + if(min > 0) + for(var i = 0; i < lines.length; i++) + lines[i] = lines[i].substr(min); + + return lines.join('\n'); + } + + // This function returns a portions of the string from pos1 to pos2 inclusive + function Copy(string, pos1, pos2) + { + return string.substr(pos1, pos2 - pos1); + } + + var pos = 0; + + if(code == null) + code = ''; + + this.originalCode = code; + this.code = Chop(Unindent(code)); + this.div = this.CreateElement('DIV'); + this.bar = this.CreateElement('DIV'); + this.ol = this.CreateElement('OL'); + this.matches = new Array(); + + this.div.className = 'dp-highlighter'; + this.div.highlighter = this; + + this.bar.className = 'bar'; + + // set the first line + this.ol.start = this.firstLine; + + if(this.CssClass != null) + this.ol.className = this.CssClass; + + if(this.collapse) + this.div.className += ' collapsed'; + + if(this.noGutter) + this.div.className += ' nogutter'; + + // replace tabs with spaces + if(this.tabsToSpaces == true) + this.code = this.ProcessSmartTabs(this.code); + + this.ProcessRegexList(); + + // if no matches found, add entire code as plain text + if(this.matches.length == 0) + { + this.AddBit(this.code, null); + this.SwitchToList(); + this.div.appendChild(this.ol); + return; + } + + // sort the matches + this.matches = this.matches.sort(dp.sh.Highlighter.SortCallback); + + // The following loop checks to see if any of the matches are inside + // of other matches. This process would get rid of highligted strings + // inside comments, keywords inside strings and so on. + for(var i = 0; i < this.matches.length; i++) + if(this.IsInside(this.matches[i])) + this.matches[i] = null; + + // Finally, go through the final list of matches and pull the all + // together adding everything in between that isn't a match. + for(var i = 0; i < this.matches.length; i++) + { + var match = this.matches[i]; + + if(match == null || match.length == 0) + continue; + + this.AddBit(Copy(this.code, pos, match.index), null); + this.AddBit(match.value, match.css); + + pos = match.index + match.length; + } + + this.AddBit(this.code.substr(pos), null); + + this.SwitchToList(); + this.div.appendChild(this.bar); + this.div.appendChild(this.ol); +} + +dp.sh.Highlighter.prototype.GetKeywords = function(str) +{ + return '\\b' + str.replace(/ /g, '\\b|\\b') + '\\b'; +} + +// highlightes all elements identified by name and gets source code from specified property +dp.sh.HighlightAll = function(name, showGutter /* optional */, showControls /* optional */, collapseAll /* optional */, firstLine /* optional */, showColumns /* optional */) +{ + function FindValue() + { + var a = arguments; + + for(var i = 0; i < a.length; i++) + { + if(a[i] == null) + continue; + + if(typeof(a[i]) == 'string' && a[i] != '') + return a[i] + ''; + + if(typeof(a[i]) == 'object' && a[i].value != '') + return a[i].value + ''; + } + + return null; + } + + function IsOptionSet(value, list) + { + for(var i = 0; i < list.length; i++) + if(list[i] == value) + return true; + + return false; + } + + function GetOptionValue(name, list, defaultValue) + { + var regex = new RegExp('^' + name + '\\[(\\w+)\\]$', 'gi'); + var matches = null; + + for(var i = 0; i < list.length; i++) + if((matches = regex.exec(list[i])) != null) + return matches[1]; + + return defaultValue; + } + + function FindTagsByName(list, name, tagName) + { + var tags = document.getElementsByTagName(tagName); + + for(var i = 0; i < tags.length; i++) + if(tags[i].getAttribute('name') == name) + list.push(tags[i]); + } + + var elements = []; + var highlighter = null; + var registered = {}; + var propertyName = 'innerHTML'; + + // for some reason IE doesn't find
 by name, however it does see them just fine by tag name... 
+	FindTagsByName(elements, name, 'pre');
+	FindTagsByName(elements, name, 'textarea');
+
+	if(elements.length == 0)
+		return;
+
+	// register all brushes
+	for(var brush in dp.sh.Brushes)
+	{
+		var aliases = dp.sh.Brushes[brush].Aliases;
+
+		if(aliases == null)
+			continue;
+		
+		for(var i = 0; i < aliases.length; i++)
+			registered[aliases[i]] = brush;
+	}
+
+	for(var i = 0; i < elements.length; i++)
+	{
+		var element = elements[i];
+		var options = FindValue(
+				element.attributes['class'], element.className, 
+				element.attributes['language'], element.language
+				);
+		var language = '';
+		
+		if(options == null)
+			continue;
+		
+		options = options.split(':');
+		
+		language = options[0].toLowerCase();
+
+		if(registered[language] == null)
+			continue;
+		
+		// instantiate a brush
+		highlighter = new dp.sh.Brushes[registered[language]]();
+		
+		// hide the original element
+		element.style.display = 'none';
+
+		highlighter.noGutter = (showGutter == null) ? IsOptionSet('nogutter', options) : !showGutter;
+		highlighter.addControls = (showControls == null) ? !IsOptionSet('nocontrols', options) : showControls;
+		highlighter.collapse = (collapseAll == null) ? IsOptionSet('collapse', options) : collapseAll;
+		highlighter.showColumns = (showColumns == null) ? IsOptionSet('showcolumns', options) : showColumns;
+
+		// write out custom brush style
+		if(highlighter.Style)
+			document.write('');
+		
+		// first line idea comes from Andrew Collington, thanks!
+		highlighter.firstLine = (firstLine == null) ? parseInt(GetOptionValue('firstline', options, 1)) : firstLine;
+
+		highlighter.Highlight(element[propertyName]);
+		
+		highlighter.source = element;
+
+		element.parentNode.insertBefore(highlighter.div, element);
+	}	
+}
diff --git a/mod/assignment/type/program/syntaxhighlighter/Styles/SyntaxHighlighter.css b/mod/assignment/type/program/syntaxhighlighter/Styles/SyntaxHighlighter.css
new file mode 100644
index 0000000000..709058d0eb
--- /dev/null
+++ b/mod/assignment/type/program/syntaxhighlighter/Styles/SyntaxHighlighter.css
@@ -0,0 +1,158 @@
+.dp-highlighter
+{
+	font-family: "Consolas", "Courier New", Courier, mono;
+	font-size: 12px;
+	background-color: #E7E5DC;
+	width: 99%;
+	overflow: auto;
+	margin: 18px 0px 18px 0px;
+	padding-top: 1px; /* adds a little border on top when controls are hidden */
+}
+
+.dp-highlighter .bar
+{
+	padding-left: 45px;
+}
+
+.dp-highlighter.collapsed .bar, 
+.dp-highlighter.nogutter .bar 
+{
+	padding-left: 0px;
+}
+
+.dp-highlighter ol 
+{
+	list-style: decimal; /* for ie */
+	list-style: decimal-leading-zero; /* better look for others */
+	background-color: #fff;
+	margin: 0px 0px 1px 45px; /* 1px bottom margin seems to fix occasional Firefox scrolling */
+	padding: 0px;
+	color: #5C5C5C;
+}
+
+.dp-highlighter.nogutter ol
+{
+	list-style-type: none !important; 
+	margin-left: 0px; 
+}
+
+.dp-highlighter ol li,
+.dp-highlighter .columns div
+{
+	border-left: 3px solid #6CE26C;
+	background-color: #f8f8f8;
+	padding-left: 10px;
+	line-height: 14px;
+}
+
+.dp-highlighter.nogutter ol li,
+.dp-highlighter.nogutter .columns div
+{
+	border: 0;
+}
+
+.dp-highlighter .columns 
+{
+	color: gray;
+	overflow: hidden;
+	width: 100%;
+}
+
+.dp-highlighter .columns div 
+{
+	padding-bottom: 5px;
+}
+
+.dp-highlighter ol li.alt 
+{
+	background-color: #fff; 
+}
+
+.dp-highlighter ol li span 
+{
+	color: Black;
+}
+
+/* Adjust some properties when collapsed */
+
+.dp-highlighter.collapsed ol 
+{
+	margin: 0px;
+}
+
+.dp-highlighter.collapsed ol li 
+{ 
+	display: none;
+}
+
+/* Additional modifications when in print-view */
+
+.dp-highlighter.printing 
+{
+	border: none;
+}
+
+.dp-highlighter.printing .tools 
+{
+	display: none !important;
+}
+
+.dp-highlighter.printing li 
+{
+	display: list-item !important;
+}
+
+/* Styles for the tools */
+
+.dp-highlighter .tools 
+{
+	padding: 3px 8px 3px 10px;
+	font: 9px Verdana, Geneva, Arial, Helvetica, sans-serif;
+	color: silver;
+	background-color: #f8f8f8;
+	text-align1: right;
+	padding-bottom: 10px;
+	border-left: 3px solid #6CE26C;
+}
+
+.dp-highlighter.nogutter .tools
+{
+	border-left: 0;
+}
+
+.dp-highlighter.collapsed .tools
+{
+	border-bottom: 0;
+}
+
+.dp-highlighter .tools a
+{
+	font-size: 9px;
+	color: #a0a0a0;
+	text-decoration: none;
+	margin-right: 10px;
+}
+
+.dp-highlighter .tools a:hover
+{
+	color: red;
+	text-decoration: underline;
+}
+
+/* About dialog styles */
+
+.dp-about { background-color: #fff; margin: 0px; padding: 0px; }
+.dp-about table { width: 100%; height: 100%; font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; }
+.dp-about td { padding: 10px; vertical-align: top; }
+.dp-about .copy { border-bottom: 1px solid #ACA899; height: 95%; }
+.dp-about .title { color: red; font-weight: bold; }
+.dp-about .para { margin: 0 0 4px 0; }
+.dp-about .footer { background-color: #ECEADB; border-top: 1px solid #fff; text-align: right; }
+.dp-about .close { font-size: 11px; font-family: Tahoma, Verdana, Arial, sans-serif !important; background-color: #ECEADB; width: 60px; height: 22px; }
+
+/* Language specific styles */
+
+.dp-highlighter .comment, .dp-highlighter .comments { color: #008200; }
+.dp-highlighter .string { color: blue; }
+.dp-highlighter .keyword { color: #069; font-weight: bold; }
+.dp-highlighter .preprocessor { color: gray; }
diff --git a/mod/assignment/type/program/syntaxhighlighter/Styles/TestPages.css b/mod/assignment/type/program/syntaxhighlighter/Styles/TestPages.css
new file mode 100644
index 0000000000..ec8f851253
--- /dev/null
+++ b/mod/assignment/type/program/syntaxhighlighter/Styles/TestPages.css
@@ -0,0 +1,63 @@
+@import url(undohtml.css);
+
+body
+{
+	background-color: #fff;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 12px;
+	padding: 20px;
+}
+
+h1, h2, h3 { font-weight: normal; margin: 0px 0px 10px 0px; }
+h1 { font-size: 200%; border-bottom: 5px solid #f0f0f0; padding-bottom: 5px; font-family: "Trebuchet MS", Trebuchet, Verdana, sans-serif; }
+h2 { font-size: 150%; margin-left: 20%; }
+h3 { font-size: 120%; }
+
+li a
+{
+	color: blue;
+	text-decoration: none;
+}
+
+li a:hover
+{
+	background-color: #FFFF00 !important;
+}
+
+li a:visited
+{
+	background-color: #ececec;
+}
+
+.layout
+{
+	position: relative;
+}
+
+.column1
+{
+	width: 15%;
+	border-right: 15px solid silver;
+	background-color: #f0f0f0;
+	padding-top: 10px;
+	padding-bottom: 10px;
+}
+
+.column1 h3
+{
+	margin-left: 10px;
+}
+
+.column2
+{
+	position: absolute;
+	left: 20%;
+	top: 0px;
+	width: 75%;
+}
+
+.footer
+{
+	margin-top: 20px;
+	width: 15%;
+}
\ No newline at end of file
diff --git a/mod/assignment/type/program/syntaxhighlighter/Templates/Test.dwt b/mod/assignment/type/program/syntaxhighlighter/Templates/Test.dwt
new file mode 100644
index 0000000000..a041ff9f28
--- /dev/null
+++ b/mod/assignment/type/program/syntaxhighlighter/Templates/Test.dwt
@@ -0,0 +1,80 @@
+
+
+
+
+dp.SyntaxHighlighter Tests
+
+
+
+
+
+
+

dp.SyntaxHighlighter 1.4.1 Tests and Samples (dreamprojections.com/syntaxhighlighter)

+ +

Title

+ +
+ +
+

Languages:

+
    +
  1. C#
  2. +
  3. CSS
  4. +
  5. C++
  6. +
  7. Delphi
  8. +
  9. Java
  10. +
  11. JavaScript
  12. +
  13. PHP
  14. +
  15. Python
  16. +
  17. Ruby
  18. +
  19. SQL
  20. +
  21. Visual Basic
  22. +
  23. XML / HTML
  24. +
+

Features:

+
    +
  1. Smart tabs
  2. +
  3. First line
  4. +
  5. Expand code
  6. +
  7. Show columns
  8. +
  9. No gutter
  10. +
  11. No controls
  12. +
+
+ +
+Text body before. +
+ +
+
+ +
+Text body after. +
+
+ + + + + + + + + + + + + + + + + + + + -- 2.39.5