MDL-9628 At the suggestion of Tim Hunt, interchanged the position of grade letter...
[moodle-pu.git] / mod / assignment / lib.php
blob13b00745f9bd6731610479537900e5ded648fa52
1 <?PHP // $Id$
2 /**
3 * assignment_base is the base class for assignment types
5 * This class provides all the functionality for an assignment
6 */
8 DEFINE ('ASSIGNMENT_COUNT_WORDS', 1);
9 DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2);
11 if (!isset($CFG->assignment_maxbytes)) {
12 set_config("assignment_maxbytes", 1024000); // Default maximum size for all assignments
14 if (!isset($CFG->assignment_itemstocount)) {
15 set_config("assignment_itemstocount", ASSIGNMENT_COUNT_WORDS); // Default item to count
18 /**
19 * Standard base class for all assignment submodules (assignment types).
21 class assignment_base {
23 var $cm;
24 var $course;
25 var $assignment;
26 var $strassignment;
27 var $strassignments;
28 var $strsubmissions;
29 var $strlastmodified;
30 var $navigation;
31 var $pagetitle;
32 var $currentgroup;
33 var $usehtmleditor;
34 var $defaultformat;
35 var $context;
36 var $lockedgrades;
38 /**
39 * Constructor for the base assignment class
41 * Constructor for the base assignment class.
42 * If cmid is set create the cm, course, assignment objects.
43 * If the assignment is hidden and the user is not a teacher then
44 * this prints a page header and notice.
46 * @param cmid integer, the current course module id - not set for new assignments
47 * @param assignment object, usually null, but if we have it we pass it to save db access
48 * @param cm object, usually null, but if we have it we pass it to save db access
49 * @param course object, usually null, but if we have it we pass it to save db access
51 function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
52 if ($cmid == 'staticonly') {
53 //use static functions only!
54 return;
57 global $CFG;
59 if ($cm) {
60 $this->cm = $cm;
61 } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
62 error('Course Module ID was incorrect');
65 $this->context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
67 if ($course) {
68 $this->course = $course;
69 } else if (! $this->course = get_record('course', 'id', $this->cm->course)) {
70 error('Course is misconfigured');
73 if ($assignment) {
74 $this->assignment = $assignment;
75 } else if (! $this->assignment = get_record('assignment', 'id', $this->cm->instance)) {
76 error('assignment ID was incorrect');
79 $this->assignment->cmidnumber = $this->cm->id; // compatibility with modedit assignment obj
80 $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
82 require_once($CFG->libdir.'/gradelib.php');
83 $this->lockedgrades = grade_is_locked($this->course->id, 'mod', 'assignment', $this->assignment->id, 0);
85 $this->strassignment = get_string('modulename', 'assignment');
86 $this->strassignments = get_string('modulenameplural', 'assignment');
87 $this->strsubmissions = get_string('submissions', 'assignment');
88 $this->strlastmodified = get_string('lastmodified');
90 $this->navigation[] = array('name' => $this->strassignments, 'link' => "index.php?id={$this->course->id}", 'type' => 'activity');
92 $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true));
94 // visibility
95 $context = get_context_instance(CONTEXT_MODULE, $cmid);
96 if (!$this->cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) {
97 $pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment);
98 $this->navigation[] = array('name' => $this->strassignment, 'link' => '', 'type' => 'activityinstance');
99 $navigation = build_navigation($this->navigation);
101 print_header($pagetitle, $this->course->fullname, "$this->navigation $this->strassignment",
102 "", "", true, '', navmenu($this->course, $this->cm));
103 notice(get_string("activityiscurrentlyhidden"), "$CFG->wwwroot/course/view.php?id={$this->course->id}");
105 $this->currentgroup = get_and_set_current_group($this->course, groupmode($this->course, $this->cm));
107 /// Set up things for a HTML editor if it's needed
108 if ($this->usehtmleditor = can_use_html_editor()) {
109 $this->defaultformat = FORMAT_HTML;
110 } else {
111 $this->defaultformat = FORMAT_MOODLE;
116 * Display the assignment, used by view.php
118 * This in turn calls the methods producing individual parts of the page
120 function view() {
122 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
123 require_capability('mod/assignment:view', $context);
125 add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
126 $this->assignment->id, $this->cm->id);
128 $this->view_header();
130 $this->view_intro();
132 $this->view_dates();
134 $this->view_feedback();
136 $this->view_footer();
140 * Display the header and top of a page
142 * (this doesn't change much for assignment types)
143 * This is used by the view() method to print the header of view.php but
144 * it can be used on other pages in which case the string to denote the
145 * page in the navigation trail should be passed as an argument
147 * @param $subpage string Description of subpage to be used in navigation trail
149 function view_header($subpage='') {
151 global $CFG;
154 if ($subpage) {
155 $this->navigation[] = array('name' => format_string($this->assignment->name,true), 'link' => "view.php?id={$this->cm->id}", 'type' => 'activityinstance');
156 $this->navigation[] = array('name' => $subpage, 'link' => '', 'type' => 'title');
157 } else {
158 $this->navigation[] = array('name' => format_string($this->assignment->name,true), 'link' => '', 'type' => 'activityinstance');
161 $navigation = build_navigation($this->navigation);
163 print_header($this->pagetitle, $this->course->fullname, $navigation, '', '',
164 true, update_module_button($this->cm->id, $this->course->id, $this->strassignment),
165 navmenu($this->course, $this->cm));
167 $groupmode = groupmode($this->course, $this->cm);
168 $currentgroup = setup_and_print_groups($this->course, $groupmode, 'view.php?id=' . $this->cm->id);
170 echo '<div class="reportlink">'.$this->submittedlink().'</div>';
171 echo '<div class="clearer"></div>';
176 * Display the assignment intro
178 * This will most likely be extended by assignment type plug-ins
179 * The default implementation prints the assignment description in a box
181 function view_intro() {
182 print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
183 $formatoptions = new stdClass;
184 $formatoptions->noclean = true;
185 echo format_text($this->assignment->description, $this->assignment->format, $formatoptions);
186 print_simple_box_end();
190 * Display the assignment dates
192 * Prints the assignment start and end dates in a box.
193 * This will be suitable for most assignment types
195 function view_dates() {
196 if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
197 return;
200 print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
201 echo '<table>';
202 if ($this->assignment->timeavailable) {
203 echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
204 echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
206 if ($this->assignment->timedue) {
207 echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
208 echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
210 echo '</table>';
211 print_simple_box_end();
216 * Display the bottom and footer of a page
218 * This default method just prints the footer.
219 * This will be suitable for most assignment types
221 function view_footer() {
222 print_footer($this->course);
226 * Display the feedback to the student
228 * This default method prints the teacher picture and name, date when marked,
229 * grade and teacher submissioncomment.
231 * @param $submission object The submission object or NULL in which case it will be loaded
233 function view_feedback($submission=NULL) {
234 global $USER;
236 if (!$submission) { /// Get submission for this assignment
237 $submission = $this->get_submission($USER->id);
240 if (empty($submission->timemarked)) { /// Nothing to show, so print nothing
241 return;
244 /// We need the teacher info
245 if (! $teacher = get_record('user', 'id', $submission->teacher)) {
246 error('Could not find the teacher');
249 /// Print the feedback
250 print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher));
252 echo '<table cellspacing="0" class="feedback">';
254 echo '<tr>';
255 echo '<td class="left picture">';
256 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
257 echo '</td>';
258 echo '<td class="topic">';
259 echo '<div class="from">';
260 echo '<div class="fullname">'.fullname($teacher).'</div>';
261 echo '<div class="time">'.userdate($submission->timemarked).'</div>';
262 echo '</div>';
263 echo '</td>';
264 echo '</tr>';
266 echo '<tr>';
267 echo '<td class="left side">&nbsp;</td>';
268 echo '<td class="content">';
269 if ($this->assignment->grade) {
270 echo '<div class="grade">';
271 echo get_string("grade").': '.$this->display_grade($submission->grade);
272 echo '</div>';
273 echo '<div class="clearer"></div>';
276 echo '<div class="comment">';
277 echo format_text($submission->submissioncomment, $submission->format);
278 echo '</div>';
279 echo '</tr>';
281 echo '</table>';
285 * Returns a link with info about the state of the assignment submissions
287 * This is used by view_header to put this link at the top right of the page.
288 * For teachers it gives the number of submitted assignments with a link
289 * For students it gives the time of their submission.
290 * This will be suitable for most assignment types.
291 * @return string
293 function submittedlink() {
294 global $USER;
296 $submitted = '';
298 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
299 if (has_capability('mod/assignment:grade', $context)) {
301 // if this user can mark and is put in a group
302 // then he can only see/mark submission in his own groups
303 if (!has_capability('moodle/course:managegroups', $context) and (groupmode($this->course, $this->cm) == SEPARATEGROUPS)) {
304 $count = $this->count_real_submissions($this->currentgroup); // Only their groups
305 } else {
306 $count = $this->count_real_submissions(); // Everyone
308 $submitted = '<a href="submissions.php?id='.$this->cm->id.'">'.
309 get_string('viewsubmissions', 'assignment', $count).'</a>';
310 } else {
311 if (!empty($USER->id)) {
312 if ($submission = $this->get_submission($USER->id)) {
313 if ($submission->timemodified) {
314 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
315 $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
316 } else {
317 $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
324 return $submitted;
328 function setup_elements(&$mform) {
333 * Create a new assignment activity
335 * Given an object containing all the necessary data,
336 * (defined by the form in mod.html) this function
337 * will create a new instance and return the id number
338 * of the new instance.
339 * The due data is added to the calendar
340 * This is common to all assignment types.
342 * @param $assignment object The data from the form on mod.html
343 * @return int The id of the assignment
345 function add_instance($assignment) {
346 global $COURSE;
348 $assignment->timemodified = time();
349 $assignment->courseid = $assignment->course;
351 if ($returnid = insert_record("assignment", $assignment)) {
352 $assignment->id = $returnid;
354 if ($assignment->timedue) {
355 $event = new object();
356 $event->name = $assignment->name;
357 $event->description = $assignment->description;
358 $event->courseid = $assignment->course;
359 $event->groupid = 0;
360 $event->userid = 0;
361 $event->modulename = 'assignment';
362 $event->instance = $returnid;
363 $event->eventtype = 'due';
364 $event->timestart = $assignment->timedue;
365 $event->timeduration = 0;
367 add_event($event);
370 $assignment = stripslashes_recursive($assignment);
371 assignment_grade_item_update($assignment);
376 return $returnid;
380 * Deletes an assignment activity
382 * Deletes all database records, files and calendar events for this assignment.
383 * @param $assignment object The assignment to be deleted
384 * @return boolean False indicates error
386 function delete_instance($assignment) {
387 global $CFG;
389 $assignment->courseid = $assignment->course;
391 $result = true;
393 if (! delete_records('assignment_submissions', 'assignment', $assignment->id)) {
394 $result = false;
397 if (! delete_records('assignment', 'id', $assignment->id)) {
398 $result = false;
401 if (! delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id)) {
402 $result = false;
405 // delete file area with all attachments - ignore errors
406 require_once($CFG->libdir.'/filelib.php');
407 fulldelete($CFG->dataroot.'/'.$assignment->course.'/'.$CFG->moddata.'/assignment/'.$assignment->id);
409 assignment_grade_item_delete($assignment);
411 return $result;
415 * Updates a new assignment activity
417 * Given an object containing all the necessary data,
418 * (defined by the form in mod.html) this function
419 * will update the assignment instance and return the id number
420 * The due date is updated in the calendar
421 * This is common to all assignment types.
423 * @param $assignment object The data from the form on mod.html
424 * @return int The assignment id
426 function update_instance($assignment) {
427 global $COURSE;
429 $assignment->timemodified = time();
431 $assignment->id = $assignment->instance;
432 $assignment->courseid = $assignment->course;
434 if (!update_record('assignment', $assignment)) {
435 return false;
438 if ($assignment->timedue) {
439 $event = new object();
441 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
443 $event->name = $assignment->name;
444 $event->description = $assignment->description;
445 $event->timestart = $assignment->timedue;
447 update_event($event);
448 } else {
449 $event = new object();
450 $event->name = $assignment->name;
451 $event->description = $assignment->description;
452 $event->courseid = $assignment->course;
453 $event->groupid = 0;
454 $event->userid = 0;
455 $event->modulename = 'assignment';
456 $event->instance = $assignment->id;
457 $event->eventtype = 'due';
458 $event->timestart = $assignment->timedue;
459 $event->timeduration = 0;
461 add_event($event);
463 } else {
464 delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id);
467 // get existing grade item
468 $assignment = stripslashes_recursive($assignment);
470 assignment_grade_item_update($assignment);
472 return true;
476 * Update grade item for this submission.
478 function update_grade($submission) {
479 assignment_update_grades($this->assignment, $submission->userid);
483 * Top-level function for handling of submissions called by submissions.php
485 * This is for handling the teacher interaction with the grading interface
486 * This should be suitable for most assignment types.
488 * @param $mode string Specifies the kind of teacher interaction taking place
490 function submissions($mode) {
491 ///The main switch is changed to facilitate
492 ///1) Batch fast grading
493 ///2) Skip to the next one on the popup
494 ///3) Save and Skip to the next one on the popup
496 //make user global so we can use the id
497 global $USER;
499 // no grading when grades are locked
500 if ($this->lockedgrades) {
501 $mode = 'all';
504 switch ($mode) {
505 case 'grade': // We are in a popup window grading
506 if ($submission = $this->process_feedback()) {
507 //IE needs proper header with encoding
508 print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name));
509 print_heading(get_string('changessaved'));
510 print $this->update_main_listing($submission);
512 close_window();
513 break;
515 case 'single': // We are in a popup window displaying submission
516 $this->display_submission();
517 break;
519 case 'all': // Main window, display everything
520 $this->display_submissions();
521 break;
523 case 'fastgrade':
524 ///do the fast grading stuff - this process should work for all 3 subclasses
525 $grading = false;
526 $commenting = false;
527 $col = false;
528 if (isset($_POST['submissioncomment'])) {
529 $col = 'submissioncomment';
530 $commenting = true;
532 if (isset($_POST['menu'])) {
533 $col = 'menu';
534 $grading = true;
536 if (!$col) {
537 //both submissioncomment and grade columns collapsed..
538 $this->display_submissions();
539 break;
541 foreach ($_POST[$col] as $id => $unusedvalue){
543 $id = (int)$id; //clean parameter name
545 $this->process_outcomes($id);
547 if (!$submission = $this->get_submission($id)) {
548 $submission = $this->prepare_new_submission($id);
549 $newsubmission = true;
550 } else {
551 $newsubmission = false;
553 unset($submission->data1); // Don't need to update this.
554 unset($submission->data2); // Don't need to update this.
556 //for fast grade, we need to check if any changes take place
557 $updatedb = false;
559 if ($grading) {
560 $grade = $_POST['menu'][$id];
561 $updatedb = $updatedb || ($submission->grade != $grade);
562 $submission->grade = $grade;
563 } else {
564 if (!$newsubmission) {
565 unset($submission->grade); // Don't need to update this.
568 if ($commenting) {
569 $commentvalue = trim($_POST['submissioncomment'][$id]);
570 $updatedb = $updatedb || ($submission->submissioncomment != stripslashes($commentvalue));
571 $submission->submissioncomment = $commentvalue;
572 } else {
573 unset($submission->submissioncomment); // Don't need to update this.
576 $submission->teacher = $USER->id;
577 $submission->mailed = $updatedb?0:$submission->mailed;//only change if it's an update
578 $submission->timemarked = time();
580 //if it is not an update, we don't change the last modified time etc.
581 //this will also not write into database if no submissioncomment and grade is entered.
583 if ($updatedb){
584 if ($newsubmission) {
585 if (!$sid = insert_record('assignment_submissions', $submission)) {
586 return false;
588 $submission->id = $sid;
589 } else {
590 if (!update_record('assignment_submissions', $submission)) {
591 return false;
595 // triger grade event
596 $this->update_grade($submission);
598 //add to log only if updating
599 add_to_log($this->course->id, 'assignment', 'update grades',
600 'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid,
601 $submission->userid, $this->cm->id);
606 print_heading(get_string('changessaved'));
607 $this->display_submissions();
608 break;
611 case 'next':
612 /// We are currently in pop up, but we want to skip to next one without saving.
613 /// This turns out to be similar to a single case
614 /// The URL used is for the next submission.
616 $this->display_submission();
617 break;
619 case 'saveandnext':
620 ///We are in pop up. save the current one and go to the next one.
621 //first we save the current changes
622 if ($submission = $this->process_feedback()) {
623 //print_heading(get_string('changessaved'));
624 $extra_javascript = $this->update_main_listing($submission);
627 //then we display the next submission
628 $this->display_submission($extra_javascript);
629 break;
631 default:
632 echo "something seriously is wrong!!";
633 break;
638 * Helper method updating the listing on the main script from popup using javascript
640 * @param $submission object The submission whose data is to be updated on the main page
642 function update_main_listing($submission) {
643 global $SESSION, $CFG;
645 $output = '';
647 $perpage = get_user_preferences('assignment_perpage', 10);
649 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
651 /// Run some Javascript to try and update the parent page
652 $output .= '<script type="text/javascript">'."\n<!--\n";
653 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
654 if ($quickgrade){
655 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
656 .trim($submission->submissioncomment).'";'."\n";
657 } else {
658 $output.= 'opener.document.getElementById("com'.$submission->userid.
659 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
663 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
664 //echo optional_param('menuindex');
665 if ($quickgrade){
666 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
667 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
668 } else {
669 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
670 $this->display_grade($submission->grade)."\";\n";
673 //need to add student's assignments in there too.
674 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
675 $submission->timemodified) {
676 $output.= 'opener.document.getElementById("ts'.$submission->userid.
677 '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
680 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
681 $submission->timemarked) {
682 $output.= 'opener.document.getElementById("tt'.$submission->userid.
683 '").innerHTML="'.userdate($submission->timemarked)."\";\n";
686 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
687 $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
688 $buttontext = get_string('update');
689 $button = link_to_popup_window ('/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;userid='.$submission->userid.'&amp;mode=single'.'&amp;offset='.(optional_param('offset', '', PARAM_INT)-1),
690 'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid);
691 $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
694 if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcomes'])) {
695 if ($outcomes_data = grade_get_outcomes($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid)) {
696 foreach($outcomes_data as $n=>$data) {
697 if ($data->locked) {
698 continue;
701 if ($quickgrade){
702 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
703 '").selectedIndex="'.$data->grade.'";'."\n";
704 } else {
705 $options = make_grades_menu(-$data->scaleid);
706 $options[0] = get_string('nooutcome', 'grades');
707 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$data->grade]."\";\n";
715 $output .= "\n-->\n</script>";
716 return $output;
720 * Return a grade in user-friendly form, whether it's a scale or not
722 * @param $grade
723 * @return string User-friendly representation of grade
725 function display_grade($grade) {
727 static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
729 if ($this->assignment->grade >= 0) { // Normal number
730 if ($grade == -1) {
731 return '-';
732 } else {
733 return $grade.' / '.$this->assignment->grade;
736 } else { // Scale
737 if (empty($scalegrades[$this->assignment->id])) {
738 if ($scale = get_record('scale', 'id', -($this->assignment->grade))) {
739 $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
740 } else {
741 return '-';
744 if (isset($scalegrades[$this->assignment->id][$grade])) {
745 return $scalegrades[$this->assignment->id][$grade];
747 return '-';
752 * Display a single submission, ready for grading on a popup window
754 * This default method prints the teacher info and submissioncomment box at the top and
755 * the student info and submission at the bottom.
756 * This method also fetches the necessary data in order to be able to
757 * provide a "Next submission" button.
758 * Calls preprocess_submission() to give assignment type plug-ins a chance
759 * to process submissions before they are graded
760 * This method gets its arguments from the page parameters userid and offset
762 function display_submission($extra_javascript = '') {
764 global $CFG;
765 require_once($CFG->libdir.'/gradelib.php');
767 $userid = required_param('userid', PARAM_INT);
768 $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
770 if (!$user = get_record('user', 'id', $userid)) {
771 error('No such user!');
774 if (!$submission = $this->get_submission($user->id)) {
775 $submission = $this->prepare_new_submission($userid);
777 if ($submission->timemodified > $submission->timemarked) {
778 $subtype = 'assignmentnew';
779 } else {
780 $subtype = 'assignmentold';
783 /// construct SQL, using current offset to find the data of the next student
784 $course = $this->course;
785 $assignment = $this->assignment;
786 $cm = $this->cm;
787 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
789 /// Get all ppl that can submit assignments
791 $currentgroup = get_and_set_current_group($course, groupmode($course, $cm));
793 $users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id, u.id', '', '', '', $currentgroup, '', false);
795 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
796 s.id AS submissionid, s.grade, s.submissioncomment,
797 s.timemodified, s.timemarked ';
798 $sql = 'FROM '.$CFG->prefix.'user u '.
799 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
800 AND s.assignment = '.$this->assignment->id.' '.
801 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
803 require_once($CFG->libdir.'/tablelib.php');
805 if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) {
806 $sort = 'ORDER BY '.$sort.' ';
809 $nextid = 0;
810 if (($auser = get_records_sql($select.$sql.$sort, $offset+1, 1)) !== false) {
811 $nextuser = array_shift($auser);
812 /// Calculate user status
813 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified);
814 $nextid = $nextuser->id;
817 print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name));
819 /// Print any extra javascript needed for saveandnext
820 echo $extra_javascript;
822 ///SOme javascript to help with setting up >.>
824 echo '<script type="text/javascript">'."\n";
825 echo 'function setNext(){'."\n";
826 echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
827 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
828 echo '}'."\n";
830 echo 'function saveNext(){'."\n";
831 echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
832 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
833 echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
834 echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
835 echo '}'."\n";
837 echo '</script>'."\n";
838 echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
840 ///Start of teacher info row
842 echo '<tr>';
843 echo '<td class="picture teacher">';
844 if ($submission->teacher) {
845 $teacher = get_record('user', 'id', $submission->teacher);
846 } else {
847 global $USER;
848 $teacher = $USER;
850 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
851 echo '</td>';
852 echo '<td class="content">';
853 echo '<form id="submitform" action="submissions.php" method="post">';
854 echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
855 echo '<input type="hidden" name="offset" value="'.($offset+1).'" />';
856 echo '<input type="hidden" name="userid" value="'.$userid.'" />';
857 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
858 echo '<input type="hidden" name="mode" value="grade" />';
859 echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
861 //new hidden field, initialized to -1.
862 echo '<input type="hidden" name="saveuserid" value="-1" />';
864 if ($submission->timemarked) {
865 echo '<div class="from">';
866 echo '<div class="fullname">'.fullname($teacher, true).'</div>';
867 echo '<div class="time">'.userdate($submission->timemarked).'</div>';
868 echo '</div>';
870 echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
871 choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1);
872 echo '</div>';
874 echo '<div class="clearer"></div>';
876 if (!empty($CFG->enableoutcomes) and $outcomes_data = grade_get_outcomes($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid)) {
877 foreach($outcomes_data as $n=>$data) {
878 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.format_string($data->name).'</label> ';
879 $options = make_grades_menu(-$data->scaleid);
880 if ($data->locked) {
881 $options[0] = get_string('nooutcome', 'grades');
882 echo $options[$data->grade];
883 } else {
884 choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $data->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
886 echo '</div>';
887 echo '<div class="clearer"></div>';
892 $this->preprocess_submission($submission);
894 print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id);
896 if ($this->usehtmleditor) {
897 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
898 } else {
899 echo '<div class="format">';
900 choose_from_menu(format_text_menu(), "format", $submission->format, "");
901 helpbutton("textformat", get_string("helpformatting"));
902 echo '</div>';
905 ///Print Buttons in Single View
906 echo '<div class="buttons">';
907 echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
908 echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
909 //if there are more to be graded.
910 if ($nextid) {
911 echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
912 echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
914 echo '</div>';
915 echo '</div></form>';
917 $customfeedback = $this->custom_feedbackform($submission, true);
918 if (!empty($customfeedback)) {
919 echo $customfeedback;
922 echo '</td></tr>';
924 ///End of teacher info row, Start of student info row
925 echo '<tr>';
926 echo '<td class="picture user">';
927 print_user_picture($user->id, $this->course->id, $user->picture);
928 echo '</td>';
929 echo '<td class="topic">';
930 echo '<div class="from">';
931 echo '<div class="fullname">'.fullname($user, true).'</div>';
932 if ($submission->timemodified) {
933 echo '<div class="time">'.userdate($submission->timemodified).
934 $this->display_lateness($submission->timemodified).'</div>';
936 echo '</div>';
937 $this->print_user_files($user->id);
938 echo '</td>';
939 echo '</tr>';
941 ///End of student info row
943 echo '</table>';
945 if ($this->usehtmleditor) {
946 use_html_editor();
949 print_footer('none');
953 * Preprocess submission before grading
955 * Called by display_submission()
956 * The default type does nothing here.
957 * @param $submission object The submission object
959 function preprocess_submission(&$submission) {
963 * Display all the submissions ready for grading
965 function display_submissions() {
966 global $CFG, $db, $USER;
967 require_once($CFG->libdir.'/gradelib.php');
969 /* first we check to see if the form has just been submitted
970 * to request user_preference updates
973 if (isset($_POST['updatepref'])){
974 $perpage = optional_param('perpage', 10, PARAM_INT);
975 $perpage = ($perpage <= 0) ? 10 : $perpage ;
976 set_user_preference('assignment_perpage', $perpage);
977 set_user_preference('assignment_quickgrade', optional_param('quickgrade',0, PARAM_BOOL));
980 /* next we get perpage and quickgrade (allow quick grade) params
981 * from database
983 $perpage = get_user_preferences('assignment_perpage', 10);
985 if ($this->lockedgrades) {
986 $quickgrade = 0;
987 } else {
988 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
991 if (!empty($CFG->enableoutcomes) and grade_get_outcomes($this->course->id, 'mod', 'assignment', $this->assignment->id)) {
992 $uses_outcomes = true;
993 } else {
994 $uses_outcomes = false;
997 $teacherattempts = true; /// Temporary measure
998 $page = optional_param('page', 0, PARAM_INT);
999 $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1001 /// Some shortcuts to make the code read better
1003 $course = $this->course;
1004 $assignment = $this->assignment;
1005 $cm = $this->cm;
1007 $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1009 add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
1011 $navlinks = array();
1012 $navlinks[] = array('name' => $this->strassignments, 'link' => "index.php?id=$course->id", 'type' => 'activity');
1013 $navlinks[] = array('name' => format_string($this->assignment->name,true),
1014 'link' => "view.php?a={$this->assignment->id}",
1015 'type' => 'activityinstance');
1016 $navlinks[] = array('name' => $this->strsubmissions, 'link' => '', 'type' => 'title');
1017 $navigation = build_navigation($navlinks);
1019 print_header_simple(format_string($this->assignment->name,true), "", $navigation,
1020 '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm));
1022 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1024 /// find out current groups mode
1025 $groupmode = groupmode($course, $cm);
1026 $currentgroup = setup_and_print_groups($course, $groupmode, 'submissions.php?id=' . $this->cm->id);
1028 /// Get all ppl that are allowed to submit assignments
1029 $users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', $currentgroup, '', false);
1031 $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status');
1032 if ($uses_outcomes) {
1033 $tablecolumns[] = ''; // no sorting based on outcomes column
1036 $tableheaders = array('',
1037 get_string('fullname'),
1038 get_string('grade'),
1039 get_string('comment', 'assignment'),
1040 get_string('lastmodified').' ('.$course->student.')',
1041 get_string('lastmodified').' ('.$course->teacher.')',
1042 get_string('status'));
1043 if ($uses_outcomes) {
1044 $tableheaders[] = get_string('outcomes', 'grades');
1047 require_once($CFG->libdir.'/tablelib.php');
1048 $table = new flexible_table('mod-assignment-submissions');
1050 $table->define_columns($tablecolumns);
1051 $table->define_headers($tableheaders);
1052 $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
1054 $table->sortable(true, 'lastname');//sorted by lastname by default
1055 $table->collapsible(true);
1056 $table->initialbars(true);
1058 $table->column_suppress('picture');
1059 $table->column_suppress('fullname');
1061 $table->column_class('picture', 'picture');
1062 $table->column_class('fullname', 'fullname');
1063 $table->column_class('grade', 'grade');
1064 $table->column_class('submissioncomment', 'comment');
1065 $table->column_class('timemodified', 'timemodified');
1066 $table->column_class('timemarked', 'timemarked');
1067 $table->column_class('status', 'status');
1068 if ($uses_outcomes) {
1069 $table->column_class('outcomes', 'outcomes');
1072 $table->set_attribute('cellspacing', '0');
1073 $table->set_attribute('id', 'attempts');
1074 $table->set_attribute('class', 'submissions');
1075 $table->set_attribute('width', '90%');
1076 //$table->set_attribute('align', 'center');
1078 // Start working -- this is necessary as soon as the niceties are over
1079 $table->setup();
1081 /// Check to see if groups are being used in this assignment
1083 if (!$teacherattempts) {
1084 $teachers = get_course_teachers($course->id);
1085 if (!empty($teachers)) {
1086 $keys = array_keys($teachers);
1088 foreach ($keys as $key) {
1089 unset($users[$key]);
1093 if (empty($users)) {
1094 print_heading(get_string('noattempts','assignment'));
1095 return true;
1098 /// Construct the SQL
1100 if ($where = $table->get_sql_where()) {
1101 $where .= ' AND ';
1104 if ($sort = $table->get_sql_sort()) {
1105 $sort = ' ORDER BY '.$sort;
1108 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
1109 s.id AS submissionid, s.grade, s.submissioncomment,
1110 s.timemodified, s.timemarked ';
1111 $sql = 'FROM '.$CFG->prefix.'user u '.
1112 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
1113 AND s.assignment = '.$this->assignment->id.' '.
1114 'WHERE '.$where.'u.id IN ('.implode(',', array_keys($users)).') ';
1116 $table->pagesize($perpage, count($users));
1118 ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1119 $offset = $page * $perpage;
1121 $strupdate = get_string('update');
1122 $strgrade = get_string('grade');
1123 $grademenu = make_grades_menu($this->assignment->grade);
1125 if (($ausers = get_records_sql($select.$sql.$sort, $table->get_page_start(), $table->get_page_size())) !== false) {
1127 foreach ($ausers as $auser) {
1128 /// Calculate user status
1129 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
1130 $picture = print_user_picture($auser->id, $course->id, $auser->picture, false, true);
1132 if (empty($auser->submissionid)) {
1133 $auser->grade = -1; //no submission yet
1136 if (!empty($auser->submissionid)) {
1137 ///Prints student answer and student modified date
1138 ///attach file or print link to student answer, depending on the type of the assignment.
1139 ///Refer to print_student_answer in inherited classes.
1140 if ($auser->timemodified > 0) {
1141 $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
1142 . userdate($auser->timemodified).'</div>';
1143 } else {
1144 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1146 ///Print grade, dropdown or text
1147 if ($auser->timemarked > 0) {
1148 $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
1150 if ($quickgrade) {
1151 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1152 'menu['.$auser->id.']', $auser->grade,
1153 get_string('nograde'),'',-1,true,false,$tabindex++);
1154 $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
1155 } else {
1156 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1159 } else {
1160 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1161 if ($quickgrade) {
1162 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1163 'menu['.$auser->id.']', $auser->grade,
1164 get_string('nograde'),'',-1,true,false,$tabindex++);
1165 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1166 } else {
1167 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1170 ///Print Comment
1171 if ($quickgrade) {
1172 $comment = '<div id="com'.$auser->id.'">'
1173 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1174 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1175 } else {
1176 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
1178 } else {
1179 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1180 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1181 $status = '<div id="st'.$auser->id.'">&nbsp;</div>';
1183 if ($quickgrade) { // allow editing
1184 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1185 'menu['.$auser->id.']', $auser->grade,
1186 get_string('nograde'),'',-1,true,false,$tabindex++);
1187 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1188 } else {
1189 $grade = '<div id="g'.$auser->id.'">-</div>';
1192 if ($quickgrade) {
1193 $comment = '<div id="com'.$auser->id.'">'
1194 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1195 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1196 } else {
1197 $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1201 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
1202 $auser->status = 0;
1203 } else {
1204 $auser->status = 1;
1207 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
1209 if ($this->lockedgrades) {
1210 $status = get_string('gradeitemlocked', 'grades');
1211 } else {
1212 ///No more buttons, we use popups ;-).
1213 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1214 . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;offset='.$offset++;
1215 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780,
1216 $buttontext, 'none', true, 'button'.$auser->id);
1218 $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
1221 $outcomes = '';
1223 if ($uses_outcomes and $outcomes_data = grade_get_outcomes($this->course->id, 'mod', 'assignment', $this->assignment->id, $auser->id)) {
1225 foreach($outcomes_data as $n=>$data) {
1226 $outcomes .= '<div class="outcome"><label>'.format_string($data->name).'</label>';
1227 $options = make_grades_menu(-$data->scaleid);
1229 if ($data->locked or !$quickgrade) {
1230 $options[0] = get_string('nooutcome', 'grades');
1231 $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$data->grade].'</span>';
1232 } else {
1233 $outcomes .= ' ';
1234 $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']',
1235 $data->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id);
1237 $outcomes .= '</div>';
1242 $row = array($picture, fullname($auser), $grade, $comment, $studentmodified, $teachermodified, $status);
1243 if ($uses_outcomes) {
1244 $row[] = $outcomes;
1247 $table->add_data($row);
1251 /// Print quickgrade form around the table
1252 if ($quickgrade){
1253 echo '<form action="submissions.php" id="fastg" method="post">';
1254 echo '<div>';
1255 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
1256 echo '<input type="hidden" name="mode" value="fastgrade" />';
1257 echo '<input type="hidden" name="page" value="'.$page.'" />';
1258 echo '</div>';
1259 echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1262 $table->print_html(); /// Print the whole table
1264 if ($quickgrade){
1265 echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1266 echo '</form>';
1268 /// End of fast grading form
1270 /// Mini form for setting user preference
1271 echo '<br />';
1272 echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post">';
1273 echo '<fieldset class="invisiblefieldset">';
1274 echo '<input type="hidden" id="updatepref" name="updatepref" value="1" />';
1275 echo '<table id="optiontable">';
1276 echo '<tr align="right"><td>';
1277 echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1278 echo ':</td>';
1279 echo '<td align="left">';
1280 echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1281 helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1282 echo '</td></tr>';
1283 if (!$this->lockedgrades) {
1284 echo '<tr align="right">';
1285 echo '<td>';
1286 print_string('quickgrade','assignment');
1287 echo ':</td>';
1288 echo '<td align="left">';
1289 if ($quickgrade){
1290 echo '<input type="checkbox" name="quickgrade" value="1" checked="checked" />';
1291 } else {
1292 echo '<input type="checkbox" name="quickgrade" value="1" />';
1294 helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1295 echo '</td></tr>';
1297 echo '<tr>';
1298 echo '<td colspan="2" align="right">';
1299 echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1300 echo '</td></tr></table>';
1301 echo '</fieldset>';
1302 echo '</form>';
1303 ///End of mini form
1304 print_footer($this->course);
1308 * Process teacher feedback submission
1310 * This is called by submissions() when a grading even has taken place.
1311 * It gets its data from the submitted form.
1312 * @return object The updated submission object
1314 function process_feedback() {
1316 global $USER;
1318 if (!$feedback = data_submitted()) { // No incoming data?
1319 return false;
1322 ///For save and next, we need to know the userid to save, and the userid to go
1323 ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1324 ///as the userid to store
1325 if ((int)$feedback->saveuserid !== -1){
1326 $feedback->userid = $feedback->saveuserid;
1329 if (!empty($feedback->cancel)) { // User hit cancel button
1330 return false;
1333 // store outcomes if needed
1334 $this->process_outcomes($feedback->userid);
1336 $submission = $this->get_submission($feedback->userid, true); // Get or make one
1338 $submission->grade = $feedback->grade;
1339 $submission->submissioncomment = $feedback->submissioncomment;
1340 $submission->format = $feedback->format;
1341 $submission->teacher = $USER->id;
1342 $submission->mailed = 0; // Make sure mail goes out (again, even)
1343 $submission->timemarked = time();
1345 unset($submission->data1); // Don't need to update this.
1346 unset($submission->data2); // Don't need to update this.
1348 if (empty($submission->timemodified)) { // eg for offline assignments
1349 // $submission->timemodified = time();
1352 if (! update_record('assignment_submissions', $submission)) {
1353 return false;
1356 // triger grade event
1357 $this->update_grade($submission);
1359 add_to_log($this->course->id, 'assignment', 'update grades',
1360 'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1362 return $submission;
1366 function process_outcomes($userid) {
1367 global $CFG, $USER;
1369 if (empty($CFG->enableoutcomes)) {
1370 return;
1373 require_once($CFG->libdir.'/gradelib.php');
1375 if (!$formdata = data_submitted()) {
1376 return;
1379 $data = array();
1380 if ($outcomes = grade_get_outcomes($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid)) {
1381 foreach($outcomes as $n=>$old) {
1382 $name = 'outcome_'.$n;
1383 if (isset($formdata->{$name}[$userid]) and $old->grade != $formdata->{$name}[$userid]) {
1384 $data[$n] = $formdata->{$name}[$userid];
1388 if (count($data) > 0) {
1389 grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1395 * Load the submission object for a particular user
1397 * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1398 * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1399 * @return object The submission
1401 function get_submission($userid=0, $createnew=false) {
1402 global $USER;
1404 if (empty($userid)) {
1405 $userid = $USER->id;
1408 $submission = get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
1410 if ($submission || !$createnew) {
1411 return $submission;
1413 $newsubmission = $this->prepare_new_submission($userid);
1414 if (!insert_record("assignment_submissions", $newsubmission)) {
1415 error("Could not insert a new empty submission");
1418 return get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
1422 * Instantiates a new submission object for a given user
1424 * Sets the assignment, userid and times, everything else is set to default values.
1425 * @param $userid int The userid for which we want a submission object
1426 * @return object The submission
1428 function prepare_new_submission($userid) {
1429 $submission = new Object;
1430 $submission->assignment = $this->assignment->id;
1431 $submission->userid = $userid;
1432 //$submission->timecreated = time();
1433 $submission->timecreated = '';
1434 // teachers should not be modifying modified date, except offline assignments
1435 $submission->timemodified = $submission->timecreated;
1436 $submission->numfiles = 0;
1437 $submission->data1 = '';
1438 $submission->data2 = '';
1439 $submission->grade = -1;
1440 $submission->submissioncomment = '';
1441 $submission->format = 0;
1442 $submission->teacher = 0;
1443 $submission->timemarked = 0;
1444 $submission->mailed = 0;
1445 return $submission;
1449 * Return all assignment submissions by ENROLLED students (even empty)
1451 * @param $sort string optional field names for the ORDER BY in the sql query
1452 * @param $dir string optional specifying the sort direction, defaults to DESC
1453 * @return array The submission objects indexed by id
1455 function get_submissions($sort='', $dir='DESC') {
1456 return assignment_get_all_submissions($this->assignment, $sort, $dir);
1460 * Counts all real assignment submissions by ENROLLED students (not empty ones)
1462 * @param $groupid int optional If nonzero then count is restricted to this group
1463 * @return int The number of submissions
1465 function count_real_submissions($groupid=0) {
1466 return assignment_count_real_submissions($this->assignment, $groupid);
1470 * Alerts teachers by email of new or changed assignments that need grading
1472 * First checks whether the option to email teachers is set for this assignment.
1473 * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1474 * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1475 * @param $submission object The submission that has changed
1477 function email_teachers($submission) {
1478 global $CFG;
1480 if (empty($this->assignment->emailteachers)) { // No need to do anything
1481 return;
1484 $user = get_record('user', 'id', $submission->userid);
1486 if ($teachers = $this->get_graders($user)) {
1488 $strassignments = get_string('modulenameplural', 'assignment');
1489 $strassignment = get_string('modulename', 'assignment');
1490 $strsubmitted = get_string('submitted', 'assignment');
1492 foreach ($teachers as $teacher) {
1493 $info = new object();
1494 $info->username = fullname($user, true);
1495 $info->assignment = format_string($this->assignment->name,true);
1496 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1498 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1499 $posttext = $this->email_teachers_text($info);
1500 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
1502 @email_to_user($teacher, $user, $postsubject, $posttext, $posthtml); // If it fails, oh well, too bad.
1508 * Returns a list of teachers that should be grading given submission
1510 function get_graders($user) {
1511 //potential graders
1512 $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1514 $graders = array();
1515 if (groupmode($this->course, $this->cm) == SEPARATEGROUPS) { // Separate groups are being used
1516 if ($groups = user_group($this->course->id, $user->id)) { // Try to find all groups
1517 foreach ($groups as $group) {
1518 foreach ($potgraders as $t) {
1519 if ($t->id == $user->id) {
1520 continue; // do not send self
1522 if (groups_is_member($group->id, $t->id)) {
1523 $graders[$t->id] = $t;
1527 } else {
1528 // user not in group, try to find graders without group
1529 foreach ($potgraders as $t) {
1530 if ($t->id == $user->id) {
1531 continue; // do not send self
1533 if (!user_group($this->course->id, $t->id)) { //ugly hack
1534 $graders[$t->id] = $t;
1538 } else {
1539 foreach ($potgraders as $t) {
1540 if ($t->id == $user->id) {
1541 continue; // do not send self
1543 $graders[$t->id] = $t;
1546 return $graders;
1550 * Creates the text content for emails to teachers
1552 * @param $info object The info used by the 'emailteachermail' language string
1553 * @return string
1555 function email_teachers_text($info) {
1556 $posttext = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '.
1557 format_string($this->assignment->name)."\n";
1558 $posttext .= '---------------------------------------------------------------------'."\n";
1559 $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
1560 $posttext .= "\n---------------------------------------------------------------------\n";
1561 return $posttext;
1565 * Creates the html content for emails to teachers
1567 * @param $info object The info used by the 'emailteachermailhtml' language string
1568 * @return string
1570 function email_teachers_html($info) {
1571 global $CFG;
1572 $posthtml = '<p><font face="sans-serif">'.
1573 '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'.
1574 '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
1575 '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>';
1576 $posthtml .= '<hr /><font face="sans-serif">';
1577 $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1578 $posthtml .= '</font><hr />';
1579 return $posthtml;
1583 * Produces a list of links to the files uploaded by a user
1585 * @param $userid int optional id of the user. If 0 then $USER->id is used.
1586 * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1587 * @return string optional
1589 function print_user_files($userid=0, $return=false) {
1590 global $CFG, $USER;
1592 if (!$userid) {
1593 if (!isloggedin()) {
1594 return '';
1596 $userid = $USER->id;
1599 $filearea = $this->file_area_name($userid);
1601 $output = '';
1603 if ($basedir = $this->file_area($userid)) {
1604 if ($files = get_directory_list($basedir)) {
1605 require_once($CFG->libdir.'/filelib.php');
1606 foreach ($files as $key => $file) {
1608 $icon = mimeinfo('icon', $file);
1610 if ($CFG->slasharguments) {
1611 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
1612 } else {
1613 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
1616 $output .= '<img align="middle" src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.
1617 '<a href="'.$ffurl.'" >'.$file.'</a><br />';
1622 $output = '<div class="files">'.$output.'</div>';
1624 if ($return) {
1625 return $output;
1627 echo $output;
1631 * Count the files uploaded by a given user
1633 * @param $userid int The user id
1634 * @return int
1636 function count_user_files($userid) {
1637 global $CFG;
1639 $filearea = $this->file_area_name($userid);
1641 if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) {
1642 if ($files = get_directory_list($basedir)) {
1643 return count($files);
1646 return 0;
1650 * Creates a directory file name, suitable for make_upload_directory()
1652 * @param $userid int The user id
1653 * @return string path to file area
1655 function file_area_name($userid) {
1656 global $CFG;
1658 return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid;
1662 * Makes an upload directory
1664 * @param $userid int The user id
1665 * @return string path to file area.
1667 function file_area($userid) {
1668 return make_upload_directory( $this->file_area_name($userid) );
1672 * Returns true if the student is allowed to submit
1674 * Checks that the assignment has started and, if the option to prevent late
1675 * submissions is set, also checks that the assignment has not yet closed.
1676 * @return boolean
1678 function isopen() {
1679 $time = time();
1680 if ($this->assignment->preventlate && $this->assignment->timedue) {
1681 return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
1682 } else {
1683 return ($this->assignment->timeavailable <= $time);
1688 * Return an outline of the user's interaction with the assignment
1690 * The default method prints the grade and timemodified
1691 * @param $user object
1692 * @return object with properties ->info and ->time
1694 function user_outline($user) {
1695 if ($submission = $this->get_submission($user->id)) {
1697 $result = new object();
1698 $result->info = get_string('grade').': '.$this->display_grade($submission->grade);
1699 $result->time = $submission->timemodified;
1700 return $result;
1702 return NULL;
1706 * Print complete information about the user's interaction with the assignment
1708 * @param $user object
1710 function user_complete($user) {
1711 if ($submission = $this->get_submission($user->id)) {
1712 if ($basedir = $this->file_area($user->id)) {
1713 if ($files = get_directory_list($basedir)) {
1714 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1715 foreach ($files as $file) {
1716 $countfiles .= "; $file";
1721 print_simple_box_start();
1722 echo get_string("lastmodified").": ";
1723 echo userdate($submission->timemodified);
1724 echo $this->display_lateness($submission->timemodified);
1726 $this->print_user_files($user->id);
1728 echo '<br />';
1730 if (empty($submission->timemarked)) {
1731 print_string("notgradedyet", "assignment");
1732 } else {
1733 $this->view_feedback($submission);
1736 print_simple_box_end();
1738 } else {
1739 print_string("notsubmittedyet", "assignment");
1744 * Return a string indicating how late a submission is
1746 * @param $timesubmitted int
1747 * @return string
1749 function display_lateness($timesubmitted) {
1750 return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
1754 * Empty method stub for all delete actions.
1756 function delete() {
1757 //nothing by default
1758 redirect('view.php?id='.$this->cm->id);
1762 * Empty custom feedback grading form.
1764 function custom_feedbackform($submission, $return=false) {
1765 //nothing by default
1766 return '';
1770 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1771 * for the course (see resource).
1773 * Given a course_module object, this function returns any "extra" information that may be needed
1774 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
1776 * @param $coursemodule object The coursemodule object (record).
1777 * @return object An object on information that the coures will know about (most noticeably, an icon).
1780 function get_coursemodule_info($coursemodule) {
1781 return false;
1784 } ////// End of the assignment_base class
1788 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1791 * Deletes an assignment instance
1793 * This is done by calling the delete_instance() method of the assignment type class
1795 function assignment_delete_instance($id){
1796 global $CFG;
1798 if (! $assignment = get_record('assignment', 'id', $id)) {
1799 return false;
1802 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1803 $assignmentclass = "assignment_$assignment->assignmenttype";
1804 $ass = new $assignmentclass();
1805 return $ass->delete_instance($assignment);
1810 * Updates an assignment instance
1812 * This is done by calling the update_instance() method of the assignment type class
1814 function assignment_update_instance($assignment){
1815 global $CFG;
1817 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1819 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1820 $assignmentclass = "assignment_$assignment->assignmenttype";
1821 $ass = new $assignmentclass();
1822 return $ass->update_instance($assignment);
1827 * Adds an assignment instance
1829 * This is done by calling the add_instance() method of the assignment type class
1831 function assignment_add_instance($assignment) {
1832 global $CFG;
1834 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1836 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1837 $assignmentclass = "assignment_$assignment->assignmenttype";
1838 $ass = new $assignmentclass();
1839 return $ass->add_instance($assignment);
1844 * Returns an outline of a user interaction with an assignment
1846 * This is done by calling the user_outline() method of the assignment type class
1848 function assignment_user_outline($course, $user, $mod, $assignment) {
1849 global $CFG;
1851 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1852 $assignmentclass = "assignment_$assignment->assignmenttype";
1853 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1854 return $ass->user_outline($user);
1858 * Prints the complete info about a user's interaction with an assignment
1860 * This is done by calling the user_complete() method of the assignment type class
1862 function assignment_user_complete($course, $user, $mod, $assignment) {
1863 global $CFG;
1865 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1866 $assignmentclass = "assignment_$assignment->assignmenttype";
1867 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1868 return $ass->user_complete($user);
1872 * Function to be run periodically according to the moodle cron
1874 * Finds all assignment notifications that have yet to be mailed out, and mails them
1876 function assignment_cron () {
1878 global $CFG, $USER;
1880 /// Notices older than 1 day will not be mailed. This is to avoid the problem where
1881 /// cron has not been running for a long time, and then suddenly people are flooded
1882 /// with mail from the past few weeks or months
1884 $timenow = time();
1885 $endtime = $timenow - $CFG->maxeditingtime;
1886 $starttime = $endtime - 24 * 3600; /// One day earlier
1888 if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
1890 $CFG->enablerecordcache = true; // We want all the caching we can get
1892 $realuser = clone($USER);
1894 foreach ($submissions as $key => $submission) {
1895 if (! set_field("assignment_submissions", "mailed", "1", "id", "$submission->id")) {
1896 echo "Could not update the mailed field for id $submission->id. Not mailed.\n";
1897 unset($submissions[$key]);
1901 $timenow = time();
1903 foreach ($submissions as $submission) {
1905 echo "Processing assignment submission $submission->id\n";
1907 if (! $user = get_record("user", "id", "$submission->userid")) {
1908 echo "Could not find user $post->userid\n";
1909 continue;
1912 if (! $course = get_record("course", "id", "$submission->course")) {
1913 echo "Could not find course $submission->course\n";
1914 continue;
1917 /// Override the language and timezone of the "current" user, so that
1918 /// mail is customised for the receiver.
1919 $USER = $user;
1920 course_setup($course);
1922 if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) {
1923 echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n";
1924 continue;
1927 if (! $teacher = get_record("user", "id", "$submission->teacher")) {
1928 echo "Could not find teacher $submission->teacher\n";
1929 continue;
1932 if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
1933 echo "Could not find course module for assignment id $submission->assignment\n";
1934 continue;
1937 if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
1938 continue;
1941 $strassignments = get_string("modulenameplural", "assignment");
1942 $strassignment = get_string("modulename", "assignment");
1944 $assignmentinfo = new object();
1945 $assignmentinfo->teacher = fullname($teacher);
1946 $assignmentinfo->assignment = format_string($submission->name,true);
1947 $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
1949 $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true);
1950 $posttext = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n";
1951 $posttext .= "---------------------------------------------------------------------\n";
1952 $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
1953 $posttext .= "---------------------------------------------------------------------\n";
1955 if ($user->mailformat == 1) { // HTML
1956 $posthtml = "<p><font face=\"sans-serif\">".
1957 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
1958 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
1959 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
1960 $posthtml .= "<hr /><font face=\"sans-serif\">";
1961 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
1962 $posthtml .= "</font><hr />";
1963 } else {
1964 $posthtml = "";
1967 if (! email_to_user($user, $teacher, $postsubject, $posttext, $posthtml)) {
1968 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
1972 $USER = $realuser;
1973 course_setup(SITEID); // reset cron user language, theme and timezone settings
1977 return true;
1981 * Return grade for given user or all users.
1983 * @param int $assignmentid id of assignment
1984 * @param int $userid optional user id, 0 means all users
1985 * @return array array of grades, false if none
1987 function assignment_get_user_grades($assignment, $userid=0) {
1988 global $CFG;
1990 $user = $userid ? "AND u.id = $userid" : "";
1992 $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat
1993 FROM {$CFG->prefix}user u, {$CFG->prefix}assignment_submissions s
1994 WHERE u.id = s.userid AND s.assignment = $assignment->id
1995 $user";
1997 return get_records_sql($sql);
2001 * Update grades by firing grade_updated event
2003 * @param object $assignment null means all assignments
2004 * @param int $userid specific user only, 0 mean all
2006 function assignment_update_grades($assignment=null, $userid=0, $nullifnone=true) {
2007 global $CFG;
2008 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2009 require_once($CFG->libdir.'/gradelib.php');
2012 if ($assignment != null) {
2013 if ($grades = assignment_get_user_grades($assignment, $userid)) {
2014 foreach($grades as $k=>$v) {
2015 if ($v->rawgrade == -1) {
2016 $grades[$k]->rawgrade = null;
2019 grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades);
2022 } else {
2023 $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
2024 FROM {$CFG->prefix}assignment a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
2025 WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2026 if ($rs = get_recordset_sql($sql)) {
2027 if ($rs->RecordCount() > 0) {
2028 while ($assignment = rs_fetch_next_record($rs)) {
2029 assignment_grade_item_update($assignment);
2030 if ($assignment->grade != 0) {
2031 assignment_update_grades($assignment);
2035 rs_close($rs);
2041 * Create grade item for given assignment
2043 * @param object $assignment object with extra cmidnumber
2044 * @return int 0 if ok, error code otherwise
2046 function assignment_grade_item_update($assignment) {
2047 global $CFG;
2048 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2049 require_once($CFG->libdir.'/gradelib.php');
2052 if (!isset($assignment->courseid)) {
2053 $assignment->courseid = $assignment->course;
2056 $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
2058 if ($assignment->grade > 0) {
2059 $params['gradetype'] = GRADE_TYPE_VALUE;
2060 $params['grademax'] = $assignment->grade;
2061 $params['grademin'] = 0;
2063 } else if ($assignment->grade < 0) {
2064 $params['gradetype'] = GRADE_TYPE_SCALE;
2065 $params['scaleid'] = -$assignment->grade;
2067 } else {
2068 $params['gradetype'] = GRADE_TYPE_NONE;
2071 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, $params);
2075 * Delete grade item for given assignment
2077 * @param object $assignment object
2078 * @return object assignment
2080 function assignment_grade_item_delete($assignment) {
2081 global $CFG;
2082 require_once($CFG->libdir.'/gradelib.php');
2084 if (!isset($assignment->courseid)) {
2085 $assignment->courseid = $assignment->course;
2088 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
2092 * Something wants to change the grade from outside using "grade_updated" event.
2095 function assignment_grade_handler($eventdata) {
2096 global $CFG, $USER;
2098 if ($eventdata->source == 'mod/assignment') {
2099 // event from assignment itself
2100 return true;
2103 if ($eventdata->itemtype != 'mod' or $eventdata->itemmodule != 'assignment') {
2104 //not for us - ignore it
2105 return true;
2108 if (!$assignment = get_record('assignment', 'id', $eventdata->iteminstance)) {
2109 return true;
2111 if (! $course = get_record('course', 'id', $assignment->course)) {
2112 return true;
2114 if (! $cm = get_coursemodule_from_instance('assignment', $assignment->id, $course->id)) {
2115 return true;
2118 // Load up the required assignment class
2119 require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
2120 $assignmentclass = 'assignment_'.$assignment->assignmenttype;
2121 $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
2123 $old = $assignmentinstance->get_submission($eventdata->userid, true); // Get or make one
2124 $submission = new object();
2125 $submission->id = $old->id;
2126 $submission->userid = $old->userid;
2127 $submission->teacher = $USER->id;
2128 $submission->timemarked = time();
2130 if (is_null($eventdata->rawgrade)) {
2131 $submission->grade = -1;
2132 } else {
2133 $submission->grade = (int)$eventdata->rawgrade; // round it for now
2134 if ($old->grade != $submission->grade) {
2135 $submission->mailed = 0; // Make sure mail goes out (again, even)
2139 if (isset($eventdata->feedback)) {
2140 $submission->submissioncomment = addslashes($eventdata->feedback);
2143 if (isset($eventdata->feedbackformat)) {
2144 $submission->format = (int)$eventdata->feedbackformat;
2147 if (isset($eventdata->feedback) && ($old->submissioncomment != $eventdata->feedback or $old->format != $submission->format)) {
2148 $submission->mailed = 0; // Make sure mail goes out (again, even)
2151 if (!update_record('assignment_submissions', $submission)) {
2152 //return false;
2155 // TODO: add proper logging
2156 add_to_log($course->id, 'assignment', 'update grades',
2157 'submissions.php?id='.$assignment->id.'&user='.$submission->userid, $submission->userid, $cm->id);
2159 return true;
2163 * Returns the users with data in one assignment (students and teachers)
2165 * @param $assignmentid int
2166 * @return array of user objects
2168 function assignment_get_participants($assignmentid) {
2170 global $CFG;
2172 //Get students
2173 $students = get_records_sql("SELECT DISTINCT u.id, u.id
2174 FROM {$CFG->prefix}user u,
2175 {$CFG->prefix}assignment_submissions a
2176 WHERE a.assignment = '$assignmentid' and
2177 u.id = a.userid");
2178 //Get teachers
2179 $teachers = get_records_sql("SELECT DISTINCT u.id, u.id
2180 FROM {$CFG->prefix}user u,
2181 {$CFG->prefix}assignment_submissions a
2182 WHERE a.assignment = '$assignmentid' and
2183 u.id = a.teacher");
2185 //Add teachers to students
2186 if ($teachers) {
2187 foreach ($teachers as $teacher) {
2188 $students[$teacher->id] = $teacher;
2191 //Return students array (it contains an array of unique users)
2192 return ($students);
2196 * Checks if a scale is being used by an assignment
2198 * This is used by the backup code to decide whether to back up a scale
2199 * @param $assignmentid int
2200 * @param $scaleid int
2201 * @return boolean True if the scale is used by the assignment
2203 function assignment_scale_used ($assignmentid, $scaleid) {
2205 $return = false;
2207 $rec = get_record('assignment','id',$assignmentid,'grade',-$scaleid);
2209 if (!empty($rec) && !empty($scaleid)) {
2210 $return = true;
2213 return $return;
2217 * Make sure up-to-date events are created for all assignment instances
2219 * This standard function will check all instances of this module
2220 * and make sure there are up-to-date events created for each of them.
2221 * If courseid = 0, then every assignment event in the site is checked, else
2222 * only assignment events belonging to the course specified are checked.
2223 * This function is used, in its new format, by restore_refresh_events()
2225 * @param $courseid int optional If zero then all assignments for all courses are covered
2226 * @return boolean Always returns true
2228 function assignment_refresh_events($courseid = 0) {
2230 if ($courseid == 0) {
2231 if (! $assignments = get_records("assignment")) {
2232 return true;
2234 } else {
2235 if (! $assignments = get_records("assignment", "course", $courseid)) {
2236 return true;
2239 $moduleid = get_field('modules', 'id', 'name', 'assignment');
2241 foreach ($assignments as $assignment) {
2242 $event = NULL;
2243 $event->name = addslashes($assignment->name);
2244 $event->description = addslashes($assignment->description);
2245 $event->timestart = $assignment->timedue;
2247 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
2248 update_event($event);
2250 } else {
2251 $event->courseid = $assignment->course;
2252 $event->groupid = 0;
2253 $event->userid = 0;
2254 $event->modulename = 'assignment';
2255 $event->instance = $assignment->id;
2256 $event->eventtype = 'due';
2257 $event->timeduration = 0;
2258 $event->visible = get_field('course_modules', 'visible', 'module', $moduleid, 'instance', $assignment->id);
2259 add_event($event);
2263 return true;
2267 * Print recent activity from all assignments in a given course
2269 * This is used by the recent activity block
2271 function assignment_print_recent_activity($course, $isteacher, $timestart) {
2272 global $CFG;
2274 $content = false;
2275 $assignments = array();
2277 if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
2278 'course = \''.$course->id.'\' AND '.
2279 'module = \'assignment\' AND '.
2280 'action = \'upload\' ', 'time ASC')) {
2281 return false;
2284 foreach ($logs as $log) {
2285 //Create a temp valid module structure (course,id)
2286 $tempmod = new object();
2287 $tempmod->course = $log->course;
2288 $tempmod->id = $log->info;
2289 //Obtain the visible property from the instance
2290 $modvisible = instance_is_visible($log->module,$tempmod);
2292 //Only if the mod is visible
2293 if ($modvisible) {
2294 if ($info = assignment_log_info($log)) {
2295 $assignments[$log->info] = $info;
2296 $assignments[$log->info]->time = $log->time;
2297 $assignments[$log->info]->url = str_replace('&', '&amp;', $log->url);
2302 if (!empty($assignments)) {
2303 print_headline(get_string('newsubmissions', 'assignment').':');
2304 foreach ($assignments as $assignment) {
2305 print_recent_activity_note($assignment->time, $assignment, $assignment->name,
2306 $CFG->wwwroot.'/mod/assignment/'.$assignment->url);
2308 $content = true;
2311 return $content;
2316 * Returns all assignments since a given time.
2318 * If assignment is specified then this restricts the results
2320 function assignment_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $assignment="0", $user="", $groupid="") {
2322 global $CFG;
2324 if ($assignment) {
2325 $assignmentselect = " AND cm.id = '$assignment'";
2326 } else {
2327 $assignmentselect = "";
2329 if ($user) {
2330 $userselect = " AND u.id = '$user'";
2331 } else {
2332 $userselect = "";
2335 $assignments = get_records_sql("SELECT asub.*, u.firstname, u.lastname, u.picture, u.id as userid,
2336 a.grade as maxgrade, name, cm.instance, cm.section, a.assignmenttype
2337 FROM {$CFG->prefix}assignment_submissions asub,
2338 {$CFG->prefix}user u,
2339 {$CFG->prefix}assignment a,
2340 {$CFG->prefix}course_modules cm
2341 WHERE asub.timemodified > '$sincetime'
2342 AND asub.userid = u.id $userselect
2343 AND a.id = asub.assignment $assignmentselect
2344 AND cm.course = '$courseid'
2345 AND cm.instance = a.id
2346 ORDER BY asub.timemodified ASC");
2348 if (empty($assignments))
2349 return;
2351 foreach ($assignments as $assignment) {
2352 if (empty($groupid) || ismember($groupid, $assignment->userid)) {
2354 $tmpactivity = new Object;
2356 $tmpactivity->type = "assignment";
2357 $tmpactivity->defaultindex = $index;
2358 $tmpactivity->instance = $assignment->instance;
2359 $tmpactivity->name = $assignment->name;
2360 $tmpactivity->section = $assignment->section;
2362 $tmpactivity->content->grade = $assignment->grade;
2363 $tmpactivity->content->maxgrade = $assignment->maxgrade;
2364 $tmpactivity->content->type = $assignment->assignmenttype;
2366 $tmpactivity->user->userid = $assignment->userid;
2367 $tmpactivity->user->fullname = fullname($assignment);
2368 $tmpactivity->user->picture = $assignment->picture;
2370 $tmpactivity->timestamp = $assignment->timemodified;
2372 $activities[] = $tmpactivity;
2374 $index++;
2378 return;
2382 * Print recent activity from all assignments in a given course
2384 * This is used by course/recent.php
2386 function assignment_print_recent_mod_activity($activity, $course, $detail=false) {
2387 global $CFG;
2389 echo '<table border="0" cellpadding="3" cellspacing="0">';
2391 echo "<tr><td class=\"userpicture\" valign=\"top\">";
2392 print_user_picture($activity->user->userid, $course, $activity->user->picture);
2393 echo "</td><td width=\"100%\"><font size=2>";
2395 if ($detail) {
2396 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
2397 "class=\"icon\" alt=\"$activity->type\"> ";
2398 echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=" . $activity->instance . "\">"
2399 . format_string($activity->name,true) . "</a> - ";
2403 if (has_capability('moodle/course:viewrecent', get_context_instance(CONTEXT_COURSE, $course))) {
2404 $grades = "(" . $activity->content->grade . " / " . $activity->content->maxgrade . ") ";
2406 $assignment->id = $activity->instance;
2407 $assignment->course = $course;
2408 $user->id = $activity->user->userid;
2410 echo $grades;
2411 echo "<br />";
2413 echo "<a href=\"$CFG->wwwroot/user/view.php?id="
2414 . $activity->user->userid . "&amp;course=$course\">"
2415 . $activity->user->fullname . "</a> ";
2417 echo " - " . userdate($activity->timestamp);
2419 echo "</font></td></tr>";
2420 echo "</table>";
2423 /// GENERIC SQL FUNCTIONS
2426 * Fetch info from logs
2428 * @param $log object with properties ->info (the assignment id) and ->userid
2429 * @return array with assignment name and user firstname and lastname
2431 function assignment_log_info($log) {
2432 global $CFG;
2433 return get_record_sql("SELECT a.name, u.firstname, u.lastname
2434 FROM {$CFG->prefix}assignment a,
2435 {$CFG->prefix}user u
2436 WHERE a.id = '$log->info'
2437 AND u.id = '$log->userid'");
2441 * Return list of marked submissions that have not been mailed out for currently enrolled students
2443 * @return array
2445 function assignment_get_unmailed_submissions($starttime, $endtime) {
2447 global $CFG;
2449 return get_records_sql("SELECT s.*, a.course, a.name
2450 FROM {$CFG->prefix}assignment_submissions s,
2451 {$CFG->prefix}assignment a
2452 WHERE s.mailed = 0
2453 AND s.timemarked <= $endtime
2454 AND s.timemarked >= $starttime
2455 AND s.assignment = a.id");
2457 /* return get_records_sql("SELECT s.*, a.course, a.name
2458 FROM {$CFG->prefix}assignment_submissions s,
2459 {$CFG->prefix}assignment a,
2460 {$CFG->prefix}user_students us
2461 WHERE s.mailed = 0
2462 AND s.timemarked <= $endtime
2463 AND s.timemarked >= $starttime
2464 AND s.assignment = a.id
2465 AND s.userid = us.userid
2466 AND a.course = us.course");
2471 * Counts all real assignment submissions by ENROLLED students (not empty ones)
2473 * There are also assignment type methods count_real_submissions() wich in the default
2474 * implementation simply call this function.
2475 * @param $groupid int optional If nonzero then count is restricted to this group
2476 * @return int The number of submissions
2478 function assignment_count_real_submissions($assignment, $groupid=0) {
2479 global $CFG;
2481 if ($groupid) { /// How many in a particular group?
2482 return count_records_sql("SELECT COUNT(DISTINCT gm.userid, gm.groupid)
2483 FROM {$CFG->prefix}assignment_submissions a,
2484 ".groups_members_from_sql()."
2485 WHERE a.assignment = $assignment->id
2486 AND a.timemodified > 0
2487 AND ".groups_members_where_sql($groupid, 'a.userid'));
2488 } else {
2489 $cm = get_coursemodule_from_instance('assignment', $assignment->id);
2490 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2492 // this is all the users with this capability set, in this context or higher
2493 if ($users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2494 foreach ($users as $user) {
2495 $array[] = $user->id;
2498 $userlists = '('.implode(',',$array).')';
2500 return count_records_sql("SELECT COUNT(*)
2501 FROM {$CFG->prefix}assignment_submissions
2502 WHERE assignment = '$assignment->id'
2503 AND timemodified > 0
2504 AND userid IN $userlists ");
2505 } else {
2506 return 0; // no users enroled in course
2513 * Return all assignment submissions by ENROLLED students (even empty)
2515 * There are also assignment type methods get_submissions() wich in the default
2516 * implementation simply call this function.
2517 * @param $sort string optional field names for the ORDER BY in the sql query
2518 * @param $dir string optional specifying the sort direction, defaults to DESC
2519 * @return array The submission objects indexed by id
2521 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2522 /// Return all assignment submissions by ENROLLED students (even empty)
2523 global $CFG;
2525 if ($sort == "lastname" or $sort == "firstname") {
2526 $sort = "u.$sort $dir";
2527 } else if (empty($sort)) {
2528 $sort = "a.timemodified DESC";
2529 } else {
2530 $sort = "a.$sort $dir";
2533 /* not sure this is needed at all since assignmenet already has a course define, so this join?
2534 $select = "s.course = '$assignment->course' AND";
2535 if ($assignment->course == SITEID) {
2536 $select = '';
2539 return get_records_sql("SELECT a.*
2540 FROM {$CFG->prefix}assignment_submissions a,
2541 {$CFG->prefix}user u
2542 WHERE u.id = a.userid
2543 AND a.assignment = '$assignment->id'
2544 ORDER BY $sort");
2546 /* return get_records_sql("SELECT a.*
2547 FROM {$CFG->prefix}assignment_submissions a,
2548 {$CFG->prefix}user_students s,
2549 {$CFG->prefix}user u
2550 WHERE a.userid = s.userid
2551 AND u.id = a.userid
2552 AND $select a.assignment = '$assignment->id'
2553 ORDER BY $sort");
2558 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2559 * for the course (see resource).
2561 * Given a course_module object, this function returns any "extra" information that may be needed
2562 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
2564 * @param $coursemodule object The coursemodule object (record).
2565 * @return object An object on information that the coures will know about (most noticeably, an icon).
2568 function assignment_get_coursemodule_info($coursemodule) {
2569 global $CFG;
2571 if (! $assignment = get_record('assignment', 'id', $coursemodule->instance)) {
2572 return false;
2575 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2576 $assignmentclass = "assignment_$assignment->assignmenttype";
2577 $ass = new $assignmentclass($coursemodule->id, $assignment);
2579 return $ass->get_coursemodule_info($coursemodule);
2584 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
2587 * Returns an array of installed assignment types indexed and sorted by name
2589 * @return array The index is the name of the assignment type, the value its full name from the language strings
2591 function assignment_types() {
2592 $types = array();
2593 $names = get_list_of_plugins('mod/assignment/type');
2594 foreach ($names as $name) {
2595 $types[$name] = get_string('type'.$name, 'assignment');
2597 asort($types);
2598 return $types;
2602 * Executes upgrade scripts for assignment types when necessary
2604 function assignment_upgrade_submodules() {
2605 global $CFG;
2607 $types = assignment_types();
2609 include($CFG->dirroot.'/mod/assignment/version.php'); // defines $module with version etc
2611 foreach ($types as $type => $typename) {
2613 $fullpath = $CFG->dirroot.'/mod/assignment/type/'.$type;
2615 /// Check for an external version file (defines $submodule)
2617 if (!is_readable($fullpath .'/version.php')) {
2618 continue;
2620 include_once($fullpath .'/version.php');
2622 /// Check whether we need to upgrade
2624 if (!isset($submodule->version)) {
2625 continue;
2628 /// Make sure this submodule will work with this assignment version
2630 if (isset($submodule->requires) and ($submodule->requires > $module->version)) {
2631 notify("Assignment submodule '$type' is too new for your assignment");
2632 continue;
2635 /// If the submodule is new, then let's install it!
2637 $currentversion = 'assignment_'.$type.'_version';
2639 if (!isset($CFG->$currentversion)) { // First install!
2640 set_config($currentversion, $submodule->version); // Must keep track of version
2642 if (!is_readable($fullpath .'/db/'.$CFG->dbtype.'.sql')) {
2643 continue;
2646 upgrade_log_start();
2647 $db->debug=true;
2648 if (!modify_database($fullpath .'/db/'.$CFG->dbtype.'.sql')) {
2649 notify("Error installing tables for submodule '$type'!");
2651 $db->debug=false;
2652 continue;
2655 /// See if we need to upgrade
2657 if ($submodule->version <= $CFG->$currentversion) {
2658 continue;
2661 /// Look for the upgrade file
2663 if (!is_readable($fullpath .'/db/'.$CFG->dbtype.'.php')) {
2664 continue;
2667 include_once($fullpath .'/db/'. $CFG->dbtype .'.php'); // defines assignment_xxx_upgrade
2669 /// Perform the upgrade
2671 $upgrade_function = 'assignment_'.$type.'_upgrade';
2672 if (function_exists($upgrade_function)) {
2673 upgrade_log_start();
2674 $db->debug=true;
2675 if ($upgrade_function($CFG->$currentversion)) {
2676 $db->debug=false;
2677 set_config($currentversion, $submodule->version);
2679 $db->debug=false;
2684 function assignment_print_overview($courses, &$htmlarray) {
2686 global $USER, $CFG;
2688 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
2689 return array();
2692 if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2693 return;
2696 // Do assignment_base::isopen() here without loading the whole thing for speed
2697 foreach ($assignments as $key => $assignment) {
2698 $time = time();
2699 if ($assignment->timedue) {
2700 if ($assignment->preventlate) {
2701 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
2702 } else {
2703 $isopen = ($assignment->timeavailable <= $time);
2706 if (empty($isopen) || empty($assignment->timedue)) {
2707 unset($assignments[$key]);
2711 $strduedate = get_string('duedate', 'assignment');
2712 $strduedateno = get_string('duedateno', 'assignment');
2713 $strgraded = get_string('graded', 'assignment');
2714 $strnotgradedyet = get_string('notgradedyet', 'assignment');
2715 $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2716 $strsubmitted = get_string('submitted', 'assignment');
2717 $strassignment = get_string('modulename', 'assignment');
2718 $strreviewed = get_string('reviewed','assignment');
2720 foreach ($assignments as $assignment) {
2721 $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
2722 '<a '.($assignment->visible ? '':' class="dimmed"').
2723 'title="'.$strassignment.'" href="'.$CFG->wwwroot.
2724 '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
2725 $assignment->name.'</a></div>';
2726 if ($assignment->timedue) {
2727 $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
2728 } else {
2729 $str .= '<div class="info">'.$strduedateno.'</div>';
2731 $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
2732 if (has_capability('mod/assignment:grade', $context)) {
2734 // count how many people can submit
2735 $submissions = 0; // init
2736 if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2737 foreach ($students as $student) {
2738 if (get_records_sql("SELECT id,id FROM {$CFG->prefix}assignment_submissions
2739 WHERE assignment = $assignment->id AND
2740 userid = $student->id AND
2741 teacher = 0 AND
2742 timemarked = 0")) {
2743 $submissions++;
2748 if ($submissions) {
2749 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2751 } else {
2752 $sql = "SELECT *
2753 FROM {$CFG->prefix}assignment_submissions
2754 WHERE userid = '$USER->id'
2755 AND assignment = '{$assignment->id}'";
2756 if ($submission = get_record_sql($sql)) {
2757 if ($submission->teacher == 0 && $submission->timemarked == 0) {
2758 $str .= $strsubmitted . ', ' . $strnotgradedyet;
2759 } else if ($submission->grade <= 0) {
2760 $str .= $strsubmitted . ', ' . $strreviewed;
2761 } else {
2762 $str .= $strsubmitted . ', ' . $strgraded;
2764 } else {
2765 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
2768 $str .= '</div>';
2769 if (empty($htmlarray[$assignment->course]['assignment'])) {
2770 $htmlarray[$assignment->course]['assignment'] = $str;
2771 } else {
2772 $htmlarray[$assignment->course]['assignment'] .= $str;
2777 function assignment_display_lateness($timesubmitted, $timedue) {
2778 if (!$timedue) {
2779 return '';
2781 $time = $timedue - $timesubmitted;
2782 if ($time < 0) {
2783 $timetext = get_string('late', 'assignment', format_time($time));
2784 return ' (<span class="late">'.$timetext.'</span>)';
2785 } else {
2786 $timetext = get_string('early', 'assignment', format_time($time));
2787 return ' (<span class="early">'.$timetext.'</span>)';
2791 function assignment_get_view_actions() {
2792 return array('view');
2795 function assignment_get_post_actions() {
2796 return array('upload');
2799 function assignment_get_types() {
2800 global $CFG;
2801 $types = array();
2803 $type = new object();
2804 $type->modclass = MOD_CLASS_ACTIVITY;
2805 $type->type = "assignment_group_start";
2806 $type->typestr = '--'.get_string('modulenameplural', 'assignment');
2807 $types[] = $type;
2809 $standardassignments = array('upload','online','uploadsingle','offline');
2810 foreach ($standardassignments as $assignmenttype) {
2811 $type = new object();
2812 $type->modclass = MOD_CLASS_ACTIVITY;
2813 $type->type = "assignment&amp;type=$assignmenttype";
2814 $type->typestr = get_string("type$assignmenttype", 'assignment');
2815 $types[] = $type;
2818 /// Drop-in extra assignment types
2819 $assignmenttypes = get_list_of_plugins('mod/assignment/type');
2820 foreach ($assignmenttypes as $assignmenttype) {
2821 if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
2822 continue;
2824 if (!in_array($assignmenttype, $standardassignments)) {
2825 $type = new object();
2826 $type->modclass = MOD_CLASS_ACTIVITY;
2827 $type->type = "assignment&amp;type=$assignmenttype";
2828 $type->typestr = get_string("type$assignmenttype", 'assignment');
2829 $types[] = $type;
2833 $type = new object();
2834 $type->modclass = MOD_CLASS_ACTIVITY;
2835 $type->type = "assignment_group_end";
2836 $type->typestr = '--';
2837 $types[] = $type;
2839 return $types;