MDL-15666 - change all the portfolio plugins and callers to use exceptions
[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');
9 require_once($CFG->libdir.'/formslib.php');
11 DEFINE ('ASSIGNMENT_COUNT_WORDS', 1);
12 DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2);
14 /**
15  * Standard base class for all assignment submodules (assignment types).
16  */
17 class assignment_base {
19     var $cm;
20     var $course;
21     var $assignment;
22     var $strassignment;
23     var $strassignments;
24     var $strsubmissions;
25     var $strlastmodified;
26     var $pagetitle;
27     var $usehtmleditor;
28     var $defaultformat;
29     var $context;
30     var $type;
32     /**
33      * Constructor for the base assignment class
34      *
35      * Constructor for the base assignment class.
36      * If cmid is set create the cm, course, assignment objects.
37      * If the assignment is hidden and the user is not a teacher then
38      * this prints a page header and notice.
39      *
40      * @param cmid   integer, the current course module id - not set for new assignments
41      * @param assignment   object, usually null, but if we have it we pass it to save db access
42      * @param cm   object, usually null, but if we have it we pass it to save db access
43      * @param course   object, usually null, but if we have it we pass it to save db access
44      */
45     function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
46         global $COURSE, $DB;
48         if ($cmid == 'staticonly') {
49             //use static functions only!
50             return;
51         }
53         global $CFG;
55         if ($cm) {
56             $this->cm = $cm;
57         } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
58             print_error('invalidcoursemodule');
59         }
61         $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
63         if ($course) {
64             $this->course = $course;
65         } else if ($this->cm->course == $COURSE->id) {
66             $this->course = $COURSE;
67         } else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
68             print_error('invalidid', 'assignment');
69         }
71         if ($assignment) {
72             $this->assignment = $assignment;
73         } else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
74             print_error('invalidid', 'assignment');
75         }
77         $this->assignment->cmidnumber = $this->cm->id;     // compatibility with modedit assignment obj
78         $this->assignment->courseid   = $this->course->id; // compatibility with modedit assignment obj
80         $this->strassignment = get_string('modulename', 'assignment');
81         $this->strassignments = get_string('modulenameplural', 'assignment');
82         $this->strsubmissions = get_string('submissions', 'assignment');
83         $this->strlastmodified = get_string('lastmodified');
84         $this->pagetitle = strip_tags($this->course->shortname.': '.$this->strassignment.': '.format_string($this->assignment->name,true));
86         // visibility handled by require_login() with $cm parameter
87         // get current group only when really needed
89     /// Set up things for a HTML editor if it's needed
90         if ($this->usehtmleditor = can_use_html_editor()) {
91             $this->defaultformat = FORMAT_HTML;
92         } else {
93             $this->defaultformat = FORMAT_MOODLE;
94         }
95     }
97     /**
98      * Display the assignment, used by view.php
99      *
100      * This in turn calls the methods producing individual parts of the page
101      */
102     function view() {
104         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
105         require_capability('mod/assignment:view', $context);
107         add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
108                    $this->assignment->id, $this->cm->id);
110         $this->view_header();
112         $this->view_intro();
114         $this->view_dates();
116         $this->view_feedback();
118         $this->view_footer();
119     }
121     /**
122      * Display the header and top of a page
123      *
124      * (this doesn't change much for assignment types)
125      * This is used by the view() method to print the header of view.php but
126      * it can be used on other pages in which case the string to denote the
127      * page in the navigation trail should be passed as an argument
128      *
129      * @param $subpage string Description of subpage to be used in navigation trail
130      */
131     function view_header($subpage='') {
133         global $CFG;
136         if ($subpage) {
137             $navigation = build_navigation($subpage, $this->cm);
138         } else {
139             $navigation = build_navigation('', $this->cm);
140         }
142         print_header($this->pagetitle, $this->course->fullname, $navigation, '', '',
143                      true, update_module_button($this->cm->id, $this->course->id, $this->strassignment),
144                      navmenu($this->course, $this->cm));
146         groups_print_activity_menu($this->cm, 'view.php?id=' . $this->cm->id);
148         echo '<div class="reportlink">'.$this->submittedlink().'</div>';
149         echo '<div class="clearer"></div>';
150     }
153     /**
154      * Display the assignment intro
155      *
156      * This will most likely be extended by assignment type plug-ins
157      * The default implementation prints the assignment description in a box
158      */
159     function view_intro() {
160         print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
161         $formatoptions = new stdClass;
162         $formatoptions->noclean = true;
163         echo format_text($this->assignment->description, $this->assignment->format, $formatoptions);
164         print_simple_box_end();
165     }
167     /**
168      * Display the assignment dates
169      *
170      * Prints the assignment start and end dates in a box.
171      * This will be suitable for most assignment types
172      */
173     function view_dates() {
174         if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
175             return;
176         }
178         print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
179         echo '<table>';
180         if ($this->assignment->timeavailable) {
181             echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
182             echo '    <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
183         }
184         if ($this->assignment->timedue) {
185             echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
186             echo '    <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
187         }
188         echo '</table>';
189         print_simple_box_end();
190     }
193     /**
194      * Display the bottom and footer of a page
195      *
196      * This default method just prints the footer.
197      * This will be suitable for most assignment types
198      */
199     function view_footer() {
200         print_footer($this->course);
201     }
203     /**
204      * Display the feedback to the student
205      *
206      * This default method prints the teacher picture and name, date when marked,
207      * grade and teacher submissioncomment.
208      *
209      * @param $submission object The submission object or NULL in which case it will be loaded
210      */
211     function view_feedback($submission=NULL) {
212         global $USER, $CFG, $DB;
213         require_once($CFG->libdir.'/gradelib.php');
215         if (!has_capability('mod/assignment:submit', $this->context, $USER->id, false)) {
216             // can not submit assignments -> no feedback
217             return;
218         }
220         if (!$submission) { /// Get submission for this assignment
221             $submission = $this->get_submission($USER->id);
222         }
224         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $USER->id);
225         $item = $grading_info->items[0];
226         $grade = $item->grades[$USER->id];
228         if ($grade->hidden or $grade->grade === false) { // hidden or error
229             return;
230         }
232         if ($grade->grade === null and empty($grade->str_feedback)) {   /// Nothing to show yet
233             return;
234         }
236         $graded_date = $grade->dategraded;
237         $graded_by   = $grade->usermodified;
239     /// We need the teacher info
240         if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
241             print_error('cannotfindteacher');
242         }
244     /// Print the feedback
245         print_heading(get_string('feedbackfromteacher', 'assignment', $this->course->teacher)); // TODO: fix teacher string
247         echo '<table cellspacing="0" class="feedback">';
249         echo '<tr>';
250         echo '<td class="left picture">';
251         if ($teacher) {
252             print_user_picture($teacher, $this->course->id, $teacher->picture);
253         }
254         echo '</td>';
255         echo '<td class="topic">';
256         echo '<div class="from">';
257         if ($teacher) {
258             echo '<div class="fullname">'.fullname($teacher).'</div>';
259         }
260         echo '<div class="time">'.userdate($graded_date).'</div>';
261         echo '</div>';
262         echo '</td>';
263         echo '</tr>';
265         echo '<tr>';
266         echo '<td class="left side">&nbsp;</td>';
267         echo '<td class="content">';
268         echo '<div class="grade">';
269         echo get_string("grade").': '.$grade->str_long_grade;
270         echo '</div>';
271         echo '<div class="clearer"></div>';
273         echo '<div class="comment">';
274         echo $grade->str_feedback;
275         echo '</div>';
276         echo '</tr>';
278         echo '</table>';
279     }
281     /**
282      * Returns a link with info about the state of the assignment submissions
283      *
284      * This is used by view_header to put this link at the top right of the page.
285      * For teachers it gives the number of submitted assignments with a link
286      * For students it gives the time of their submission.
287      * This will be suitable for most assignment types.
288      * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
289      * @return string
290      */
291     function submittedlink($allgroups=false) {
292         global $USER;
293         global $CFG;
295         $submitted = '';
296         $urlbase = "{$CFG->wwwroot}/mod/assignment/";
298         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
299         if (has_capability('mod/assignment:grade', $context)) {
300             if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
301                 $group = 0;
302             } else {
303                 $group = groups_get_activity_group($this->cm);
304             }
305             if ($count = $this->count_real_submissions($group)) {
306                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
307                              get_string('viewsubmissions', 'assignment', $count).'</a>';
308             } else {
309                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
310                              get_string('noattempts', 'assignment').'</a>';
311             }
312         } else {
313             if (!empty($USER->id)) {
314                 if ($submission = $this->get_submission($USER->id)) {
315                     if ($submission->timemodified) {
316                         if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
317                             $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
318                         } else {
319                             $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
320                         }
321                     }
322                 }
323             }
324         }
326         return $submitted;
327     }
330     function setup_elements(&$mform) {
332     }
334     /**
335      * Create a new assignment activity
336      *
337      * Given an object containing all the necessary data,
338      * (defined by the form in mod_form.php) this function
339      * will create a new instance and return the id number
340      * of the new instance.
341      * The due data is added to the calendar
342      * This is common to all assignment types.
343      *
344      * @param $assignment object The data from the form on mod_form.php
345      * @return int The id of the assignment
346      */
347     function add_instance($assignment) {
348         global $COURSE, $DB;
350         $assignment->timemodified = time();
351         $assignment->courseid = $assignment->course;
353         if ($returnid = $DB->insert_record("assignment", $assignment)) {
354             $assignment->id = $returnid;
356             if ($assignment->timedue) {
357                 $event = new object();
358                 $event->name        = $assignment->name;
359                 $event->description = $assignment->description;
360                 $event->courseid    = $assignment->course;
361                 $event->groupid     = 0;
362                 $event->userid      = 0;
363                 $event->modulename  = 'assignment';
364                 $event->instance    = $returnid;
365                 $event->eventtype   = 'due';
366                 $event->timestart   = $assignment->timedue;
367                 $event->timeduration = 0;
369                 add_event($event);
370             }
372             assignment_grade_item_update($assignment);
374         }
377         return $returnid;
378     }
380     /**
381      * Deletes an assignment activity
382      *
383      * Deletes all database records, files and calendar events for this assignment.
384      * @param $assignment object The assignment to be deleted
385      * @return boolean False indicates error
386      */
387     function delete_instance($assignment) {
388         global $CFG, $DB;
390         $assignment->courseid = $assignment->course;
392         $result = true;
394         // now get rid of all files
395         $fs = get_file_storage();
396         if ($cm = get_coursemodule_from_instance('assignment', $assignment->id)) {
397             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
398             $fs->delete_area_files($context->id);
399         }
401         if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
402             $result = false;
403         }
405         if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
406             $result = false;
407         }
409         if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
410             $result = false;
411         }
413         assignment_grade_item_delete($assignment);
415         return $result;
416     }
418     /**
419      * Updates a new assignment activity
420      *
421      * Given an object containing all the necessary data,
422      * (defined by the form in mod_form.php) this function
423      * will update the assignment instance and return the id number
424      * The due date is updated in the calendar
425      * This is common to all assignment types.
426      *
427      * @param $assignment object The data from the form on mod_form.php
428      * @return int The assignment id
429      */
430     function update_instance($assignment) {
431         global $COURSE, $DB;
433         $assignment->timemodified = time();
435         $assignment->id = $assignment->instance;
436         $assignment->courseid = $assignment->course;
438         if (!$DB->update_record('assignment', $assignment)) {
439             return false;
440         }
442         if ($assignment->timedue) {
443             $event = new object();
445             if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
447                 $event->name        = $assignment->name;
448                 $event->description = $assignment->description;
449                 $event->timestart   = $assignment->timedue;
451                 update_event($event);
452             } else {
453                 $event = new object();
454                 $event->name        = $assignment->name;
455                 $event->description = $assignment->description;
456                 $event->courseid    = $assignment->course;
457                 $event->groupid     = 0;
458                 $event->userid      = 0;
459                 $event->modulename  = 'assignment';
460                 $event->instance    = $assignment->id;
461                 $event->eventtype   = 'due';
462                 $event->timestart   = $assignment->timedue;
463                 $event->timeduration = 0;
465                 add_event($event);
466             }
467         } else {
468             $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
469         }
471         // get existing grade item
472         assignment_grade_item_update($assignment);
474         return true;
475     }
477     /**
478      * Update grade item for this submission.
479      */
480     function update_grade($submission) {
481         assignment_update_grades($this->assignment, $submission->userid);
482     }
484     /**
485      * Top-level function for handling of submissions called by submissions.php
486      *
487      * This is for handling the teacher interaction with the grading interface
488      * This should be suitable for most assignment types.
489      *
490      * @param $mode string Specifies the kind of teacher interaction taking place
491      */
492     function submissions($mode) {
493         ///The main switch is changed to facilitate
494         ///1) Batch fast grading
495         ///2) Skip to the next one on the popup
496         ///3) Save and Skip to the next one on the popup
498         //make user global so we can use the id
499         global $USER;
501         $mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
502         if (is_null($mailinfo)) {
503             $mailinfo = get_user_preferences('assignment_mailinfo', 0);
504         } else {
505             set_user_preference('assignment_mailinfo', $mailinfo);
506         }
508         switch ($mode) {
509             case 'grade':                         // We are in a popup window grading
510                 if ($submission = $this->process_feedback()) {
511                     //IE needs proper header with encoding
512                     print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment->name));
513                     print_heading(get_string('changessaved'));
514                     print $this->update_main_listing($submission);
515                 }
516                 close_window();
517                 break;
519             case 'single':                        // We are in a popup window displaying submission
520                 $this->display_submission();
521                 break;
523             case 'all':                          // Main window, display everything
524                 $this->display_submissions();
525                 break;
527             case 'fastgrade':
528                 ///do the fast grading stuff  - this process should work for all 3 subclasses
530                 $grading    = false;
531                 $commenting = false;
532                 $col        = false;
533                 if (isset($_POST['submissioncomment'])) {
534                     $col = 'submissioncomment';
535                     $commenting = true;
536                 }
537                 if (isset($_POST['menu'])) {
538                     $col = 'menu';
539                     $grading = true;
540                 }
541                 if (!$col) {
542                     //both submissioncomment and grade columns collapsed..
543                     $this->display_submissions();
544                     break;
545                 }
547                 foreach ($_POST[$col] as $id => $unusedvalue){
549                     $id = (int)$id; //clean parameter name
551                     $this->process_outcomes($id);
553                     if (!$submission = $this->get_submission($id)) {
554                         $submission = $this->prepare_new_submission($id);
555                         $newsubmission = true;
556                     } else {
557                         $newsubmission = false;
558                     }
559                     unset($submission->data1);  // Don't need to update this.
560                     unset($submission->data2);  // Don't need to update this.
562                     //for fast grade, we need to check if any changes take place
563                     $updatedb = false;
565                     if ($grading) {
566                         $grade = $_POST['menu'][$id];
567                         $updatedb = $updatedb || ($submission->grade != $grade);
568                         $submission->grade = $grade;
569                     } else {
570                         if (!$newsubmission) {
571                             unset($submission->grade);  // Don't need to update this.
572                         }
573                     }
574                     if ($commenting) {
575                         $commentvalue = trim($_POST['submissioncomment'][$id]);
576                         $updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
577                         $submission->submissioncomment = $commentvalue;
578                     } else {
579                         unset($submission->submissioncomment);  // Don't need to update this.
580                     }
582                     $submission->teacher    = $USER->id;
583                     if ($updatedb) {
584                         $submission->mailed = (int)(!$mailinfo);
585                     }
587                     $submission->timemarked = time();
589                     //if it is not an update, we don't change the last modified time etc.
590                     //this will also not write into database if no submissioncomment and grade is entered.
592                     if ($updatedb){
593                         if ($newsubmission) {
594                             if (!isset($submission->submissioncomment)) {
595                                 $submission->submissioncomment = '';
596                             }
597                             if (!$sid = $DB->insert_record('assignment_submissions', $submission)) {
598                                 return false;
599                             }
600                             $submission->id = $sid;
601                         } else {
602                             if (!$DB->update_record('assignment_submissions', $submission)) {
603                                 return false;
604                             }
605                         }
607                         // triger grade event
608                         $this->update_grade($submission);
610                         //add to log only if updating
611                         add_to_log($this->course->id, 'assignment', 'update grades',
612                                    'submissions.php?id='.$this->assignment->id.'&user='.$submission->userid,
613                                    $submission->userid, $this->cm->id);
614                     }
616                 }
618                 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true);
620                 $this->display_submissions($message);
621                 break;
624             case 'next':
625                 /// We are currently in pop up, but we want to skip to next one without saving.
626                 ///    This turns out to be similar to a single case
627                 /// The URL used is for the next submission.
629                 $this->display_submission();
630                 break;
632             case 'saveandnext':
633                 ///We are in pop up. save the current one and go to the next one.
634                 //first we save the current changes
635                 if ($submission = $this->process_feedback()) {
636                     //print_heading(get_string('changessaved'));
637                     $extra_javascript = $this->update_main_listing($submission);
638                 }
640                 //then we display the next submission
641                 $this->display_submission($extra_javascript);
642                 break;
644             default:
645                 echo "something seriously is wrong!!";
646                 break;
647         }
648     }
650     /**
651     * Helper method updating the listing on the main script from popup using javascript
652     *
653     * @param $submission object The submission whose data is to be updated on the main page
654     */
655     function update_main_listing($submission) {
656         global $SESSION, $CFG;
658         $output = '';
660         $perpage = get_user_preferences('assignment_perpage', 10);
662         $quickgrade = get_user_preferences('assignment_quickgrade', 0);
664         /// Run some Javascript to try and update the parent page
665         $output .= '<script type="text/javascript">'."\n<!--\n";
666         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
667             if ($quickgrade){
668                 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
669                 .trim($submission->submissioncomment).'";'."\n";
670              } else {
671                 $output.= 'opener.document.getElementById("com'.$submission->userid.
672                 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
673             }
674         }
676         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
677             //echo optional_param('menuindex');
678             if ($quickgrade){
679                 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
680                 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
681             } else {
682                 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
683                 $this->display_grade($submission->grade)."\";\n";
684             }
685         }
686         //need to add student's assignments in there too.
687         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
688             $submission->timemodified) {
689             $output.= 'opener.document.getElementById("ts'.$submission->userid.
690                  '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
691         }
693         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
694             $submission->timemarked) {
695             $output.= 'opener.document.getElementById("tt'.$submission->userid.
696                  '").innerHTML="'.userdate($submission->timemarked)."\";\n";
697         }
699         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
700             $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
701             $buttontext = get_string('update');
702             $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),
703                       'grade'.$submission->userid, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid);
704             $output.= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
705         }
707         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
709         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
710             $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
711             '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
712         }
714         if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
716             if (!empty($grading_info->outcomes)) {
717                 foreach($grading_info->outcomes as $n=>$outcome) {
718                     if ($outcome->grades[$submission->userid]->locked) {
719                         continue;
720                     }
722                     if ($quickgrade){
723                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
724                         '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
726                     } else {
727                         $options = make_grades_menu(-$outcome->scaleid);
728                         $options[0] = get_string('nooutcome', 'grades');
729                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
730                     }
732                 }
733             }
734         }
736         $output .= "\n-->\n</script>";
737         return $output;
738     }
740     /**
741      *  Return a grade in user-friendly form, whether it's a scale or not
742      *
743      * @param $grade
744      * @return string User-friendly representation of grade
745      */
746     function display_grade($grade) {
747         global $DB;
749         static $scalegrades = array();   // Cache scales for each assignment - they might have different scales!!
751         if ($this->assignment->grade >= 0) {    // Normal number
752             if ($grade == -1) {
753                 return '-';
754             } else {
755                 return $grade.' / '.$this->assignment->grade;
756             }
758         } else {                                // Scale
759             if (empty($scalegrades[$this->assignment->id])) {
760                 if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
761                     $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
762                 } else {
763                     return '-';
764                 }
765             }
766             if (isset($scalegrades[$this->assignment->id][$grade])) {
767                 return $scalegrades[$this->assignment->id][$grade];
768             }
769             return '-';
770         }
771     }
773     /**
774      *  Display a single submission, ready for grading on a popup window
775      *
776      * This default method prints the teacher info and submissioncomment box at the top and
777      * the student info and submission at the bottom.
778      * This method also fetches the necessary data in order to be able to
779      * provide a "Next submission" button.
780      * Calls preprocess_submission() to give assignment type plug-ins a chance
781      * to process submissions before they are graded
782      * This method gets its arguments from the page parameters userid and offset
783      */
784     function display_submission($extra_javascript = '') {
785         global $CFG, $DB;
786         require_once($CFG->libdir.'/gradelib.php');
787         require_once($CFG->libdir.'/tablelib.php');
789         $userid = required_param('userid', PARAM_INT);
790         $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
792         if (!$user = $DB->get_record('user', array('id'=>$userid))) {
793             print_error('nousers');
794         }
796         if (!$submission = $this->get_submission($user->id)) {
797             $submission = $this->prepare_new_submission($userid);
798         }
799         if ($submission->timemodified > $submission->timemarked) {
800             $subtype = 'assignmentnew';
801         } else {
802             $subtype = 'assignmentold';
803         }
805         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
806         $disabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
808     /// construct SQL, using current offset to find the data of the next student
809         $course     = $this->course;
810         $assignment = $this->assignment;
811         $cm         = $this->cm;
812         $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
814         /// Get all ppl that can submit assignments
816         $currentgroup = groups_get_activity_group($cm);
817         if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) {
818             $users = array_keys($users);
819         }
821         // if groupmembersonly used, remove users who are not in any group
822         if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
823             if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
824                 $users = array_intersect($users, array_keys($groupingusers));
825             }
826         }
828         $nextid = 0;
830         if ($users) {
831             $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt,
832                               s.id AS submissionid, s.grade, s.submissioncomment,
833                               s.timemodified, s.timemarked,
834                               COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
835             $sql = 'FROM {user} u '.
836                    'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
837                                                                       AND s.assignment = '.$this->assignment->id.' '.
838                    'WHERE u.id IN ('.implode(',', $users).') ';
840             if ($sort = flexible_table::get_sql_sort('mod-assignment-submissions')) {
841                 $sort = 'ORDER BY '.$sort.' ';
842             }
844             if (($auser = $DB->get_records_sql($select.$sql.$sort, null, $offset+1, 1)) !== false) {
845                 $nextuser = array_shift($auser);
846             /// Calculate user status
847                 $nextuser->status = ($nextuser->timemarked > 0) && ($nextuser->timemarked >= $nextuser->timemodified);
848                 $nextid = $nextuser->id;
849             }
850         }
852         print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment->name));
854         /// Print any extra javascript needed for saveandnext
855         echo $extra_javascript;
857         ///SOme javascript to help with setting up >.>
859         echo '<script type="text/javascript">'."\n";
860         echo 'function setNext(){'."\n";
861         echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
862         echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
863         echo '}'."\n";
865         echo 'function saveNext(){'."\n";
866         echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
867         echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
868         echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
869         echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
870         echo '}'."\n";
872         echo '</script>'."\n";
873         echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
875         ///Start of teacher info row
877         echo '<tr>';
878         echo '<td class="picture teacher">';
879         if ($submission->teacher) {
880             $teacher = $DB->get_record('user', array('id'=>$submission->teacher));
881         } else {
882             global $USER;
883             $teacher = $USER;
884         }
885         print_user_picture($teacher, $this->course->id, $teacher->picture);
886         echo '</td>';
887         echo '<td class="content">';
888         echo '<form id="submitform" action="submissions.php" method="post">';
889         echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
890         echo '<input type="hidden" name="offset" value="'.($offset+1).'" />';
891         echo '<input type="hidden" name="userid" value="'.$userid.'" />';
892         echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
893         echo '<input type="hidden" name="mode" value="grade" />';
894         echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
896         //new hidden field, initialized to -1.
897         echo '<input type="hidden" name="saveuserid" value="-1" />';
899         if ($submission->timemarked) {
900             echo '<div class="from">';
901             echo '<div class="fullname">'.fullname($teacher, true).'</div>';
902             echo '<div class="time">'.userdate($submission->timemarked).'</div>';
903             echo '</div>';
904         }
905         echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
906         choose_from_menu(make_grades_menu($this->assignment->grade), 'grade', $submission->grade, get_string('nograde'), '', -1, false, $disabled);
907         echo '</div>';
909         echo '<div class="clearer"></div>';
910         echo '<div class="finalgrade">'.get_string('finalgrade', 'grades').': '.$grading_info->items[0]->grades[$userid]->str_grade.'</div>';
911         echo '<div class="clearer"></div>';
913         if (!empty($CFG->enableoutcomes)) {
914             foreach($grading_info->outcomes as $n=>$outcome) {
915                 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$outcome->name.'</label> ';
916                 $options = make_grades_menu(-$outcome->scaleid);
917                 if ($outcome->grades[$submission->userid]->locked) {
918                     $options[0] = get_string('nooutcome', 'grades');
919                     echo $options[$outcome->grades[$submission->userid]->grade];
920                 } else {
921                     choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $outcome->grades[$submission->userid]->grade, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
922                 }
923                 echo '</div>';
924                 echo '<div class="clearer"></div>';
925             }
926         }
929         $this->preprocess_submission($submission);
931         if ($disabled) {
932             echo '<div class="disabledfeedback">'.$grading_info->items[0]->grades[$userid]->str_feedback.'</div>';
934         } else {
935             print_textarea($this->usehtmleditor, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment, $this->course->id);
936             if ($this->usehtmleditor) {
937                 echo '<input type="hidden" name="format" value="'.FORMAT_HTML.'" />';
938             } else {
939                 echo '<div class="format">';
940                 choose_from_menu(format_text_menu(), "format", $submission->format, "");
941                 helpbutton("textformat", get_string("helpformatting"));
942                 echo '</div>';
943             }
944         }
946         $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : '';
948         ///Print Buttons in Single View
949         echo '<input type="hidden" name="mailinfo" value="0" />';
950         echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' /><label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>';
951         echo '<div class="buttons">';
952         echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
953         echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
954         //if there are more to be graded.
955         if ($nextid) {
956             echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
957             echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
958         }
959         echo '</div>';
960         echo '</div></form>';
962         $customfeedback = $this->custom_feedbackform($submission, true);
963         if (!empty($customfeedback)) {
964             echo $customfeedback;
965         }
967         echo '</td></tr>';
969         ///End of teacher info row, Start of student info row
970         echo '<tr>';
971         echo '<td class="picture user">';
972         print_user_picture($user, $this->course->id, $user->picture);
973         echo '</td>';
974         echo '<td class="topic">';
975         echo '<div class="from">';
976         echo '<div class="fullname">'.fullname($user, true).'</div>';
977         if ($submission->timemodified) {
978             echo '<div class="time">'.userdate($submission->timemodified).
979                                      $this->display_lateness($submission->timemodified).'</div>';
980         }
981         echo '</div>';
982         $this->print_user_files($user->id);
983         echo '</td>';
984         echo '</tr>';
986         ///End of student info row
988         echo '</table>';
990         if (!$disabled and $this->usehtmleditor) {
991             use_html_editor();
992         }
994         print_footer('none');
995     }
997     /**
998      *  Preprocess submission before grading
999      *
1000      * Called by display_submission()
1001      * The default type does nothing here.
1002      * @param $submission object The submission object
1003      */
1004     function preprocess_submission(&$submission) {
1005     }
1007     /**
1008      *  Display all the submissions ready for grading
1009      */
1010     function display_submissions($message='') {
1011         global $CFG, $DB, $USER, $DB;
1012         require_once($CFG->libdir.'/gradelib.php');
1014         /* first we check to see if the form has just been submitted
1015          * to request user_preference updates
1016          */
1018         if (isset($_POST['updatepref'])){
1019             $perpage = optional_param('perpage', 10, PARAM_INT);
1020             $perpage = ($perpage <= 0) ? 10 : $perpage ;
1021             set_user_preference('assignment_perpage', $perpage);
1022             set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
1023         }
1025         /* next we get perpage and quickgrade (allow quick grade) params
1026          * from database
1027          */
1028         $perpage    = get_user_preferences('assignment_perpage', 10);
1030         $quickgrade = get_user_preferences('assignment_quickgrade', 0);
1032         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
1034         if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
1035             $uses_outcomes = true;
1036         } else {
1037             $uses_outcomes = false;
1038         }
1040         $page    = optional_param('page', 0, PARAM_INT);
1041         $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1043     /// Some shortcuts to make the code read better
1045         $course     = $this->course;
1046         $assignment = $this->assignment;
1047         $cm         = $this->cm;
1049         $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1051         add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment->id, $this->assignment->id, $this->cm->id);
1053         $navigation = build_navigation($this->strsubmissions, $this->cm);
1054         print_header_simple(format_string($this->assignment->name,true), "", $navigation,
1055                 '', '', true, update_module_button($cm->id, $course->id, $this->strassignment), navmenu($course, $cm));
1057         $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
1058         if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
1059             echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">'
1060                 . get_string('seeallcoursegrades', 'grades') . '</a></div>';
1061         }
1063         if (!empty($message)) {
1064             echo $message;   // display messages here if any
1065         }
1067         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1069     /// Check to see if groups are being used in this assignment
1071         /// find out current groups mode
1072         $groupmode = groups_get_activity_groupmode($cm);
1073         $currentgroup = groups_get_activity_group($cm, true);
1074         groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm->id);
1076         /// Get all ppl that are allowed to submit assignments
1077         if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $currentgroup, '', false)) {
1078             $users = array_keys($users);
1079         }
1081         // if groupmembersonly used, remove users who are not in any group
1082         if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
1083             if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
1084                 $users = array_intersect($users, array_keys($groupingusers));
1085             }
1086         }
1088         $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade');
1089         if ($uses_outcomes) {
1090             $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
1091         }
1093         $tableheaders = array('',
1094                               get_string('fullname'),
1095                               get_string('grade'),
1096                               get_string('comment', 'assignment'),
1097                               get_string('lastmodified').' ('.$course->student.')',
1098                               get_string('lastmodified').' ('.$course->teacher.')',
1099                               get_string('status'),
1100                               get_string('finalgrade', 'grades'));
1101         if ($uses_outcomes) {
1102             $tableheaders[] = get_string('outcome', 'grades');
1103         }
1105         require_once($CFG->libdir.'/tablelib.php');
1106         $table = new flexible_table('mod-assignment-submissions');
1108         $table->define_columns($tablecolumns);
1109         $table->define_headers($tableheaders);
1110         $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
1112         $table->sortable(true, 'lastname');//sorted by lastname by default
1113         $table->collapsible(true);
1114         $table->initialbars(true);
1116         $table->column_suppress('picture');
1117         $table->column_suppress('fullname');
1119         $table->column_class('picture', 'picture');
1120         $table->column_class('fullname', 'fullname');
1121         $table->column_class('grade', 'grade');
1122         $table->column_class('submissioncomment', 'comment');
1123         $table->column_class('timemodified', 'timemodified');
1124         $table->column_class('timemarked', 'timemarked');
1125         $table->column_class('status', 'status');
1126         $table->column_class('finalgrade', 'finalgrade');
1127         if ($uses_outcomes) {
1128             $table->column_class('outcome', 'outcome');
1129         }
1131         $table->set_attribute('cellspacing', '0');
1132         $table->set_attribute('id', 'attempts');
1133         $table->set_attribute('class', 'submissions');
1134         $table->set_attribute('width', '100%');
1135         //$table->set_attribute('align', 'center');
1137         $table->no_sorting('finalgrade');
1138         $table->no_sorting('outcome');
1140         // Start working -- this is necessary as soon as the niceties are over
1141         $table->setup();
1143         if (empty($users)) {
1144             print_heading(get_string('nosubmitusers','assignment'));
1145             return true;
1146         }
1148     /// Construct the SQL
1150         if ($where = $table->get_sql_where()) {
1151             $where .= ' AND ';
1152         }
1154         if ($sort = $table->get_sql_sort()) {
1155             $sort = ' ORDER BY '.$sort;
1156         }
1158         $select = 'SELECT u.id, u.firstname, u.lastname, u.picture, u.imagealt,
1159                           s.id AS submissionid, s.grade, s.submissioncomment,
1160                           s.timemodified, s.timemarked,
1161                           COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
1162         $sql = 'FROM {user} u '.
1163                'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
1164                                                                   AND s.assignment = '.$this->assignment->id.' '.
1165                'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
1167         $table->pagesize($perpage, count($users));
1169         ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1170         $offset = $page * $perpage;
1172         $strupdate = get_string('update');
1173         $strgrade  = get_string('grade');
1174         $grademenu = make_grades_menu($this->assignment->grade);
1176         if (($ausers = $DB->get_records_sql($select.$sql.$sort, null, $table->get_page_start(), $table->get_page_size())) !== false) {
1177             $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
1178             foreach ($ausers as $auser) {
1179                 $final_grade = $grading_info->items[0]->grades[$auser->id];
1180                 $grademax = $grading_info->items[0]->grademax;
1181                 $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
1182                 $locked_overridden = 'locked';
1183                 if ($final_grade->overridden) {
1184                     $locked_overridden = 'overridden';
1185                 }
1187             /// Calculate user status
1188                 $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
1189                 $picture = print_user_picture($auser, $course->id, $auser->picture, false, true);
1191                 if (empty($auser->submissionid)) {
1192                     $auser->grade = -1; //no submission yet
1193                 }
1195                 if (!empty($auser->submissionid)) {
1196                 ///Prints student answer and student modified date
1197                 ///attach file or print link to student answer, depending on the type of the assignment.
1198                 ///Refer to print_student_answer in inherited classes.
1199                     if ($auser->timemodified > 0) {
1200                         $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
1201                                          . userdate($auser->timemodified).'</div>';
1202                     } else {
1203                         $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1204                     }
1205                 ///Print grade, dropdown or text
1206                     if ($auser->timemarked > 0) {
1207                         $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
1209                         if ($final_grade->locked or $final_grade->overridden) {
1210                             $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1211                         } else if ($quickgrade) {
1212                             $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1213                                                      'menu['.$auser->id.']', $auser->grade,
1214                                                      get_string('nograde'),'',-1,true,false,$tabindex++);
1215                             $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
1216                         } else {
1217                             $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1218                         }
1220                     } else {
1221                         $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1222                         if ($final_grade->locked or $final_grade->overridden) {
1223                             $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1224                         } else if ($quickgrade) {
1225                             $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1226                                                      'menu['.$auser->id.']', $auser->grade,
1227                                                      get_string('nograde'),'',-1,true,false,$tabindex++);
1228                             $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1229                         } else {
1230                             $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1231                         }
1232                     }
1233                 ///Print Comment
1234                     if ($final_grade->locked or $final_grade->overridden) {
1235                         $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
1237                     } else if ($quickgrade) {
1238                         $comment = '<div id="com'.$auser->id.'">'
1239                                  . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1240                                  . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1241                     } else {
1242                         $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
1243                     }
1244                 } else {
1245                     $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1246                     $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1247                     $status          = '<div id="st'.$auser->id.'">&nbsp;</div>';
1249                     if ($final_grade->locked or $final_grade->overridden) {
1250                         $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
1251                     } else if ($quickgrade) {   // allow editing
1252                         $menu = choose_from_menu(make_grades_menu($this->assignment->grade),
1253                                                  'menu['.$auser->id.']', $auser->grade,
1254                                                  get_string('nograde'),'',-1,true,false,$tabindex++);
1255                         $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1256                     } else {
1257                         $grade = '<div id="g'.$auser->id.'">-</div>';
1258                     }
1260                     if ($final_grade->locked or $final_grade->overridden) {
1261                         $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
1262                     } else if ($quickgrade) {
1263                         $comment = '<div id="com'.$auser->id.'">'
1264                                  . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1265                                  . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1266                     } else {
1267                         $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1268                     }
1269                 }
1271                 if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
1272                     $auser->status = 0;
1273                 } else {
1274                     $auser->status = 1;
1275                 }
1277                 $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
1279                 ///No more buttons, we use popups ;-).
1280                 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1281                            . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;offset='.$offset++;
1282                 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id, $buttontext, 600, 780,
1283                                                 $buttontext, 'none', true, 'button'.$auser->id);
1285                 $status  = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
1287                 $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
1289                 $outcomes = '';
1291                 if ($uses_outcomes) {
1293                     foreach($grading_info->outcomes as $n=>$outcome) {
1294                         $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
1295                         $options = make_grades_menu(-$outcome->scaleid);
1297                         if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
1298                             $options[0] = get_string('nooutcome', 'grades');
1299                             $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
1300                         } else {
1301                             $outcomes .= ' ';
1302                             $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id.']',
1303                                         $outcome->grades[$auser->id]->grade, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id);
1304                         }
1305                         $outcomes .= '</div>';
1306                     }
1307                 }
1309                                 $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser) . '</a>';
1310                 $row = array($picture, $userlink, $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
1311                 if ($uses_outcomes) {
1312                     $row[] = $outcomes;
1313                 }
1315                 $table->add_data($row);
1316             }
1317         }
1319         /// Print quickgrade form around the table
1320         if ($quickgrade){
1321             echo '<form action="submissions.php" id="fastg" method="post">';
1322             echo '<div>';
1323             echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
1324             echo '<input type="hidden" name="mode" value="fastgrade" />';
1325             echo '<input type="hidden" name="page" value="'.$page.'" />';
1326             echo '</div>';
1327         }
1329         $table->print_html();  /// Print the whole table
1331         if ($quickgrade){
1332             $lastmailinfo = get_user_preferences('assignment_mailinfo', 1) ? 'checked="checked"' : '';
1333             echo '<div class="fgcontrols">';
1334             echo '<div class="emailnotification">';
1335             echo '<label for="mailinfo">'.get_string('enableemailnotification','assignment').'</label>';
1336             echo '<input type="hidden" name="mailinfo" value="0" />';
1337             echo '<input type="checkbox" id="mailinfo" name="mailinfo" value="1" '.$lastmailinfo.' />';
1338             helpbutton('emailnotification', get_string('enableemailnotification', 'assignment'), 'assignment').'</p></div>';
1339             echo '</div>';
1340             echo '<div class="fastgbutton"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1341             echo '</div>';
1342             echo '</form>';
1343         }
1344         /// End of fast grading form
1346         /// Mini form for setting user preference
1347         echo '<div class="qgprefs">';
1348         echo '<form id="options" action="submissions.php?id='.$this->cm->id.'" method="post"><div>';
1349         echo '<input type="hidden" name="updatepref" value="1" />';
1350         echo '<table id="optiontable">';
1351         echo '<tr><td>';
1352         echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1353         echo '</td>';
1354         echo '<td>';
1355         echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1356         helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1357         echo '</td></tr>';
1358         echo '<tr><td>';
1359         echo '<label for="quickgrade">'.get_string('quickgrade','assignment').'</label>';
1360         echo '</td>';
1361         echo '<td>';
1362         $checked = $quickgrade ? 'checked="checked"' : '';
1363         echo '<input type="checkbox" id="quickgrade" name="quickgrade" value="1" '.$checked.' />';
1364         helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1365         echo '</td></tr>';
1366         echo '<tr><td colspan="2">';
1367         echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1368         echo '</td></tr></table>';
1369         echo '</div></form></div>';
1370         ///End of mini form
1371         print_footer($this->course);
1372     }
1374     /**
1375      *  Process teacher feedback submission
1376      *
1377      * This is called by submissions() when a grading even has taken place.
1378      * It gets its data from the submitted form.
1379      * @return object The updated submission object
1380      */
1381     function process_feedback() {
1382         global $CFG, $USER, $DB;
1383         require_once($CFG->libdir.'/gradelib.php');
1385         if (!$feedback = data_submitted()) {      // No incoming data?
1386             return false;
1387         }
1389         ///For save and next, we need to know the userid to save, and the userid to go
1390         ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1391         ///as the userid to store
1392         if ((int)$feedback->saveuserid !== -1){
1393             $feedback->userid = $feedback->saveuserid;
1394         }
1396         if (!empty($feedback->cancel)) {          // User hit cancel button
1397             return false;
1398         }
1400         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
1402         // store outcomes if needed
1403         $this->process_outcomes($feedback->userid);
1405         $submission = $this->get_submission($feedback->userid, true);  // Get or make one
1407         if (!$grading_info->items[0]->grades[$feedback->userid]->locked and
1408             !$grading_info->items[0]->grades[$feedback->userid]->overridden) {
1410             $submission->grade      = $feedback->grade;
1411             $submission->submissioncomment    = $feedback->submissioncomment;
1412             $submission->format     = $feedback->format;
1413             $submission->teacher    = $USER->id;
1414             $mailinfo = get_user_preferences('assignment_mailinfo', 0);
1415             if (!$mailinfo) {
1416                 $submission->mailed = 1;       // treat as already mailed
1417             } else {
1418                 $submission->mailed = 0;       // Make sure mail goes out (again, even)
1419             }
1420             $submission->timemarked = time();
1422             unset($submission->data1);  // Don't need to update this.
1423             unset($submission->data2);  // Don't need to update this.
1425             if (empty($submission->timemodified)) {   // eg for offline assignments
1426                 // $submission->timemodified = time();
1427             }
1429             if (! $DB->update_record('assignment_submissions', $submission)) {
1430                 return false;
1431             }
1433             // triger grade event
1434             $this->update_grade($submission);
1436             add_to_log($this->course->id, 'assignment', 'update grades',
1437                        'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1438         }
1440         return $submission;
1442     }
1444     function process_outcomes($userid) {
1445         global $CFG, $USER;
1447         if (empty($CFG->enableoutcomes)) {
1448             return;
1449         }
1451         require_once($CFG->libdir.'/gradelib.php');
1453         if (!$formdata = data_submitted()) {
1454             return;
1455         }
1457         $data = array();
1458         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
1460         if (!empty($grading_info->outcomes)) {
1461             foreach($grading_info->outcomes as $n=>$old) {
1462                 $name = 'outcome_'.$n;
1463                 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
1464                     $data[$n] = $formdata->{$name}[$userid];
1465                 }
1466             }
1467         }
1468         if (count($data) > 0) {
1469             grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1470         }
1472     }
1474     /**
1475      * Load the submission object for a particular user
1476      *
1477      * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1478      * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1479      * @param bool $teachermodified student submission set if false
1480      * @return object The submission
1481      */
1482     function get_submission($userid=0, $createnew=false, $teachermodified=false) {
1483         global $USER, $DB;
1485         if (empty($userid)) {
1486             $userid = $USER->id;
1487         }
1489         $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1491         if ($submission || !$createnew) {
1492             return $submission;
1493         }
1494         $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
1495         if (!$DB->insert_record("assignment_submissions", $newsubmission)) {
1496             print_error('cannotinsertempty', 'assignment');
1497         }
1499         return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1500     }
1502     /**
1503      * Instantiates a new submission object for a given user
1504      *
1505      * Sets the assignment, userid and times, everything else is set to default values.
1506      * @param $userid int The userid for which we want a submission object
1507      * @param bool $teachermodified student submission set if false
1508      * @return object The submission
1509      */
1510     function prepare_new_submission($userid, $teachermodified=false) {
1511         $submission = new Object;
1512         $submission->assignment   = $this->assignment->id;
1513         $submission->userid       = $userid;
1514         //$submission->timecreated  = time();
1515         $submission->timecreated = '';
1516         // teachers should not be modifying modified date, except offline assignments
1517         if ($teachermodified) {
1518             $submission->timemodified = 0;
1519         } else {
1520             $submission->timemodified = $submission->timecreated;
1521         }
1522         $submission->numfiles     = 0;
1523         $submission->data1        = '';
1524         $submission->data2        = '';
1525         $submission->grade        = -1;
1526         $submission->submissioncomment      = '';
1527         $submission->format       = 0;
1528         $submission->teacher      = 0;
1529         $submission->timemarked   = 0;
1530         $submission->mailed       = 0;
1531         return $submission;
1532     }
1534     /**
1535      * Return all assignment submissions by ENROLLED students (even empty)
1536      *
1537      * @param $sort string optional field names for the ORDER BY in the sql query
1538      * @param $dir string optional specifying the sort direction, defaults to DESC
1539      * @return array The submission objects indexed by id
1540      */
1541     function get_submissions($sort='', $dir='DESC') {
1542         return assignment_get_all_submissions($this->assignment, $sort, $dir);
1543     }
1545     /**
1546      * Counts all real assignment submissions by ENROLLED students (not empty ones)
1547      *
1548      * @param $groupid int optional If nonzero then count is restricted to this group
1549      * @return int The number of submissions
1550      */
1551     function count_real_submissions($groupid=0) {
1552         return assignment_count_real_submissions($this->cm, $groupid);
1553     }
1555     /**
1556      * Alerts teachers by email of new or changed assignments that need grading
1557      *
1558      * First checks whether the option to email teachers is set for this assignment.
1559      * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1560      * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1561      * @param $submission object The submission that has changed
1562      */
1563     function email_teachers($submission) {
1564         global $CFG, $DB;
1566         if (empty($this->assignment->emailteachers)) {          // No need to do anything
1567             return;
1568         }
1570         $user = $DB->get_record('user', array('id'=>$submission->userid));
1572         if ($teachers = $this->get_graders($user)) {
1574             $strassignments = get_string('modulenameplural', 'assignment');
1575             $strassignment  = get_string('modulename', 'assignment');
1576             $strsubmitted  = get_string('submitted', 'assignment');
1578             foreach ($teachers as $teacher) {
1579                 $info = new object();
1580                 $info->username = fullname($user, true);
1581                 $info->assignment = format_string($this->assignment->name,true);
1582                 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1584                 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1585                 $posttext = $this->email_teachers_text($info);
1586                 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
1588                 $eventdata = new object();
1589                 $eventdata->modulename       = 'assignment';
1590                 $eventdata->userfrom         = $user;
1591                 $eventdata->userto           = $teacher;
1592                 $eventdata->subject          = $postsubject;
1593                 $eventdata->fullmessage      = $posttext;
1594                 $eventdata->fullmessageformat = FORMAT_PLAIN;
1595                 $eventdata->fullmessagehtml  = $posthtml;
1596                 $eventdata->smallmessage     = '';
1597                 if ( events_trigger('message_send', $eventdata) > 0 ){
1598                 }
1599             }
1600         }
1601     }
1603     function send_file($filearea, $args) {
1604         debugging('plugin does not implement file sending', DEBUG_DEVELOPER);
1605         return false;
1606     }
1608     /**
1609      * Returns a list of teachers that should be grading given submission
1610      */
1611     function get_graders($user) {
1612         //potential graders
1613         $potgraders = get_users_by_capability($this->context, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1615         $graders = array();
1616         if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) {   // Separate groups are being used
1617             if ($groups = groups_get_all_groups($this->course->id, $user->id)) {  // Try to find all groups
1618                 foreach ($groups as $group) {
1619                     foreach ($potgraders as $t) {
1620                         if ($t->id == $user->id) {
1621                             continue; // do not send self
1622                         }
1623                         if (groups_is_member($group->id, $t->id)) {
1624                             $graders[$t->id] = $t;
1625                         }
1626                     }
1627                 }
1628             } else {
1629                 // user not in group, try to find graders without group
1630                 foreach ($potgraders as $t) {
1631                     if ($t->id == $user->id) {
1632                         continue; // do not send self
1633                     }
1634                     if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
1635                         $graders[$t->id] = $t;
1636                     }
1637                 }
1638             }
1639         } else {
1640             foreach ($potgraders as $t) {
1641                 if ($t->id == $user->id) {
1642                     continue; // do not send self
1643                 }
1644                 $graders[$t->id] = $t;
1645             }
1646         }
1647         return $graders;
1648     }
1650     /**
1651      * Creates the text content for emails to teachers
1652      *
1653      * @param $info object The info used by the 'emailteachermail' language string
1654      * @return string
1655      */
1656     function email_teachers_text($info) {
1657         $posttext  = format_string($this->course->shortname).' -> '.$this->strassignments.' -> '.
1658                      format_string($this->assignment->name)."\n";
1659         $posttext .= '---------------------------------------------------------------------'."\n";
1660         $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
1661         $posttext .= "\n---------------------------------------------------------------------\n";
1662         return $posttext;
1663     }
1665      /**
1666      * Creates the html content for emails to teachers
1667      *
1668      * @param $info object The info used by the 'emailteachermailhtml' language string
1669      * @return string
1670      */
1671     function email_teachers_html($info) {
1672         global $CFG;
1673         $posthtml  = '<p><font face="sans-serif">'.
1674                      '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname).'</a> ->'.
1675                      '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
1676                      '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name).'</a></font></p>';
1677         $posthtml .= '<hr /><font face="sans-serif">';
1678         $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1679         $posthtml .= '</font><hr />';
1680         return $posthtml;
1681     }
1683     /**
1684      * Produces a list of links to the files uploaded by a user
1685      *
1686      * @param $userid int optional id of the user. If 0 then $USER->id is used.
1687      * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1688      * @return string optional
1689      */
1690     function print_user_files($userid=0, $return=false) {
1691         global $CFG, $USER;
1693         if (!$userid) {
1694             if (!isloggedin()) {
1695                 return '';
1696             }
1697             $userid = $USER->id;
1698         }
1700         $output = '';
1702         $fs = get_file_storage();
1703         $browser = get_file_browser();
1705         $found = false;
1707         if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "timemodified", false)) {
1708             $button = new portfolio_add_button();
1709             foreach ($files as $file) {
1710                 $filename = $file->get_filename();
1711                 $found = true;
1712                 $mimetype = $file->get_mimetype();
1713                 $icon = mimeinfo_from_type('icon', $mimetype);
1714                 $path = $browser->encodepath($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/assignment_submission/'.$userid.'/'.$filename);
1715                 $output .= '<a href="'.$path.'" ><img src="'.$CFG->pixpath.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.s($filename).'</a>';
1716                 if ($this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
1717                     $button->set_formats(portfolio_format_from_file($file));
1718                     $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'file' => $file->get_id()));
1719                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
1720                 }
1721                 $output .= '<br />';
1722             }
1723             if (count($files) > 1  && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
1724                 $button->set_formats(PORTFOLIO_PORMAT_FILE);
1725                 $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id));
1726                 $output .= '<br />'  . $button->to_html();
1727             }
1728         }
1730         $output = '<div class="files">'.$output.'</div>';
1732         if ($return) {
1733             return $output;
1734         }
1735         echo $output;
1736     }
1738     /**
1739      * Count the files uploaded by a given user
1740      *
1741      * @param $userid int The user id
1742      * @return int
1743      */
1744     function count_user_files($userid) {
1745         $fs = get_file_storage();
1746         $files = $fs->get_area_files($this->context->id, 'assignment_submission', $userid, "id", false);
1747         return count($files);
1748     }
1750     /**
1751      * Returns true if the student is allowed to submit
1752      *
1753      * Checks that the assignment has started and, if the option to prevent late
1754      * submissions is set, also checks that the assignment has not yet closed.
1755      * @return boolean
1756      */
1757     function isopen() {
1758         $time = time();
1759         if ($this->assignment->preventlate && $this->assignment->timedue) {
1760             return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
1761         } else {
1762             return ($this->assignment->timeavailable <= $time);
1763         }
1764     }
1767     /**
1768      * Return true if is set description is hidden till available date
1769      *
1770      * This is needed by calendar so that hidden descriptions do not
1771      * come up in upcoming events.
1772      *
1773      * Check that description is hidden till available date
1774      * By default return false
1775      * Assignments types should implement this method if needed
1776      * @return boolen
1777      */
1778     function description_is_hidden() {
1779         return false;
1780     }
1782     /**
1783      * Return an outline of the user's interaction with the assignment
1784      *
1785      * The default method prints the grade and timemodified
1786      * @param $user object
1787      * @return object with properties ->info and ->time
1788      */
1789     function user_outline($user) {
1790         if ($submission = $this->get_submission($user->id)) {
1792             $result = new object();
1793             $result->info = get_string('grade').': '.$this->display_grade($submission->grade);
1794             $result->time = $submission->timemodified;
1795             return $result;
1796         }
1797         return NULL;
1798     }
1800     /**
1801      * Print complete information about the user's interaction with the assignment
1802      *
1803      * @param $user object
1804      */
1805     function user_complete($user) {
1806         if ($submission = $this->get_submission($user->id)) {
1808             $fs = get_file_storage();
1809             $browser = get_file_browser();
1811             if ($files = $fs->get_area_files($this->context->id, 'assignment_submission', $user->id, "timemodified", false)) {
1812                 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1813                 foreach ($files as $file) {
1814                     $countfiles .= "; ".$file->get_filename();
1815                 }
1816             }
1818             print_simple_box_start();
1819             echo get_string("lastmodified").": ";
1820             echo userdate($submission->timemodified);
1821             echo $this->display_lateness($submission->timemodified);
1823             $this->print_user_files($user->id);
1825             echo '<br />';
1827             if (empty($submission->timemarked)) {
1828                 print_string("notgradedyet", "assignment");
1829             } else {
1830                 $this->view_feedback($submission);
1831             }
1833             print_simple_box_end();
1835         } else {
1836             print_string("notsubmittedyet", "assignment");
1837         }
1838     }
1840     /**
1841      * Return a string indicating how late a submission is
1842      *
1843      * @param $timesubmitted int
1844      * @return string
1845      */
1846     function display_lateness($timesubmitted) {
1847         return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
1848     }
1850     /**
1851      * Empty method stub for all delete actions.
1852      */
1853     function delete() {
1854         //nothing by default
1855         redirect('view.php?id='.$this->cm->id);
1856     }
1858     /**
1859      * Empty custom feedback grading form.
1860      */
1861     function custom_feedbackform($submission, $return=false) {
1862         //nothing by default
1863         return '';
1864     }
1866     /**
1867      * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1868      * for the course (see resource).
1869      *
1870      * Given a course_module object, this function returns any "extra" information that may be needed
1871      * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
1872      *
1873      * @param $coursemodule object The coursemodule object (record).
1874      * @return object An object on information that the coures will know about (most noticeably, an icon).
1875      *
1876      */
1877     function get_coursemodule_info($coursemodule) {
1878         return false;
1879     }
1881     /**
1882      * Plugin cron method - do not use $this here, create new assignment instances if needed.
1883      * @return void
1884      */
1885     function cron() {
1886         //no plugin cron by default - override if needed
1887     }
1889     /**
1890      * Reset all submissions
1891      */
1892     function reset_userdata($data) {
1893         global $CFG, $DB;
1895         if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
1896             return array(); // no assignments of this type present
1897         }
1899         $componentstr = get_string('modulenameplural', 'assignment');
1900         $status = array();
1902         $typestr = get_string('type'.$this->type, 'assignment');
1904         if (!empty($data->reset_assignment_submissions)) {
1905             $assignmentssql = "SELECT a.id
1906                                  FROM {assignment} a
1907                                 WHERE a.course=? AND a.assignmenttype=?";
1908             $params = array($data->courseid, $this->type);
1910             // now get rid of all submissions and responses
1911             $fs = get_file_storage();
1912             if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
1913                 foreach ($assignments as $assignmentid=>$unused) {
1914                     if (!$cm = get_coursemodule_from_instance('assignment', $assignmentid)) {
1915                         continue;
1916                     }
1917                     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1918                     $fs->delete_area_files($context->id, 'assignment_submission');
1919                     $fs->delete_area_files($context->id, 'assignment_response');
1920                 }
1921             }
1923             $DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
1925             $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
1927             if (empty($data->reset_gradebook_grades)) {
1928                 // remove all grades from gradebook
1929                 assignment_reset_gradebook($data->courseid, $this->type);
1930             }
1931         }
1933         /// updating dates - shift may be negative too
1934         if ($data->timeshift) {
1935             shift_course_mod_dates('assignment', array('timedue', 'timeavailable'), $data->timeshift, $data->courseid);
1936             $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged').': '.$typestr, 'error'=>false);
1937         }
1939         return $status;
1940     }
1943     function portfolio_exportable() {
1944         return false;
1945     }
1946 } ////// End of the assignment_base class
1948 class mod_assignment_upload_file_form extends moodleform {
1949     function definition() {
1950         $mform = $this->_form;
1951         $instance = $this->_customdata;
1953         //TODO: improve upload size checking
1954         $mform->setMaxFileSize($instance->assignment->maxbytes);
1956         // visible elements
1957         $mform->addElement('file', 'newfile', get_string('uploadafile'));
1959         // hidden params
1960         $mform->addElement('hidden', 'id', $instance->cm->id);
1961         $mform->setType('id', PARAM_INT);
1962         $mform->addElement('hidden', 'action', 'uploadfile');
1963         $mform->setType('action', PARAM_ALPHA);
1965         // buttons
1966         $this->add_action_buttons(false, get_string('uploadthisfile'));
1967     }
1971 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1973 /**
1974  * Code to be executed when a module is installed
1975  */
1976 function assignment_install() {
1977     return true;
1980 /**
1981  * Deletes an assignment instance
1982  *
1983  * This is done by calling the delete_instance() method of the assignment type class
1984  */
1985 function assignment_delete_instance($id){
1986     global $CFG, $DB;
1988     if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
1989         return false;
1990     }
1992     // fall back to base class if plugin missing
1993     $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
1994     if (file_exists($classfile)) {
1995         require_once($classfile);
1996         $assignmentclass = "assignment_$assignment->assignmenttype";
1998     } else {
1999         debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
2000         $assignmentclass = "assignment_base";
2001     }
2003     $ass = new $assignmentclass();
2004     return $ass->delete_instance($assignment);
2008 /**
2009  * Updates an assignment instance
2010  *
2011  * This is done by calling the update_instance() method of the assignment type class
2012  */
2013 function assignment_update_instance($assignment){
2014     global $CFG;
2016     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
2018     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2019     $assignmentclass = "assignment_$assignment->assignmenttype";
2020     $ass = new $assignmentclass();
2021     return $ass->update_instance($assignment);
2025 /**
2026  * Adds an assignment instance
2027  *
2028  * This is done by calling the add_instance() method of the assignment type class
2029  */
2030 function assignment_add_instance($assignment) {
2031     global $CFG;
2033     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_SAFEDIR);
2035     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2036     $assignmentclass = "assignment_$assignment->assignmenttype";
2037     $ass = new $assignmentclass();
2038     return $ass->add_instance($assignment);
2042 /**
2043  * Returns an outline of a user interaction with an assignment
2044  *
2045  * This is done by calling the user_outline() method of the assignment type class
2046  */
2047 function assignment_user_outline($course, $user, $mod, $assignment) {
2048     global $CFG;
2050     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2051     $assignmentclass = "assignment_$assignment->assignmenttype";
2052     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2053     return $ass->user_outline($user);
2056 /**
2057  * Prints the complete info about a user's interaction with an assignment
2058  *
2059  * This is done by calling the user_complete() method of the assignment type class
2060  */
2061 function assignment_user_complete($course, $user, $mod, $assignment) {
2062     global $CFG;
2064     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2065     $assignmentclass = "assignment_$assignment->assignmenttype";
2066     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2067     return $ass->user_complete($user);
2070 /**
2071  * Function to be run periodically according to the moodle cron
2072  *
2073  * Finds all assignment notifications that have yet to be mailed out, and mails them
2074  */
2075 function assignment_cron () {
2076     global $CFG, $USER, $DB;
2078     /// first execute all crons in plugins
2079     if ($plugins = get_list_of_plugins('mod/assignment/type')) {
2080         foreach ($plugins as $plugin) {
2081             require_once("$CFG->dirroot/mod/assignment/type/$plugin/assignment.class.php");
2082             $assignmentclass = "assignment_$plugin";
2083             $ass = new $assignmentclass();
2084             $ass->cron();
2085         }
2086     }
2088     /// Notices older than 1 day will not be mailed.  This is to avoid the problem where
2089     /// cron has not been running for a long time, and then suddenly people are flooded
2090     /// with mail from the past few weeks or months
2092     $timenow   = time();
2093     $endtime   = $timenow - $CFG->maxeditingtime;
2094     $starttime = $endtime - 24 * 3600;   /// One day earlier
2096     if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
2098         $realuser = clone($USER);
2100         foreach ($submissions as $key => $submission) {
2101             if (! $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id))) {
2102                 echo "Could not update the mailed field for id $submission->id.  Not mailed.\n";
2103                 unset($submissions[$key]);
2104             }
2105         }
2107         $timenow = time();
2109         foreach ($submissions as $submission) {
2111             echo "Processing assignment submission $submission->id\n";
2113             if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
2114                 echo "Could not find user $post->userid\n";
2115                 continue;
2116             }
2118             if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
2119                 echo "Could not find course $submission->course\n";
2120                 continue;
2121             }
2123             /// Override the language and timezone of the "current" user, so that
2124             /// mail is customised for the receiver.
2125             $USER = $user;
2126             course_setup($course);
2128             if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $submission->course), $user->id)) {
2129                 echo fullname($user)." not an active participant in " . format_string($course->shortname) . "\n";
2130                 continue;
2131             }
2133             if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
2134                 echo "Could not find teacher $submission->teacher\n";
2135                 continue;
2136             }
2138             if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
2139                 echo "Could not find course module for assignment id $submission->assignment\n";
2140                 continue;
2141             }
2143             if (! $mod->visible) {    /// Hold mail notification for hidden assignments until later
2144                 continue;
2145             }
2147             $strassignments = get_string("modulenameplural", "assignment");
2148             $strassignment  = get_string("modulename", "assignment");
2150             $assignmentinfo = new object();
2151             $assignmentinfo->teacher = fullname($teacher);
2152             $assignmentinfo->assignment = format_string($submission->name,true);
2153             $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
2155             $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name,true);
2156             $posttext  = "$course->shortname -> $strassignments -> ".format_string($submission->name,true)."\n";
2157             $posttext .= "---------------------------------------------------------------------\n";
2158             $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
2159             $posttext .= "---------------------------------------------------------------------\n";
2161             if ($user->mailformat == 1) {  // HTML
2162                 $posthtml = "<p><font face=\"sans-serif\">".
2163                 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
2164                 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2165                 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
2166                 $posthtml .= "<hr /><font face=\"sans-serif\">";
2167                 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2168                 $posthtml .= "</font><hr />";
2169             } else {
2170                 $posthtml = "";
2171             }
2173             $eventdata = new object();
2174             $eventdata->modulename       = 'assignment';
2175             $eventdata->userfrom         = $teacher;
2176             $eventdata->userto           = $user;
2177             $eventdata->subject          = $postsubject;
2178             $eventdata->fullmessage      = $posttext;
2179             $eventdata->fullmessageformat = FORMAT_PLAIN;
2180             $eventdata->fullmessagehtml  = $posthtml;
2181             $eventdata->smallmessage     = '';
2182             if ( events_trigger('message_send', $eventdata) > 0 ){
2183                 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2184             }
2185         }
2187         $USER = $realuser;
2188         course_setup(SITEID); // reset cron user language, theme and timezone settings
2190     }
2192     return true;
2195 /**
2196  * Return grade for given user or all users.
2197  *
2198  * @param int $assignmentid id of assignment
2199  * @param int $userid optional user id, 0 means all users
2200  * @return array array of grades, false if none
2201  */
2202 function assignment_get_user_grades($assignment, $userid=0) {
2203     global $CFG, $DB;
2205     if ($userid) {
2206         $user = "AND u.id = :userid";
2207         $params = array('userid'=>$userid);
2208     } else {
2209         $user = "";
2210     }
2211     $params['aid'] = $assignment->id;
2213     $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
2214                    s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
2215               FROM {user} u, {assignment_submissions} s
2216              WHERE u.id = s.userid AND s.assignment = :aid
2217                    $user";
2219     return $DB->get_records_sql($sql, $params);
2222 /**
2223  * Update activity grades
2224  *
2225  * @param object $assignment
2226  * @param int $userid specific user only, 0 means all
2227  */
2228 function assignment_update_grades($assignment, $userid=0, $nullifnone=true) {
2229     global $CFG, $DB;
2230     require_once($CFG->libdir.'/gradelib.php');
2232     if ($assignment->grade == 0) {
2233         assignment_grade_item_update($assignment);
2235     } else 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);
2243     } else {
2244         assignment_grade_item_update($assignment);
2245     }
2248 /**
2249  * Update all grades in gradebook.
2250  */
2251 function assignment_upgrade_grades() {
2252     global $DB;
2254     $sql = "SELECT COUNT('x')
2255               FROM {assignment} a, {course_modules} cm, {modules} m
2256              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2257     $count = $DB->count_records_sql($sql);
2259     $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
2260               FROM {assignment} a, {course_modules} cm, {modules} m
2261              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2262     if ($rs = $DB->get_recordset_sql($sql)) {
2263         // too much debug output
2264         $prevdebug = $DB->get_debug();
2265         $DB->set_debug(false);
2266         $pbar = new progress_bar('assignmentupgradegrades', 500, true);
2267         $i=0;
2268         foreach ($rs as $assignment) {
2269             $i++;
2270             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
2271             assignment_update_grades($assignment);
2272             $pbar->update($i, $count, "Updating Assignment grades ($i/$count).");
2273         }
2274         $DB->set_debug($prevdebug);
2275         $rs->close();
2276         upgrade_set_timeout(); // reset to default timeout
2277     }
2280 /**
2281  * Create grade item for given assignment
2282  *
2283  * @param object $assignment object with extra cmidnumber
2284  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
2285  * @return int 0 if ok, error code otherwise
2286  */
2287 function assignment_grade_item_update($assignment, $grades=NULL) {
2288     global $CFG;
2289     require_once($CFG->libdir.'/gradelib.php');
2291     if (!isset($assignment->courseid)) {
2292         $assignment->courseid = $assignment->course;
2293     }
2295     $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
2297     if ($assignment->grade > 0) {
2298         $params['gradetype'] = GRADE_TYPE_VALUE;
2299         $params['grademax']  = $assignment->grade;
2300         $params['grademin']  = 0;
2302     } else if ($assignment->grade < 0) {
2303         $params['gradetype'] = GRADE_TYPE_SCALE;
2304         $params['scaleid']   = -$assignment->grade;
2306     } else {
2307         $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
2308     }
2310     if ($grades  === 'reset') {
2311         $params['reset'] = true;
2312         $grades = NULL;
2313     }
2315     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
2318 /**
2319  * Delete grade item for given assignment
2320  *
2321  * @param object $assignment object
2322  * @return object assignment
2323  */
2324 function assignment_grade_item_delete($assignment) {
2325     global $CFG;
2326     require_once($CFG->libdir.'/gradelib.php');
2328     if (!isset($assignment->courseid)) {
2329         $assignment->courseid = $assignment->course;
2330     }
2332     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
2335 /**
2336  * Returns the users with data in one assignment (students and teachers)
2337  *
2338  * @param $assignmentid int
2339  * @return array of user objects
2340  */
2341 function assignment_get_participants($assignmentid) {
2342     global $CFG, $DB;
2344     //Get students
2345     $students = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
2346                                         FROM {user} u,
2347                                              {assignment_submissions} a
2348                                        WHERE a.assignment = ? and
2349                                              u.id = a.userid", array($assignmentid));
2350     //Get teachers
2351     $teachers = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
2352                                         FROM {user} u,
2353                                              {assignment_submissions} a
2354                                        WHERE a.assignment = ? and
2355                                              u.id = a.teacher", array($assignmentid));
2357     //Add teachers to students
2358     if ($teachers) {
2359         foreach ($teachers as $teacher) {
2360             $students[$teacher->id] = $teacher;
2361         }
2362     }
2363     //Return students array (it contains an array of unique users)
2364     return ($students);
2367 function assignment_pluginfile($course, $cminfo, $context, $filearea, $args) {
2368     global $CFG, $DB;
2370     if (!$assignment = $DB->get_record('assignment', array('id'=>$cminfo->instance))) {
2371         return false;
2372     }
2373     if (!$cm = get_coursemodule_from_instance('assignment', $assignment->id, $course->id)) {
2374         return false;
2375     }
2377     require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
2378     $assignmentclass = 'assignment_'.$assignment->assignmenttype;
2379     $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
2381     return $assignmentinstance->send_file($filearea, $args);
2383 /**
2384  * Checks if a scale is being used by an assignment
2385  *
2386  * This is used by the backup code to decide whether to back up a scale
2387  * @param $assignmentid int
2388  * @param $scaleid int
2389  * @return boolean True if the scale is used by the assignment
2390  */
2391 function assignment_scale_used($assignmentid, $scaleid) {
2392     global $DB;
2394     $return = false;
2396     $rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
2398     if (!empty($rec) && !empty($scaleid)) {
2399         $return = true;
2400     }
2402     return $return;
2405 /**
2406  * Checks if scale is being used by any instance of assignment
2407  *
2408  * This is used to find out if scale used anywhere
2409  * @param $scaleid int
2410  * @return boolean True if the scale is used by any assignment
2411  */
2412 function assignment_scale_used_anywhere($scaleid) {
2413     global $DB;
2415     if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
2416         return true;
2417     } else {
2418         return false;
2419     }
2422 /**
2423  * Make sure up-to-date events are created for all assignment instances
2424  *
2425  * This standard function will check all instances of this module
2426  * and make sure there are up-to-date events created for each of them.
2427  * If courseid = 0, then every assignment event in the site is checked, else
2428  * only assignment events belonging to the course specified are checked.
2429  * This function is used, in its new format, by restore_refresh_events()
2430  *
2431  * @param $courseid int optional If zero then all assignments for all courses are covered
2432  * @return boolean Always returns true
2433  */
2434 function assignment_refresh_events($courseid = 0) {
2435     global $DB;
2437     if ($courseid == 0) {
2438         if (! $assignments = $DB->get_records("assignment")) {
2439             return true;
2440         }
2441     } else {
2442         if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
2443             return true;
2444         }
2445     }
2446     $moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
2448     foreach ($assignments as $assignment) {
2449         $event = NULL;
2450         $event->name        = $assignment->name;
2451         $event->description = $assignment->description;
2452         $event->timestart   = $assignment->timedue;
2454         if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
2455             update_event($event);
2457         } else {
2458             $event->courseid    = $assignment->course;
2459             $event->groupid     = 0;
2460             $event->userid      = 0;
2461             $event->modulename  = 'assignment';
2462             $event->instance    = $assignment->id;
2463             $event->eventtype   = 'due';
2464             $event->timeduration = 0;
2465             $event->visible     = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
2466             add_event($event);
2467         }
2469     }
2470     return true;
2473 /**
2474  * Print recent activity from all assignments in a given course
2475  *
2476  * This is used by the recent activity block
2477  */
2478 function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
2479     global $CFG, $USER, $DB;
2481     // do not use log table if possible, it may be huge
2483     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
2484                                                      u.firstname, u.lastname, u.email, u.picture
2485                                                 FROM {assignment_submissions} asb
2486                                                      JOIN {assignment} a      ON a.id = asb.assignment
2487                                                      JOIN {course_modules} cm ON cm.instance = a.id
2488                                                      JOIN {modules} md        ON md.id = cm.module
2489                                                      JOIN {user} u            ON u.id = asb.userid
2490                                                WHERE asb.timemodified > ? AND
2491                                                      a.course = ? AND
2492                                                      md.name = 'assignment'
2493                                             ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
2494          return false;
2495     }
2497     $modinfo =& get_fast_modinfo($course); // reference needed because we might load the groups
2498     $show    = array();
2499     $grader  = array();
2501     foreach($submissions as $submission) {
2502         if (!array_key_exists($submission->cmid, $modinfo->cms)) {
2503             continue;
2504         }
2505         $cm = $modinfo->cms[$submission->cmid];
2506         if (!$cm->uservisible) {
2507             continue;
2508         }
2509         if ($submission->userid == $USER->id) {
2510             $show[] = $submission;
2511             continue;
2512         }
2514         // the act of sumbitting of assignment may be considered private - only graders will see it if specified
2515         if (empty($CFG->assignment_showrecentsubmissions)) {
2516             if (!array_key_exists($cm->id, $grader)) {
2517                 $grader[$cm->id] = has_capability('moodle/grade:viewall', get_context_instance(CONTEXT_MODULE, $cm->id));
2518             }
2519             if (!$grader[$cm->id]) {
2520                 continue;
2521             }
2522         }
2524         $groupmode = groups_get_activity_groupmode($cm, $course);
2526         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2527             if (isguestuser()) {
2528                 // shortcut - guest user does not belong into any group
2529                 continue;
2530             }
2532             if (is_null($modinfo->groups)) {
2533                 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
2534             }
2536             // this will be slow - show only users that share group with me in this cm
2537             if (empty($modinfo->groups[$cm->id])) {
2538                 continue;
2539             }
2540             $usersgroups =  groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
2541             if (is_array($usersgroups)) {
2542                 $usersgroups = array_keys($usersgroups);
2543                 $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
2544                 if (empty($intersect)) {
2545                     continue;
2546                 }
2547             }
2548         }
2549         $show[] = $submission;
2550     }
2552     if (empty($show)) {
2553         return false;
2554     }
2556     print_headline(get_string('newsubmissions', 'assignment').':');
2558     foreach ($show as $submission) {
2559         $cm = $modinfo->cms[$submission->cmid];
2560         $link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
2561         print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
2562     }
2564     return true;
2568 /**
2569  * Returns all assignments since a given time in specified forum.
2570  */
2571 function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0)  {
2572     global $CFG, $COURSE, $USER, $DB;
2574     if ($COURSE->id == $courseid) {
2575         $course = $COURSE;
2576     } else {
2577         $course = $DB->get_record('course', array('id'=>$courseid));
2578     }
2580     $modinfo =& get_fast_modinfo($course);
2582     $cm = $modinfo->cms[$cmid];
2584     $params = array();
2585     if ($userid) {
2586         $userselect = "AND u.id = :userid";
2587         $params['userid'] = $userid;
2588     } else {
2589         $userselect = "";
2590     }
2592     if ($groupid) {
2593         $groupselect = "AND gm.groupid = :groupid";
2594         $groupjoin   = "JOIN {groups_members} gm ON  gm.userid=u.id";
2595         $params['groupid'] = $groupid;
2596     } else {
2597         $groupselect = "";
2598         $groupjoin   = "";
2599     }
2601     $params['cminstance'] = $cm->instance;
2602     $params['timestart'] = $timestart;
2604     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, asb.userid,
2605                                                      u.firstname, u.lastname, u.email, u.picture
2606                                                 FROM {assignment_submissions} asb
2607                                                 JOIN {assignment} a      ON a.id = asb.assignment
2608                                                 JOIN {user} u            ON u.id = asb.userid
2609                                           $groupjoin
2610                                                WHERE asb.timemodified > :timestart AND a.id = :cminstance
2611                                                      $userselect $groupselect
2612                                             ORDER BY asb.timemodified ASC", $params)) {
2613          return;
2614     }
2616     $groupmode       = groups_get_activity_groupmode($cm, $course);
2617     $cm_context      = get_context_instance(CONTEXT_MODULE, $cm->id);
2618     $grader          = has_capability('moodle/grade:viewall', $cm_context);
2619     $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
2620     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cm_context);
2622     if (is_null($modinfo->groups)) {
2623         $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
2624     }
2626     $show = array();
2628     foreach($submissions as $submission) {
2629         if ($submission->userid == $USER->id) {
2630             $show[] = $submission;
2631             continue;
2632         }
2633         // the act of submitting of assignment may be considered private - only graders will see it if specified
2634         if (empty($CFG->assignment_showrecentsubmissions)) {
2635             if (!$grader) {
2636                 continue;
2637             }
2638         }
2640         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
2641             if (isguestuser()) {
2642                 // shortcut - guest user does not belong into any group
2643                 continue;
2644             }
2646             // this will be slow - show only users that share group with me in this cm
2647             if (empty($modinfo->groups[$cm->id])) {
2648                 continue;
2649             }
2650             $usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
2651             if (is_array($usersgroups)) {
2652                 $usersgroups = array_keys($usersgroups);
2653                 $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
2654                 if (empty($intersect)) {
2655                     continue;
2656                 }
2657             }
2658         }
2659         $show[] = $submission;
2660     }
2662     if (empty($show)) {
2663         return;
2664     }
2666     if ($grader) {
2667         require_once($CFG->libdir.'/gradelib.php');
2668         $userids = array();
2669         foreach ($show as $id=>$submission) {
2670             $userids[] = $submission->userid;
2672         }
2673         $grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
2674     }
2676     $aname = format_string($cm->name,true);
2677     foreach ($show as $submission) {
2678         $tmpactivity = new object();
2680         $tmpactivity->type         = 'assignment';
2681         $tmpactivity->cmid         = $cm->id;
2682         $tmpactivity->name         = $aname;
2683         $tmpactivity->sectionnum   = $cm->sectionnum;
2684         $tmpactivity->timestamp    = $submission->timemodified;
2686         if ($grader) {
2687             $tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
2688         }
2690         $tmpactivity->user->userid   = $submission->userid;
2691         $tmpactivity->user->fullname = fullname($submission, $viewfullnames);
2692         $tmpactivity->user->picture  = $submission->picture;
2694         $activities[$index++] = $tmpactivity;
2695     }
2697     return;
2700 /**
2701  * Print recent activity from all assignments in a given course
2702  *
2703  * This is used by course/recent.php
2704  */
2705 function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames)  {
2706     global $CFG;
2708     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
2710     echo "<tr><td class=\"userpicture\" valign=\"top\">";
2711     print_user_picture($activity->user->userid, $courseid, $activity->user->picture);
2712     echo "</td><td>";
2714     if ($detail) {
2715         $modname = $modnames[$activity->type];
2716         echo '<div class="title">';
2717         echo "<img src=\"$CFG->modpixpath/assignment/icon.gif\" ".
2718              "class=\"icon\" alt=\"$modname\">";
2719         echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id={$activity->cmid}\">{$activity->name}</a>";
2720         echo '</div>';
2721     }
2723     if (isset($activity->grade)) {
2724         echo '<div class="grade">';
2725         echo get_string('grade').': ';
2726         echo $activity->grade;
2727         echo '</div>';
2728     }
2730     echo '<div class="user">';
2731     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->userid}&amp;course=$courseid\">"
2732          ."{$activity->user->fullname}</a>  - ".userdate($activity->timestamp);
2733     echo '</div>';
2735     echo "</td></tr></table>";
2738 /// GENERIC SQL FUNCTIONS
2740 /**
2741  * Fetch info from logs
2742  *
2743  * @param $log object with properties ->info (the assignment id) and ->userid
2744  * @return array with assignment name and user firstname and lastname
2745  */
2746 function assignment_log_info($log) {
2747     global $CFG, $DB;
2749     return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
2750                                   FROM {assignment} a, {user} u
2751                                  WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
2754 /**
2755  * Return list of marked submissions that have not been mailed out for currently enrolled students
2756  *
2757  * @return array
2758  */
2759 function assignment_get_unmailed_submissions($starttime, $endtime) {
2760     global $CFG, $DB;
2762     return $DB->get_records_sql("SELECT s.*, a.course, a.name
2763                                    FROM {assignment_submissions} s,
2764                                         {assignment} a
2765                                   WHERE s.mailed = 0
2766                                         AND s.timemarked <= ?
2767                                         AND s.timemarked >= ?
2768                                         AND s.assignment = a.id", array($endtime, $starttime));
2771 /**
2772  * Counts all real assignment submissions by ENROLLED students (not empty ones)
2773  *
2774  * There are also assignment type methods count_real_submissions() wich in the default
2775  * implementation simply call this function.
2776  * @param $groupid int optional If nonzero then count is restricted to this group
2777  * @return int The number of submissions
2778  */
2779 function assignment_count_real_submissions($cm, $groupid=0) {
2780     global $CFG, $DB;
2782     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2784     // this is all the users with this capability set, in this context or higher
2785     if ($users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id', '', '', '', $groupid, '', false)) {
2786         $users = array_keys($users);
2787     }
2789     // if groupmembersonly used, remove users who are not in any group
2790     if ($users and !empty($CFG->enablegroupings) and $cm->groupmembersonly) {
2791         if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
2792             $users = array_intersect($users, array_keys($groupingusers));
2793         }
2794     }
2796     if (empty($users)) {
2797         return 0;
2798     }
2800     $userlists = implode(',', $users);
2802     return $DB->count_records_sql("SELECT COUNT('x')
2803                                      FROM {assignment_submissions}
2804                                     WHERE assignment = ? AND
2805                                           timemodified > 0 AND
2806                                           userid IN ($userlists)", array($cm->instance));
2810 /**
2811  * Return all assignment submissions by ENROLLED students (even empty)
2812  *
2813  * There are also assignment type methods get_submissions() wich in the default
2814  * implementation simply call this function.
2815  * @param $sort string optional field names for the ORDER BY in the sql query
2816  * @param $dir string optional specifying the sort direction, defaults to DESC
2817  * @return array The submission objects indexed by id
2818  */
2819 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2820 /// Return all assignment submissions by ENROLLED students (even empty)
2821     global $CFG, $DB;
2823     if ($sort == "lastname" or $sort == "firstname") {
2824         $sort = "u.$sort $dir";
2825     } else if (empty($sort)) {
2826         $sort = "a.timemodified DESC";
2827     } else {
2828         $sort = "a.$sort $dir";
2829     }
2831     /* not sure this is needed at all since assignmenet already has a course define, so this join?
2832     $select = "s.course = '$assignment->course' AND";
2833     if ($assignment->course == SITEID) {
2834         $select = '';
2835     }*/
2837     return $DB->get_records_sql("SELECT a.*
2838                                    FROM {assignment_submissions} a, {user} u
2839                                   WHERE u.id = a.userid
2840                                         AND a.assignment = ?
2841                                ORDER BY $sort", array($assignment->id));
2845 /**
2846  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2847  * for the course (see resource).
2848  *
2849  * Given a course_module object, this function returns any "extra" information that may be needed
2850  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
2851  *
2852  * @param $coursemodule object The coursemodule object (record).
2853  * @return object An object on information that the coures will know about (most noticeably, an icon).
2854  *
2855  */
2856 function assignment_get_coursemodule_info($coursemodule) {
2857     global $CFG, $DB;
2859     if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance), 'id, assignmenttype, name')) {
2860         return false;
2861     }
2863     $libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
2865     if (file_exists($libfile)) {
2866         require_once($libfile);
2867         $assignmentclass = "assignment_$assignment->assignmenttype";
2868         $ass = new $assignmentclass('staticonly');
2869         if ($result = $ass->get_coursemodule_info($coursemodule)) {
2870             return $result;
2871         } else {
2872             $info = new object();
2873             $info->name = $assignment->name;
2874             return $info;
2875         }
2877     } else {
2878         debugging('Incorrect assignment type: '.$assignment->assignmenttype);
2879         return false;
2880     }
2885 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS  ///////////////////////////////////////
2887 /**
2888  * Returns an array of installed assignment types indexed and sorted by name
2889  *
2890  * @return array The index is the name of the assignment type, the value its full name from the language strings
2891  */
2892 function assignment_types() {
2893     $types = array();
2894     $names = get_list_of_plugins('mod/assignment/type');
2895     foreach ($names as $name) {
2896         $types[$name] = get_string('type'.$name, 'assignment');
2897     }
2898     asort($types);
2899     return $types;
2902 /**
2903  * Executes upgrade scripts for assignment types when necessary
2904  */
2905 function assignment_upgrade_submodules() {
2906     global $CFG;
2908 /// Install/upgrade assignment types (it uses, simply, the standard plugin architecture)
2909     upgrade_plugins('assignment_type', 'mod/assignment/type', "$CFG->wwwroot/$CFG->admin/index.php");
2913 function assignment_print_overview($courses, &$htmlarray) {
2914     global $USER, $CFG, $DB;
2916     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
2917         return array();
2918     }
2920     if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2921         return;
2922     }
2924     // Do assignment_base::isopen() here without loading the whole thing for speed
2925     foreach ($assignments as $key => $assignment) {
2926         $time = time();
2927         if ($assignment->timedue) {
2928             if ($assignment->preventlate) {
2929                 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
2930             } else {
2931                 $isopen = ($assignment->timeavailable <= $time);
2932             }
2933         }
2934         if (empty($isopen) || empty($assignment->timedue)) {
2935             unset($assignments[$key]);
2936         }
2937     }
2939     $strduedate = get_string('duedate', 'assignment');
2940     $strduedateno = get_string('duedateno', 'assignment');
2941     $strgraded = get_string('graded', 'assignment');
2942     $strnotgradedyet = get_string('notgradedyet', 'assignment');
2943     $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2944     $strsubmitted = get_string('submitted', 'assignment');
2945     $strassignment = get_string('modulename', 'assignment');
2946     $strreviewed = get_string('reviewed','assignment');
2948     foreach ($assignments as $assignment) {
2949         $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
2950                '<a '.($assignment->visible ? '':' class="dimmed"').
2951                'title="'.$strassignment.'" href="'.$CFG->wwwroot.
2952                '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
2953                $assignment->name.'</a></div>';
2954         if ($assignment->timedue) {
2955             $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
2956         } else {
2957             $str .= '<div class="info">'.$strduedateno.'</div>';
2958         }
2959         $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
2960         if (has_capability('mod/assignment:grade', $context)) {
2962             // count how many people can submit
2963             $submissions = 0; // init
2964             if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2965                  foreach ($students as $student) {
2966                     if ($DB->record_exists_sql("SELECT id
2967                                                   FROM {assignment_submissions}
2968                                                  WHERE assignment = ? AND
2969                                                        userid = ? AND
2970                                                        teacher = 0 AND
2971                                                        timemarked = 0", array($assignment->id, $student->id))) {
2972                         $submissions++;
2973                     }
2974                 }
2975             }
2977             if ($submissions) {
2978                 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2979             }
2980         } else {
2981             $sql = "SELECT *
2982                       FROM {assignment_submissions}
2983                      WHERE userid = ?
2984                        AND assignment = ?";
2985             $params = array($USER->id, $assignment->id);
2986             if ($submission = $DB->get_record_sql($sql, $params)) {
2987                 if ($submission->teacher == 0 && $submission->timemarked == 0) {
2988                     $str .= $strsubmitted . ', ' . $strnotgradedyet;
2989                 } else if ($submission->grade <= 0) {
2990                     $str .= $strsubmitted . ', ' . $strreviewed;
2991                 } else {
2992                     $str .= $strsubmitted . ', ' . $strgraded;
2993                 }
2994             } else {
2995                 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
2996             }
2997         }
2998         $str .= '</div>';
2999         if (empty($htmlarray[$assignment->course]['assignment'])) {
3000             $htmlarray[$assignment->course]['assignment'] = $str;
3001         } else {
3002             $htmlarray[$assignment->course]['assignment'] .= $str;
3003         }
3004     }
3007 function assignment_display_lateness($timesubmitted, $timedue) {
3008     if (!$timedue) {
3009         return '';
3010     }
3011     $time = $timedue - $timesubmitted;
3012     if ($time < 0) {
3013         $timetext = get_string('late', 'assignment', format_time($time));
3014         return ' (<span class="late">'.$timetext.'</span>)';
3015     } else {
3016         $timetext = get_string('early', 'assignment', format_time($time));
3017         return ' (<span class="early">'.$timetext.'</span>)';
3018     }
3021 function assignment_get_view_actions() {
3022     return array('view');
3025 function assignment_get_post_actions() {
3026     return array('upload');
3029 function assignment_get_types() {
3030     global $CFG;
3031     $types = array();
3033     $type = new object();
3034     $type->modclass = MOD_CLASS_ACTIVITY;
3035     $type->type = "assignment_group_start";
3036     $type->typestr = '--'.get_string('modulenameplural', 'assignment');
3037     $types[] = $type;
3039     $standardassignments = array('upload','online','uploadsingle','offline');
3040     foreach ($standardassignments as $assignmenttype) {
3041         $type = new object();
3042         $type->modclass = MOD_CLASS_ACTIVITY;
3043         $type->type = "assignment&amp;type=$assignmenttype";
3044         $type->typestr = get_string("type$assignmenttype", 'assignment');
3045         $types[] = $type;
3046     }
3048     /// Drop-in extra assignment types
3049     $assignmenttypes = get_list_of_plugins('mod/assignment/type');
3050     foreach ($assignmenttypes as $assignmenttype) {
3051         if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) {  // Not wanted
3052             continue;
3053         }
3054         if (!in_array($assignmenttype, $standardassignments)) {
3055             $type = new object();
3056             $type->modclass = MOD_CLASS_ACTIVITY;
3057             $type->type = "assignment&amp;type=$assignmenttype";
3058             $type->typestr = get_string("type$assignmenttype", 'assignment');
3059             $types[] = $type;
3060         }
3061     }
3063     $type = new object();
3064     $type->modclass = MOD_CLASS_ACTIVITY;
3065     $type->type = "assignment_group_end";
3066     $type->typestr = '--';
3067     $types[] = $type;
3069     return $types;
3072 /**
3073  * Removes all grades from gradebook
3074  * @param int $courseid
3075  * @param string optional type
3076  */
3077 function assignment_reset_gradebook($courseid, $type='') {
3078     global $CFG, $DB;
3080     $params = array('courseid'=>$courseid);
3081     if ($type) {
3082         $type = "AND a.assignmenttype= :type";
3083         $params['type'] = $type;
3084     }
3086     $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
3087               FROM {assignment} a, {course_modules} cm, {modules} m
3088              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid $type";
3090     if ($assignments = $DB->get_records_sql($sql, $params)) {
3091         foreach ($assignments as $assignment) {
3092             assignment_grade_item_update($assignment, 'reset');
3093         }
3094     }
3097 /**
3098  * This function is used by the reset_course_userdata function in moodlelib.
3099  * This function will remove all posts from the specified assignment
3100  * and clean up any related data.
3101  * @param $data the data submitted from the reset course.
3102  * @return array status array
3103  */
3104 function assignment_reset_userdata($data) {
3105     global $CFG;
3107     $status = array();
3109     foreach (get_list_of_plugins('mod/assignment/type') as $type) {
3110         require_once("$CFG->dirroot/mod/assignment/type/$type/assignment.class.php");
3111         $assignmentclass = "assignment_$type";
3112         $ass = new $assignmentclass();
3113         $status = array_merge($status, $ass->reset_userdata($data));
3114     }
3116     return $status;
3119 /**
3120  * Implementation of the function for printing the form elements that control
3121  * whether the course reset functionality affects the assignment.
3122  * @param $mform form passed by reference
3123  */
3124 function assignment_reset_course_form_definition(&$mform) {
3125     $mform->addElement('header', 'assignmentheader', get_string('modulenameplural', 'assignment'));
3126     $mform->addElement('advcheckbox', 'reset_assignment_submissions', get_string('deleteallsubmissions','assignment'));
3129 /**
3130  * Course reset form defaults.
3131  */
3132 function assignment_reset_course_form_defaults($course) {
3133     return array('reset_assignment_submissions'=>1);
3136 /**
3137  * Returns all other caps used in module
3138  */
3139 function assignment_get_extra_capabilities() {
3140     return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames');
3143 require_once($CFG->libdir . '/portfoliolib.php');
3144 class assignment_portfolio_caller extends portfolio_module_caller_base {
3146     private $assignment;
3147     private $assignmentfile;
3148     private $files;
3149     protected $fileid;
3151     public static function expected_callbackargs() {
3152         return array(
3153             'id'     => true,
3154             'fileid' => false,
3155         );
3156     }
3158     public function load_data() {
3159         global $DB, $CFG;
3161         if (! $this->cm = get_coursemodule_from_id('assignment', $this->id)) {
3162             throw new portfolio_caller_exception('invalidcoursemodule');
3163         }
3165         if (! $assignment = $DB->get_record("assignment", array("id"=>$this->cm->instance))) {
3166             throw new portfolio_caller_exception('invalidid', 'assignment');
3167         }
3169         $this->assignmentfile = $CFG->dirroot . '/mod/assignment/type/' . $assignment->assignmenttype . '/assignment.class.php';
3170         require_once($this->assignmentfile);
3171         $assignmentclass = "assignment_$assignment->assignmenttype";
3173         $this->assignment = new $assignmentclass($this->cm->id, $assignment, $this->cm);
3175         if (!$this->assignment->portfolio_exportable()) {
3176             throw new portfolio_caller_exception('notexportable', 'portfolio', $this->get_return_url());
3177         }
3179         $fs = get_file_storage();
3180         if ($this->fileid) {
3181             $f = $fs->get_file_by_id($this->fileid);
3182             $this->files = array($f);
3183         } else {
3184             $this->files = $fs->get_area_files($this->assignment->context->id, 'assignment_submission', $this->user->id, '', false);
3185         }
3186         if (is_array($this->files) && count($this->files) == 1) {
3187             $f = array_values($this->files);
3188             $this->supportedformats = array(portfolio_format_from_file($f[0]));
3189         }
3190         if (empty($this->supportedformats) && is_callable(array($this->assignment, 'portfolio_supported_formats'))) {
3191             $this->supportedformats = $this->assignment->portfolio_supported_formats();
3192         }
3193     }
3195     public function prepare_package() {
3196         global $CFG;
3197         if (is_callable(array($this->assignment, 'portfolio_prepare_package'))) {
3198             return $this->assignment->portfolio_prepare_package($this->exporter, $this->user->id);
3199         }
3200         foreach ($this->files as $file) {
3201             $this->exporter->copy_existing_file($file);
3202         }
3203     }
3205     public function get_sha1() {
3206         global $CFG;
3207         if (is_callable(array($this->assignment, 'portfolio_get_sha1'))) {
3208             return $this->assignment->portfolio_get_sha1($this->user->id);
3209         }
3210         $sha1s = array();
3211         foreach ($this->files as $file) {
3212             $sha1s[] = $file->get_contenthash();
3213         }
3214         asort($sha1s);
3215         return sha1(implode('', $sha1s));
3217     }
3219     public function expected_time() {
3220         if (is_callable(array($this->assignment, 'portfolio_get_expected_time'))) {
3221             return $this->assignment->portfolio_get_expected_time();
3222         }
3223         if (is_array($this->files)) {
3224             return portfolio_expected_time_file($this->files);
3225         }
3226         return PORTFOLIO_TIME_LOW;
3227     }
3229     public function check_permissions() {
3230         $context = get_context_instance(CONTEXT_MODULE, $this->assignment->cm->id);
3231         return has_capability('mod/assignment:exportownsubmission', $context);
3232     }
3234     public function __wakeup() {
3235         global $CFG;
3236         if (empty($CFG)) {
3237             return true; // too early yet
3238         }
3239         require_once($this->assignmentfile);
3240         $this->assignment = unserialize(serialize($this->assignment));
3241     }
3243     public static function display_name() {
3244         return get_string('modulename', 'assignment');
3245     }
3248 /**
3249  * @param string $feature FEATURE_xx constant for requested feature
3250  * @return mixed True if module supports feature, null if doesn't know
3251  */
3252 function assignment_supports($feature) {
3253     switch($feature) {
3254         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3255         case FEATURE_GRADE_HAS_GRADE: return true;
3256         default: return null;
3257     }
3259 ?>