diff --git a/lang/en_utf8/assignment.php b/lang/en_utf8/assignment.php
index 8af0a8c405a..c68d79217a3 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 6eedcdff2d0..0f55ba3b7fc 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 00000000000..1682fec4008
--- /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 '';
+ 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 = ' '.
+ # ''.$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 .= ' '.
+ # ''.$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 '
';
+ 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 . '¤tgroup=' . $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 '';
+ }
+
+ /// End of fast grading form
+ /// Mini form for setting user preference
+ 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 '';
+ print_user_picture($user->id, $this->course->id, $user->picture);
+ echo ' ';
+ 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 ' ';
+ echo ' ';
+ ///End of student info row
+
+ ///Start of teacher info row
+ echo '';
+ 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 '';
+ $customfeedback = $this->custom_feedbackform($submission, true);
+ if (!empty ($customfeedback)) {
+ echo $customfeedback;
+ }
+ echo ' ';
+ 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 .= '';
+ }
+
+ if ($runtimeerrors) {
+ $output .= ' ';
+ $output .= 'Runtime output: ';
+ $output .= '';
+ }
+
+ $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 00000000000..f24ae7cae71
--- /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 00000000000..5ffe5ffbd7b
--- /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 00000000000..e7b6a13e953
--- /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 <
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mod/assignment/type/program/source.html b/mod/assignment/type/program/source.html
new file mode 100644
index 00000000000..6353977a339
--- /dev/null
+++ b/mod/assignment/type/program/source.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 00000000000..47995905cf8
--- /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 00000000000..2cfe37185b2
Binary files /dev/null and b/mod/assignment/type/program/syntaxhighlighter/Scripts/clipboard.swf differ
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 00000000000..b8beffb5618
--- /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 00000000000..e3ee84be2d8
--- /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... "},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 00000000000..d79c0099c78
--- /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... '
+ },
+ 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 00000000000..709058d0eb6
--- /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 00000000000..ec8f8512537
--- /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 00000000000..a041ff9f285
--- /dev/null
+++ b/mod/assignment/type/program/syntaxhighlighter/Templates/Test.dwt
@@ -0,0 +1,80 @@
+
+
+
+
+dp.SyntaxHighlighter Tests
+
+
+
+
+
+
+
+
+Title
+
+
+
+
+
+
+Text body before.
+
+
+
+
+
+
+Text body after.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+