30cdb12e91157b2a7921db8d2d173d070bda4bd8
[moodle.git] / mod / assignment / lib.php
1 <?PHP  // $Id$
2 /**
3  * assignment_base is the base class for assignment types
4  *
5  * This class provides all the functionality for an assignment
6  */
8 require_once($CFG->libdir.'/eventslib.php');
10 DEFINE ('ASSIGNMENT_COUNT_WORDS', 1);
11 DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2);
13 /**
14  * Standard base class for all assignment submodules (assignment types).
15  */
16 class assignment_base {
18     var $cm;
19     var $course;
20     var $assignment;
21     var $strassignment;
22     var $strassignments;
23     var $strsubmissions;
24     var $strlastmodified;
25     var $pagetitle;
26     var $usehtmleditor;
27     var $defaultformat;
28     var $context;
29     var $type;
31     /**
32      * Constructor for the base assignment class
33      *
34      * Constructor for the base assignment class.
35      * If cmid is set create the cm, course, assignment objects.
36      * If the assignment is hidden and the user is not a teacher then
37      * this prints a page header and notice.
38      *
39      * @param cmid   integer, the current course module id - not set for new assignments
40      * @param assignment   object, usually null, but if we have it we pass it to save db access
41      * @param cm   object, usually null, but if we have it we pass it to save db access
42      * @param course   object, usually null, but if we have it we pass it to save db access
43      */
44     function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
45         global $COURSE, $DB;
47         if ($cmid == 'staticonly') {
48             //use static functions only!
49             return;
50         }
52         global $CFG;
54         if ($cm) {
55             $this->cm = $cm;
56         } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
57             print_error('invalidcoursemodule');
58         }
60         $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
62         if ($course) {
63             $this->course = $course;
64         } else if ($this->cm->course == $COURSE->id) {
65             $this->course = $COURSE;
66         } else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
67             print_error('invalidid', 'assignment');
68         }
70         if ($assignment) {
71             $this->assignment = $assignment;
72         } else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
73             print_error('invalidid', 'assignment');
74         }
76         $this->assignment->cmidnumber = $this->cm->id;     // compatibility with modedit assignment obj
77         $this->assignment->courseid   = $this->course->id; // compatibility with modedit assignment obj
79         $this->strassignment = get_string('modulename', 'assignment');
80         $this->strassignments = get_string('modulenameplural', 'assignment');
81         $this->strsubmissions = get_string('submissions', 'assignment');
82         $this->strlastmodified = get_string('lastmodified');
83         $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true));
85         // visibility handled by require_login() with $cm parameter
86         // get current group only when really needed
88     /// Set up things for a HTML editor if it's needed
89         if ($this->usehtmleditor = can_use_html_editor()) {
90             $this->defaultformat = FORMAT_HTML;
91         } else {
92             $this->defaultformat = FORMAT_MOODLE;
93         }
94     }
96     /**
97      * Display the assignment, used by view.php
98      *
99      * This in turn calls the methods producing individual parts of the page
100      */
101     function view() {
103         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
104         require_capability('mod/assignment:view', $context);
106         add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
107                    $this->assignment->id, $this->cm->id);
109         $this->view_header();
111         $this->view_intro();
113         $this->view_dates();
115         $this->view_feedback();
117         $this->view_footer();
118     }
120     /**
121      * Display the header and top of a page
122      *
123      * (this doesn't change much for assignment types)
124      * This is used by the view() method to print the header of view.php but
125      * it can be used on other pages in which case the string to denote the
126      * page in the navigation trail should be passed as an argument
127      *
128      * @param $subpage string Description of subpage to be used in navigation trail
129      */
130     function view_header($subpage='') {
132         global $CFG;
135         if ($subpage) {
136             $navigation = build_navigation($subpage, $this->cm);
137         } else {
138             $navigation = build_navigation('', $this->cm);
139         }
141         print_header($this->pagetitle, $this->course->fullname, $navigation, '', '',
142                      true, update_module_button($this->cm->id, $this->course->id, $this->strassignment),
143                      navmenu($this->course, $this->cm));
145         groups_print_activity_menu($this->cm, 'view.php?id=' . $this->cm->id);
147         echo '<div class="reportlink">'.$this->submittedlink().'</div>';
148         echo '<div class="clearer"></div>';
149     }
152     /**
153      * Display the assignment intro
154      *
155      * This will most likely be extended by assignment type plug-ins
156      * The default implementation prints the assignment description in a box
157      */
158     function view_intro() {
159         print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
160         $formatoptions = new stdClass;
161         $formatoptions->noclean = true;
162         echo format_text($this->assignment->description, $this->assignment->format, $formatoptions);
163         print_simple_box_end();
164     }
166     /**
167      * Display the assignment dates
168      *
169      * Prints the assignment start and end dates in a box.
170      * This will be suitable for most assignment types
171      */
172     function view_dates() {
173         if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
174             return;
175         }
177         print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
178         echo '<table>';
179         if ($this->assignment->timeavailable) {
180             echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
181             echo '    <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
182         }
183         if ($this->assignment->timedue) {
184             echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
185             echo '    <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
186         }
187         echo '</table>';
188         print_simple_box_end();
189     }
192     /**
193      * Display the bottom and footer of a page
194      *
195      * This default method just prints the footer.
196      * This will be suitable for most assignment types
197      */
198     function view_footer() {
199         print_footer($this->course);
200     }
202     /**
203      * Display the feedback to the student
204      *
205      * This default method prints the teacher picture and name, date when marked,
206      * grade and teacher submissioncomment.
207      *
208      * @param $submission object The submission object or NULL in which case it will be loaded
209      */
210     function view_feedback($submission=NULL) {
211         global $USER, $CFG, $DB;
212         require_once($CFG->libdir.'/gradelib.php');
214         if (!has_capability('mod/assignment:submit', $this->context, $USER->id, false)) {
215             // can not submit assignments -> no feedback
216             return;
217         }
219         if (!$submission) { /// Get submission for this assignment
220             $submission = $this->get_submission($USER->id);
221         }
223         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
224         $item = $grading_info->items[0];
225         $grade = $item->grades[$USER->id];
227         if ($grade->hidden or $grade->grade === false) { // hidden or error
228             return;
229         }
231         if ($grade->grade === null and empty($grade->str_feedback)) {   /// Nothing to show yet
232             return;
233         }
235         $graded_date = $grade->dategraded;
236         $graded_by   = $grade->usermodified;
238     /// We need the teacher info
239         if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
240             print_error('cannotfindteacher');
241         }
243     /// Print the feedback
244         print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher)); // TODO: fix teacher string
246         echo '<table cellspacing="0" class="feedback">';
248         echo '<tr>';
249         echo '<td class="left picture">';
250         if ($teacher) {
251             print_user_picture($teacher, $this->course->id, $teacher->picture);
252         }
253         echo '</td>';
254         echo '<td class="topic">';
255         echo '<div class="from">';
256         if ($teacher) {
257             echo '<div class="fullname">'.fullname($teacher).'</div>';
258         }
259         echo '<div class="time">'.userdate($graded_date).'</div>';
260         echo '</div>';
261         echo '</td>';
262         echo '</tr>';
264         echo '<tr>';
265         echo '<td class="left side">&nbsp;</td>';
266         echo '<td class="content">';
267         echo '<div class="grade">';
268         echo get_string("grade").': '.$grade->str_long_grade;
269         echo '</div>';
270         echo '<div class="clearer"></div>';
272         echo '<div class="comment">';
273         echo $grade->str_feedback;
274         echo '</div>';
275         echo '</tr>';
277         echo '</table>';
278     }
280     /**
281      * Returns a link with info about the state of the assignment submissions
282      *
283      * This is used by view_header to put this link at the top right of the page.
284      * For teachers it gives the number of submitted assignments with a link
285      * For students it gives the time of their submission.
286      * This will be suitable for most assignment types.
287      * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
288      * @return string
289      */
290     function submittedlink($allgroups=false) {
291         global $USER;
292         global $CFG;
294         $submitted = '';
295         $urlbase = "{$CFG->wwwroot}/mod/assignment/";
297         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
298         if (has_capability('mod/assignment:grade', $context)) {
299             if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
300                 $group = 0;
301             } else {
302                 $group = groups_get_activity_group($this->cm);
303             }
304             if ($count = $this->count_real_submissions($group)) {
305                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
306                              get_string('viewsubmissions', 'assignment', $count).'</a>';
307             } else {
308                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
309                              get_string('noattempts', 'assignment').'</a>';
310             }
311         } else {
312             if (!empty($USER->id)) {
313                 if ($submission = $this->get_submission($USER->id)) {
314                     if ($submission->timemodified) {
315                         if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
316                             $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
317                         } else {
318                             $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
319                         }
320                     }
321                 }
322             }
323         }
325         return $submitted;
326     }
329     function setup_elements(&$mform) {
331     }
333     /**
334      * Create a new assignment activity
335      *
336      * Given an object containing all the necessary data,
337      * (defined by the form in mod_form.php) this function
338      * will create a new instance and return the id number
339      * of the new instance.
340      * The due data is added to the calendar
341      * This is common to all assignment types.
342      *
343      * @param $assignment object The data from the form on mod_form.php
344      * @return int The id of the assignment
345      */
346     function add_instance($assignment) {
347         global $COURSE, $DB;
349         $assignment->timemodified = time();
350         $assignment->courseid = $assignment->course;
352         if ($returnid = $DB->insert_record("assignment", $assignment)) {
353             $assignment->id = $returnid;
355             if ($assignment->timedue) {
356                 $event = new object();
357                 $event->name        = $assignment->name;
358                 $event->description = $assignment->description;
359                 $event->courseid    = $assignment->course;
360                 $event->groupid     = 0;
361                 $event->userid      = 0;
362                 $event->modulename  = 'assignment';
363                 $event->instance    = $returnid;
364                 $event->eventtype   = 'due';
365                 $event->timestart   = $assignment->timedue;
366                 $event->timeduration = 0;
368                 add_event($event);
369             }
371             assignment_grade_item_update($assignment);
373         }
376         return $returnid;
377     }
379     /**
380      * Deletes an assignment activity
381      *
382      * Deletes all database records, files and calendar events for this assignment.
383      * @param $assignment object The assignment to be deleted
384      * @return boolean False indicates error
385      */
386     function delete_instance($assignment) {
387         global $CFG, $DB;
389         $assignment->courseid = $assignment->course;
391         $result = true;
393         if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
394             $result = false;
395         }
397         if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
398             $result = false;
399         }
401         if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
402             $result = false;
403         }
405         // delete file area with all attachments - ignore errors
406         require_once($CFG->libdir.'/filelib.php');
407         fulldelete($CFG->dataroot.'/'.$assignment->course.'/'.$CFG->moddata.'/assignment/'.$assignment->id);
409         assignment_grade_item_delete($assignment);
411         return $result;
412     }
414     /**
415      * Updates a new assignment activity
416      *
417      * Given an object containing all the necessary data,
418      * (defined by the form in mod_form.php) this function
419      * will update the assignment instance and return the id number
420      * The due date is updated in the calendar
421      * This is common to all assignment types.
422      *
423      * @param $assignment object The data from the form on mod_form.php
424      * @return int The assignment id
425      */
426     function update_instance($assignment) {
427         global $COURSE, $DB;
429         $assignment->timemodified = time();
431         $assignment->id = $assignment->instance;
432         $assignment->courseid = $assignment->course;
434         if (!$DB->update_record('assignment', $assignment)) {
435             return false;
436         }
438         if ($assignment->timedue) {
439             $event = new object();
441             if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
443                 $event->name        = $assignment->name;
444                 $event->description = $assignment->description;
445                 $event->timestart   = $assignment->timedue;
447                 update_event($event);
448             } else {
449                 $event = new object();
450                 $event->name        = $assignment->name;
451                 $event->description = $assignment->description;
452                 $event->courseid    = $assignment->course;
453                 $event->groupid     = 0;
454                 $event->userid      = 0;
455                 $event->modulename  = 'assignment';
456                 $event->instance    = $assignment->id;
457                 $event->eventtype   = 'due';
458                 $event->timestart   = $assignment->timedue;
459                 $event->timeduration = 0;
461                 add_event($event);
462             }
463         } else {
464             $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
465         }
467         // get existing grade item
468         assignment_grade_item_update($assignment);
470         return true;
471     }
473     /**
474      * Update grade item for this submission.
475      */
476     function update_grade($submission) {
477         assignment_update_grades($this->assignment, $submission->userid);
478     }
480     /**
481      * Top-level function for handling of submissions called by submissions.php
482      *
483      * This is for handling the teacher interaction with the grading interface
484      * This should be suitable for most assignment types.
485      *
486      * @param $mode string Specifies the kind of teacher interaction taking place
487      */
488     function submissions($mode) {
489         ///The main switch is changed to facilitate
490         ///1) Batch fast grading
491         ///2) Skip to the next one on the popup
492         ///3) Save and Skip to the next one on the popup
494         //make user global so we can use the id
495         global $USER;
497         $mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
498         if (is_null($mailinfo)) {
499             $mailinfo = get_user_preferences('assignment_mailinfo', 0);
500         } else {
501             set_user_preference('assignment_mailinfo', $mailinfo);
502         }
504         switch ($mode) {
505             case 'grade':                         // We are in a popup window grading
506                 if ($submission = $this->process_feedback()) {
507                     //IE needs proper header with encoding
508                     print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name));
509                     print_heading(get_string('changessaved'));
510                     print $this->update_main_listing($submission);
511                 }
512                 close_window();
513                 break;
515             case 'single':                        // We are in a popup window displaying submission
516                 $this->display_submission();
517                 break;
519             case 'all':                          // Main window, display everything
520                 $this->display_submissions();
521                 break;
523             case 'fastgrade':
524                 ///do the fast grading stuff  - this process should work for all 3 subclasses
526                 $grading    = false;
527                 $commenting = false;
528                 $col        = false;
529                 if (isset($_POST['submissioncomment'])) {
530                     $col = 'submissioncomment';
531                     $commenting = true;
532                 }
533                 if (isset($_POST['menu'])) {
534                     $col = 'menu';
535                     $grading = true;
536                 }
537                 if (!$col) {
538                     //both submissioncomment and grade columns collapsed..
539                     $this->display_submissions();
540                     break;
541                 }
543                 foreach ($_POST[$col] as $id => $unusedvalue){
545                     $id = (int)$id; //clean parameter name
547                     $this->process_outcomes($id);
549                     if (!$submission = $this->get_submission($id)) {
550                         $submission = $this->prepare_new_submission($id);
551                         $newsubmission = true;
552                     } else {
553                         $newsubmission = false;
554                     }
555                     unset($submission->data1);  // Don't need to update this.
556                     unset($submission->data2);  // Don't need to update this.
558                     //for fast grade, we need to check if any changes take place
559                     $updatedb = false;
561                     if ($grading) {
562                         $grade = $_POST['menu'][$id];
563                         $updatedb = $updatedb || ($submission->grade != $grade);
564                         $submission->grade = $grade;
565                     } else {
566                         if (!$newsubmission) {
567                             unset($submission->grade);  // Don't need to update this.
568                         }
569                     }
570                     if ($commenting) {
571                         $commentvalue = trim($_POST['submissioncomment'][$id]);
572                         $updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
573                         $submission->submissioncomment = $commentvalue;
574                     } else {
575                         unset($submission->submissioncomment);  // Don't need to update this.
576                     }
578                     $submission->teacher    = $USER->id;
579                     if ($updatedb) {
580                         $submission->mailed = (int)(!$mailinfo);
581                     }
583                     $submission->timemarked = time();
585                     //if it is not an update, we don't change the last modified time etc.
586                     //this will also not write into database if no submissioncomment and grade is entered.
588                     if ($updatedb){
589                         if ($newsubmission) {
590                             if (!isset($submission->submissioncomment)) {
591                                 $submission->submissioncomment = '';
592                             }
593                             if (!$sid = $DB->insert_record('assignment_submissions', $submission)) {
594                                 return false;
595                             }
596                             $submission->id = $sid;
597                         } else {
598                             if (!$DB->update_record('assignment_submissions', $submission)) {
599                                 return false;
600                             }
601                         }
603                         // triger grade event
604                         $this->update_grade($submission);
606                         //add to log only if updating
607                         add_to_log($this->course->id, 'assignment', 'update grades',
608                                    'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid,
609                                    $submission->userid, $this->cm->id);
610                     }
612                 }
614                 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true);
616                 $this->display_submissions($message);
617                 break;
620             case 'next':
621                 /// We are currently in pop up, but we want to skip to next one without saving.
622                 ///    This turns out to be similar to a single case
623                 /// The URL used is for the next submission.
625                 $this->display_submission();
626                 break;
628             case 'saveandnext':
629                 ///We are in pop up. save the current one and go to the next one.
630                 //first we save the current changes
631                 if ($submission = $this->process_feedback()) {
632                     //print_heading(get_string('changessaved'));
633                     $extra_javascript = $this->update_main_listing($submission);
634                 }
636                 //then we display the next submission
637                 $this->display_submission($extra_javascript);
638                 break;
640             default:
641                 echo "something seriously is wrong!!";
642                 break;
643         }
644     }
646     /**
647     * Helper method updating the listing on the main script from popup using javascript
648     *
649     * @param $submission object The submission whose data is to be updated on the main page
650     */
651     function update_main_listing($submission) {
652         global $SESSION, $CFG;
654         $output = '';
656         $perpage = get_user_preferences('assignment_perpage', 10);
658         $quickgrade = get_user_preferences('assignment_quickgrade', 0);
660         /// Run some Javascript to try and update the parent page
661         $output .= '<script type="text/javascript">'."\n<!--\n";
662         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
663             if ($quickgrade){
664                 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
665                 .trim($submission->submissioncomment).'";'."\n";
666              } else {
667                 $output.= 'opener.document.getElementById("com'.$submission->userid.
668                 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
669             }
670         }
672         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
673             //echo optional_param('menuindex');
674             if ($quickgrade){
675                 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
676                 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
677             } else {
678                 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
679                 $this->display_grade($submission->grade)."\";\n";
680             }
681         }
682         //need to add student's assignments in there too.
683         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
684             $submission->timemodified) {
685             $output.= 'opener.document.getElementById("ts'.$submission->userid.
686                  '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
687         }
689         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
690             $submission->timemarked) {
691             $output.= 'opener.document.getElementById("tt'.$submission->userid.
692                  '").innerHTML="'.userdate($submission->timemarked)."\";\n";
693         }
695         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
696             $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
697             $buttontext = get_string('update');
698             $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),
699                       'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid);
700             $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
701         }
703         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
705         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
706             $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
707             '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
708         }
710         if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
712             if (!empty($grading_info->outcomes)) {
713                 foreach($grading_info->outcomes as $n=>$outcome) {
714                     if ($outcome->grades[$submission->userid]->locked) {
715                         continue;
716                     }
718                     if ($quickgrade){
719                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
720                         '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
722                     } else {
723                         $options = make_grades_menu(-$outcome->scaleid);
724                         $options[0] = get_string('nooutcome', 'grades');
725                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
726                     }
728                 }
729             }
730         }
732         $output .= "\n-->\n</script>";
733         return $output;
734     }
736     /**
737      *  Return a grade in user-friendly form, whether it's a scale or not
738      *
739      * @param $grade
740      * @return string User-friendly representation of grade
741      */
742     function display_grade($grade) {
743         global $DB;
745         static $scalegrades = array();   // Cache scales for each assignment - they might have different scales!!
747         if ($this->assignment->grade >= 0) {    // Normal number
748             if ($grade == -1) {
749                 return '-';
750             } else {
751                 return $grade.' / '.$this->assignment->grade;
752             }
754         } else {                                // Scale
755             if (empty($scalegrades[$this->assignment->id])) {
756                 if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
757                     $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
758                 } else {
759                     return '-';
760                 }
761             }
762             if (isset($scalegrades[$this->assignment->id][$grade])) {
763                 return $scalegrades[$this->assignment->id][$grade];
764             }
765             return '-';
766         }
767     }
769     /**
770      *  Display a single submission, ready for grading on a popup window
771      *
772      * This default method prints the teacher info and submissioncomment box at the top and
773      * the student info and submission at the bottom.
774      * This method also fetches the necessary data in order to be able to
775      * provide a "Next submission" button.
776      * Calls preprocess_submission() to give assignment type plug-ins a chance
777      * to process submissions before they are graded
778      * This method gets its arguments from the page parameters userid and offset
779      */
780     function display_submission($extra_javascript = '') {
781         global $CFG, $DB;
782         require_once($CFG->libdir.'/gradelib.php');
783         require_once($CFG->libdir.'/tablelib.php');
785         $userid = required_param('userid', PARAM_INT);
786         $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
788         if (!$user = $DB->get_record('user', array('id'=>$userid))) {
789             print_error('nousers');
790         }
792         if (!$submission = $this->get_submission($user->id)) {
793             $submission = $this->prepare_new_submission($userid);
794         }
795         if ($submission->timemodified > $submission->timemarked) {
796             $subtype = 'assignmentnew';
797         } else {
798             $subtype = 'assignmentold';
799         }
801         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
802         $disabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
804     /// construct SQL, using current offset to find the data of the next student
805         $course     = $this->course;
806         $assignment = $this->assignment;
807         $cm         = $this->cm;
808         $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
810         /// Get all ppl that can submit assignments
812         $currentgroup = groups_get_activity_group($cm);
813         if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) {
814             $users = array_keys($users);
815         }
817         // if groupmembersonly used, remove users who are not in any group
818         if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
819             if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
820                 $users = array_intersect($users, array_keys($groupingusers));
821             }
822         }
824         $nextid = 0;
826         if ($users) {
827             $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt,
828                               s.id AS submissionid, s.grade, s.submissioncomment,
829                               s.timemodified, s.timemarked,
830                               COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
831             $sql = 'FROM {user} u '.
832                    'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
833                                                                       AND s.assignment = '.$this->assignment->id.' '.
834                    'WHERE u.id IN ('.implode(',', $users).') ';
836             if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) {
837                 $sort = 'ORDER BY '.$sort.' ';
838             }
840             if (($auser = $DB->get_records_sql($select.$sql.$sort, null, $offset+1, 1)) !== false) {
841                 $nextuser = array_shift($auser);
842             /// Calculate user status
843                 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified);
844                 $nextid = $nextuser->id;
845             }
846         }
848         print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name));
850         /// Print any extra javascript needed for saveandnext
851         echo $extra_javascript;
853         ///SOme javascript to help with setting up >.>
855         echo '<script type="text/javascript">'."\n";
856         echo 'function setNext(){'."\n";
857         echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
858         echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
859         echo '}'."\n";
861         echo 'function saveNext(){'."\n";
862         echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
863         echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
864         echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
865         echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
866         echo '}'."\n";
868         echo '</script>'."\n";
869         echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
871         ///Start of teacher info row
873         echo '<tr>';
874         echo '<td class="picture teacher">';
875         if ($submission->teacher) {
876             $teacher = $DB->get_record('user', array('id'=>$submission->teacher));
877         } else {
878             global $USER;
879             $teacher = $USER;
880         }
881         print_user_picture($teacher, $this->course->id, $teacher->picture);
882         echo '</td>';
883         echo '<td class="content">';
884         echo '<form id="submitform" action="submissions.php" method="post">';
885         echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
886         echo '<input type="hidden" name="offset" value="'.($offset+1).'" />';
887         echo '<input type="hidden" name="userid" value="'.$userid.'" />';
888         echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
889         echo '<input type="hidden" name="mode" value="grade" />';
890         echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
892         //new hidden field, initialized to -1.
893         echo '<input type="hidden" name="saveuserid" value="-1" />';
895         if ($submission->timemarked) {
896             echo '<div class="from">';
897             echo '<div class="fullname">'.fullname($teacher, true).'</div>';
898             echo '<div class="time">'.userdate($submission->timemarked).'</div>';
899             echo '</div>';
900         }
901         echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
902         choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1, false, $disabled);
903         echo '</div>';
905         echo '<div class="clearer"></div>';
906         echo '<div class="finalgrade">'.get_string('finalgrade', 'grades').': '.$grading_info->items[0]->grades[$userid]->str_grade.'</div>';
907         echo '<div class="clearer"></div>';
909         if (!empty($CFG->enableoutcomes)) {
910             foreach($grading_info->outcomes as $n=>$outcome) {
911                 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$outcome->name.'</label> ';
912                 $options = make_grades_menu(-$outcome->scaleid);
913                 if ($outcome->grades[$submission->userid]->locked) {
914                     $options[0] = get_string('nooutcome', 'grades');
915                     echo $options[$outcome->grades[$submission->userid]->grade];
916                 } else {
917                     choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $outcome->grades[$submission->userid]->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
918                 }
919                 echo '</div>';
920                 echo '<div class="clearer"></div>';
921             }
922         }
925         $this->preprocess_submission($submission);
927         if ($disabled) {
928             echo '<div class="disabledfeedback">'.$grading_info->items[0]->grades[$userid]->str_feedback.'</div>';
930         } else {
931             print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id);
932             if ($this->usehtmleditor) {
933                 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
934             } else {
935                 echo '<div class="format">';
936                 choose_from_menu(format_text_menu(), "format", $submission->format, "");
937                 helpbutton("textformat", get_string("helpformatting"));
938                 echo '</div>';
939             }
940         }
942         $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : '';
944         ///Print Buttons in Single View
945         echo '<input type="hidden" name="mailinfo" value="0" />';
946         echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' /><label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>';
947         echo '<div class="buttons">';
948         echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
949         echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
950         //if there are more to be graded.
951         if ($nextid) {
952             echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
953             echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
954         }
955         echo '</div>';
956         echo '</div></form>';
958         $customfeedback = $this->custom_feedbackform($submission, true);
959         if (!empty($customfeedback)) {
960             echo $customfeedback;
961         }
963         echo '</td></tr>';
965         ///End of teacher info row, Start of student info row
966         echo '<tr>';
967         echo '<td class="picture user">';
968         print_user_picture($user, $this->course->id, $user->picture);
969         echo '</td>';
970         echo '<td class="topic">';
971         echo '<div class="from">';
972         echo '<div class="fullname">'.fullname($user, true).'</div>';
973         if ($submission->timemodified) {
974             echo '<div class="time">'.userdate($submission->timemodified).
975                                      $this->display_lateness($submission->timemodified).'</div>';
976         }
977         echo '</div>';
978         $this->print_user_files($user->id);
979         echo '</td>';
980         echo '</tr>';
982         ///End of student info row
984         echo '</table>';
986         if (!$disabled and $this->usehtmleditor) {
987             use_html_editor();
988         }
990         print_footer('none');
991     }
993     /**
994      *  Preprocess submission before grading
995      *
996      * Called by display_submission()
997      * The default type does nothing here.
998      * @param $submission object The submission object
999      */
1000     function preprocess_submission(&$submission) {
1001     }
1003     /**
1004      *  Display all the submissions ready for grading
1005      */
1006     function display_submissions($message='') {
1007         global $CFG, $DB, $USER, $DB;
1008         require_once($CFG->libdir.'/gradelib.php');
1010         /* first we check to see if the form has just been submitted
1011          * to request user_preference updates
1012          */
1014         if (isset($_POST['updatepref'])){
1015             $perpage = optional_param('perpage', 10, PARAM_INT);
1016             $perpage = ($perpage <= 0) ? 10 : $perpage ;
1017             set_user_preference('assignment_perpage', $perpage);
1018             set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
1019         }
1021         /* next we get perpage and quickgrade (allow quick grade) params
1022          * from database
1023          */
1024         $perpage    = get_user_preferences('assignment_perpage', 10);
1026         $quickgrade = get_user_preferences('assignment_quickgrade', 0);
1028         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
1030         if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
1031             $uses_outcomes = true;
1032         } else {
1033             $uses_outcomes = false;
1034         }
1036         $page    = optional_param('page', 0, PARAM_INT);
1037         $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1039     /// Some shortcuts to make the code read better
1041         $course     = $this->course;
1042         $assignment = $this->assignment;
1043         $cm         = $this->cm;
1045         $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1047         add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
1049         $navigation = build_navigation($this->strsubmissions, $this->cm);
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         $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
1054         if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
1055             echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">'
1056                 . get_string('seeallcoursegrades', 'grades') . '</a></div>';
1057         }
1059         if (!empty($message)) {
1060             echo $message;   // display messages here if any
1061         }
1063         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1065     /// Check to see if groups are being used in this assignment
1067         /// find out current groups mode
1068         $groupmode = groups_get_activity_groupmode($cm);
1069         $currentgroup = groups_get_activity_group($cm, true);
1070         groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm->id);
1072         /// Get all ppl that are allowed to submit assignments
1073         if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) {
1074             $users = array_keys($users);
1075         }
1077         // if groupmembersonly used, remove users who are not in any group
1078         if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
1079             if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
1080                 $users = array_intersect($users, array_keys($groupingusers));
1081             }
1082         }
1084         $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade');
1085         if ($uses_outcomes) {
1086             $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
1087         }
1089         $tableheaders = array('',
1090                               get_string('fullname'),
1091                               get_string('grade'),
1092                               get_string('comment', 'assignment'),
1093                               get_string('lastmodified').' ('.$course->student.')',
1094                               get_string('lastmodified').' ('.$course->teacher.')',
1095                               get_string('status'),
1096                               get_string('finalgrade', 'grades'));
1097         if ($uses_outcomes) {
1098             $tableheaders[] = get_string('outcome', 'grades');
1099         }
1101         require_once($CFG->libdir.'/tablelib.php');
1102         $table = new flexible_table('mod-assignment-submissions');
1104         $table->define_columns($tablecolumns);
1105         $table->define_headers($tableheaders);
1106         $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
1108         $table->sortable(true, 'lastname');//sorted by lastname by default
1109         $table->collapsible(true);
1110         $table->initialbars(true);
1112         $table->column_suppress('picture');
1113         $table->column_suppress('fullname');
1115         $table->column_class('picture', 'picture');
1116         $table->column_class('fullname', 'fullname');
1117         $table->column_class('grade', 'grade');
1118         $table->column_class('submissioncomment', 'comment');
1119         $table->column_class('timemodified', 'timemodified');
1120         $table->column_class('timemarked', 'timemarked');
1121         $table->column_class('status', 'status');
1122         $table->column_class('finalgrade', 'finalgrade');
1123         if ($uses_outcomes) {
1124             $table->column_class('outcome', 'outcome');
1125         }
1127         $table->set_attribute('cellspacing', '0');
1128         $table->set_attribute('id', 'attempts');
1129         $table->set_attribute('class', 'submissions');
1130         $table->set_attribute('width', '100%');
1131         //$table->set_attribute('align', 'center');
1133         $table->no_sorting('finalgrade');
1134         $table->no_sorting('outcome');
1136         // Start working -- this is necessary as soon as the niceties are over
1137         $table->setup();
1139         if (empty($users)) {
1140             print_heading(get_string('nosubmitusers','assignment'));
1141             return true;
1142         }
1144     /// Construct the SQL
1146         if ($where = $table->get_sql_where()) {
1147             $where .= ' AND ';
1148         }
1150         if ($sort = $table->get_sql_sort()) {
1151             $sort = ' ORDER BY '.$sort;
1152         }
1154         $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt,
1155                           s.id AS submissionid, s.grade, s.submissioncomment,
1156                           s.timemodified, s.timemarked,
1157                           COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
1158         $sql = 'FROM {user} u '.
1159                'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
1160                                                                   AND s.assignment = '.$this->assignment->id.' '.
1161                'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
1163         $table->pagesize($perpage, count($users));
1165         ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1166         $offset = $page * $perpage;
1168         $strupdate = get_string('update');
1169         $strgrade  = get_string('grade');
1170         $grademenu = make_grades_menu($this->assignment->grade);
1172         if (($ausers = $DB->get_records_sql($select.$sql.$sort, null, $table->get_page_start(), $table->get_page_size())) !== false) {
1173             $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
1174             foreach ($ausers as $auser) {
1175                 $final_grade = $grading_info->items[0]->grades[$auser->id];
1176                 $grademax = $grading_info->items[0]->grademax;
1177                 $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
1178                 $locked_overridden = 'locked';
1179                 if ($final_grade->overridden) {
1180                     $locked_overridden = 'overridden';
1181                 }
1183             /// Calculate user status
1184                 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
1185                 $picture = print_user_picture($auser, $course->id, $auser->picture, false, true);
1187                 if (empty($auser->submissionid)) {
1188                     $auser->grade = -1; //no submission yet
1189                 }
1191                 if (!empty($auser->submissionid)) {
1192                 ///Prints student answer and student modified date
1193                 ///attach file or print link to student answer, depending on the type of the assignment.
1194                 ///Refer to print_student_answer in inherited classes.
1195                     if ($auser->timemodified > 0) {
1196                         $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
1197                                          . userdate($auser->timemodified).'</div>';
1198                     } else {
1199                         $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1200                     }
1201                 ///Print grade, dropdown or text
1202                     if ($auser->timemarked > 0) {
1203                         $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
1205                         if ($final_grade->locked or $final_grade->overridden) {
1206                             $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1207                         } else if ($quickgrade) {
1208                             $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1209                                                      'menu['.$auser->id.']', $auser->grade,
1210                                                      get_string('nograde'),'',-1,true,false,$tabindex++);
1211                             $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
1212                         } else {
1213                             $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1214                         }
1216                     } else {
1217                         $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1218                         if ($final_grade->locked or $final_grade->overridden) {
1219                             $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1220                         } else if ($quickgrade) {
1221                             $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1222                                                      'menu['.$auser->id.']', $auser->grade,
1223                                                      get_string('nograde'),'',-1,true,false,$tabindex++);
1224                             $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1225                         } else {
1226                             $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1227                         }
1228                     }
1229                 ///Print Comment
1230                     if ($final_grade->locked or $final_grade->overridden) {
1231                         $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
1233                     } else if ($quickgrade) {
1234                         $comment = '<div id="com'.$auser->id.'">'
1235                                  . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1236                                  . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1237                     } else {
1238                         $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
1239                     }
1240                 } else {
1241                     $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1242                     $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1243                     $status          = '<div id="st'.$auser->id.'">&nbsp;</div>';
1245                     if ($final_grade->locked or $final_grade->overridden) {
1246                         $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
1247                     } else if ($quickgrade) {   // allow editing
1248                         $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1249                                                  'menu['.$auser->id.']', $auser->grade,
1250                                                  get_string('nograde'),'',-1,true,false,$tabindex++);
1251                         $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1252                     } else {
1253                         $grade = '<div id="g'.$auser->id.'">-</div>';
1254                     }
1256                     if ($final_grade->locked or $final_grade->overridden) {
1257                         $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
1258                     } else if ($quickgrade) {
1259                         $comment = '<div id="com'.$auser->id.'">'
1260                                  . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1261                                  . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1262                     } else {
1263                         $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1264                     }
1265                 }
1267                 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
1268                     $auser->status = 0;
1269                 } else {
1270                     $auser->status = 1;
1271                 }
1273                 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
1275                 ///No more buttons, we use popups ;-).
1276                 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1277                            . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;offset='.$offset++;
1278                 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780,
1279                                                 $buttontext, 'none', true, 'button'.$auser->id);
1281                 $status  = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
1283                 $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
1285                 $outcomes = '';
1287                 if ($uses_outcomes) {
1289                     foreach($grading_info->outcomes as $n=>$outcome) {
1290                         $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
1291                         $options = make_grades_menu(-$outcome->scaleid);
1293                         if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
1294                             $options[0] = get_string('nooutcome', 'grades');
1295                             $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
1296                         } else {
1297                             $outcomes .= ' ';
1298                             $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']',
1299                                         $outcome->grades[$auser->id]->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id);
1300                         }
1301                         $outcomes .= '</div>';
1302                     }
1303                 }
1305                                 $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser) . '</a>';
1306                 $row = array($picture, $userlink, $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
1307                 if ($uses_outcomes) {
1308                     $row[] = $outcomes;
1309                 }
1311                 $table->add_data($row);
1312             }
1313         }
1315         /// Print quickgrade form around the table
1316         if ($quickgrade){
1317             echo '<form action="submissions.php" id="fastg" method="post">';
1318             echo '<div>';
1319             echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
1320             echo '<input type="hidden" name="mode" value="fastgrade" />';
1321             echo '<input type="hidden" name="page" value="'.$page.'" />';
1322             echo '</div>';
1323         }
1325         $table->print_html();  /// Print the whole table
1327         if ($quickgrade){
1328             $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : '';
1329             echo '<div class="fgcontrols">';
1330             echo '<div class="emailnotification">';
1331             echo '<label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>';
1332             echo '<input type="hidden" name="mailinfo" value="0" />';
1333             echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' />';
1334             helpbutton('emailnotification', get_string('enableemailnotification', 'assignment'), 'assignment').'</p></div>';
1335             echo '</div>';
1336             echo '<div class="fastgbutton"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1337             echo '</div>';
1338             echo '</form>';
1339         }
1340         /// End of fast grading form
1342         /// Mini form for setting user preference
1343         echo '<div class="qgprefs">';
1344         echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post"><div>';
1345         echo '<input type="hidden" name="updatepref" value="1" />';
1346         echo '<table id="optiontable">';
1347         echo '<tr><td>';
1348         echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1349         echo '</td>';
1350         echo '<td>';
1351         echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1352         helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1353         echo '</td></tr>';
1354         echo '<tr><td>';
1355         echo '<label for="quickgrade">'.get_string('quickgrade','assignment').'</label>';
1356         echo '</td>';
1357         echo '<td>';
1358         $checked = $quickgrade ? 'checked="checked"' : '';
1359         echo '<input type="checkbox" id="quickgrade" name="quickgrade" value="1" '.$checked.' />';
1360         helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1361         echo '</td></tr>';
1362         echo '<tr><td colspan="2">';
1363         echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1364         echo '</td></tr></table>';
1365         echo '</div></form></div>';
1366         ///End of mini form
1367         print_footer($this->course);
1368     }
1370     /**
1371      *  Process teacher feedback submission
1372      *
1373      * This is called by submissions() when a grading even has taken place.
1374      * It gets its data from the submitted form.
1375      * @return object The updated submission object
1376      */
1377     function process_feedback() {
1378         global $CFG, $USER;
1379         require_once($CFG->libdir.'/gradelib.php');
1381         if (!$feedback = data_submitted()) {      // No incoming data?
1382             return false;
1383         }
1385         ///For save and next, we need to know the userid to save, and the userid to go
1386         ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1387         ///as the userid to store
1388         if ((int)$feedback->saveuserid !== -1){
1389             $feedback->userid = $feedback->saveuserid;
1390         }
1392         if (!empty($feedback->cancel)) {          // User hit cancel button
1393             return false;
1394         }
1396         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
1398         // store outcomes if needed
1399         $this->process_outcomes($feedback->userid);
1401         $submission = $this->get_submission($feedback->userid, true);  // Get or make one
1403         if (!$grading_info->items[0]->grades[$feedback->userid]->locked and
1404             !$grading_info->items[0]->grades[$feedback->userid]->overridden) {
1406             $submission->grade      = $feedback->grade;
1407             $submission->submissioncomment    = $feedback->submissioncomment;
1408             $submission->format     = $feedback->format;
1409             $submission->teacher    = $USER->id;
1410             $mailinfo = get_user_preferences('assignment_mailinfo', 0);
1411             if (!$mailinfo) {
1412                 $submission->mailed = 1;       // treat as already mailed
1413             } else {
1414                 $submission->mailed = 0;       // Make sure mail goes out (again, even)
1415             }
1416             $submission->timemarked = time();
1418             unset($submission->data1);  // Don't need to update this.
1419             unset($submission->data2);  // Don't need to update this.
1421             if (empty($submission->timemodified)) {   // eg for offline assignments
1422                 // $submission->timemodified = time();
1423             }
1425             if (! $DB->update_record('assignment_submissions', $submission)) {
1426                 return false;
1427             }
1429             // triger grade event
1430             $this->update_grade($submission);
1432             add_to_log($this->course->id, 'assignment', 'update grades',
1433                        'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1434         }
1436         return $submission;
1438     }
1440     function process_outcomes($userid) {
1441         global $CFG, $USER;
1443         if (empty($CFG->enableoutcomes)) {
1444             return;
1445         }
1447         require_once($CFG->libdir.'/gradelib.php');
1449         if (!$formdata = data_submitted()) {
1450             return;
1451         }
1453         $data = array();
1454         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
1456         if (!empty($grading_info->outcomes)) {
1457             foreach($grading_info->outcomes as $n=>$old) {
1458                 $name = 'outcome_'.$n;
1459                 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
1460                     $data[$n] = $formdata->{$name}[$userid];
1461                 }
1462             }
1463         }
1464         if (count($data) > 0) {
1465             grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1466         }
1468     }
1470     /**
1471      * Load the submission object for a particular user
1472      *
1473      * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1474      * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1475      * @param bool $teachermodified student submission set if false
1476      * @return object The submission
1477      */
1478     function get_submission($userid=0, $createnew=false, $teachermodified=false) {
1479         global $USER, $DB;
1481         if (empty($userid)) {
1482             $userid = $USER->id;
1483         }
1485         $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1487         if ($submission || !$createnew) {
1488             return $submission;
1489         }
1490         $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
1491         if (!$DB->insert_record("assignment_submissions", $newsubmission)) {
1492             print_error('cannotinsertempty', 'assignment');
1493         }
1495         return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1496     }
1498     /**
1499      * Instantiates a new submission object for a given user
1500      *
1501      * Sets the assignment, userid and times, everything else is set to default values.
1502      * @param $userid int The userid for which we want a submission object
1503      * @param bool $teachermodified student submission set if false
1504      * @return object The submission
1505      */
1506     function prepare_new_submission($userid, $teachermodified=false) {
1507         $submission = new Object;
1508         $submission->assignment   = $this->assignment->id;
1509         $submission->userid       = $userid;
1510         //$submission->timecreated  = time();
1511         $submission->timecreated = '';
1512         // teachers should not be modifying modified date, except offline assignments
1513         if ($teachermodified) {
1514             $submission->timemodified = 0;
1515         } else {
1516             $submission->timemodified = $submission->timecreated;
1517         }
1518         $submission->numfiles     = 0;
1519         $submission->data1        = '';
1520         $submission->data2        = '';
1521         $submission->grade        = -1;
1522         $submission->submissioncomment      = '';
1523         $submission->format       = 0;
1524         $submission->teacher      = 0;
1525         $submission->timemarked   = 0;
1526         $submission->mailed       = 0;
1527         return $submission;
1528     }
1530     /**
1531      * Return all assignment submissions by ENROLLED students (even empty)
1532      *
1533      * @param $sort string optional field names for the ORDER BY in the sql query
1534      * @param $dir string optional specifying the sort direction, defaults to DESC
1535      * @return array The submission objects indexed by id
1536      */
1537     function get_submissions($sort='', $dir='DESC') {
1538         return assignment_get_all_submissions($this->assignment, $sort, $dir);
1539     }
1541     /**
1542      * Counts all real assignment submissions by ENROLLED students (not empty ones)
1543      *
1544      * @param $groupid int optional If nonzero then count is restricted to this group
1545      * @return int The number of submissions
1546      */
1547     function count_real_submissions($groupid=0) {
1548         return assignment_count_real_submissions($this->cm, $groupid);
1549     }
1551     /**
1552      * Alerts teachers by email of new or changed assignments that need grading
1553      *
1554      * First checks whether the option to email teachers is set for this assignment.
1555      * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1556      * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1557      * @param $submission object The submission that has changed
1558      */
1559     function email_teachers($submission) {
1560         global $CFG, $DB;
1562         if (empty($this->assignment->emailteachers)) {          // No need to do anything
1563             return;
1564         }
1566         $user = $DB->get_record('user', array('id'=>$submission->userid));
1568         if ($teachers = $this->get_graders($user)) {
1570             $strassignments = get_string('modulenameplural', 'assignment');
1571             $strassignment  = get_string('modulename', 'assignment');
1572             $strsubmitted  = get_string('submitted', 'assignment');
1574             foreach ($teachers as $teacher) {
1575                 $info = new object();
1576                 $info->username = fullname($user, true);
1577                 $info->assignment = format_string($this->assignment->name,true);
1578                 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1580                 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1581                 $posttext = $this->email_teachers_text($info);
1582                 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
1584                 $eventdata = new object();
1585                 $eventdata->modulename       = 'assignment';
1586                 $eventdata->userfrom         = $user;
1587                 $eventdata->userto           = $teacher;
1588                 $eventdata->subject          = $postsubject;
1589                 $eventdata->fullmessage      = $posttext;
1590                 $eventdata->fullmessageformat = FORMAT_PLAIN;
1591                 $eventdata->fullmessagehtml  = $posthtml;
1592                 $eventdata->smallmessage     = '';
1593                 if ( events_trigger('message_send', $eventdata) > 0 ){
1594                 }
1596                 /*
1597                 @email_to_user($teacher, $user, $postsubject, $posttext, $posthtml);  // If it fails, oh well, too bad.
1598                 */
1599             }
1600         }
1601     }
1603     /**
1604      * Returns a list of teachers that should be grading given submission
1605      */
1606     function get_graders($user) {
1607         //potential graders
1608         $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1610         $graders = array();
1611         if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) {   // Separate groups are being used
1612             if ($groups = groups_get_all_groups($this->course->id, $user->id)) {  // Try to find all groups
1613                 foreach ($groups as $group) {
1614                     foreach ($potgraders as $t) {
1615                         if ($t->id == $user->id) {
1616                             continue; // do not send self
1617                         }
1618                         if (groups_is_member($group->id, $t->id)) {
1619                             $graders[$t->id] = $t;
1620                         }
1621                     }
1622                 }
1623             } else {
1624                 // user not in group, try to find graders without group
1625                 foreach ($potgraders as $t) {
1626                     if ($t->id == $user->id) {
1627                         continue; // do not send self
1628                     }
1629                     if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
1630                         $graders[$t->id] = $t;
1631                     }
1632                 }
1633             }
1634         } else {
1635             foreach ($potgraders as $t) {
1636                 if ($t->id == $user->id) {
1637                     continue; // do not send self
1638                 }
1639                 $graders[$t->id] = $t;
1640             }
1641         }
1642         return $graders;
1643     }
1645     /**
1646      * Creates the text content for emails to teachers
1647      *
1648      * @param $info object The info used by the 'emailteachermail' language string
1649      * @return string
1650      */
1651     function email_teachers_text($info) {
1652         $posttext  = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '.
1653                      format_string($this->assignment->name)."\n";
1654         $posttext .= '---------------------------------------------------------------------'."\n";
1655         $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
1656         $posttext .= "\n---------------------------------------------------------------------\n";
1657         return $posttext;
1658     }
1660      /**
1661      * Creates the html content for emails to teachers
1662      *
1663      * @param $info object The info used by the 'emailteachermailhtml' language string
1664      * @return string
1665      */
1666     function email_teachers_html($info) {
1667         global $CFG;
1668         $posthtml  = '<p><font face="sans-serif">'.
1669                      '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'.
1670                      '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
1671                      '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>';
1672         $posthtml .= '<hr /><font face="sans-serif">';
1673         $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1674         $posthtml .= '</font><hr />';
1675         return $posthtml;
1676     }
1678     /**
1679      * Produces a list of links to the files uploaded by a user
1680      *
1681      * @param $userid int optional id of the user. If 0 then $USER->id is used.
1682      * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1683      * @return string optional
1684      */
1685     function print_user_files($userid=0, $return=false) {
1686         global $CFG, $USER;
1688         if (!$userid) {
1689             if (!isloggedin()) {
1690                 return '';
1691             }
1692             $userid = $USER->id;
1693         }
1695         $filearea = $this->file_area_name($userid);
1697         $output = '';
1699         if ($basedir = $this->file_area($userid)) {
1700             if ($files = get_directory_list($basedir)) {
1701                 require_once($CFG->libdir.'/filelib.php');
1702                 $p = array(
1703                     'userid' => $userid,
1704                     'assignmentid' => $this->cm->id,
1705                 );
1706                 foreach ($files as $key => $file) {
1708                     $icon = mimeinfo('icon', $file);
1709                     $ffurl = get_file_url("$filearea/$file", array('forcedownload'=>1));
1711                     $output .= '<img src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.
1712                             '<a href="'.$ffurl.'" >'.$file.'</a>';
1713                     if ($this->portfolio_exportable() && true) { // @todo replace with capability check
1714                         $p['file'] = $file;
1715                         $output .= portfolio_add_button('assignment_portfolio_caller', $p, null, false, true);
1716                     }
1717                     $output .= '<br />';
1718                 }
1719                 if ($this->portfolio_exportable() && true) { //@todo replace with check capability
1720                     unset($p['file']);// for all files
1721                     $output .= '<br />' . portfolio_add_button('assignment_portfolio_caller', $p, null, true, true);
1722                 }
1723             }
1724         }
1726         $output = '<div class="files">'.$output.'</div>';
1728         if ($return) {
1729             return $output;
1730         }
1731         echo $output;
1732     }
1734     /**
1735      * Count the files uploaded by a given user
1736      *
1737      * @param $userid int The user id
1738      * @return int
1739      */
1740     function count_user_files($userid) {
1741         global $CFG;
1743         $filearea = $this->file_area_name($userid);
1745         if ( is_dir($CFG->dataroot.'/'.$filearea) && $basedir = $this->file_area($userid)) {
1746             if ($files = get_directory_list($basedir)) {
1747                 return count($files);
1748             }
1749         }
1750         return 0;
1751     }
1753     /**
1754      * Creates a directory file name, suitable for make_upload_directory()
1755      *
1756      * @param $userid int The user id
1757      * @return string path to file area
1758      */
1759     function file_area_name($userid) {
1760         global $CFG;
1762         return $this->course->id.'/'.$CFG->moddata.'/assignment/'.$this->assignment->id.'/'.$userid;
1763     }
1765     /**
1766      * Makes an upload directory
1767      *
1768      * @param $userid int The user id
1769      * @return string path to file area.
1770      */
1771     function file_area($userid) {
1772         return make_upload_directory( $this->file_area_name($userid) );
1773     }
1775     /**
1776      * Returns true if the student is allowed to submit
1777      *
1778      * Checks that the assignment has started and, if the option to prevent late
1779      * submissions is set, also checks that the assignment has not yet closed.
1780      * @return boolean
1781      */
1782     function isopen() {
1783         $time = time();
1784         if ($this->assignment->preventlate && $this->assignment->timedue) {
1785             return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
1786         } else {
1787             return ($this->assignment->timeavailable <= $time);
1788         }
1789     }
1792     /**
1793      * Return true if is set description is hidden till available date
1794      *
1795      * This is needed by calendar so that hidden descriptions do not
1796      * come up in upcoming events.
1797      *
1798      * Check that description is hidden till available date
1799      * By default return false
1800      * Assignments types should implement this method if needed
1801      * @return boolen
1802      */
1803     function description_is_hidden() {
1804         return false;
1805     }
1807     /**
1808      * Return an outline of the user's interaction with the assignment
1809      *
1810      * The default method prints the grade and timemodified
1811      * @param $user object
1812      * @return object with properties ->info and ->time
1813      */
1814     function user_outline($user) {
1815         if ($submission = $this->get_submission($user->id)) {
1817             $result = new object();
1818             $result->info = get_string('grade').': '.$this->display_grade($submission->grade);
1819             $result->time = $submission->timemodified;
1820             return $result;
1821         }
1822         return NULL;
1823     }
1825     /**
1826      * Print complete information about the user's interaction with the assignment
1827      *
1828      * @param $user object
1829      */
1830     function user_complete($user) {
1831         if ($submission = $this->get_submission($user->id)) {
1832             if ($basedir = $this->file_area($user->id)) {
1833                 if ($files = get_directory_list($basedir)) {
1834                     $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1835                     foreach ($files as $file) {
1836                         $countfiles .= "; $file";
1837                     }
1838                 }
1839             }
1841             print_simple_box_start();
1842             echo get_string("lastmodified").": ";
1843             echo userdate($submission->timemodified);
1844             echo $this->display_lateness($submission->timemodified);
1846             $this->print_user_files($user->id);
1848             echo '<br />';
1850             if (empty($submission->timemarked)) {
1851                 print_string("notgradedyet", "assignment");
1852             } else {
1853                 $this->view_feedback($submission);
1854             }
1856             print_simple_box_end();
1858         } else {
1859             print_string("notsubmittedyet", "assignment");
1860         }
1861     }
1863     /**
1864      * Return a string indicating how late a submission is
1865      *
1866      * @param $timesubmitted int
1867      * @return string
1868      */
1869     function display_lateness($timesubmitted) {
1870         return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
1871     }
1873     /**
1874      * Empty method stub for all delete actions.
1875      */
1876     function delete() {
1877         //nothing by default
1878         redirect('view.php?id='.$this->cm->id);
1879     }
1881     /**
1882      * Empty custom feedback grading form.
1883      */
1884     function custom_feedbackform($submission, $return=false) {
1885         //nothing by default
1886         return '';
1887     }
1889     /**
1890      * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1891      * for the course (see resource).
1892      *
1893      * Given a course_module object, this function returns any "extra" information that may be needed
1894      * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
1895      *
1896      * @param $coursemodule object The coursemodule object (record).
1897      * @return object An object on information that the coures will know about (most noticeably, an icon).
1898      *
1899      */
1900     function get_coursemodule_info($coursemodule) {
1901         return false;
1902     }
1904     /**
1905      * Plugin cron method - do not use $this here, create new assignment instances if needed.
1906      * @return void
1907      */
1908     function cron() {
1909         //no plugin cron by default - override if needed
1910     }
1912     /**
1913      * Reset all submissions
1914      */
1915     function reset_userdata($data) {
1916         global $CFG, $DB;
1917         require_once($CFG->libdir.'/filelib.php');
1919         if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
1920             return array(); // no assignments of this type present
1921         }
1923         $componentstr = get_string('modulenameplural', 'assignment');
1924         $status = array();
1926         $typestr = get_string('type'.$this->type, 'assignment');
1928         if (!empty($data->reset_assignment_submissions)) {
1929             $assignmentssql = "SELECT a.id
1930                                  FROM {assignment} a
1931                                 WHERE a.course=? AND a.assignmenttype=?";
1932             $params = array($data->courseid, $this->type);
1934             $DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
1936             if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
1937                 foreach ($assignments as $assignmentid=>$unused) {
1938                     fulldelete($CFG->dataroot.'/'.$data->courseid.'/moddata/assignment/'.$assignmentid);
1939                 }
1940             }
1942             $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
1944             if (empty($data->reset_gradebook_grades)) {
1945                 // remove all grades from gradebook
1946                 assignment_reset_gradebook($data->courseid, $this->type);
1947             }
1948         }
1950         /// updating dates - shift may be negative too
1951         if ($data->timeshift) {
1952             shift_course_mod_dates('assignment', array('timedue', 'timeavailable'), $data->timeshift, $data->courseid);
1953             $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged').': '.$typestr, 'error'=>false);
1954         }
1956         return $status;
1957     }
1960     function portfolio_exportable() {
1961         return false;
1962     }
1963 } ////// End of the assignment_base class
1967 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1969 /** 
1970  * Code to be executed when a module is installed
1971  */ 
1972 function assignment_install() {
1973     return true; 
1976 /**
1977  * Deletes an assignment instance
1978  *
1979  * This is done by calling the delete_instance() method of the assignment type class
1980  */
1981 function assignment_delete_instance($id){
1982     global $CFG, $DB;
1984     if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
1985         return false;
1986     }
1988     // fall back to base class if plugin missing
1989     $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
1990     if (file_exists($classfile)) {
1991         require_once($classfile);
1992         $assignmentclass = "assignment_$assignment->assignmenttype";
1994     } else {
1995         debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
1996         $assignmentclass = "assignment_base";
1997     }
1999     $ass = new $assignmentclass();
2000     return $ass->delete_instance($assignment);
2004 /**
2005  * Updates an assignment instance
2006  *
2007  * This is done by calling the update_instance() method of the assignment type class
2008  */
2009 function assignment_update_instance($assignment){
2010     global $CFG;
2012     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
2014     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2015     $assignmentclass = "assignment_$assignment->assignmenttype";
2016     $ass = new $assignmentclass();
2017     return $ass->update_instance($assignment);
2021 /**
2022  * Adds an assignment instance
2023  *
2024  * This is done by calling the add_instance() method of the assignment type class
2025  */
2026 function assignment_add_instance($assignment) {
2027     global $CFG;
2029     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
2031     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2032     $assignmentclass = "assignment_$assignment->assignmenttype";
2033     $ass = new $assignmentclass();
2034     return $ass->add_instance($assignment);
2038 /**
2039  * Returns an outline of a user interaction with an assignment
2040  *
2041  * This is done by calling the user_outline() method of the assignment type class
2042  */
2043 function assignment_user_outline($course, $user, $mod, $assignment) {
2044     global $CFG;
2046     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2047     $assignmentclass = "assignment_$assignment->assignmenttype";
2048     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2049     return $ass->user_outline($user);
2052 /**
2053  * Prints the complete info about a user's interaction with an assignment
2054  *
2055  * This is done by calling the user_complete() method of the assignment type class
2056  */
2057 function assignment_user_complete($course, $user, $mod, $assignment) {
2058     global $CFG;
2060     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2061     $assignmentclass = "assignment_$assignment->assignmenttype";
2062     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2063     return $ass->user_complete($user);
2066 /**
2067  * Function to be run periodically according to the moodle cron
2068  *
2069  * Finds all assignment notifications that have yet to be mailed out, and mails them
2070  */
2071 function assignment_cron () {
2072     global $CFG, $USER, $DB;
2074     /// first execute all crons in plugins
2075     if ($plugins = get_list_of_plugins('mod/assignment/type')) {
2076         foreach ($plugins as $plugin) {
2077             require_once("$CFG->dirroot/mod/assignment/type/$plugin/assignment.class.php");
2078             $assignmentclass = "assignment_$plugin";
2079             $ass = new $assignmentclass();
2080             $ass->cron();
2081         }
2082     }
2084     /// Notices older than 1 day will not be mailed.  This is to avoid the problem where
2085     /// cron has not been running for a long time, and then suddenly people are flooded
2086     /// with mail from the past few weeks or months
2088     $timenow   = time();
2089     $endtime   = $timenow - $CFG->maxeditingtime;
2090     $starttime = $endtime - 24 * 3600;   /// One day earlier
2092     if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
2094         $realuser = clone($USER);
2096         foreach ($submissions as $key => $submission) {
2097             if (! $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id))) {
2098                 echo "Could not update the mailed field for id $submission->id.  Not mailed.\n";
2099                 unset($submissions[$key]);
2100             }
2101         }
2103         $timenow = time();
2105         foreach ($submissions as $submission) {
2107             echo "Processing assignment submission $submission->id\n";
2109             if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
2110                 echo "Could not find user $post->userid\n";
2111                 continue;
2112             }
2114             if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
2115                 echo "Could not find course $submission->course\n";
2116                 continue;
2117             }
2119             /// Override the language and timezone of the "current" user, so that
2120             /// mail is customised for the receiver.
2121             $USER = $user;
2122             course_setup($course);
2124             if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) {
2125                 echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n";
2126                 continue;
2127             }
2129             if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
2130                 echo "Could not find teacher $submission->teacher\n";
2131                 continue;
2132             }
2134             if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
2135                 echo "Could not find course module for assignment id $submission->assignment\n";
2136                 continue;
2137             }
2139             if (! $mod->visible) {    /// Hold mail notification for hidden assignments until later
2140                 continue;
2141             }
2143             $strassignments = get_string("modulenameplural", "assignment");
2144             $strassignment  = get_string("modulename", "assignment");
2146             $assignmentinfo = new object();
2147             $assignmentinfo->teacher = fullname($teacher);
2148             $assignmentinfo->assignment = format_string($submission->name,true);
2149             $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
2151             $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true);
2152             $posttext  = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n";
2153             $posttext .= "---------------------------------------------------------------------\n";
2154             $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
2155             $posttext .= "---------------------------------------------------------------------\n";
2157             if ($user->mailformat == 1) {  // HTML
2158                 $posthtml = "<p><font face=\"sans-serif\">".
2159                 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
2160                 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2161                 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
2162                 $posthtml .= "<hr /><font face=\"sans-serif\">";
2163                 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2164                 $posthtml .= "</font><hr />";
2165             } else {
2166                 $posthtml = "";
2167             }
2169             $eventdata = new object();
2170             $eventdata->modulename       = 'assignment';
2171             $eventdata->userfrom         = $teacher;
2172             $eventdata->userto           = $user;
2173             $eventdata->subject          = $postsubject;
2174             $eventdata->fullmessage      = $posttext;
2175             $eventdata->fullmessageformat = FORMAT_PLAIN;
2176             $eventdata->fullmessagehtml  = $posthtml;
2177             $eventdata->smallmessage     = '';
2178             if ( events_trigger('message_send', $eventdata) > 0 ){
2179                 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2180             }
2182             /*
2183             if (! email_to_user($user, $teacher, $postsubject, $posttext, $posthtml)) {
2184                 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2185             }
2186             */
2187         }
2189         $USER = $realuser;
2190         course_setup(SITEID); // reset cron user language, theme and timezone settings
2192     }
2194     return true;
2197 /**
2198  * Return grade for given user or all users.
2199  *
2200  * @param int $assignmentid id of assignment
2201  * @param int $userid optional user id, 0 means all users
2202  * @return array array of grades, false if none
2203  */
2204 function assignment_get_user_grades($assignment, $userid=0) {
2205     global $CFG, $DB;
2207     if ($userid) {
2208         $user = "AND u.id = :userid";
2209         $params = array('userid'=>$userid);
2210     } else {
2211         $user = "";
2212     }
2213     $params['aid'] = $assignment->id;
2215     $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
2216                    s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
2217               FROM {user} u, {assignment_submissions} s
2218              WHERE u.id = s.userid AND s.assignment = :aid
2219                    $user";
2221     return $DB->get_records_sql($sql, $params);
2224 /**
2225  * Update grades by firing grade_updated event
2226  *
2227  * @param object $assignment null means all assignments
2228  * @param int $userid specific user only, 0 mean all
2229  */
2230 function assignment_update_grades($assignment=null, $userid=0, $nullifnone=true) {
2231     global $CFG, $DB;
2232     require_once($CFG->libdir.'/gradelib.php');
2234     if ($assignment != null) {
2235         if ($grades = assignment_get_user_grades($assignment, $userid)) {
2236             foreach($grades as $k=>$v) {
2237                 if ($v->rawgrade == -1) {
2238                     $grades[$k]->rawgrade = null;
2239                 }
2240             }
2241             assignment_grade_item_update($assignment, $grades);
2242         } else {
2243             assignment_grade_item_update($assignment);
2244         }
2246     } else {
2247         $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
2248                   FROM {assignment} a, {course_modules} cm, {modules} m
2249                  WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2250         if ($rs = $DB->get_recordset_sql($sql)) {
2251             foreach ($rs as $assignment) {
2252                 if ($assignment->grade != 0) {
2253                     assignment_update_grades($assignment);
2254                 } else {
2255                     assignment_grade_item_update($assignment);
2256                 }
2257             }
2258             $rs->close();
2259         }
2260     }
2263 /**
2264  * Create grade item for given assignment
2265  *
2266  * @param object $assignment object with extra cmidnumber
2267  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
2268  * @return int 0 if ok, error code otherwise
2269  */
2270 function assignment_grade_item_update($assignment, $grades=NULL) {
2271     global $CFG;
2272     require_once($CFG->libdir.'/gradelib.php');
2274     if (!isset($assignment->courseid)) {
2275         $assignment->courseid = $assignment->course;
2276     }
2278     $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
2280     if ($assignment->grade > 0) {
2281         $params['gradetype'] = GRADE_TYPE_VALUE;
2282         $params['grademax']  = $assignment->grade;
2283         $params['grademin']  = 0;
2285     } else if ($assignment->grade < 0) {
2286         $params['gradetype'] = GRADE_TYPE_SCALE;
2287         $params['scaleid']   = -$assignment->grade;
2289     } else {
2290         $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
2291     }
2293     if ($grades  === 'reset') {
2294         $params['reset'] = true;
2295         $grades = NULL;
2296     }
2298     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
2301 /**
2302  * Delete grade item for given assignment
2303  *
2304  * @param object $assignment object
2305  * @return object assignment
2306  */
2307 function assignment_grade_item_delete($assignment) {
2308     global $CFG;
2309     require_once($CFG->libdir.'/gradelib.php');
2311     if (!isset($assignment->courseid)) {
2312         $assignment->courseid = $assignment->course;
2313     }
2315     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
2318 /**
2319  * Returns the users with data in one assignment (students and teachers)
2320  *
2321  * @param $assignmentid int
2322  * @return array of user objects
2323  */
2324 function assignment_get_participants($assignmentid) {
2325     global $CFG, $DB;
2327     //Get students
2328     $students = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
2329                                         FROM {user} u,
2330                                              {assignment_submissions} a
2331                                        WHERE a.assignment = ? and
2332                                              u.id = a.userid", array($assignmentid));
2333     //Get teachers
2334     $teachers = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
2335                                         FROM {user} u,
2336                                              {assignment_submissions} a
2337                                        WHERE a.assignment = ? and
2338                                              u.id = a.teacher", array($assignmentid));
2340     //Add teachers to students
2341     if ($teachers) {
2342         foreach ($teachers as $teacher) {
2343             $students[$teacher->id] = $teacher;
2344         }
2345     }
2346     //Return students array (it contains an array of unique users)
2347     return ($students);
2350 /**
2351  * Checks if a scale is being used by an assignment
2352  *
2353  * This is used by the backup code to decide whether to back up a scale
2354  * @param $assignmentid int
2355  * @param $scaleid int
2356  * @return boolean True if the scale is used by the assignment
2357  */
2358 function assignment_scale_used($assignmentid, $scaleid) {
2359     global $DB;
2361     $return = false;
2363     $rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
2365     if (!empty($rec) && !empty($scaleid)) {
2366         $return = true;
2367     }
2369     return $return;
2372 /**
2373  * Checks if scale is being used by any instance of assignment
2374  *
2375  * This is used to find out if scale used anywhere
2376  * @param $scaleid int
2377  * @return boolean True if the scale is used by any assignment
2378  */
2379 function assignment_scale_used_anywhere($scaleid) {
2380     global $DB;
2382     if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
2383         return true;
2384     } else {
2385         return false;
2386     }
2389 /**
2390  * Make sure up-to-date events are created for all assignment instances
2391  *
2392  * This standard function will check all instances of this module
2393  * and make sure there are up-to-date events created for each of them.
2394  * If courseid = 0, then every assignment event in the site is checked, else
2395  * only assignment events belonging to the course specified are checked.
2396  * This function is used, in its new format, by restore_refresh_events()
2397  *
2398  * @param $courseid int optional If zero then all assignments for all courses are covered
2399  * @return boolean Always returns true
2400  */
2401 function assignment_refresh_events($courseid = 0) {
2402     global $DB;
2404     if ($courseid == 0) {
2405         if (! $assignments = $DB->get_records("assignment")) {
2406             return true;
2407         }
2408     } else {
2409         if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
2410             return true;
2411         }
2412     }
2413     $moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
2415     foreach ($assignments as $assignment) {
2416         $event = NULL;
2417         $event->name        = $assignment->name;
2418         $event->description = $assignment->description;
2419         $event->timestart   = $assignment->timedue;
2421         if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
2422             update_event($event);
2424         } else {
2425             $event->courseid    = $assignment->course;
2426             $event->groupid     = 0;
2427             $event->userid      = 0;
2428             $event->modulename  = 'assignment';
2429             $event->instance    = $assignment->id;
2430             $event->eventtype   = 'due';
2431             $event->timeduration = 0;
2432             $event->visible     = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
2433             add_event($event);
2434         }
2436     }
2437     return true;
2440 /**
2441  * Print recent activity from all assignments in a given course
2442  *
2443  * This is used by the recent activity block
2444  */
2445 function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
2446     global $CFG, $USER, $DB;
2448     // do not use log table if possible, it may be huge
2450     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
2451                                                      u.firstname, u.lastname, u.email, u.picture
2452                                                 FROM {assignment_submissions} asb
2453                                                      JOIN {assignment} a      ON a.id = asb.assignment
2454                                                      JOIN {course_modules} cm ON cm.instance = a.id
2455                                                      JOIN {modules} md        ON md.id = cm.module
2456                                                      JOIN {user} u            ON u.id = asb.userid
2457                                                WHERE asb.timemodified > ? AND
2458                                                      a.course = ? AND
2459                                                      md.name = 'assignment'
2460                                             ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
2461          return false;
2462     }
2464     $modinfo =& get_fast_modinfo($course); // reference needed because we might load the groups
2465     $show    = array();
2466     $grader  = array();
2468     foreach($submissions as $submission) {
2469         if (!array_key_exists($submission->cmid, $modinfo->cms)) {
2470             continue;
2471         }
2472         $cm = $modinfo->cms[$submission->cmid];
2473         if (!$cm->uservisible) {
2474             continue;
2475         }
2476         if ($submission->userid == $USER->id) {
2477             $show[] = $submission;
2478             continue;
2479         }
2481         // the act of sumitting of assignemnt may be considered private - only graders will see it if specified
2482         if (empty($CFG->assignment_showrecentsubmissions)) {
2483             if (!array_key_exists($cm->id, $grader)) {
2484                 $grader[$cm->id] = has_capability('moodle/grade:viewall', get_context_instance(CONTEXT_MODULE, $cm->id));
2485             }
2486             if (!$grader[$cm->id]) {
2487                 continue;
2488             }
2489         }
2491         $groupmode = groups_get_activity_groupmode($cm, $course);
2493         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2494             if (isguestuser()) {
2495                 // shortcut - guest user does not belong into any group
2496                 continue;
2497             }
2499             if (is_null($modinfo->groups)) {
2500                 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
2501             }
2503             // this will be slow - show only users that share group with me in this cm
2504             if (empty($modinfo->groups[$cm->id])) {
2505                 continue;
2506             }
2507             $usersgroups =  groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
2508             if (is_array($usersgroups)) {
2509                 $usersgroups = array_keys($usersgroups);
2510                 $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
2511                 if (empty($intersect)) {
2512                     continue;
2513                 }
2514             }
2515         }
2516         $show[] = $submission;
2517     }
2519     if (empty($show)) {
2520         return false;
2521     }
2523     print_headline(get_string('newsubmissions', 'assignment').':');
2525     foreach ($show as $submission) {
2526         $cm = $modinfo->cms[$submission->cmid];
2527         $link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
2528         print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
2529     }
2531     return true;
2535 /**
2536  * Returns all assignments since a given time in specified forum.
2537  */
2538 function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0)  {
2539     global $CFG, $COURSE, $USER, $DB;
2541     if ($COURSE->id == $courseid) {
2542         $course = $COURSE;
2543     } else {
2544         $course = $DB->get_record('course', array('id'=>$courseid));
2545     }
2547     $modinfo =& get_fast_modinfo($course);
2549     $cm = $modinfo->cms[$cmid];
2551     $params = array();
2552     if ($userid) {
2553         $userselect = "AND u.id = :userid";
2554         $params['userid'] = $userid;
2555     } else {
2556         $userselect = "";
2557     }
2559     if ($groupid) {
2560         $groupselect = "AND gm.groupid = :groupid";
2561         $groupjoin   = "JOIN {groups_members} gm ON  gm.userid=u.id";
2562         $params['groupid'] = $groupid;
2563     } else {
2564         $groupselect = "";
2565         $groupjoin   = "";
2566     }
2568     $params['cminstance'] = $cm->instance;
2569     $params['timestart'] = $timestart;
2571     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, asb.userid,
2572                                                      u.firstname, u.lastname, u.email, u.picture
2573                                                 FROM {assignment_submissions} asb
2574                                                 JOIN {assignment} a      ON a.id = asb.assignment
2575                                                 JOIN {user} u            ON u.id = asb.userid
2576                                           $groupjoin
2577                                                WHERE asb.timemodified > :timestart AND a.id = :cminstance
2578                                                      $userselect $groupselect
2579                                             ORDER BY asb.timemodified ASC", $params)) {
2580          return;
2581     }
2583     $groupmode       = groups_get_activity_groupmode($cm, $course);
2584     $cm_context      = get_context_instance(CONTEXT_MODULE, $cm->id);
2585     $grader          = has_capability('moodle/grade:viewall', $cm_context);
2586     $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
2587     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cm_context);
2589     if (is_null($modinfo->groups)) {
2590         $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
2591     }
2593     $show = array();
2595     foreach($submissions as $submission) {
2596         if ($submission->userid == $USER->id) {
2597             $show[] = $submission;
2598             continue;
2599         }
2601         // the act of sumitting of assignemnt may be considered private - only graders will see it if specified
2602         if (!empty($CFG->assignment_showrecentsubmissions)) {
2603             if (!$grader) {
2604                 continue;
2605             }
2606         }
2608         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
2609             if (isguestuser()) {
2610                 // shortcut - guest user does not belong into any group
2611                 continue;
2612             }
2614             // this will be slow - show only users that share group with me in this cm
2615             if (empty($modinfo->groups[$cm->id])) {
2616                 continue;
2617             }
2618             $usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
2619             if (is_array($usersgroups)) {
2620                 $usersgroups = array_keys($usersgroups);
2621                 $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
2622                 if (empty($intersect)) {
2623                     continue;
2624                 }
2625             }
2626         }
2627         $show[] = $submission;
2628     }
2630     if (empty($show)) {
2631         return;
2632     }
2634     if ($grader) {
2635         require_once($CFG->libdir.'/gradelib.php');
2636         $userids = array();
2637         foreach ($show as $id=>$submission) {
2638             $userids[] = $submission->userid;
2640         }
2641         $grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
2642     }
2644     $aname = format_string($cm->name,true);
2645     foreach ($show as $submission) {
2646         $tmpactivity = new object();
2648         $tmpactivity->type         = 'assignment';
2649         $tmpactivity->cmid         = $cm->id;
2650         $tmpactivity->name         = $aname;
2651         $tmpactivity->sectionnum   = $cm->sectionnum;
2652         $tmpactivity->timestamp    = $submission->timemodified;
2654         if ($grader) {
2655             $tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
2656         }
2658         $tmpactivity->user->userid   = $submission->userid;
2659         $tmpactivity->user->fullname = fullname($submission, $viewfullnames);
2660         $tmpactivity->user->picture  = $submission->picture;
2662         $activities[$index++] = $tmpactivity;
2663     }
2665     return;
2668 /**
2669  * Print recent activity from all assignments in a given course
2670  *
2671  * This is used by course/recent.php
2672  */
2673 function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames)  {
2674     global $CFG;
2676     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
2678     echo "<tr><td class=\"userpicture\" valign=\"top\">";
2679     print_user_picture($activity->user->userid, $courseid, $activity->user->picture);
2680     echo "</td><td>";
2682     if ($detail) {
2683         $modname = $modnames[$activity->type];
2684         echo '<div class="title">';
2685         echo "<img src=\"$CFG->modpixpath/assignment/icon.gif\" ".
2686              "class=\"icon\" alt=\"$modname\">";
2687         echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id={$activity->cmid}\">{$activity->name}</a>";
2688         echo '</div>';
2689     }
2691     if (isset($activity->grade)) {
2692         echo '<div class="grade">';
2693         echo get_string('grade').': ';
2694         echo $activity->grade;
2695         echo '</div>';
2696     }
2698     echo '<div class="user">';
2699     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->userid}&amp;course=$courseid\">"
2700          ."{$activity->user->fullname}</a>  - ".userdate($activity->timestamp);
2701     echo '</div>';
2703     echo "</td></tr></table>";
2706 /// GENERIC SQL FUNCTIONS
2708 /**
2709  * Fetch info from logs
2710  *
2711  * @param $log object with properties ->info (the assignment id) and ->userid
2712  * @return array with assignment name and user firstname and lastname
2713  */
2714 function assignment_log_info($log) {
2715     global $CFG, $DB;
2717     return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
2718                                   FROM {assignment} a, {user} u
2719                                  WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
2722 /**
2723  * Return list of marked submissions that have not been mailed out for currently enrolled students
2724  *
2725  * @return array
2726  */
2727 function assignment_get_unmailed_submissions($starttime, $endtime) {
2728     global $CFG, $DB;
2730     return $DB->get_records_sql("SELECT s.*, a.course, a.name
2731                                    FROM {assignment_submissions} s,
2732                                         {assignment} a
2733                                   WHERE s.mailed = 0
2734                                         AND s.timemarked <= ?
2735                                         AND s.timemarked >= ?
2736                                         AND s.assignment = a.id", array($endtime, $starttime));
2739 /**
2740  * Counts all real assignment submissions by ENROLLED students (not empty ones)
2741  *
2742  * There are also assignment type methods count_real_submissions() wich in the default
2743  * implementation simply call this function.
2744  * @param $groupid int optional If nonzero then count is restricted to this group
2745  * @return int The number of submissions
2746  */
2747 function assignment_count_real_submissions($cm, $groupid=0) {
2748     global $CFG, $DB;
2750     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2752     // this is all the users with this capability set, in this context or higher
2753     if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $groupid, '', false)) {
2754         $users = array_keys($users);
2755     }
2757     // if groupmembersonly used, remove users who are not in any group
2758     if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2759         if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
2760             $users = array_intersect($users, array_keys($groupingusers));
2761         }
2762     }
2764     if (empty($users)) {
2765         return 0;
2766     }
2768     $userlists = implode(',', $users);
2770     return $DB->count_records_sql("SELECT COUNT('x')
2771                                      FROM {assignment_submissions}
2772                                     WHERE assignment = ? AND
2773                                           timemodified > 0 AND
2774                                           userid IN ($userlists)", array($cm->instance));
2778 /**
2779  * Return all assignment submissions by ENROLLED students (even empty)
2780  *
2781  * There are also assignment type methods get_submissions() wich in the default
2782  * implementation simply call this function.
2783  * @param $sort string optional field names for the ORDER BY in the sql query
2784  * @param $dir string optional specifying the sort direction, defaults to DESC
2785  * @return array The submission objects indexed by id
2786  */
2787 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2788 /// Return all assignment submissions by ENROLLED students (even empty)
2789     global $CFG, $DB;
2791     if ($sort == "lastname" or $sort == "firstname") {
2792         $sort = "u.$sort $dir";
2793     } else if (empty($sort)) {
2794         $sort = "a.timemodified DESC";
2795     } else {
2796         $sort = "a.$sort $dir";
2797     }
2799     /* not sure this is needed at all since assignmenet already has a course define, so this join?
2800     $select = "s.course = '$assignment->course' AND";
2801     if ($assignment->course == SITEID) {
2802         $select = '';
2803     }*/
2805     return $DB->get_records_sql("SELECT a.*
2806                                    FROM {assignment_submissions} a, {user} u
2807                                   WHERE u.id = a.userid
2808                                         AND a.assignment = ?
2809                                ORDER BY $sort", array($assignment->id));
2813 /**
2814  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2815  * for the course (see resource).
2816  *
2817  * Given a course_module object, this function returns any "extra" information that may be needed
2818  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
2819  *
2820  * @param $coursemodule object The coursemodule object (record).
2821  * @return object An object on information that the coures will know about (most noticeably, an icon).
2822  *
2823  */
2824 function assignment_get_coursemodule_info($coursemodule) {
2825     global $CFG, $DB;
2827     if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance), 'id, assignmenttype, name')) {
2828         return false;
2829     }
2831     $libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
2833     if (file_exists($libfile)) {
2834         require_once($libfile);
2835         $assignmentclass = "assignment_$assignment->assignmenttype";
2836         $ass = new $assignmentclass('staticonly');
2837         if ($result = $ass->get_coursemodule_info($coursemodule)) {
2838             return $result;
2839         } else {
2840             $info = new object();
2841             $info->name = $assignment->name;
2842             return $info;
2843         }
2845     } else {
2846         debugging('Incorrect assignment type: '.$assignment->assignmenttype);
2847         return false;
2848     }
2853 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS  ///////////////////////////////////////
2855 /**
2856  * Returns an array of installed assignment types indexed and sorted by name
2857  *
2858  * @return array The index is the name of the assignment type, the value its full name from the language strings
2859  */
2860 function assignment_types() {
2861     $types = array();
2862     $names = get_list_of_plugins('mod/assignment/type');
2863     foreach ($names as $name) {
2864         $types[$name] = get_string('type'.$name, 'assignment');
2865     }
2866     asort($types);
2867     return $types;
2870 /**
2871  * Executes upgrade scripts for assignment types when necessary
2872  */
2873 function assignment_upgrade_submodules() {
2874     global $CFG;
2876 /// Install/upgrade assignment types (it uses, simply, the standard plugin architecture)
2877     upgrade_plugins('assignment_type', 'mod/assignment/type', "$CFG->wwwroot/$CFG->admin/index.php");
2881 function assignment_print_overview($courses, &$htmlarray) {
2882     global $USER, $CFG, $DB;
2884     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
2885         return array();
2886     }
2888     if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2889         return;
2890     }
2892     // Do assignment_base::isopen() here without loading the whole thing for speed
2893     foreach ($assignments as $key => $assignment) {
2894         $time = time();
2895         if ($assignment->timedue) {
2896             if ($assignment->preventlate) {
2897                 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
2898             } else {
2899                 $isopen = ($assignment->timeavailable <= $time);
2900             }
2901         }
2902         if (empty($isopen) || empty($assignment->timedue)) {
2903             unset($assignments[$key]);
2904         }
2905     }
2907     $strduedate = get_string('duedate', 'assignment');
2908     $strduedateno = get_string('duedateno', 'assignment');
2909     $strgraded = get_string('graded', 'assignment');
2910     $strnotgradedyet = get_string('notgradedyet', 'assignment');
2911     $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2912     $strsubmitted = get_string('submitted', 'assignment');
2913     $strassignment = get_string('modulename', 'assignment');
2914     $strreviewed = get_string('reviewed','assignment');
2916     foreach ($assignments as $assignment) {
2917         $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
2918                '<a '.($assignment->visible ? '':' class="dimmed"').
2919                'title="'.$strassignment.'" href="'.$CFG->wwwroot.
2920                '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
2921                $assignment->name.'</a></div>';
2922         if ($assignment->timedue) {
2923             $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
2924         } else {
2925             $str .= '<div class="info">'.$strduedateno.'</div>';
2926         }
2927         $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
2928         if (has_capability('mod/assignment:grade', $context)) {
2930             // count how many people can submit
2931             $submissions = 0; // init
2932             if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2933                  foreach ($students as $student) {
2934                     if ($DB->record_exists_sql("SELECT id
2935                                                   FROM {assignment_submissions}
2936                                                  WHERE assignment = ? AND
2937                                                        userid = ? AND
2938                                                        teacher = 0 AND
2939                                                        timemarked = 0", array($assignment->id, $student->id))) {
2940                         $submissions++;
2941                     }
2942                 }
2943             }
2945             if ($submissions) {
2946                 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2947             }
2948         } else {
2949             $sql = "SELECT *
2950                       FROM {assignment_submissions}
2951                      WHERE userid = ?
2952                        AND assignment = ?";
2953             $params = array($USER->id, $assignment->id);
2954             if ($submission = $DB->get_record_sql($sql, $params)) {
2955                 if ($submission->teacher == 0 && $submission->timemarked == 0) {
2956                     $str .= $strsubmitted . ', ' . $strnotgradedyet;
2957                 } else if ($submission->grade <= 0) {
2958                     $str .= $strsubmitted . ', ' . $strreviewed;
2959                 } else {
2960                     $str .= $strsubmitted . ', ' . $strgraded;
2961                 }
2962             } else {
2963                 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
2964             }
2965         }
2966         $str .= '</div>';
2967         if (empty($htmlarray[$assignment->course]['assignment'])) {
2968             $htmlarray[$assignment->course]['assignment'] = $str;
2969         } else {
2970             $htmlarray[$assignment->course]['assignment'] .= $str;
2971         }
2972     }
2975 function assignment_display_lateness($timesubmitted, $timedue) {
2976     if (!$timedue) {
2977         return '';
2978     }
2979     $time = $timedue - $timesubmitted;
2980     if ($time < 0) {
2981         $timetext = get_string('late', 'assignment', format_time($time));
2982         return ' (<span class="late">'.$timetext.'</span>)';
2983     } else {
2984         $timetext = get_string('early', 'assignment', format_time($time));
2985         return ' (<span class="early">'.$timetext.'</span>)';
2986     }
2989 function assignment_get_view_actions() {
2990     return array('view');
2993 function assignment_get_post_actions() {
2994     return array('upload');
2997 function assignment_get_types() {
2998     global $CFG;
2999     $types = array();
3001     $type = new object();
3002     $type->modclass = MOD_CLASS_ACTIVITY;
3003     $type->type = "assignment_group_start";
3004     $type->typestr = '--'.get_string('modulenameplural', 'assignment');
3005     $types[] = $type;
3007     $standardassignments = array('upload','online','uploadsingle','offline');
3008     foreach ($standardassignments as $assignmenttype) {
3009         $type = new object();
3010         $type->modclass = MOD_CLASS_ACTIVITY;
3011         $type->type = "assignment&amp;type=$assignmenttype";
3012         $type->typestr = get_string("type$assignmenttype", 'assignment');
3013         $types[] = $type;
3014     }
3016     /// Drop-in extra assignment types
3017     $assignmenttypes = get_list_of_plugins('mod/assignment/type');
3018     foreach ($assignmenttypes as $assignmenttype) {
3019         if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) {  // Not wanted
3020             continue;
3021         }
3022         if (!in_array($assignmenttype, $standardassignments)) {
3023             $type = new object();
3024             $type->modclass = MOD_CLASS_ACTIVITY;
3025             $type->type = "assignment&amp;type=$assignmenttype";
3026             $type->typestr = get_string("type$assignmenttype", 'assignment');
3027             $types[] = $type;
3028         }
3029     }
3031     $type = new object();
3032     $type->modclass = MOD_CLASS_ACTIVITY;
3033     $type->type = "assignment_group_end";
3034     $type->typestr = '--';
3035     $types[] = $type;
3037     return $types;
3040 /**
3041  * Removes all grades from gradebook
3042  * @param int $courseid
3043  * @param string optional type
3044  */
3045 function assignment_reset_gradebook($courseid, $type='') {
3046     global $CFG, $DB;
3048     $params = array('courseid'=>$courseid);
3049     if ($type) {
3050         $type = "AND a.assignmenttype= :type";
3051         $params['type'] = $type;
3052     }
3054     $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
3055               FROM {assignment} a, {course_modules} cm, {modules} m
3056              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid $type";
3058     if ($assignments = $DB->get_records_sql($sql, $params)) {
3059         foreach ($assignments as $assignment) {
3060             assignment_grade_item_update($assignment, 'reset');
3061         }
3062     }
3065 /**
3066  * This function is used by the reset_course_userdata function in moodlelib.
3067  * This function will remove all posts from the specified assignment
3068  * and clean up any related data.
3069  * @param $data the data submitted from the reset course.
3070  * @return array status array
3071  */
3072 function assignment_reset_userdata($data) {
3073     global $CFG;
3075     $status = array();
3077     foreach (get_list_of_plugins('mod/assignment/type') as $type) {
3078         require_once("$CFG->dirroot/mod/assignment/type/$type/assignment.class.php");
3079         $assignmentclass = "assignment_$type";
3080         $ass = new $assignmentclass();
3081         $status = array_merge($status, $ass->reset_userdata($data));
3082     }
3084     return $status;
3087 /**
3088  * Implementation of the function for printing the form elements that control
3089  * whether the course reset functionality affects the assignment.
3090  * @param $mform form passed by reference
3091  */
3092 function assignment_reset_course_form_definition(&$mform) {
3093     $mform->addElement('header', 'assignmentheader', get_string('modulenameplural', 'assignment'));
3094     $mform->addElement('advcheckbox', 'reset_assignment_submissions', get_string('deleteallsubmissions','assignment'));
3097 /**
3098  * Course reset form defaults.
3099  */
3100 function assignment_reset_course_form_defaults($course) {
3101     return array('reset_assignment_submissions'=>1);
3104 /**
3105  * Returns all other caps used in module
3106  */
3107 function assignment_get_extra_capabilities() {
3108     return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames');
3111 require_once($CFG->libdir . '/portfoliolib.php');
3112 class assignment_portfolio_caller extends portfolio_module_caller_base {
3114     private $assignment;
3115     private $assignmentfile;
3116     private $userid;
3117     private $file;
3119     public function __construct($callbackargs) {
3120         global $DB, $CFG;
3122         if (! $this->cm = get_coursemodule_from_id('assignment', $callbackargs['assignmentid'])) {
3123             print_error('invalidcoursemodule');
3124         }
3126         if (! $assignment = $DB->get_record("assignment", array("id"=>$this->cm->instance))) {
3127             print_error('invalidid', 'assignment');
3128         }
3130         $this->assignmentfile = $CFG->dirroot . '/mod/assignment/type/' . $assignment->assignmenttype . '/assignment.class.php';
3131         require_once($this->assignmentfile);
3132         $assignmentclass = "assignment_$assignment->assignmenttype";
3133         $this->assignment= new $assignmentclass($this->cm->id, $assignment, $this->cm);
3134         if (!$this->assignment->portfolio_exportable()) {
3135             print_error('notexportable', 'portfolio', $this->get_return_url());
3136         }
3137         $this->userid = $callbackargs['userid'];
3138         $this->file = (array_key_exists('file', $callbackargs)) ? $callbackargs['file'] : null;
3139     }
3141     public function prepare_package($tempdir) {
3142         global $CFG;
3143         if (is_callable(array($this->assignment, 'portfolio_prepare_package'))) {
3144             return $this->assignment->portfolio_prepare_package($tempdir);
3145         }
3146         // default...
3147         $filearea = $CFG->dataroot . '/' . $this->assignment->file_area_name($this->userid);
3148         //@todo  penny this is a dreadful thing to have to call (replace with files api anyway)
3149         require_once($CFG->dirroot . '/backup/lib.php');
3150         if ($this->file) {
3151             return backup_copy_file($filearea . '/' . $this->file, $tempdir . '/' . $this->file);
3152         }
3153         return backup_copy_file($filearea, $tempdir);
3154     }
3156     public function get_sha1() {
3157         global $CFG;
3158         if (is_callable(array($this->assignment, 'portfolio_get_sha1'))) {
3159             return $this->assignment->portfolio_get_sha1();
3160         }
3161         // default ...
3162         $filearea = $CFG->dataroot . '/' . $this->assignment->file_area_name($this->userid);
3163         if ($this->file) {
3164             return sha1_file($filearea . '/' . $this->file);
3165         }
3166         $sha1s = array();
3167         foreach (get_directory_list($filearea) as $file) {
3168             $sha1s[] = sha1_file($filearea . '/' . $file);
3169         }
3170         asort($sha1s);
3171         return sha1(implode('', $sha1s));
3173     }
3175     public function expected_time() {
3176         return PORTFOLIO_TIME_MODERATE; // @TODO check uploaded file size
3177     }
3179     public function check_permissions() {
3180         return has_capability('mod/assignment:export-upload-files', get_context_instance(CONTEXT_MODULE, $this->assignment->cm->id));
3181     }
3183     public function __wakeup() {
3184         global $CFG;
3185         if (empty($CFG)) {
3186             return true; // too early yet
3187         }
3188         require_once($this->assignmentfile);
3189         $this->assignment = unserialize(serialize($this->assignment));
3190     }
3192     public static function display_name() {
3193         return get_string('modulename', 'assignment');
3194     }
3197 /**
3198  * @param string $feature FEATURE_xx constant for requested feature
3199  * @return mixed True if module supports feature, null if doesn't know
3200  */
3201 function assignment_supports($feature) {
3202     switch($feature) {
3203         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3204         case FEATURE_GRADE_HAS_GRADE: return true;
3205         default: return null;
3206     }
3208 ?>