MDL-11515:
[moodle-linuxchix.git] / mod / assignment / lib.php
blob5f583fc97afcb859e610d00f3e05948a007f28a4
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;
37 /**
38 * Constructor for the base assignment class
40 * Constructor for the base assignment class.
41 * If cmid is set create the cm, course, assignment objects.
42 * If the assignment is hidden and the user is not a teacher then
43 * this prints a page header and notice.
45 * @param cmid integer, the current course module id - not set for new assignments
46 * @param assignment object, usually null, but if we have it we pass it to save db access
47 * @param cm object, usually null, but if we have it we pass it to save db access
48 * @param course object, usually null, but if we have it we pass it to save db access
50 function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
51 if ($cmid == 'staticonly') {
52 //use static functions only!
53 return;
56 global $CFG;
58 if ($cm) {
59 $this->cm = $cm;
60 } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
61 error('Course Module ID was incorrect');
64 $this->context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
66 if ($course) {
67 $this->course = $course;
68 } else if (! $this->course = get_record('course', 'id', $this->cm->course)) {
69 error('Course is misconfigured');
72 if ($assignment) {
73 $this->assignment = $assignment;
74 } else if (! $this->assignment = get_record('assignment', 'id', $this->cm->instance)) {
75 error('assignment ID was incorrect');
78 $this->assignment->cmidnumber = $this->cm->id; // compatibility with modedit assignment obj
79 $this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
81 $this->strassignment = get_string('modulename', 'assignment');
82 $this->strassignments = get_string('modulenameplural', 'assignment');
83 $this->strsubmissions = get_string('submissions', 'assignment');
84 $this->strlastmodified = get_string('lastmodified');
86 $this->navigation[] = array('name' => $this->strassignments, 'link' => "index.php?id={$this->course->id}", 'type' => 'activity');
88 $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true));
90 // visibility
91 $context = get_context_instance(CONTEXT_MODULE, $cmid);
92 if (!$this->cm->visible and !has_capability('moodle/course:viewhiddenactivities', $context)) {
93 $pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment);
94 $this->navigation[] = array('name' => $this->strassignment, 'link' => '', 'type' => 'activityinstance');
95 $navigation = build_navigation($this->navigation);
97 print_header($pagetitle, $this->course->fullname, $this->navigation,
98 "", "", true, '', navmenu($this->course, $this->cm));
99 notice(get_string("activityiscurrentlyhidden"), "$CFG->wwwroot/course/view.php?id={$this->course->id}");
101 $this->currentgroup = groups_get_activity_group($this->cm);
103 /// Set up things for a HTML editor if it's needed
104 if ($this->usehtmleditor = can_use_html_editor()) {
105 $this->defaultformat = FORMAT_HTML;
106 } else {
107 $this->defaultformat = FORMAT_MOODLE;
112 * Display the assignment, used by view.php
114 * This in turn calls the methods producing individual parts of the page
116 function view() {
118 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
119 require_capability('mod/assignment:view', $context);
121 add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
122 $this->assignment->id, $this->cm->id);
124 $this->view_header();
126 $this->view_intro();
128 $this->view_dates();
130 $this->view_feedback();
132 $this->view_footer();
136 * Display the header and top of a page
138 * (this doesn't change much for assignment types)
139 * This is used by the view() method to print the header of view.php but
140 * it can be used on other pages in which case the string to denote the
141 * page in the navigation trail should be passed as an argument
143 * @param $subpage string Description of subpage to be used in navigation trail
145 function view_header($subpage='') {
147 global $CFG;
150 if ($subpage) {
151 $this->navigation[] = array('name' => format_string($this->assignment->name,true), 'link' => "view.php?id={$this->cm->id}", 'type' => 'activityinstance');
152 $this->navigation[] = array('name' => $subpage, 'link' => '', 'type' => 'title');
153 } else {
154 $this->navigation[] = array('name' => format_string($this->assignment->name,true), 'link' => '', 'type' => 'activityinstance');
157 $navigation = build_navigation($this->navigation);
159 print_header($this->pagetitle, $this->course->fullname, $navigation, '', '',
160 true, update_module_button($this->cm->id, $this->course->id, $this->strassignment),
161 navmenu($this->course, $this->cm));
163 $groupmode = groups_get_activity_groupmode($this->cm);
164 $currentgroup = groups_get_activity_group($this->cm);
165 groups_print_activity_menu($this->cm, 'view.php?id=' . $this->cm->id);
167 echo '<div class="reportlink">'.$this->submittedlink().'</div>';
168 echo '<div class="clearer"></div>';
173 * Display the assignment intro
175 * This will most likely be extended by assignment type plug-ins
176 * The default implementation prints the assignment description in a box
178 function view_intro() {
179 print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
180 $formatoptions = new stdClass;
181 $formatoptions->noclean = true;
182 echo format_text($this->assignment->description, $this->assignment->format, $formatoptions);
183 print_simple_box_end();
187 * Display the assignment dates
189 * Prints the assignment start and end dates in a box.
190 * This will be suitable for most assignment types
192 function view_dates() {
193 if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
194 return;
197 print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
198 echo '<table>';
199 if ($this->assignment->timeavailable) {
200 echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
201 echo ' <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
203 if ($this->assignment->timedue) {
204 echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
205 echo ' <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
207 echo '</table>';
208 print_simple_box_end();
213 * Display the bottom and footer of a page
215 * This default method just prints the footer.
216 * This will be suitable for most assignment types
218 function view_footer() {
219 print_footer($this->course);
223 * Display the feedback to the student
225 * This default method prints the teacher picture and name, date when marked,
226 * grade and teacher submissioncomment.
228 * @param $submission object The submission object or NULL in which case it will be loaded
230 function view_feedback($submission=NULL) {
231 global $USER, $CFG;
232 require_once($CFG->libdir.'/gradelib.php');
234 if (!has_capability('mod/assignment:submit', $this->context, $USER->id, false)) {
235 // can not submit assignments -> no feedback
236 return;
239 if (!$submission) { /// Get submission for this assignment
240 $submission = $this->get_submission($USER->id);
243 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
244 $item = $grading_info->items[0];
245 $grade = $item->grades[$USER->id];
247 if ($grade->hidden or $grade->grade === false) { // hidden or error
248 return;
251 if ($grade->grade === null and empty($feedback)) { /// Nothing to show yet
252 return;
255 if ($grade->overridden) {
256 $graded_date = $grade->overridden;
257 $graded_by = $grade->usermodified;
258 } else {
259 $graded_date = $submission->timemarked;
260 $graded_by = $submission->teacher;
263 /// We need the teacher info
264 $teacher = get_record('user', 'id', $graded_by);
266 /// Print the feedback
267 print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher)); // TODO: fix teacher string
269 echo '<table cellspacing="0" class="feedback">';
271 echo '<tr>';
272 echo '<td class="left picture">';
273 if ($teacher) {
274 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
276 echo '</td>';
277 echo '<td class="topic">';
278 echo '<div class="from">';
279 if ($teacher) {
280 echo '<div class="fullname">'.fullname($teacher).'</div>';
282 echo '<div class="time">'.userdate($graded_date).'</div>';
283 echo '</div>';
284 echo '</td>';
285 echo '</tr>';
287 echo '<tr>';
288 echo '<td class="left side">&nbsp;</td>';
289 echo '<td class="content">';
290 echo '<div class="grade">';
291 echo get_string("grade").': '.$grade->str_grade;
292 echo '</div>';
293 echo '<div class="clearer"></div>';
295 echo '<div class="comment">';
296 echo $grade->str_feedback;
297 echo '</div>';
298 echo '</tr>';
300 echo '</table>';
304 * Returns a link with info about the state of the assignment submissions
306 * This is used by view_header to put this link at the top right of the page.
307 * For teachers it gives the number of submitted assignments with a link
308 * For students it gives the time of their submission.
309 * This will be suitable for most assignment types.
310 * @return string
312 function submittedlink() {
313 global $USER;
315 $submitted = '';
317 $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
318 if (has_capability('mod/assignment:grade', $context)) {
320 // if this user can mark and is put in a group
321 // then he can only see/mark submission in his own groups
322 if (!has_capability('moodle/course:managegroups', $context) and (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS)) {
323 $count = $this->count_real_submissions($this->currentgroup); // Only their groups
324 } else {
325 $count = $this->count_real_submissions(); // Everyone
327 $submitted = '<a href="submissions.php?id='.$this->cm->id.'">'.
328 get_string('viewsubmissions', 'assignment', $count).'</a>';
329 } else {
330 if (!empty($USER->id)) {
331 if ($submission = $this->get_submission($USER->id)) {
332 if ($submission->timemodified) {
333 if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
334 $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
335 } else {
336 $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
343 return $submitted;
347 function setup_elements(&$mform) {
352 * Create a new assignment activity
354 * Given an object containing all the necessary data,
355 * (defined by the form in mod.html) this function
356 * will create a new instance and return the id number
357 * of the new instance.
358 * The due data is added to the calendar
359 * This is common to all assignment types.
361 * @param $assignment object The data from the form on mod.html
362 * @return int The id of the assignment
364 function add_instance($assignment) {
365 global $COURSE;
367 $assignment->timemodified = time();
368 $assignment->courseid = $assignment->course;
370 if ($returnid = insert_record("assignment", $assignment)) {
371 $assignment->id = $returnid;
373 if ($assignment->timedue) {
374 $event = new object();
375 $event->name = $assignment->name;
376 $event->description = $assignment->description;
377 $event->courseid = $assignment->course;
378 $event->groupid = 0;
379 $event->userid = 0;
380 $event->modulename = 'assignment';
381 $event->instance = $returnid;
382 $event->eventtype = 'due';
383 $event->timestart = $assignment->timedue;
384 $event->timeduration = 0;
386 add_event($event);
389 $assignment = stripslashes_recursive($assignment);
390 assignment_grade_item_update($assignment);
395 return $returnid;
399 * Deletes an assignment activity
401 * Deletes all database records, files and calendar events for this assignment.
402 * @param $assignment object The assignment to be deleted
403 * @return boolean False indicates error
405 function delete_instance($assignment) {
406 global $CFG;
408 $assignment->courseid = $assignment->course;
410 $result = true;
412 if (! delete_records('assignment_submissions', 'assignment', $assignment->id)) {
413 $result = false;
416 if (! delete_records('assignment', 'id', $assignment->id)) {
417 $result = false;
420 if (! delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id)) {
421 $result = false;
424 // delete file area with all attachments - ignore errors
425 require_once($CFG->libdir.'/filelib.php');
426 fulldelete($CFG->dataroot.'/'.$assignment->course.'/'.$CFG->moddata.'/assignment/'.$assignment->id);
428 assignment_grade_item_delete($assignment);
430 return $result;
434 * Updates a new assignment activity
436 * Given an object containing all the necessary data,
437 * (defined by the form in mod.html) this function
438 * will update the assignment instance and return the id number
439 * The due date is updated in the calendar
440 * This is common to all assignment types.
442 * @param $assignment object The data from the form on mod.html
443 * @return int The assignment id
445 function update_instance($assignment) {
446 global $COURSE;
448 $assignment->timemodified = time();
450 $assignment->id = $assignment->instance;
451 $assignment->courseid = $assignment->course;
453 if (!update_record('assignment', $assignment)) {
454 return false;
457 if ($assignment->timedue) {
458 $event = new object();
460 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
462 $event->name = $assignment->name;
463 $event->description = $assignment->description;
464 $event->timestart = $assignment->timedue;
466 update_event($event);
467 } else {
468 $event = new object();
469 $event->name = $assignment->name;
470 $event->description = $assignment->description;
471 $event->courseid = $assignment->course;
472 $event->groupid = 0;
473 $event->userid = 0;
474 $event->modulename = 'assignment';
475 $event->instance = $assignment->id;
476 $event->eventtype = 'due';
477 $event->timestart = $assignment->timedue;
478 $event->timeduration = 0;
480 add_event($event);
482 } else {
483 delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id);
486 // get existing grade item
487 $assignment = stripslashes_recursive($assignment);
489 assignment_grade_item_update($assignment);
491 return true;
495 * Update grade item for this submission.
497 function update_grade($submission) {
498 assignment_update_grades($this->assignment, $submission->userid);
502 * Top-level function for handling of submissions called by submissions.php
504 * This is for handling the teacher interaction with the grading interface
505 * This should be suitable for most assignment types.
507 * @param $mode string Specifies the kind of teacher interaction taking place
509 function submissions($mode) {
510 ///The main switch is changed to facilitate
511 ///1) Batch fast grading
512 ///2) Skip to the next one on the popup
513 ///3) Save and Skip to the next one on the popup
515 //make user global so we can use the id
516 global $USER;
518 switch ($mode) {
519 case 'grade': // We are in a popup window grading
520 if ($submission = $this->process_feedback()) {
521 //IE needs proper header with encoding
522 print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name));
523 print_heading(get_string('changessaved'));
524 print $this->update_main_listing($submission);
526 close_window();
527 break;
529 case 'single': // We are in a popup window displaying submission
530 $this->display_submission();
531 break;
533 case 'all': // Main window, display everything
534 $this->display_submissions();
535 break;
537 case 'fastgrade':
538 ///do the fast grading stuff - this process should work for all 3 subclasses
539 $grading = false;
540 $commenting = false;
541 $col = false;
542 if (isset($_POST['submissioncomment'])) {
543 $col = 'submissioncomment';
544 $commenting = true;
546 if (isset($_POST['menu'])) {
547 $col = 'menu';
548 $grading = true;
550 if (!$col) {
551 //both submissioncomment and grade columns collapsed..
552 $this->display_submissions();
553 break;
555 foreach ($_POST[$col] as $id => $unusedvalue){
557 $id = (int)$id; //clean parameter name
559 $this->process_outcomes($id);
561 if (!$submission = $this->get_submission($id)) {
562 $submission = $this->prepare_new_submission($id);
563 $newsubmission = true;
564 } else {
565 $newsubmission = false;
567 unset($submission->data1); // Don't need to update this.
568 unset($submission->data2); // Don't need to update this.
570 //for fast grade, we need to check if any changes take place
571 $updatedb = false;
573 if ($grading) {
574 $grade = $_POST['menu'][$id];
575 $updatedb = $updatedb || ($submission->grade != $grade);
576 $submission->grade = $grade;
577 } else {
578 if (!$newsubmission) {
579 unset($submission->grade); // Don't need to update this.
582 if ($commenting) {
583 $commentvalue = trim($_POST['submissioncomment'][$id]);
584 $updatedb = $updatedb || ($submission->submissioncomment != stripslashes($commentvalue));
585 $submission->submissioncomment = $commentvalue;
586 } else {
587 unset($submission->submissioncomment); // Don't need to update this.
590 $submission->teacher = $USER->id;
591 $submission->mailed = $updatedb?0:$submission->mailed;//only change if it's an update
592 $submission->timemarked = time();
594 //if it is not an update, we don't change the last modified time etc.
595 //this will also not write into database if no submissioncomment and grade is entered.
597 if ($updatedb){
598 if ($newsubmission) {
599 if (!$sid = insert_record('assignment_submissions', $submission)) {
600 return false;
602 $submission->id = $sid;
603 } else {
604 if (!update_record('assignment_submissions', $submission)) {
605 return false;
609 // triger grade event
610 $this->update_grade($submission);
612 //add to log only if updating
613 add_to_log($this->course->id, 'assignment', 'update grades',
614 'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid,
615 $submission->userid, $this->cm->id);
620 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true);
622 $this->display_submissions($message);
623 break;
626 case 'next':
627 /// We are currently in pop up, but we want to skip to next one without saving.
628 /// This turns out to be similar to a single case
629 /// The URL used is for the next submission.
631 $this->display_submission();
632 break;
634 case 'saveandnext':
635 ///We are in pop up. save the current one and go to the next one.
636 //first we save the current changes
637 if ($submission = $this->process_feedback()) {
638 //print_heading(get_string('changessaved'));
639 $extra_javascript = $this->update_main_listing($submission);
642 //then we display the next submission
643 $this->display_submission($extra_javascript);
644 break;
646 default:
647 echo "something seriously is wrong!!";
648 break;
653 * Helper method updating the listing on the main script from popup using javascript
655 * @param $submission object The submission whose data is to be updated on the main page
657 function update_main_listing($submission) {
658 global $SESSION, $CFG;
660 $output = '';
662 $perpage = get_user_preferences('assignment_perpage', 10);
664 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
666 /// Run some Javascript to try and update the parent page
667 $output .= '<script type="text/javascript">'."\n<!--\n";
668 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
669 if ($quickgrade){
670 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
671 .trim($submission->submissioncomment).'";'."\n";
672 } else {
673 $output.= 'opener.document.getElementById("com'.$submission->userid.
674 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
678 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
679 //echo optional_param('menuindex');
680 if ($quickgrade){
681 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
682 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
683 } else {
684 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
685 $this->display_grade($submission->grade)."\";\n";
688 //need to add student's assignments in there too.
689 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
690 $submission->timemodified) {
691 $output.= 'opener.document.getElementById("ts'.$submission->userid.
692 '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
695 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
696 $submission->timemarked) {
697 $output.= 'opener.document.getElementById("tt'.$submission->userid.
698 '").innerHTML="'.userdate($submission->timemarked)."\";\n";
701 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
702 $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
703 $buttontext = get_string('update');
704 $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),
705 'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid);
706 $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
709 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
711 if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
712 $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
713 '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
716 if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
718 if (!empty($grading_info->outcomes)) {
719 foreach($grading_info->outcomes as $n=>$outcome) {
720 if ($outcome->grades[$submission->userid]->locked) {
721 continue;
724 if ($quickgrade){
725 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
726 '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
728 } else {
729 $options = make_grades_menu(-$outcome->scaleid);
730 $options[0] = get_string('nooutcome', 'grades');
731 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
738 $output .= "\n-->\n</script>";
739 return $output;
743 * Return a grade in user-friendly form, whether it's a scale or not
745 * @param $grade
746 * @return string User-friendly representation of grade
748 function display_grade($grade) {
750 static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
752 if ($this->assignment->grade >= 0) { // Normal number
753 if ($grade == -1) {
754 return '-';
755 } else {
756 return $grade.' / '.$this->assignment->grade;
759 } else { // Scale
760 if (empty($scalegrades[$this->assignment->id])) {
761 if ($scale = get_record('scale', 'id', -($this->assignment->grade))) {
762 $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
763 } else {
764 return '-';
767 if (isset($scalegrades[$this->assignment->id][$grade])) {
768 return $scalegrades[$this->assignment->id][$grade];
770 return '-';
775 * Display a single submission, ready for grading on a popup window
777 * This default method prints the teacher info and submissioncomment box at the top and
778 * the student info and submission at the bottom.
779 * This method also fetches the necessary data in order to be able to
780 * provide a "Next submission" button.
781 * Calls preprocess_submission() to give assignment type plug-ins a chance
782 * to process submissions before they are graded
783 * This method gets its arguments from the page parameters userid and offset
785 function display_submission($extra_javascript = '') {
787 global $CFG;
788 require_once($CFG->libdir.'/gradelib.php');
790 $userid = required_param('userid', PARAM_INT);
791 $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
793 if (!$user = get_record('user', 'id', $userid)) {
794 error('No such user!');
797 if (!$submission = $this->get_submission($user->id)) {
798 $submission = $this->prepare_new_submission($userid);
800 if ($submission->timemodified > $submission->timemarked) {
801 $subtype = 'assignmentnew';
802 } else {
803 $subtype = 'assignmentold';
806 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
807 $disabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
809 /// construct SQL, using current offset to find the data of the next student
810 $course = $this->course;
811 $assignment = $this->assignment;
812 $cm = $this->cm;
813 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
815 /// Get all ppl that can submit assignments
817 $currentgroup = groups_get_activity_group($cm);
819 $users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id, u.id', '', '', '', $currentgroup, '', false);
821 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
822 s.id AS submissionid, s.grade, s.submissioncomment,
823 s.timemodified, s.timemarked,
824 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
825 $sql = 'FROM '.$CFG->prefix.'user u '.
826 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
827 AND s.assignment = '.$this->assignment->id.' '.
828 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
830 require_once($CFG->libdir.'/tablelib.php');
832 if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) {
833 $sort = 'ORDER BY '.$sort.' ';
836 $nextid = 0;
837 if (($auser = get_records_sql($select.$sql.$sort, $offset+1, 1)) !== false) {
838 $nextuser = array_shift($auser);
839 /// Calculate user status
840 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified);
841 $nextid = $nextuser->id;
844 print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name));
846 /// Print any extra javascript needed for saveandnext
847 echo $extra_javascript;
849 ///SOme javascript to help with setting up >.>
851 echo '<script type="text/javascript">'."\n";
852 echo 'function setNext(){'."\n";
853 echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
854 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
855 echo '}'."\n";
857 echo 'function saveNext(){'."\n";
858 echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
859 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
860 echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
861 echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
862 echo '}'."\n";
864 echo '</script>'."\n";
865 echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
867 ///Start of teacher info row
869 echo '<tr>';
870 echo '<td class="picture teacher">';
871 if ($submission->teacher) {
872 $teacher = get_record('user', 'id', $submission->teacher);
873 } else {
874 global $USER;
875 $teacher = $USER;
877 print_user_picture($teacher->id, $this->course->id, $teacher->picture);
878 echo '</td>';
879 echo '<td class="content">';
880 echo '<form id="submitform" action="submissions.php" method="post">';
881 echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
882 echo '<input type="hidden" name="offset" value="'.($offset+1).'" />';
883 echo '<input type="hidden" name="userid" value="'.$userid.'" />';
884 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
885 echo '<input type="hidden" name="mode" value="grade" />';
886 echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
888 //new hidden field, initialized to -1.
889 echo '<input type="hidden" name="saveuserid" value="-1" />';
891 if ($submission->timemarked) {
892 echo '<div class="from">';
893 echo '<div class="fullname">'.fullname($teacher, true).'</div>';
894 echo '<div class="time">'.userdate($submission->timemarked).'</div>';
895 echo '</div>';
897 echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
898 choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1, false, $disabled);
899 echo '</div>';
901 echo '<div class="clearer"></div>';
902 echo '<div class="finalgrade">'.get_string('finalgrade', 'grades').': '.$grading_info->items[0]->grades[$userid]->str_grade.'</div>';
903 echo '<div class="clearer"></div>';
905 if (!empty($CFG->enableoutcomes)) {
906 foreach($grading_info->outcomes as $n=>$outcome) {
907 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$outcome->name.'</label> ';
908 $options = make_grades_menu(-$outcome->scaleid);
909 if ($outcome->grades[$submission->userid]->locked) {
910 $options[0] = get_string('nooutcome', 'grades');
911 echo $options[$outcome->grades[$submission->userid]->grade];
912 } else {
913 choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $outcome->grades[$submission->userid]->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
915 echo '</div>';
916 echo '<div class="clearer"></div>';
921 $this->preprocess_submission($submission);
923 if ($disabled) {
924 echo '<div class="disabledfeedback">'.$grading_info->items[0]->grades[$userid]->str_feedback.'</div>';
926 } else {
927 print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id);
928 if ($this->usehtmleditor) {
929 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
930 } else {
931 echo '<div class="format">';
932 choose_from_menu(format_text_menu(), "format", $submission->format, "");
933 helpbutton("textformat", get_string("helpformatting"));
934 echo '</div>';
938 ///Print Buttons in Single View
939 echo '<div class="buttons">';
940 echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
941 echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
942 //if there are more to be graded.
943 if ($nextid) {
944 echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
945 echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
947 echo '</div>';
948 echo '</div></form>';
950 $customfeedback = $this->custom_feedbackform($submission, true);
951 if (!empty($customfeedback)) {
952 echo $customfeedback;
955 echo '</td></tr>';
957 ///End of teacher info row, Start of student info row
958 echo '<tr>';
959 echo '<td class="picture user">';
960 print_user_picture($user->id, $this->course->id, $user->picture);
961 echo '</td>';
962 echo '<td class="topic">';
963 echo '<div class="from">';
964 echo '<div class="fullname">'.fullname($user, true).'</div>';
965 if ($submission->timemodified) {
966 echo '<div class="time">'.userdate($submission->timemodified).
967 $this->display_lateness($submission->timemodified).'</div>';
969 echo '</div>';
970 $this->print_user_files($user->id);
971 echo '</td>';
972 echo '</tr>';
974 ///End of student info row
976 echo '</table>';
978 if (!$disabled and $this->usehtmleditor) {
979 use_html_editor();
982 print_footer('none');
986 * Preprocess submission before grading
988 * Called by display_submission()
989 * The default type does nothing here.
990 * @param $submission object The submission object
992 function preprocess_submission(&$submission) {
996 * Display all the submissions ready for grading
998 function display_submissions($message='') {
999 global $CFG, $db, $USER;
1000 require_once($CFG->libdir.'/gradelib.php');
1002 /* first we check to see if the form has just been submitted
1003 * to request user_preference updates
1006 if (isset($_POST['updatepref'])){
1007 $perpage = optional_param('perpage', 10, PARAM_INT);
1008 $perpage = ($perpage <= 0) ? 10 : $perpage ;
1009 set_user_preference('assignment_perpage', $perpage);
1010 set_user_preference('assignment_quickgrade', optional_param('quickgrade',0, PARAM_BOOL));
1013 /* next we get perpage and quickgrade (allow quick grade) params
1014 * from database
1016 $perpage = get_user_preferences('assignment_perpage', 10);
1018 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
1020 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
1022 if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
1023 $uses_outcomes = true;
1024 } else {
1025 $uses_outcomes = false;
1028 $teacherattempts = true; /// Temporary measure
1029 $page = optional_param('page', 0, PARAM_INT);
1030 $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1032 /// Some shortcuts to make the code read better
1034 $course = $this->course;
1035 $assignment = $this->assignment;
1036 $cm = $this->cm;
1038 $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1040 add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
1042 $navlinks = array();
1043 $navlinks[] = array('name' => $this->strassignments, 'link' => "index.php?id=$course->id", 'type' => 'activity');
1044 $navlinks[] = array('name' => format_string($this->assignment->name,true),
1045 'link' => "view.php?a={$this->assignment->id}",
1046 'type' => 'activityinstance');
1047 $navlinks[] = array('name' => $this->strsubmissions, 'link' => '', 'type' => 'title');
1048 $navigation = build_navigation($navlinks);
1050 print_header_simple(format_string($this->assignment->name,true), "", $navigation,
1051 '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm));
1053 if (!empty($message)) {
1054 echo $message; // display messages here if any
1057 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1059 /// find out current groups mode
1060 $groupmode = groups_get_activity_groupmode($cm);
1061 $currentgroup = groups_get_activity_group($cm, true);
1062 groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm->id);
1064 /// Get all ppl that are allowed to submit assignments
1065 $users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', $currentgroup, '', false);
1066 $users = array_keys($users);
1068 if (!empty($CFG->enablegroupings) && !empty($cm->groupingid)) {
1069 $groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id');
1070 $users = array_intersect($users, array_keys($groupingusers));
1074 $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade');
1075 if ($uses_outcomes) {
1076 $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
1079 $tableheaders = array('',
1080 get_string('fullname'),
1081 get_string('grade'),
1082 get_string('comment', 'assignment'),
1083 get_string('lastmodified').' ('.$course->student.')',
1084 get_string('lastmodified').' ('.$course->teacher.')',
1085 get_string('status'),
1086 get_string('finalgrade', 'grades'));
1087 if ($uses_outcomes) {
1088 $tableheaders[] = get_string('outcome', 'grades');
1091 require_once($CFG->libdir.'/tablelib.php');
1092 $table = new flexible_table('mod-assignment-submissions');
1094 $table->define_columns($tablecolumns);
1095 $table->define_headers($tableheaders);
1096 $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
1098 $table->sortable(true, 'lastname');//sorted by lastname by default
1099 $table->collapsible(true);
1100 $table->initialbars(true);
1102 $table->column_suppress('picture');
1103 $table->column_suppress('fullname');
1105 $table->column_class('picture', 'picture');
1106 $table->column_class('fullname', 'fullname');
1107 $table->column_class('grade', 'grade');
1108 $table->column_class('submissioncomment', 'comment');
1109 $table->column_class('timemodified', 'timemodified');
1110 $table->column_class('timemarked', 'timemarked');
1111 $table->column_class('status', 'status');
1112 $table->column_class('finalgrade', 'finalgrade');
1113 if ($uses_outcomes) {
1114 $table->column_class('outcome', 'outcome');
1117 $table->set_attribute('cellspacing', '0');
1118 $table->set_attribute('id', 'attempts');
1119 $table->set_attribute('class', 'submissions');
1120 $table->set_attribute('width', '90%');
1121 //$table->set_attribute('align', 'center');
1123 $table->no_sorting('finalgrade');
1124 $table->no_sorting('outcome');
1126 // Start working -- this is necessary as soon as the niceties are over
1127 $table->setup();
1129 /// Check to see if groups are being used in this assignment
1131 if (!$teacherattempts) {
1132 $teachers = get_course_teachers($course->id);
1133 if (!empty($teachers)) {
1134 $keys = array_keys($teachers);
1136 foreach ($keys as $key) {
1137 unset($users[$key]);
1141 if (empty($users)) {
1142 print_heading(get_string('noattempts','assignment'));
1143 return true;
1146 /// Construct the SQL
1148 if ($where = $table->get_sql_where()) {
1149 $where .= ' AND ';
1152 if ($sort = $table->get_sql_sort()) {
1153 $sort = ' ORDER BY '.$sort;
1156 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
1157 s.id AS submissionid, s.grade, s.submissioncomment,
1158 s.timemodified, s.timemarked,
1159 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
1160 $sql = 'FROM '.$CFG->prefix.'user u '.
1161 'LEFT JOIN '.$CFG->prefix.'assignment_submissions s ON u.id = s.userid
1162 AND s.assignment = '.$this->assignment->id.' '.
1163 'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
1165 $table->pagesize($perpage, count($users));
1167 ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1168 $offset = $page * $perpage;
1170 $strupdate = get_string('update');
1171 $strgrade = get_string('grade');
1172 $grademenu = make_grades_menu($this->assignment->grade);
1174 if (($ausers = get_records_sql($select.$sql.$sort, $table->get_page_start(), $table->get_page_size())) !== false) {
1175 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
1176 foreach ($ausers as $auser) {
1177 $final_grade = $grading_info->items[0]->grades[$auser->id];
1178 /// Calculate user status
1179 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
1180 $picture = print_user_picture($auser->id, $course->id, $auser->picture, false, true);
1182 if (empty($auser->submissionid)) {
1183 $auser->grade = -1; //no submission yet
1186 if (!empty($auser->submissionid)) {
1187 ///Prints student answer and student modified date
1188 ///attach file or print link to student answer, depending on the type of the assignment.
1189 ///Refer to print_student_answer in inherited classes.
1190 if ($auser->timemodified > 0) {
1191 $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
1192 . userdate($auser->timemodified).'</div>';
1193 } else {
1194 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1196 ///Print grade, dropdown or text
1197 if ($auser->timemarked > 0) {
1198 $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
1200 if ($final_grade->locked or $final_grade->overridden) {
1201 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1202 } else if ($quickgrade) {
1203 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1204 'menu['.$auser->id.']', $auser->grade,
1205 get_string('nograde'),'',-1,true,false,$tabindex++);
1206 $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
1207 } else {
1208 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1211 } else {
1212 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1213 if ($final_grade->locked or $final_grade->overridden) {
1214 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1215 } else if ($quickgrade) {
1216 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1217 'menu['.$auser->id.']', $auser->grade,
1218 get_string('nograde'),'',-1,true,false,$tabindex++);
1219 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1220 } else {
1221 $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1224 ///Print Comment
1225 if ($final_grade->locked or $final_grade->overridden) {
1226 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
1228 } else if ($quickgrade) {
1229 $comment = '<div id="com'.$auser->id.'">'
1230 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1231 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1232 } else {
1233 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
1235 } else {
1236 $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1237 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1238 $status = '<div id="st'.$auser->id.'">&nbsp;</div>';
1240 if ($final_grade->locked or $final_grade->overridden) {
1241 $grade = '<div id="g'.$auser->id.'">'.$final_grade->str_grade.'</div>';
1242 } else if ($quickgrade) { // allow editing
1243 $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1244 'menu['.$auser->id.']', $auser->grade,
1245 get_string('nograde'),'',-1,true,false,$tabindex++);
1246 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1247 } else {
1248 $grade = '<div id="g'.$auser->id.'">-</div>';
1251 if ($final_grade->locked or $final_grade->overridden) {
1252 $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
1253 } else if ($quickgrade) {
1254 $comment = '<div id="com'.$auser->id.'">'
1255 . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1256 . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1257 } else {
1258 $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1262 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
1263 $auser->status = 0;
1264 } else {
1265 $auser->status = 1;
1268 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
1270 ///No more buttons, we use popups ;-).
1271 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1272 . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;offset='.$offset++;
1273 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780,
1274 $buttontext, 'none', true, 'button'.$auser->id);
1276 $status = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
1278 $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
1280 $outcomes = '';
1282 if ($uses_outcomes) {
1284 foreach($grading_info->outcomes as $n=>$outcome) {
1285 $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
1286 $options = make_grades_menu(-$outcome->scaleid);
1288 if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
1289 $options[0] = get_string('nooutcome', 'grades');
1290 $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
1291 } else {
1292 $outcomes .= ' ';
1293 $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']',
1294 $outcome->grades[$auser->id]->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id);
1296 $outcomes .= '</div>';
1301 $row = array($picture, fullname($auser), $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
1302 if ($uses_outcomes) {
1303 $row[] = $outcomes;
1306 $table->add_data($row);
1310 /// Print quickgrade form around the table
1311 if ($quickgrade){
1312 echo '<form action="submissions.php" id="fastg" method="post">';
1313 echo '<div>';
1314 echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
1315 echo '<input type="hidden" name="mode" value="fastgrade" />';
1316 echo '<input type="hidden" name="page" value="'.$page.'" />';
1317 echo '</div>';
1318 //echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1321 $table->print_html(); /// Print the whole table
1323 if ($quickgrade){
1324 echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1325 echo '</form>';
1327 /// End of fast grading form
1329 /// Mini form for setting user preference
1330 echo '<br />';
1331 echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post">';
1332 echo '<div>';
1333 echo '<input type="hidden" id="updatepref" name="updatepref" value="1" />';
1334 echo '<table id="optiontable" align="right">';
1335 echo '<tr align="right"><td>';
1336 echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1337 echo ':</td>';
1338 echo '<td>';
1339 echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1340 helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1341 echo '</td></tr>';
1342 echo '<tr align="right">';
1343 echo '<td>';
1344 print_string('quickgrade','assignment');
1345 echo ':</td>';
1346 echo '<td>';
1347 if ($quickgrade){
1348 echo '<input type="checkbox" name="quickgrade" value="1" checked="checked" />';
1349 } else {
1350 echo '<input type="checkbox" name="quickgrade" value="1" />';
1352 helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1353 echo '</td></tr>';
1354 echo '<tr>';
1355 echo '<td colspan="2" align="right">';
1356 echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1357 echo '</td></tr></table>';
1358 echo '</div>';
1359 echo '</form>';
1360 ///End of mini form
1361 print_footer($this->course);
1365 * Process teacher feedback submission
1367 * This is called by submissions() when a grading even has taken place.
1368 * It gets its data from the submitted form.
1369 * @return object The updated submission object
1371 function process_feedback() {
1372 global $CFG, $USER;
1373 require_once($CFG->libdir.'/gradelib.php');
1375 if (!$feedback = data_submitted()) { // No incoming data?
1376 return false;
1379 ///For save and next, we need to know the userid to save, and the userid to go
1380 ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1381 ///as the userid to store
1382 if ((int)$feedback->saveuserid !== -1){
1383 $feedback->userid = $feedback->saveuserid;
1386 if (!empty($feedback->cancel)) { // User hit cancel button
1387 return false;
1390 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
1392 // store outcomes if needed
1393 $this->process_outcomes($feedback->userid);
1395 $submission = $this->get_submission($feedback->userid, true); // Get or make one
1397 if (!$grading_info->items[0]->grades[$feedback->userid]->locked and
1398 !$grading_info->items[0]->grades[$feedback->userid]->overridden) {
1400 $submission->grade = $feedback->grade;
1401 $submission->submissioncomment = $feedback->submissioncomment;
1402 $submission->format = $feedback->format;
1403 $submission->teacher = $USER->id;
1404 $submission->mailed = 0; // Make sure mail goes out (again, even)
1405 $submission->timemarked = time();
1407 unset($submission->data1); // Don't need to update this.
1408 unset($submission->data2); // Don't need to update this.
1410 if (empty($submission->timemodified)) { // eg for offline assignments
1411 // $submission->timemodified = time();
1414 if (! update_record('assignment_submissions', $submission)) {
1415 return false;
1418 // triger grade event
1419 $this->update_grade($submission);
1421 add_to_log($this->course->id, 'assignment', 'update grades',
1422 'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1425 return $submission;
1429 function process_outcomes($userid) {
1430 global $CFG, $USER;
1432 if (empty($CFG->enableoutcomes)) {
1433 return;
1436 require_once($CFG->libdir.'/gradelib.php');
1438 if (!$formdata = data_submitted()) {
1439 return;
1442 $data = array();
1443 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
1445 if (!empty($grading_info->outcomes)) {
1446 foreach($grading_info->outcomes as $n=>$old) {
1447 $name = 'outcome_'.$n;
1448 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
1449 $data[$n] = $formdata->{$name}[$userid];
1453 if (count($data) > 0) {
1454 grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1460 * Load the submission object for a particular user
1462 * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1463 * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1464 * @return object The submission
1466 function get_submission($userid=0, $createnew=false) {
1467 global $USER;
1469 if (empty($userid)) {
1470 $userid = $USER->id;
1473 $submission = get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
1475 if ($submission || !$createnew) {
1476 return $submission;
1478 $newsubmission = $this->prepare_new_submission($userid);
1479 if (!insert_record("assignment_submissions", $newsubmission)) {
1480 error("Could not insert a new empty submission");
1483 return get_record('assignment_submissions', 'assignment', $this->assignment->id, 'userid', $userid);
1487 * Instantiates a new submission object for a given user
1489 * Sets the assignment, userid and times, everything else is set to default values.
1490 * @param $userid int The userid for which we want a submission object
1491 * @return object The submission
1493 function prepare_new_submission($userid) {
1494 $submission = new Object;
1495 $submission->assignment = $this->assignment->id;
1496 $submission->userid = $userid;
1497 //$submission->timecreated = time();
1498 $submission->timecreated = '';
1499 // teachers should not be modifying modified date, except offline assignments
1500 $submission->timemodified = $submission->timecreated;
1501 $submission->numfiles = 0;
1502 $submission->data1 = '';
1503 $submission->data2 = '';
1504 $submission->grade = -1;
1505 $submission->submissioncomment = '';
1506 $submission->format = 0;
1507 $submission->teacher = 0;
1508 $submission->timemarked = 0;
1509 $submission->mailed = 0;
1510 return $submission;
1514 * Return all assignment submissions by ENROLLED students (even empty)
1516 * @param $sort string optional field names for the ORDER BY in the sql query
1517 * @param $dir string optional specifying the sort direction, defaults to DESC
1518 * @return array The submission objects indexed by id
1520 function get_submissions($sort='', $dir='DESC') {
1521 return assignment_get_all_submissions($this->assignment, $sort, $dir);
1525 * Counts all real assignment submissions by ENROLLED students (not empty ones)
1527 * @param $groupid int optional If nonzero then count is restricted to this group
1528 * @return int The number of submissions
1530 function count_real_submissions($groupid=0) {
1531 return assignment_count_real_submissions($this->assignment, $groupid);
1535 * Alerts teachers by email of new or changed assignments that need grading
1537 * First checks whether the option to email teachers is set for this assignment.
1538 * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1539 * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1540 * @param $submission object The submission that has changed
1542 function email_teachers($submission) {
1543 global $CFG;
1545 if (empty($this->assignment->emailteachers)) { // No need to do anything
1546 return;
1549 $user = get_record('user', 'id', $submission->userid);
1551 if ($teachers = $this->get_graders($user)) {
1553 $strassignments = get_string('modulenameplural', 'assignment');
1554 $strassignment = get_string('modulename', 'assignment');
1555 $strsubmitted = get_string('submitted', 'assignment');
1557 foreach ($teachers as $teacher) {
1558 $info = new object();
1559 $info->username = fullname($user, true);
1560 $info->assignment = format_string($this->assignment->name,true);
1561 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1563 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1564 $posttext = $this->email_teachers_text($info);
1565 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
1567 @email_to_user($teacher, $user, $postsubject, $posttext, $posthtml); // If it fails, oh well, too bad.
1573 * Returns a list of teachers that should be grading given submission
1575 function get_graders($user) {
1576 //potential graders
1577 $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1579 $graders = array();
1580 if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used
1581 if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups
1582 foreach ($groups as $group) {
1583 foreach ($potgraders as $t) {
1584 if ($t->id == $user->id) {
1585 continue; // do not send self
1587 if (groups_is_member($group->id, $t->id)) {
1588 $graders[$t->id] = $t;
1592 } else {
1593 // user not in group, try to find graders without group
1594 foreach ($potgraders as $t) {
1595 if ($t->id == $user->id) {
1596 continue; // do not send self
1598 if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
1599 $graders[$t->id] = $t;
1603 } else {
1604 foreach ($potgraders as $t) {
1605 if ($t->id == $user->id) {
1606 continue; // do not send self
1608 $graders[$t->id] = $t;
1611 return $graders;
1615 * Creates the text content for emails to teachers
1617 * @param $info object The info used by the 'emailteachermail' language string
1618 * @return string
1620 function email_teachers_text($info) {
1621 $posttext = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '.
1622 format_string($this->assignment->name)."\n";
1623 $posttext .= '---------------------------------------------------------------------'."\n";
1624 $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
1625 $posttext .= "\n---------------------------------------------------------------------\n";
1626 return $posttext;
1630 * Creates the html content for emails to teachers
1632 * @param $info object The info used by the 'emailteachermailhtml' language string
1633 * @return string
1635 function email_teachers_html($info) {
1636 global $CFG;
1637 $posthtml = '<p><font face="sans-serif">'.
1638 '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'.
1639 '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
1640 '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>';
1641 $posthtml .= '<hr /><font face="sans-serif">';
1642 $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1643 $posthtml .= '</font><hr />';
1644 return $posthtml;
1648 * Produces a list of links to the files uploaded by a user
1650 * @param $userid int optional id of the user. If 0 then $USER->id is used.
1651 * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1652 * @return string optional
1654 function print_user_files($userid=0, $return=false) {
1655 global $CFG, $USER;
1657 if (!$userid) {
1658 if (!isloggedin()) {
1659 return '';
1661 $userid = $USER->id;
1664 $filearea = $this->file_area_name($userid);
1666 $output = '';
1668 if ($basedir = $this->file_area($userid)) {
1669 if ($files = get_directory_list($basedir)) {
1670 require_once($CFG->libdir.'/filelib.php');
1671 foreach ($files as $key => $file) {
1673 $icon = mimeinfo('icon', $file);
1675 if ($CFG->slasharguments) {
1676 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
1677 } else {
1678 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
1681 $output .= '<img align="middle" src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.
1682 '<a href="'.$ffurl.'" >'.$file.'</a><br />';
1687 $output = '<div class="files">'.$output.'</div>';
1689 if ($return) {
1690 return $output;
1692 echo $output;
1696 * Count the files uploaded by a given user
1698 * @param $userid int The user id
1699 * @return int
1701 function count_user_files($userid) {
1702 global $CFG;
1704 $filearea = $this->file_area_name($userid);
1706 if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) {
1707 if ($files = get_directory_list($basedir)) {
1708 return count($files);
1711 return 0;
1715 * Creates a directory file name, suitable for make_upload_directory()
1717 * @param $userid int The user id
1718 * @return string path to file area
1720 function file_area_name($userid) {
1721 global $CFG;
1723 return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid;
1727 * Makes an upload directory
1729 * @param $userid int The user id
1730 * @return string path to file area.
1732 function file_area($userid) {
1733 return make_upload_directory( $this->file_area_name($userid) );
1737 * Returns true if the student is allowed to submit
1739 * Checks that the assignment has started and, if the option to prevent late
1740 * submissions is set, also checks that the assignment has not yet closed.
1741 * @return boolean
1743 function isopen() {
1744 $time = time();
1745 if ($this->assignment->preventlate && $this->assignment->timedue) {
1746 return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
1747 } else {
1748 return ($this->assignment->timeavailable <= $time);
1753 * Return an outline of the user's interaction with the assignment
1755 * The default method prints the grade and timemodified
1756 * @param $user object
1757 * @return object with properties ->info and ->time
1759 function user_outline($user) {
1760 if ($submission = $this->get_submission($user->id)) {
1762 $result = new object();
1763 $result->info = get_string('grade').': '.$this->display_grade($submission->grade);
1764 $result->time = $submission->timemodified;
1765 return $result;
1767 return NULL;
1771 * Print complete information about the user's interaction with the assignment
1773 * @param $user object
1775 function user_complete($user) {
1776 if ($submission = $this->get_submission($user->id)) {
1777 if ($basedir = $this->file_area($user->id)) {
1778 if ($files = get_directory_list($basedir)) {
1779 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1780 foreach ($files as $file) {
1781 $countfiles .= "; $file";
1786 print_simple_box_start();
1787 echo get_string("lastmodified").": ";
1788 echo userdate($submission->timemodified);
1789 echo $this->display_lateness($submission->timemodified);
1791 $this->print_user_files($user->id);
1793 echo '<br />';
1795 if (empty($submission->timemarked)) {
1796 print_string("notgradedyet", "assignment");
1797 } else {
1798 $this->view_feedback($submission);
1801 print_simple_box_end();
1803 } else {
1804 print_string("notsubmittedyet", "assignment");
1809 * Return a string indicating how late a submission is
1811 * @param $timesubmitted int
1812 * @return string
1814 function display_lateness($timesubmitted) {
1815 return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
1819 * Empty method stub for all delete actions.
1821 function delete() {
1822 //nothing by default
1823 redirect('view.php?id='.$this->cm->id);
1827 * Empty custom feedback grading form.
1829 function custom_feedbackform($submission, $return=false) {
1830 //nothing by default
1831 return '';
1835 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1836 * for the course (see resource).
1838 * Given a course_module object, this function returns any "extra" information that may be needed
1839 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
1841 * @param $coursemodule object The coursemodule object (record).
1842 * @return object An object on information that the coures will know about (most noticeably, an icon).
1845 function get_coursemodule_info($coursemodule) {
1846 return false;
1850 * Plugin cron method - do not use $this here, create new assignment instances if needed.
1851 * @return void
1853 function cron() {
1854 //no plugin cron by default - override if needed
1857 } ////// End of the assignment_base class
1861 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1864 * Deletes an assignment instance
1866 * This is done by calling the delete_instance() method of the assignment type class
1868 function assignment_delete_instance($id){
1869 global $CFG;
1871 if (! $assignment = get_record('assignment', 'id', $id)) {
1872 return false;
1875 // fall back to base class if plugin missing
1876 $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
1877 if (file_exists($classfile)) {
1878 require_once($classfile);
1879 $assignmentclass = "assignment_$assignment->assignmenttype";
1881 } else {
1882 debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
1883 $assignmentclass = "assignment_base";
1886 $ass = new $assignmentclass();
1887 return $ass->delete_instance($assignment);
1892 * Updates an assignment instance
1894 * This is done by calling the update_instance() method of the assignment type class
1896 function assignment_update_instance($assignment){
1897 global $CFG;
1899 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1901 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1902 $assignmentclass = "assignment_$assignment->assignmenttype";
1903 $ass = new $assignmentclass();
1904 return $ass->update_instance($assignment);
1909 * Adds an assignment instance
1911 * This is done by calling the add_instance() method of the assignment type class
1913 function assignment_add_instance($assignment) {
1914 global $CFG;
1916 $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
1918 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1919 $assignmentclass = "assignment_$assignment->assignmenttype";
1920 $ass = new $assignmentclass();
1921 return $ass->add_instance($assignment);
1926 * Returns an outline of a user interaction with an assignment
1928 * This is done by calling the user_outline() method of the assignment type class
1930 function assignment_user_outline($course, $user, $mod, $assignment) {
1931 global $CFG;
1933 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1934 $assignmentclass = "assignment_$assignment->assignmenttype";
1935 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1936 return $ass->user_outline($user);
1940 * Prints the complete info about a user's interaction with an assignment
1942 * This is done by calling the user_complete() method of the assignment type class
1944 function assignment_user_complete($course, $user, $mod, $assignment) {
1945 global $CFG;
1947 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1948 $assignmentclass = "assignment_$assignment->assignmenttype";
1949 $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
1950 return $ass->user_complete($user);
1954 * Function to be run periodically according to the moodle cron
1956 * Finds all assignment notifications that have yet to be mailed out, and mails them
1958 function assignment_cron () {
1960 global $CFG, $USER;
1962 /// first execute all crons in plugins
1963 if ($plugins = get_list_of_plugins('mod/assignment/type')) {
1964 foreach ($plugins as $plugin) {
1965 require_once("$CFG->dirroot/mod/assignment/type/$plugin/assignment.class.php");
1966 $assignmentclass = "assignment_$plugin";
1967 $ass = new $assignmentclass();
1968 $ass->cron();
1972 /// Notices older than 1 day will not be mailed. This is to avoid the problem where
1973 /// cron has not been running for a long time, and then suddenly people are flooded
1974 /// with mail from the past few weeks or months
1976 $timenow = time();
1977 $endtime = $timenow - $CFG->maxeditingtime;
1978 $starttime = $endtime - 24 * 3600; /// One day earlier
1980 if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
1982 $CFG->enablerecordcache = true; // We want all the caching we can get
1984 $realuser = clone($USER);
1986 foreach ($submissions as $key => $submission) {
1987 if (! set_field("assignment_submissions", "mailed", "1", "id", "$submission->id")) {
1988 echo "Could not update the mailed field for id $submission->id. Not mailed.\n";
1989 unset($submissions[$key]);
1993 $timenow = time();
1995 foreach ($submissions as $submission) {
1997 echo "Processing assignment submission $submission->id\n";
1999 if (! $user = get_record("user", "id", "$submission->userid")) {
2000 echo "Could not find user $post->userid\n";
2001 continue;
2004 if (! $course = get_record("course", "id", "$submission->course")) {
2005 echo "Could not find course $submission->course\n";
2006 continue;
2009 /// Override the language and timezone of the "current" user, so that
2010 /// mail is customised for the receiver.
2011 $USER = $user;
2012 course_setup($course);
2014 if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) {
2015 echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n";
2016 continue;
2019 if (! $teacher = get_record("user", "id", "$submission->teacher")) {
2020 echo "Could not find teacher $submission->teacher\n";
2021 continue;
2024 if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
2025 echo "Could not find course module for assignment id $submission->assignment\n";
2026 continue;
2029 if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
2030 continue;
2033 $strassignments = get_string("modulenameplural", "assignment");
2034 $strassignment = get_string("modulename", "assignment");
2036 $assignmentinfo = new object();
2037 $assignmentinfo->teacher = fullname($teacher);
2038 $assignmentinfo->assignment = format_string($submission->name,true);
2039 $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
2041 $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true);
2042 $posttext = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n";
2043 $posttext .= "---------------------------------------------------------------------\n";
2044 $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
2045 $posttext .= "---------------------------------------------------------------------\n";
2047 if ($user->mailformat == 1) { // HTML
2048 $posthtml = "<p><font face=\"sans-serif\">".
2049 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
2050 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2051 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
2052 $posthtml .= "<hr /><font face=\"sans-serif\">";
2053 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2054 $posthtml .= "</font><hr />";
2055 } else {
2056 $posthtml = "";
2059 if (! email_to_user($user, $teacher, $postsubject, $posttext, $posthtml)) {
2060 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2064 $USER = $realuser;
2065 course_setup(SITEID); // reset cron user language, theme and timezone settings
2069 return true;
2073 * Return grade for given user or all users.
2075 * @param int $assignmentid id of assignment
2076 * @param int $userid optional user id, 0 means all users
2077 * @return array array of grades, false if none
2079 function assignment_get_user_grades($assignment, $userid=0) {
2080 global $CFG;
2082 $user = $userid ? "AND u.id = $userid" : "";
2084 $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat, s.teacher AS usermodified
2085 FROM {$CFG->prefix}user u, {$CFG->prefix}assignment_submissions s
2086 WHERE u.id = s.userid AND s.assignment = $assignment->id
2087 $user";
2089 return get_records_sql($sql);
2093 * Update grades by firing grade_updated event
2095 * @param object $assignment null means all assignments
2096 * @param int $userid specific user only, 0 mean all
2098 function assignment_update_grades($assignment=null, $userid=0, $nullifnone=true) {
2099 global $CFG;
2100 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2101 require_once($CFG->libdir.'/gradelib.php');
2104 if ($assignment != null) {
2105 if ($grades = assignment_get_user_grades($assignment, $userid)) {
2106 foreach($grades as $k=>$v) {
2107 if ($v->rawgrade == -1) {
2108 $grades[$k]->rawgrade = null;
2111 assignment_grade_item_update($assignment);
2112 grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades);
2115 } else {
2116 $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
2117 FROM {$CFG->prefix}assignment a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
2118 WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2119 if ($rs = get_recordset_sql($sql)) {
2120 if ($rs->RecordCount() > 0) {
2121 while ($assignment = rs_fetch_next_record($rs)) {
2122 assignment_grade_item_update($assignment);
2123 if ($assignment->grade != 0) {
2124 assignment_update_grades($assignment);
2128 rs_close($rs);
2134 * Create grade item for given assignment
2136 * @param object $assignment object with extra cmidnumber
2137 * @return int 0 if ok, error code otherwise
2139 function assignment_grade_item_update($assignment) {
2140 global $CFG;
2141 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2142 require_once($CFG->libdir.'/gradelib.php');
2145 if (!isset($assignment->courseid)) {
2146 $assignment->courseid = $assignment->course;
2149 $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
2151 if ($assignment->grade > 0) {
2152 $params['gradetype'] = GRADE_TYPE_VALUE;
2153 $params['grademax'] = $assignment->grade;
2154 $params['grademin'] = 0;
2156 } else if ($assignment->grade < 0) {
2157 $params['gradetype'] = GRADE_TYPE_SCALE;
2158 $params['scaleid'] = -$assignment->grade;
2160 } else {
2161 $params['gradetype'] = GRADE_TYPE_NONE;
2164 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, $params);
2168 * Delete grade item for given assignment
2170 * @param object $assignment object
2171 * @return object assignment
2173 function assignment_grade_item_delete($assignment) {
2174 global $CFG;
2175 require_once($CFG->libdir.'/gradelib.php');
2177 if (!isset($assignment->courseid)) {
2178 $assignment->courseid = $assignment->course;
2181 return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
2185 * Returns the users with data in one assignment (students and teachers)
2187 * @param $assignmentid int
2188 * @return array of user objects
2190 function assignment_get_participants($assignmentid) {
2192 global $CFG;
2194 //Get students
2195 $students = get_records_sql("SELECT DISTINCT u.id, u.id
2196 FROM {$CFG->prefix}user u,
2197 {$CFG->prefix}assignment_submissions a
2198 WHERE a.assignment = '$assignmentid' and
2199 u.id = a.userid");
2200 //Get teachers
2201 $teachers = get_records_sql("SELECT DISTINCT u.id, u.id
2202 FROM {$CFG->prefix}user u,
2203 {$CFG->prefix}assignment_submissions a
2204 WHERE a.assignment = '$assignmentid' and
2205 u.id = a.teacher");
2207 //Add teachers to students
2208 if ($teachers) {
2209 foreach ($teachers as $teacher) {
2210 $students[$teacher->id] = $teacher;
2213 //Return students array (it contains an array of unique users)
2214 return ($students);
2218 * Checks if a scale is being used by an assignment
2220 * This is used by the backup code to decide whether to back up a scale
2221 * @param $assignmentid int
2222 * @param $scaleid int
2223 * @return boolean True if the scale is used by the assignment
2225 function assignment_scale_used($assignmentid, $scaleid) {
2227 $return = false;
2229 $rec = get_record('assignment','id',$assignmentid,'grade',-$scaleid);
2231 if (!empty($rec) && !empty($scaleid)) {
2232 $return = true;
2235 return $return;
2239 * Checks if scale is being used by any instance of assignment
2241 * This is used to find out if scale used anywhere
2242 * @param $scaleid int
2243 * @return boolean True if the scale is used by any assignment
2245 function assignment_scale_used_anywhere($scaleid) {
2246 if ($scaleid and record_exists('assignment', 'grade', -$scaleid)) {
2247 return true;
2248 } else {
2249 return false;
2254 * Make sure up-to-date events are created for all assignment instances
2256 * This standard function will check all instances of this module
2257 * and make sure there are up-to-date events created for each of them.
2258 * If courseid = 0, then every assignment event in the site is checked, else
2259 * only assignment events belonging to the course specified are checked.
2260 * This function is used, in its new format, by restore_refresh_events()
2262 * @param $courseid int optional If zero then all assignments for all courses are covered
2263 * @return boolean Always returns true
2265 function assignment_refresh_events($courseid = 0) {
2267 if ($courseid == 0) {
2268 if (! $assignments = get_records("assignment")) {
2269 return true;
2271 } else {
2272 if (! $assignments = get_records("assignment", "course", $courseid)) {
2273 return true;
2276 $moduleid = get_field('modules', 'id', 'name', 'assignment');
2278 foreach ($assignments as $assignment) {
2279 $event = NULL;
2280 $event->name = addslashes($assignment->name);
2281 $event->description = addslashes($assignment->description);
2282 $event->timestart = $assignment->timedue;
2284 if ($event->id = get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id)) {
2285 update_event($event);
2287 } else {
2288 $event->courseid = $assignment->course;
2289 $event->groupid = 0;
2290 $event->userid = 0;
2291 $event->modulename = 'assignment';
2292 $event->instance = $assignment->id;
2293 $event->eventtype = 'due';
2294 $event->timeduration = 0;
2295 $event->visible = get_field('course_modules', 'visible', 'module', $moduleid, 'instance', $assignment->id);
2296 add_event($event);
2300 return true;
2304 * Print recent activity from all assignments in a given course
2306 * This is used by the recent activity block
2308 function assignment_print_recent_activity($course, $isteacher, $timestart) {
2309 global $CFG;
2311 $content = false;
2312 $assignments = array();
2314 if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
2315 'course = \''.$course->id.'\' AND '.
2316 'module = \'assignment\' AND '.
2317 'action = \'upload\' ', 'time ASC')) {
2318 return false;
2321 foreach ($logs as $log) {
2322 //Create a temp valid module structure (course,id)
2323 $tempmod = new object();
2324 $tempmod->course = $log->course;
2325 $tempmod->id = $log->info;
2326 //Obtain the visible property from the instance
2327 $modvisible = instance_is_visible($log->module,$tempmod);
2329 //Only if the mod is visible
2330 if ($modvisible) {
2331 if ($info = assignment_log_info($log)) {
2332 $assignments[$log->info] = $info;
2333 $assignments[$log->info]->time = $log->time;
2334 $assignments[$log->info]->url = str_replace('&', '&amp;', $log->url);
2339 if (!empty($assignments)) {
2340 print_headline(get_string('newsubmissions', 'assignment').':');
2341 foreach ($assignments as $assignment) {
2342 print_recent_activity_note($assignment->time, $assignment, $assignment->name,
2343 $CFG->wwwroot.'/mod/assignment/'.$assignment->url);
2345 $content = true;
2348 return $content;
2353 * Returns all assignments since a given time.
2355 * If assignment is specified then this restricts the results
2357 function assignment_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $assignment="0", $user="", $groupid="") {
2359 global $CFG;
2361 if ($assignment) {
2362 $assignmentselect = " AND cm.id = '$assignment'";
2363 } else {
2364 $assignmentselect = "";
2366 if ($user) {
2367 $userselect = " AND u.id = '$user'";
2368 } else {
2369 $userselect = "";
2372 $assignments = get_records_sql("SELECT asub.*, u.firstname, u.lastname, u.picture, u.id as userid,
2373 a.grade as maxgrade, name, cm.instance, cm.section, a.assignmenttype
2374 FROM {$CFG->prefix}assignment_submissions asub,
2375 {$CFG->prefix}user u,
2376 {$CFG->prefix}assignment a,
2377 {$CFG->prefix}course_modules cm
2378 WHERE asub.timemodified > '$sincetime'
2379 AND asub.userid = u.id $userselect
2380 AND a.id = asub.assignment $assignmentselect
2381 AND cm.course = '$courseid'
2382 AND cm.instance = a.id
2383 ORDER BY asub.timemodified ASC");
2385 if (empty($assignments))
2386 return;
2388 foreach ($assignments as $assignment) {
2389 if (empty($groupid) || groups_is_member($groupid, $assignment->userid)) {
2391 $tmpactivity = new Object;
2393 $tmpactivity->type = "assignment";
2394 $tmpactivity->defaultindex = $index;
2395 $tmpactivity->instance = $assignment->instance;
2396 $tmpactivity->name = $assignment->name;
2397 $tmpactivity->section = $assignment->section;
2399 $tmpactivity->content->grade = $assignment->grade;
2400 $tmpactivity->content->maxgrade = $assignment->maxgrade;
2401 $tmpactivity->content->type = $assignment->assignmenttype;
2403 $tmpactivity->user->userid = $assignment->userid;
2404 $tmpactivity->user->fullname = fullname($assignment);
2405 $tmpactivity->user->picture = $assignment->picture;
2407 $tmpactivity->timestamp = $assignment->timemodified;
2409 $activities[] = $tmpactivity;
2411 $index++;
2415 return;
2419 * Print recent activity from all assignments in a given course
2421 * This is used by course/recent.php
2423 function assignment_print_recent_mod_activity($activity, $course, $detail=false) {
2424 global $CFG;
2426 echo '<table border="0" cellpadding="3" cellspacing="0">';
2428 echo "<tr><td class=\"userpicture\" valign=\"top\">";
2429 print_user_picture($activity->user->userid, $course, $activity->user->picture);
2430 echo "</td><td width=\"100%\"><font size=2>";
2432 if ($detail) {
2433 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
2434 "class=\"icon\" alt=\"$activity->type\"> ";
2435 echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=" . $activity->instance . "\">"
2436 . format_string($activity->name,true) . "</a> - ";
2440 if (has_capability('moodle/course:viewrecent', get_context_instance(CONTEXT_COURSE, $course))) {
2441 $grades = "(" . $activity->content->grade . " / " . $activity->content->maxgrade . ") ";
2443 $assignment->id = $activity->instance;
2444 $assignment->course = $course;
2445 $user->id = $activity->user->userid;
2447 echo $grades;
2448 echo "<br />";
2450 echo "<a href=\"$CFG->wwwroot/user/view.php?id="
2451 . $activity->user->userid . "&amp;course=$course\">"
2452 . $activity->user->fullname . "</a> ";
2454 echo " - " . userdate($activity->timestamp);
2456 echo "</font></td></tr>";
2457 echo "</table>";
2460 /// GENERIC SQL FUNCTIONS
2463 * Fetch info from logs
2465 * @param $log object with properties ->info (the assignment id) and ->userid
2466 * @return array with assignment name and user firstname and lastname
2468 function assignment_log_info($log) {
2469 global $CFG;
2470 return get_record_sql("SELECT a.name, u.firstname, u.lastname
2471 FROM {$CFG->prefix}assignment a,
2472 {$CFG->prefix}user u
2473 WHERE a.id = '$log->info'
2474 AND u.id = '$log->userid'");
2478 * Return list of marked submissions that have not been mailed out for currently enrolled students
2480 * @return array
2482 function assignment_get_unmailed_submissions($starttime, $endtime) {
2484 global $CFG;
2486 return get_records_sql("SELECT s.*, a.course, a.name
2487 FROM {$CFG->prefix}assignment_submissions s,
2488 {$CFG->prefix}assignment a
2489 WHERE s.mailed = 0
2490 AND s.timemarked <= $endtime
2491 AND s.timemarked >= $starttime
2492 AND s.assignment = a.id");
2494 /* return get_records_sql("SELECT s.*, a.course, a.name
2495 FROM {$CFG->prefix}assignment_submissions s,
2496 {$CFG->prefix}assignment a,
2497 {$CFG->prefix}user_students us
2498 WHERE s.mailed = 0
2499 AND s.timemarked <= $endtime
2500 AND s.timemarked >= $starttime
2501 AND s.assignment = a.id
2502 AND s.userid = us.userid
2503 AND a.course = us.course");
2508 * Counts all real assignment submissions by ENROLLED students (not empty ones)
2510 * There are also assignment type methods count_real_submissions() wich in the default
2511 * implementation simply call this function.
2512 * @param $groupid int optional If nonzero then count is restricted to this group
2513 * @return int The number of submissions
2515 function assignment_count_real_submissions($assignment, $groupid=0) {
2516 global $CFG;
2518 if ($groupid) { /// How many in a particular group?
2519 return count_records_sql("SELECT COUNT(DISTINCT gm.userid, gm.groupid)
2520 FROM {$CFG->prefix}assignment_submissions a,
2521 {$CFG->prefix}groups_members g
2522 WHERE a.assignment = $assignment->id
2523 AND a.timemodified > 0
2524 AND g.groupid = '$groupid'
2525 AND a.userid = g.userid ");
2526 } else {
2527 $cm = get_coursemodule_from_instance('assignment', $assignment->id);
2528 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2530 // this is all the users with this capability set, in this context or higher
2531 if ($users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2532 foreach ($users as $user) {
2533 $array[] = $user->id;
2536 $userlists = '('.implode(',',$array).')';
2538 return count_records_sql("SELECT COUNT(*)
2539 FROM {$CFG->prefix}assignment_submissions
2540 WHERE assignment = '$assignment->id'
2541 AND timemodified > 0
2542 AND userid IN $userlists ");
2543 } else {
2544 return 0; // no users enroled in course
2551 * Return all assignment submissions by ENROLLED students (even empty)
2553 * There are also assignment type methods get_submissions() wich in the default
2554 * implementation simply call this function.
2555 * @param $sort string optional field names for the ORDER BY in the sql query
2556 * @param $dir string optional specifying the sort direction, defaults to DESC
2557 * @return array The submission objects indexed by id
2559 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2560 /// Return all assignment submissions by ENROLLED students (even empty)
2561 global $CFG;
2563 if ($sort == "lastname" or $sort == "firstname") {
2564 $sort = "u.$sort $dir";
2565 } else if (empty($sort)) {
2566 $sort = "a.timemodified DESC";
2567 } else {
2568 $sort = "a.$sort $dir";
2571 /* not sure this is needed at all since assignmenet already has a course define, so this join?
2572 $select = "s.course = '$assignment->course' AND";
2573 if ($assignment->course == SITEID) {
2574 $select = '';
2577 return get_records_sql("SELECT a.*
2578 FROM {$CFG->prefix}assignment_submissions a,
2579 {$CFG->prefix}user u
2580 WHERE u.id = a.userid
2581 AND a.assignment = '$assignment->id'
2582 ORDER BY $sort");
2584 /* return get_records_sql("SELECT a.*
2585 FROM {$CFG->prefix}assignment_submissions a,
2586 {$CFG->prefix}user_students s,
2587 {$CFG->prefix}user u
2588 WHERE a.userid = s.userid
2589 AND u.id = a.userid
2590 AND $select a.assignment = '$assignment->id'
2591 ORDER BY $sort");
2596 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2597 * for the course (see resource).
2599 * Given a course_module object, this function returns any "extra" information that may be needed
2600 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
2602 * @param $coursemodule object The coursemodule object (record).
2603 * @return object An object on information that the coures will know about (most noticeably, an icon).
2606 function assignment_get_coursemodule_info($coursemodule) {
2607 global $CFG;
2609 if (! $assignment = get_record('assignment', 'id', $coursemodule->instance)) {
2610 return false;
2613 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2614 $assignmentclass = "assignment_$assignment->assignmenttype";
2615 $ass = new $assignmentclass($coursemodule->id, $assignment);
2617 return $ass->get_coursemodule_info($coursemodule);
2622 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
2625 * Returns an array of installed assignment types indexed and sorted by name
2627 * @return array The index is the name of the assignment type, the value its full name from the language strings
2629 function assignment_types() {
2630 $types = array();
2631 $names = get_list_of_plugins('mod/assignment/type');
2632 foreach ($names as $name) {
2633 $types[$name] = get_string('type'.$name, 'assignment');
2635 asort($types);
2636 return $types;
2640 * Executes upgrade scripts for assignment types when necessary
2642 function assignment_upgrade_submodules() {
2644 global $CFG;
2646 /// Install/upgrade assignment types (it uses, simply, the standard plugin architecture)
2647 upgrade_plugins('assignment_type', 'mod/assignment/type', "$CFG->wwwroot/$CFG->admin/index.php");
2651 function assignment_print_overview($courses, &$htmlarray) {
2653 global $USER, $CFG;
2655 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
2656 return array();
2659 if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2660 return;
2663 // Do assignment_base::isopen() here without loading the whole thing for speed
2664 foreach ($assignments as $key => $assignment) {
2665 $time = time();
2666 if ($assignment->timedue) {
2667 if ($assignment->preventlate) {
2668 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
2669 } else {
2670 $isopen = ($assignment->timeavailable <= $time);
2673 if (empty($isopen) || empty($assignment->timedue)) {
2674 unset($assignments[$key]);
2678 $strduedate = get_string('duedate', 'assignment');
2679 $strduedateno = get_string('duedateno', 'assignment');
2680 $strgraded = get_string('graded', 'assignment');
2681 $strnotgradedyet = get_string('notgradedyet', 'assignment');
2682 $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2683 $strsubmitted = get_string('submitted', 'assignment');
2684 $strassignment = get_string('modulename', 'assignment');
2685 $strreviewed = get_string('reviewed','assignment');
2687 foreach ($assignments as $assignment) {
2688 $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
2689 '<a '.($assignment->visible ? '':' class="dimmed"').
2690 'title="'.$strassignment.'" href="'.$CFG->wwwroot.
2691 '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
2692 $assignment->name.'</a></div>';
2693 if ($assignment->timedue) {
2694 $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
2695 } else {
2696 $str .= '<div class="info">'.$strduedateno.'</div>';
2698 $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
2699 if (has_capability('mod/assignment:grade', $context)) {
2701 // count how many people can submit
2702 $submissions = 0; // init
2703 if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2704 foreach ($students as $student) {
2705 if (get_records_sql("SELECT id,id FROM {$CFG->prefix}assignment_submissions
2706 WHERE assignment = $assignment->id AND
2707 userid = $student->id AND
2708 teacher = 0 AND
2709 timemarked = 0")) {
2710 $submissions++;
2715 if ($submissions) {
2716 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2718 } else {
2719 $sql = "SELECT *
2720 FROM {$CFG->prefix}assignment_submissions
2721 WHERE userid = '$USER->id'
2722 AND assignment = '{$assignment->id}'";
2723 if ($submission = get_record_sql($sql)) {
2724 if ($submission->teacher == 0 && $submission->timemarked == 0) {
2725 $str .= $strsubmitted . ', ' . $strnotgradedyet;
2726 } else if ($submission->grade <= 0) {
2727 $str .= $strsubmitted . ', ' . $strreviewed;
2728 } else {
2729 $str .= $strsubmitted . ', ' . $strgraded;
2731 } else {
2732 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
2735 $str .= '</div>';
2736 if (empty($htmlarray[$assignment->course]['assignment'])) {
2737 $htmlarray[$assignment->course]['assignment'] = $str;
2738 } else {
2739 $htmlarray[$assignment->course]['assignment'] .= $str;
2744 function assignment_display_lateness($timesubmitted, $timedue) {
2745 if (!$timedue) {
2746 return '';
2748 $time = $timedue - $timesubmitted;
2749 if ($time < 0) {
2750 $timetext = get_string('late', 'assignment', format_time($time));
2751 return ' (<span class="late">'.$timetext.'</span>)';
2752 } else {
2753 $timetext = get_string('early', 'assignment', format_time($time));
2754 return ' (<span class="early">'.$timetext.'</span>)';
2758 function assignment_get_view_actions() {
2759 return array('view');
2762 function assignment_get_post_actions() {
2763 return array('upload');
2766 function assignment_get_types() {
2767 global $CFG;
2768 $types = array();
2770 $type = new object();
2771 $type->modclass = MOD_CLASS_ACTIVITY;
2772 $type->type = "assignment_group_start";
2773 $type->typestr = '--'.get_string('modulenameplural', 'assignment');
2774 $types[] = $type;
2776 $standardassignments = array('upload','online','uploadsingle','offline');
2777 foreach ($standardassignments as $assignmenttype) {
2778 $type = new object();
2779 $type->modclass = MOD_CLASS_ACTIVITY;
2780 $type->type = "assignment&amp;type=$assignmenttype";
2781 $type->typestr = get_string("type$assignmenttype", 'assignment');
2782 $types[] = $type;
2785 /// Drop-in extra assignment types
2786 $assignmenttypes = get_list_of_plugins('mod/assignment/type');
2787 foreach ($assignmenttypes as $assignmenttype) {
2788 if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
2789 continue;
2791 if (!in_array($assignmenttype, $standardassignments)) {
2792 $type = new object();
2793 $type->modclass = MOD_CLASS_ACTIVITY;
2794 $type->type = "assignment&amp;type=$assignmenttype";
2795 $type->typestr = get_string("type$assignmenttype", 'assignment');
2796 $types[] = $type;
2800 $type = new object();
2801 $type->modclass = MOD_CLASS_ACTIVITY;
2802 $type->type = "assignment_group_end";
2803 $type->typestr = '--';
2804 $types[] = $type;
2806 return $types;