d7115e6e318d811162f033b2fbc6b865c8b03074
[moodle.git] / mod / assignment / lib.php
1 <?PHP
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * assignment_base is the base class for assignment types
20  *
21  * This class provides all the functionality for an assignment
22  *
23  * @package   mod-assignment
24  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
25  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 /** Include eventslib.php */
29 require_once($CFG->libdir.'/eventslib.php');
30 /** Include formslib.php */
31 require_once($CFG->libdir.'/formslib.php');
32 /** Include calendar/lib.php */
33 require_once($CFG->dirroot.'/calendar/lib.php');
35 /** ASSIGNMENT_COUNT_WORDS = 1 */
36 define('ASSIGNMENT_COUNT_WORDS', 1);
37 /** ASSIGNMENT_COUNT_LETTERS = 2 */
38 define('ASSIGNMENT_COUNT_LETTERS', 2);
40 /**
41  * Standard base class for all assignment submodules (assignment types).
42  *
43  * @package   mod-assignment
44  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
45  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class assignment_base {
49     const FILTER_ALL             = 0;
50     const FILTER_SUBMITTED       = 1;
51     const FILTER_REQUIRE_GRADING = 2;
53     /** @var object */
54     var $cm;
55     /** @var object */
56     var $course;
57     /** @var stdClass */
58     var $coursecontext;
59     /** @var object */
60     var $assignment;
61     /** @var string */
62     var $strassignment;
63     /** @var string */
64     var $strassignments;
65     /** @var string */
66     var $strsubmissions;
67     /** @var string */
68     var $strlastmodified;
69     /** @var string */
70     var $pagetitle;
71     /** @var bool */
72     var $usehtmleditor;
73     /**
74      * @todo document this var
75      */
76     var $defaultformat;
77     /**
78      * @todo document this var
79      */
80     var $context;
81     /** @var string */
82     var $type;
84     /**
85      * Constructor for the base assignment class
86      *
87      * Constructor for the base assignment class.
88      * If cmid is set create the cm, course, assignment objects.
89      * If the assignment is hidden and the user is not a teacher then
90      * this prints a page header and notice.
91      *
92      * @global object
93      * @global object
94      * @param int $cmid the current course module id - not set for new assignments
95      * @param object $assignment usually null, but if we have it we pass it to save db access
96      * @param object $cm usually null, but if we have it we pass it to save db access
97      * @param object $course usually null, but if we have it we pass it to save db access
98      */
99     function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
100         global $COURSE, $DB;
102         if ($cmid == 'staticonly') {
103             //use static functions only!
104             return;
105         }
107         global $CFG;
109         if ($cm) {
110             $this->cm = $cm;
111         } else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
112             print_error('invalidcoursemodule');
113         }
115         $this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
117         if ($course) {
118             $this->course = $course;
119         } else if ($this->cm->course == $COURSE->id) {
120             $this->course = $COURSE;
121         } else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
122             print_error('invalidid', 'assignment');
123         }
124         $this->coursecontext = get_context_instance(CONTEXT_COURSE, $this->course->id);
125         $courseshortname = format_text($this->course->shortname, true, array('context' => $this->coursecontext));
127         if ($assignment) {
128             $this->assignment = $assignment;
129         } else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
130             print_error('invalidid', 'assignment');
131         }
133         $this->assignment->cmidnumber = $this->cm->idnumber; // compatibility with modedit assignment obj
134         $this->assignment->courseid   = $this->course->id; // compatibility with modedit assignment obj
136         $this->strassignment = get_string('modulename', 'assignment');
137         $this->strassignments = get_string('modulenameplural', 'assignment');
138         $this->strsubmissions = get_string('submissions', 'assignment');
139         $this->strlastmodified = get_string('lastmodified');
140         $this->pagetitle = strip_tags($courseshortname.': '.$this->strassignment.': '.format_string($this->assignment->name, true, array('context' => $this->context)));
142         // visibility handled by require_login() with $cm parameter
143         // get current group only when really needed
145     /// Set up things for a HTML editor if it's needed
146         $this->defaultformat = editors_get_preferred_format();
147     }
149     /**
150      * Display the assignment, used by view.php
151      *
152      * This in turn calls the methods producing individual parts of the page
153      */
154     function view() {
156         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
157         require_capability('mod/assignment:view', $context);
159         add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
160                    $this->assignment->id, $this->cm->id);
162         $this->view_header();
164         $this->view_intro();
166         $this->view_dates();
168         $this->view_feedback();
170         $this->view_footer();
171     }
173     /**
174      * Display the header and top of a page
175      *
176      * (this doesn't change much for assignment types)
177      * This is used by the view() method to print the header of view.php but
178      * it can be used on other pages in which case the string to denote the
179      * page in the navigation trail should be passed as an argument
180      *
181      * @global object
182      * @param string $subpage Description of subpage to be used in navigation trail
183      */
184     function view_header($subpage='') {
185         global $CFG, $PAGE, $OUTPUT;
187         if ($subpage) {
188             $PAGE->navbar->add($subpage);
189         }
191         $PAGE->set_title($this->pagetitle);
192         $PAGE->set_heading($this->course->fullname);
194         echo $OUTPUT->header();
196         groups_print_activity_menu($this->cm, $CFG->wwwroot . '/mod/assignment/view.php?id=' . $this->cm->id);
198         echo '<div class="reportlink">'.$this->submittedlink().'</div>';
199         echo '<div class="clearer"></div>';
200         if (has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM))) {
201             echo $OUTPUT->notification(get_string('upgradenotification', 'assignment'));
202             $adminurl = new moodle_url('/admin/tool/assignmentupgrade/listnotupgraded.php');
203             echo $OUTPUT->single_button($adminurl, get_string('viewassignmentupgradetool', 'assignment'));
204         }
205     }
208     /**
209      * Display the assignment intro
210      *
211      * This will most likely be extended by assignment type plug-ins
212      * The default implementation prints the assignment description in a box
213      */
214     function view_intro() {
215         global $OUTPUT;
216         echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
217         echo format_module_intro('assignment', $this->assignment, $this->cm->id);
218         echo $OUTPUT->box_end();
219         echo plagiarism_print_disclosure($this->cm->id);
220     }
222     /**
223      * Display the assignment dates
224      *
225      * Prints the assignment start and end dates in a box.
226      * This will be suitable for most assignment types
227      */
228     function view_dates() {
229         global $OUTPUT;
230         if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
231             return;
232         }
234         echo $OUTPUT->box_start('generalbox boxaligncenter', 'dates');
235         echo '<table>';
236         if ($this->assignment->timeavailable) {
237             echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
238             echo '    <td class="c1">'.userdate($this->assignment->timeavailable).'</td></tr>';
239         }
240         if ($this->assignment->timedue) {
241             echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
242             echo '    <td class="c1">'.userdate($this->assignment->timedue).'</td></tr>';
243         }
244         echo '</table>';
245         echo $OUTPUT->box_end();
246     }
249     /**
250      * Display the bottom and footer of a page
251      *
252      * This default method just prints the footer.
253      * This will be suitable for most assignment types
254      */
255     function view_footer() {
256         global $OUTPUT;
257         echo $OUTPUT->footer();
258     }
260     /**
261      * Display the feedback to the student
262      *
263      * This default method prints the teacher picture and name, date when marked,
264      * grade and teacher submissioncomment.
265      * If advanced grading is used the method render_grade from the
266      * advanced grading controller is called to display the grade.
267      *
268      * @global object
269      * @global object
270      * @global object
271      * @param object $submission The submission object or NULL in which case it will be loaded
272      */
273     function view_feedback($submission=NULL) {
274         global $USER, $CFG, $DB, $OUTPUT, $PAGE;
275         require_once($CFG->libdir.'/gradelib.php');
276         require_once("$CFG->dirroot/grade/grading/lib.php");
278         if (!$submission) { /// Get submission for this assignment
279             $userid = $USER->id;
280             $submission = $this->get_submission($userid);
281         } else {
282             $userid = $submission->userid;
283         }
284         // Check the user can submit
285         $canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
286         // If not then check if the user still has the view cap and has a previous submission
287         $canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
288         // Or if user can grade (is a teacher or admin)
289         $canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
291         if (!$canviewfeedback) {
292             // can not view or submit assignments -> no feedback
293             return;
294         }
296         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
297         $item = $grading_info->items[0];
298         $grade = $item->grades[$userid];
300         if ($grade->hidden or $grade->grade === false) { // hidden or error
301             return;
302         }
304         if ($grade->grade === null and empty($grade->str_feedback)) {   /// Nothing to show yet
305             return;
306         }
308         $graded_date = $grade->dategraded;
309         $graded_by   = $grade->usermodified;
311     /// We need the teacher info
312         if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
313             print_error('cannotfindteacher');
314         }
316     /// Print the feedback
317         echo $OUTPUT->heading(get_string('feedbackfromteacher', 'assignment', fullname($teacher)));
319         echo '<table cellspacing="0" class="feedback">';
321         echo '<tr>';
322         echo '<td class="left picture">';
323         if ($teacher) {
324             echo $OUTPUT->user_picture($teacher);
325         }
326         echo '</td>';
327         echo '<td class="topic">';
328         echo '<div class="from">';
329         if ($teacher) {
330             echo '<div class="fullname">'.fullname($teacher).'</div>';
331         }
332         echo '<div class="time">'.userdate($graded_date).'</div>';
333         echo '</div>';
334         echo '</td>';
335         echo '</tr>';
337         echo '<tr>';
338         echo '<td class="left side">&nbsp;</td>';
339         echo '<td class="content">';
340         $gradestr = '<div class="grade">'. get_string("grade").': '.$grade->str_long_grade. '</div>';
341         if (!empty($submission) && $controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
342             $controller->set_grade_range(make_grades_menu($this->assignment->grade));
343             echo $controller->render_grade($PAGE, $submission->id, $item, $gradestr, has_capability('mod/assignment:grade', $this->context));
344         } else {
345             echo $gradestr;
346         }
347         echo '<div class="clearer"></div>';
349         echo '<div class="comment">';
350         echo $grade->str_feedback;
351         echo '</div>';
352         echo '</tr>';
354          if ($this->type == 'uploadsingle') { //@TODO: move to overload view_feedback method in the class or is uploadsingle merging into upload?
355             $responsefiles = $this->print_responsefiles($submission->userid, true);
356             if (!empty($responsefiles)) {
357                 echo '<tr>';
358                 echo '<td class="left side">&nbsp;</td>';
359                 echo '<td class="content">';
360                 echo $responsefiles;
361                 echo '</tr>';
362             }
363          }
365         echo '</table>';
366     }
368     /**
369      * Returns a link with info about the state of the assignment submissions
370      *
371      * This is used by view_header to put this link at the top right of the page.
372      * For teachers it gives the number of submitted assignments with a link
373      * For students it gives the time of their submission.
374      * This will be suitable for most assignment types.
375      *
376      * @global object
377      * @global object
378      * @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
379      * @return string
380      */
381     function submittedlink($allgroups=false) {
382         global $USER;
383         global $CFG;
385         $submitted = '';
386         $urlbase = "{$CFG->wwwroot}/mod/assignment/";
388         $context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
389         if (has_capability('mod/assignment:grade', $context)) {
390             if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
391                 $group = 0;
392             } else {
393                 $group = groups_get_activity_group($this->cm);
394             }
395             if ($this->type == 'offline') {
396                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
397                              get_string('viewfeedback', 'assignment').'</a>';
398             } else if ($count = $this->count_real_submissions($group)) {
399                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
400                              get_string('viewsubmissions', 'assignment', $count).'</a>';
401             } else {
402                 $submitted = '<a href="'.$urlbase.'submissions.php?id='.$this->cm->id.'">'.
403                              get_string('noattempts', 'assignment').'</a>';
404             }
405         } else {
406             if (isloggedin()) {
407                 if ($submission = $this->get_submission($USER->id)) {
408                     // If the submission has been completed
409                     if ($this->is_submitted_with_required_data($submission)) {
410                         if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
411                             $submitted = '<span class="early">'.userdate($submission->timemodified).'</span>';
412                         } else {
413                             $submitted = '<span class="late">'.userdate($submission->timemodified).'</span>';
414                         }
415                     }
416                 }
417             }
418         }
420         return $submitted;
421     }
423     /**
424      * Returns whether the assigment supports lateness information
425      *
426      * @return bool This assignment type supports lateness (true, default) or no (false)
427      */
428     function supports_lateness() {
429         return true;
430     }
432     /**
433      * @todo Document this function
434      */
435     function setup_elements(&$mform) {
437     }
439     /**
440      * Any preprocessing needed for the settings form for
441      * this assignment type
442      *
443      * @param array $default_values - array to fill in with the default values
444      *      in the form 'formelement' => 'value'
445      * @param object $form - the form that is to be displayed
446      * @return none
447      */
448     function form_data_preprocessing(&$default_values, $form) {
449     }
451     /**
452      * Any extra validation checks needed for the settings
453      * form for this assignment type
454      *
455      * See lib/formslib.php, 'validation' function for details
456      */
457     function form_validation($data, $files) {
458         return array();
459     }
461     /**
462      * Create a new assignment activity
463      *
464      * Given an object containing all the necessary data,
465      * (defined by the form in mod_form.php) this function
466      * will create a new instance and return the id number
467      * of the new instance.
468      * The due data is added to the calendar
469      * This is common to all assignment types.
470      *
471      * @global object
472      * @global object
473      * @param object $assignment The data from the form on mod_form.php
474      * @return int The id of the assignment
475      */
476     function add_instance($assignment) {
477         global $COURSE, $DB;
479         $assignment->timemodified = time();
480         $assignment->courseid = $assignment->course;
482         $returnid = $DB->insert_record("assignment", $assignment);
483         $assignment->id = $returnid;
485         if ($assignment->timedue) {
486             $event = new stdClass();
487             $event->name        = $assignment->name;
488             $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
489             $event->courseid    = $assignment->course;
490             $event->groupid     = 0;
491             $event->userid      = 0;
492             $event->modulename  = 'assignment';
493             $event->instance    = $returnid;
494             $event->eventtype   = 'due';
495             $event->timestart   = $assignment->timedue;
496             $event->timeduration = 0;
498             calendar_event::create($event);
499         }
501         assignment_grade_item_update($assignment);
503         return $returnid;
504     }
506     /**
507      * Deletes an assignment activity
508      *
509      * Deletes all database records, files and calendar events for this assignment.
510      *
511      * @global object
512      * @global object
513      * @param object $assignment The assignment to be deleted
514      * @return boolean False indicates error
515      */
516     function delete_instance($assignment) {
517         global $CFG, $DB;
519         $assignment->courseid = $assignment->course;
521         $result = true;
523         // now get rid of all files
524         $fs = get_file_storage();
525         if ($cm = get_coursemodule_from_instance('assignment', $assignment->id)) {
526             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
527             $fs->delete_area_files($context->id);
528         }
530         if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
531             $result = false;
532         }
534         if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
535             $result = false;
536         }
538         if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
539             $result = false;
540         }
541         $mod = $DB->get_field('modules','id',array('name'=>'assignment'));
543         assignment_grade_item_delete($assignment);
545         return $result;
546     }
548     /**
549      * Updates a new assignment activity
550      *
551      * Given an object containing all the necessary data,
552      * (defined by the form in mod_form.php) this function
553      * will update the assignment instance and return the id number
554      * The due date is updated in the calendar
555      * This is common to all assignment types.
556      *
557      * @global object
558      * @global object
559      * @param object $assignment The data from the form on mod_form.php
560      * @return bool success
561      */
562     function update_instance($assignment) {
563         global $COURSE, $DB;
565         $assignment->timemodified = time();
567         $assignment->id = $assignment->instance;
568         $assignment->courseid = $assignment->course;
570         $DB->update_record('assignment', $assignment);
572         if ($assignment->timedue) {
573             $event = new stdClass();
575             if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
577                 $event->name        = $assignment->name;
578                 $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
579                 $event->timestart   = $assignment->timedue;
581                 $calendarevent = calendar_event::load($event->id);
582                 $calendarevent->update($event);
583             } else {
584                 $event = new stdClass();
585                 $event->name        = $assignment->name;
586                 $event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
587                 $event->courseid    = $assignment->course;
588                 $event->groupid     = 0;
589                 $event->userid      = 0;
590                 $event->modulename  = 'assignment';
591                 $event->instance    = $assignment->id;
592                 $event->eventtype   = 'due';
593                 $event->timestart   = $assignment->timedue;
594                 $event->timeduration = 0;
596                 calendar_event::create($event);
597             }
598         } else {
599             $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
600         }
602         // get existing grade item
603         assignment_grade_item_update($assignment);
605         return true;
606     }
608     /**
609      * Update grade item for this submission.
610      *
611      * @param stdClass $submission The submission instance
612      */
613     function update_grade($submission) {
614         assignment_update_grades($this->assignment, $submission->userid);
615     }
617     /**
618      * Top-level function for handling of submissions called by submissions.php
619      *
620      * This is for handling the teacher interaction with the grading interface
621      * This should be suitable for most assignment types.
622      *
623      * @global object
624      * @param string $mode Specifies the kind of teacher interaction taking place
625      */
626     function submissions($mode) {
627         ///The main switch is changed to facilitate
628         ///1) Batch fast grading
629         ///2) Skip to the next one on the popup
630         ///3) Save and Skip to the next one on the popup
632         //make user global so we can use the id
633         global $USER, $OUTPUT, $DB, $PAGE;
635         $mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
637         if (optional_param('next', null, PARAM_BOOL)) {
638             $mode='next';
639         }
640         if (optional_param('saveandnext', null, PARAM_BOOL)) {
641             $mode='saveandnext';
642         }
644         if (is_null($mailinfo)) {
645             if (optional_param('sesskey', null, PARAM_BOOL)) {
646                 set_user_preference('assignment_mailinfo', 0);
647             } else {
648                 $mailinfo = get_user_preferences('assignment_mailinfo', 0);
649             }
650         } else {
651             set_user_preference('assignment_mailinfo', $mailinfo);
652         }
654         if (!($this->validate_and_preprocess_feedback())) {
655             // form was submitted ('Save' or 'Save and next' was pressed, but validation failed)
656             $this->display_submission();
657             return;
658         }
660         switch ($mode) {
661             case 'grade':                         // We are in a main window grading
662                 if ($submission = $this->process_feedback()) {
663                     $this->display_submissions(get_string('changessaved'));
664                 } else {
665                     $this->display_submissions();
666                 }
667                 break;
669             case 'single':                        // We are in a main window displaying one submission
670                 if ($submission = $this->process_feedback()) {
671                     $this->display_submissions(get_string('changessaved'));
672                 } else {
673                     $this->display_submission();
674                 }
675                 break;
677             case 'all':                          // Main window, display everything
678                 $this->display_submissions();
679                 break;
681             case 'fastgrade':
682                 ///do the fast grading stuff  - this process should work for all 3 subclasses
683                 $grading    = false;
684                 $commenting = false;
685                 $col        = false;
686                 if (isset($_POST['submissioncomment'])) {
687                     $col = 'submissioncomment';
688                     $commenting = true;
689                 }
690                 if (isset($_POST['menu'])) {
691                     $col = 'menu';
692                     $grading = true;
693                 }
694                 if (!$col) {
695                     //both submissioncomment and grade columns collapsed..
696                     $this->display_submissions();
697                     break;
698                 }
700                 foreach ($_POST[$col] as $id => $unusedvalue){
702                     $id = (int)$id; //clean parameter name
704                     $this->process_outcomes($id);
706                     if (!$submission = $this->get_submission($id)) {
707                         $submission = $this->prepare_new_submission($id);
708                         $newsubmission = true;
709                     } else {
710                         $newsubmission = false;
711                     }
712                     unset($submission->data1);  // Don't need to update this.
713                     unset($submission->data2);  // Don't need to update this.
715                     //for fast grade, we need to check if any changes take place
716                     $updatedb = false;
718                     if ($grading) {
719                         $grade = $_POST['menu'][$id];
720                         $updatedb = $updatedb || ($submission->grade != $grade);
721                         $submission->grade = $grade;
722                     } else {
723                         if (!$newsubmission) {
724                             unset($submission->grade);  // Don't need to update this.
725                         }
726                     }
727                     if ($commenting) {
728                         $commentvalue = trim($_POST['submissioncomment'][$id]);
729                         $updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
730                         $submission->submissioncomment = $commentvalue;
731                     } else {
732                         unset($submission->submissioncomment);  // Don't need to update this.
733                     }
735                     $submission->teacher    = $USER->id;
736                     if ($updatedb) {
737                         $submission->mailed = (int)(!$mailinfo);
738                     }
740                     $submission->timemarked = time();
742                     //if it is not an update, we don't change the last modified time etc.
743                     //this will also not write into database if no submissioncomment and grade is entered.
745                     if ($updatedb){
746                         if ($newsubmission) {
747                             if (!isset($submission->submissioncomment)) {
748                                 $submission->submissioncomment = '';
749                             }
750                             $sid = $DB->insert_record('assignment_submissions', $submission);
751                             $submission->id = $sid;
752                         } else {
753                             $DB->update_record('assignment_submissions', $submission);
754                         }
756                         // trigger grade event
757                         $this->update_grade($submission);
759                         //add to log only if updating
760                         add_to_log($this->course->id, 'assignment', 'update grades',
761                                    'submissions.php?id='.$this->cm->id.'&user='.$submission->userid,
762                                    $submission->userid, $this->cm->id);
763                     }
765                 }
767                 $message = $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
769                 $this->display_submissions($message);
770                 break;
773             case 'saveandnext':
774                 ///We are in pop up. save the current one and go to the next one.
775                 //first we save the current changes
776                 if ($submission = $this->process_feedback()) {
777                     //print_heading(get_string('changessaved'));
778                     //$extra_javascript = $this->update_main_listing($submission);
779                 }
781             case 'next':
782                 /// We are currently in pop up, but we want to skip to next one without saving.
783                 ///    This turns out to be similar to a single case
784                 /// The URL used is for the next submission.
785                 $offset = required_param('offset', PARAM_INT);
786                 $nextid = required_param('nextid', PARAM_INT);
787                 $id = required_param('id', PARAM_INT);
788                 $filter = optional_param('filter', self::FILTER_ALL, PARAM_INT);
790                 if ($mode == 'next' || $filter !== self::FILTER_REQUIRE_GRADING) {
791                     $offset = (int)$offset+1;
792                 }
793                 $redirect = new moodle_url('submissions.php',
794                         array('id' => $id, 'offset' => $offset, 'userid' => $nextid,
795                         'mode' => 'single', 'filter' => $filter));
797                 redirect($redirect);
798                 break;
800             case 'singlenosave':
801                 $this->display_submission();
802                 break;
804             default:
805                 echo "something seriously is wrong!!";
806                 break;
807         }
808     }
810     /**
811      * Checks if grading method allows quickgrade mode. At the moment it is hardcoded
812      * that advanced grading methods do not allow quickgrade.
813      *
814      * Assignment type plugins are not allowed to override this method
815      *
816      * @return boolean
817      */
818     public final function quickgrade_mode_allowed() {
819         global $CFG;
820         require_once("$CFG->dirroot/grade/grading/lib.php");
821         if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
822             return false;
823         }
824         return true;
825     }
827     /**
828      * Helper method updating the listing on the main script from popup using javascript
829      *
830      * @global object
831      * @global object
832      * @param $submission object The submission whose data is to be updated on the main page
833      */
834     function update_main_listing($submission) {
835         global $SESSION, $CFG, $OUTPUT;
837         $output = '';
839         $perpage = get_user_preferences('assignment_perpage', 10);
841         $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
843         /// Run some Javascript to try and update the parent page
844         $output .= '<script type="text/javascript">'."\n<!--\n";
845         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['submissioncomment'])) {
846             if ($quickgrade){
847                 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid.'").value="'
848                 .trim($submission->submissioncomment).'";'."\n";
849              } else {
850                 $output.= 'opener.document.getElementById("com'.$submission->userid.
851                 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment)), 15)."\";\n";
852             }
853         }
855         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['grade'])) {
856             //echo optional_param('menuindex');
857             if ($quickgrade){
858                 $output.= 'opener.document.getElementById("menumenu'.$submission->userid.
859                 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT).'";'."\n";
860             } else {
861                 $output.= 'opener.document.getElementById("g'.$submission->userid.'").innerHTML="'.
862                 $this->display_grade($submission->grade)."\";\n";
863             }
864         }
865         //need to add student's assignments in there too.
866         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemodified']) &&
867             $submission->timemodified) {
868             $output.= 'opener.document.getElementById("ts'.$submission->userid.
869                  '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid)).userdate($submission->timemodified)."\";\n";
870         }
872         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['timemarked']) &&
873             $submission->timemarked) {
874             $output.= 'opener.document.getElementById("tt'.$submission->userid.
875                  '").innerHTML="'.userdate($submission->timemarked)."\";\n";
876         }
878         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['status'])) {
879             $output.= 'opener.document.getElementById("up'.$submission->userid.'").className="s1";';
880             $buttontext = get_string('update');
881             $url = new moodle_url('/mod/assignment/submissions.php', array(
882                     'id' => $this->cm->id,
883                     'userid' => $submission->userid,
884                     'mode' => 'single',
885                     'offset' => (optional_param('offset', '', PARAM_INT)-1)));
886             $button = $OUTPUT->action_link($url, $buttontext, new popup_action('click', $url, 'grade'.$submission->userid, array('height' => 450, 'width' => 700)), array('ttile'=>$buttontext));
888             $output .= 'opener.document.getElementById("up'.$submission->userid.'").innerHTML="'.addslashes_js($button).'";';
889         }
891         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $submission->userid);
893         if (empty($SESSION->flextable['mod-assignment-submissions']->collapse['finalgrade'])) {
894             $output.= 'opener.document.getElementById("finalgrade_'.$submission->userid.
895             '").innerHTML="'.$grading_info->items[0]->grades[$submission->userid]->str_grade.'";'."\n";
896         }
898         if (!empty($CFG->enableoutcomes) and empty($SESSION->flextable['mod-assignment-submissions']->collapse['outcome'])) {
900             if (!empty($grading_info->outcomes)) {
901                 foreach($grading_info->outcomes as $n=>$outcome) {
902                     if ($outcome->grades[$submission->userid]->locked) {
903                         continue;
904                     }
906                     if ($quickgrade){
907                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.
908                         '").selectedIndex="'.$outcome->grades[$submission->userid]->grade.'";'."\n";
910                     } else {
911                         $options = make_grades_menu(-$outcome->scaleid);
912                         $options[0] = get_string('nooutcome', 'grades');
913                         $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid.'").innerHTML="'.$options[$outcome->grades[$submission->userid]->grade]."\";\n";
914                     }
916                 }
917             }
918         }
920         $output .= "\n-->\n</script>";
921         return $output;
922     }
924     /**
925      *  Return a grade in user-friendly form, whether it's a scale or not
926      *
927      * @global object
928      * @param mixed $grade
929      * @return string User-friendly representation of grade
930      */
931     function display_grade($grade) {
932         global $DB;
934         static $scalegrades = array();   // Cache scales for each assignment - they might have different scales!!
936         if ($this->assignment->grade >= 0) {    // Normal number
937             if ($grade == -1) {
938                 return '-';
939             } else {
940                 return $grade.' / '.$this->assignment->grade;
941             }
943         } else {                                // Scale
944             if (empty($scalegrades[$this->assignment->id])) {
945                 if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
946                     $scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
947                 } else {
948                     return '-';
949                 }
950             }
951             if (isset($scalegrades[$this->assignment->id][$grade])) {
952                 return $scalegrades[$this->assignment->id][$grade];
953             }
954             return '-';
955         }
956     }
958     /**
959      *  Display a single submission, ready for grading on a popup window
960      *
961      * This default method prints the teacher info and submissioncomment box at the top and
962      * the student info and submission at the bottom.
963      * This method also fetches the necessary data in order to be able to
964      * provide a "Next submission" button.
965      * Calls preprocess_submission() to give assignment type plug-ins a chance
966      * to process submissions before they are graded
967      * This method gets its arguments from the page parameters userid and offset
968      *
969      * @global object
970      * @global object
971      * @param string $extra_javascript
972      */
973     function display_submission($offset=-1,$userid =-1, $display=true) {
974         global $CFG, $DB, $PAGE, $OUTPUT, $USER;
975         require_once($CFG->libdir.'/gradelib.php');
976         require_once($CFG->libdir.'/tablelib.php');
977         require_once("$CFG->dirroot/repository/lib.php");
978         require_once("$CFG->dirroot/grade/grading/lib.php");
979         if ($userid==-1) {
980             $userid = required_param('userid', PARAM_INT);
981         }
982         if ($offset==-1) {
983             $offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
984         }
985         $filter = optional_param('filter', 0, PARAM_INT);
987         if (!$user = $DB->get_record('user', array('id'=>$userid))) {
988             print_error('nousers');
989         }
991         if (!$submission = $this->get_submission($user->id)) {
992             $submission = $this->prepare_new_submission($userid);
993         }
994         if ($submission->timemodified > $submission->timemarked) {
995             $subtype = 'assignmentnew';
996         } else {
997             $subtype = 'assignmentold';
998         }
1000         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
1001         $gradingdisabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
1003     /// construct SQL, using current offset to find the data of the next student
1004         $course     = $this->course;
1005         $assignment = $this->assignment;
1006         $cm         = $this->cm;
1007         $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
1009         //reset filter to all for offline assignment
1010         if ($assignment->assignmenttype == 'offline' && $filter == self::FILTER_SUBMITTED) {
1011             $filter = self::FILTER_ALL;
1012         }
1013         /// Get all ppl that can submit assignments
1015         $currentgroup = groups_get_activity_group($cm);
1016         $users = get_enrolled_users($context, 'mod/assignment:submit', $currentgroup, 'u.id');
1017         if ($users) {
1018             $users = array_keys($users);
1019             // if groupmembersonly used, remove users who are not in any group
1020             if (!empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
1021                 if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
1022                     $users = array_intersect($users, array_keys($groupingusers));
1023                 }
1024             }
1025         }
1027         $nextid = 0;
1028         $where = '';
1029         if($filter == self::FILTER_SUBMITTED) {
1030             $where .= 's.timemodified > 0 AND ';
1031         } else if($filter == self::FILTER_REQUIRE_GRADING) {
1032             $where .= 's.timemarked < s.timemodified AND ';
1033         }
1035         if ($users) {
1036             $userfields = user_picture::fields('u', array('lastaccess'));
1037             $select = "SELECT $userfields,
1038                               s.id AS submissionid, s.grade, s.submissioncomment,
1039                               s.timemodified, s.timemarked,
1040                               CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
1041                                    ELSE 0 END AS status ";
1043             $sql = 'FROM {user} u '.
1044                    'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
1045                    AND s.assignment = '.$this->assignment->id.' '.
1046                    'WHERE '.$where.'u.id IN ('.implode(',', $users).') ';
1048             if ($sort = flexible_table::get_sort_for_table('mod-assignment-submissions')) {
1049                 $sort = 'ORDER BY '.$sort.' ';
1050             }
1051             $auser = $DB->get_records_sql($select.$sql.$sort, null, $offset, 2);
1053             if (is_array($auser) && count($auser)>1) {
1054                 $nextuser = next($auser);
1055                 $nextid = $nextuser->id;
1056             }
1057         }
1059         if ($submission->teacher) {
1060             $teacher = $DB->get_record('user', array('id'=>$submission->teacher));
1061         } else {
1062             global $USER;
1063             $teacher = $USER;
1064         }
1066         $this->preprocess_submission($submission);
1068         $mformdata = new stdClass();
1069         $mformdata->context = $this->context;
1070         $mformdata->maxbytes = $this->course->maxbytes;
1071         $mformdata->courseid = $this->course->id;
1072         $mformdata->teacher = $teacher;
1073         $mformdata->assignment = $assignment;
1074         $mformdata->submission = $submission;
1075         $mformdata->lateness = $this->display_lateness($submission->timemodified);
1076         $mformdata->auser = $auser;
1077         $mformdata->user = $user;
1078         $mformdata->offset = $offset;
1079         $mformdata->userid = $userid;
1080         $mformdata->cm = $this->cm;
1081         $mformdata->grading_info = $grading_info;
1082         $mformdata->enableoutcomes = $CFG->enableoutcomes;
1083         $mformdata->grade = $this->assignment->grade;
1084         $mformdata->gradingdisabled = $gradingdisabled;
1085         $mformdata->nextid = $nextid;
1086         $mformdata->submissioncomment= $submission->submissioncomment;
1087         $mformdata->submissioncommentformat= FORMAT_HTML;
1088         $mformdata->submission_content= $this->print_user_files($user->id,true);
1089         $mformdata->filter = $filter;
1090         $mformdata->mailinfo = get_user_preferences('assignment_mailinfo', 0);
1091          if ($assignment->assignmenttype == 'upload') {
1092             $mformdata->fileui_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
1093         } elseif ($assignment->assignmenttype == 'uploadsingle') {
1094             $mformdata->fileui_options = array('subdirs'=>0, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
1095         }
1096         $advancedgradingwarning = false;
1097         $gradingmanager = get_grading_manager($this->context, 'mod_assignment', 'submission');
1098         if ($gradingmethod = $gradingmanager->get_active_method()) {
1099             $controller = $gradingmanager->get_controller($gradingmethod);
1100             if ($controller->is_form_available()) {
1101                 $itemid = null;
1102                 if (!empty($submission->id)) {
1103                     $itemid = $submission->id;
1104                 }
1105                 if ($gradingdisabled && $itemid) {
1106                     $mformdata->advancedgradinginstance = $controller->get_current_instance($USER->id, $itemid);
1107                 } else if (!$gradingdisabled) {
1108                     $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
1109                     $mformdata->advancedgradinginstance = $controller->get_or_create_instance($instanceid, $USER->id, $itemid);
1110                 }
1111             } else {
1112                 $advancedgradingwarning = $controller->form_unavailable_notification();
1113             }
1114         }
1116         $submitform = new assignment_grading_form( null, $mformdata );
1118          if (!$display) {
1119             $ret_data = new stdClass();
1120             $ret_data->mform = $submitform;
1121             if (isset($mformdata->fileui_options)) {
1122                 $ret_data->fileui_options = $mformdata->fileui_options;
1123             }
1124             return $ret_data;
1125         }
1127         if ($submitform->is_cancelled()) {
1128             redirect('submissions.php?id='.$this->cm->id);
1129         }
1131         $submitform->set_data($mformdata);
1133         $PAGE->set_title($this->course->fullname . ': ' .get_string('feedback', 'assignment').' - '.fullname($user, true));
1134         $PAGE->set_heading($this->course->fullname);
1135         $PAGE->navbar->add(get_string('submissions', 'assignment'), new moodle_url('/mod/assignment/submissions.php', array('id'=>$cm->id)));
1136         $PAGE->navbar->add(fullname($user, true));
1138         echo $OUTPUT->header();
1139         echo $OUTPUT->heading(get_string('feedback', 'assignment').': '.fullname($user, true));
1141         // display mform here...
1142         if ($advancedgradingwarning) {
1143             echo $OUTPUT->notification($advancedgradingwarning, 'error');
1144         }
1145         $submitform->display();
1147         $customfeedback = $this->custom_feedbackform($submission, true);
1148         if (!empty($customfeedback)) {
1149             echo $customfeedback;
1150         }
1152         echo $OUTPUT->footer();
1153     }
1155     /**
1156      *  Preprocess submission before grading
1157      *
1158      * Called by display_submission()
1159      * The default type does nothing here.
1160      *
1161      * @param object $submission The submission object
1162      */
1163     function preprocess_submission(&$submission) {
1164     }
1166     /**
1167      *  Display all the submissions ready for grading
1168      *
1169      * @global object
1170      * @global object
1171      * @global object
1172      * @global object
1173      * @param string $message
1174      * @return bool|void
1175      */
1176     function display_submissions($message='') {
1177         global $CFG, $DB, $USER, $DB, $OUTPUT, $PAGE;
1178         require_once($CFG->libdir.'/gradelib.php');
1180         /* first we check to see if the form has just been submitted
1181          * to request user_preference updates
1182          */
1184        $filters = array(self::FILTER_ALL             => get_string('all'),
1185                         self::FILTER_REQUIRE_GRADING => get_string('requiregrading', 'assignment'));
1187         $updatepref = optional_param('updatepref', 0, PARAM_BOOL);
1188         if ($updatepref) {
1189             $perpage = optional_param('perpage', 10, PARAM_INT);
1190             $perpage = ($perpage <= 0) ? 10 : $perpage ;
1191             $filter = optional_param('filter', 0, PARAM_INT);
1192             set_user_preference('assignment_perpage', $perpage);
1193             set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
1194             set_user_preference('assignment_filter', $filter);
1195         }
1197         /* next we get perpage and quickgrade (allow quick grade) params
1198          * from database
1199          */
1200         $perpage    = get_user_preferences('assignment_perpage', 10);
1201         $quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
1202         $filter = get_user_preferences('assignment_filter', 0);
1203         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
1205         if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
1206             $uses_outcomes = true;
1207         } else {
1208             $uses_outcomes = false;
1209         }
1211         $page    = optional_param('page', 0, PARAM_INT);
1212         $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1214     /// Some shortcuts to make the code read better
1216         $course     = $this->course;
1217         $assignment = $this->assignment;
1218         $cm         = $this->cm;
1219         $hassubmission = false;
1221         // reset filter to all for offline assignment only.
1222         if ($assignment->assignmenttype == 'offline') {
1223             if ($filter == self::FILTER_SUBMITTED) {
1224                 $filter = self::FILTER_ALL;
1225             }
1226         } else {
1227             $filters[self::FILTER_SUBMITTED] = get_string('submitted', 'assignment');
1228         }
1230         $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1231         add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id);
1233         $PAGE->set_title(format_string($this->assignment->name,true));
1234         $PAGE->set_heading($this->course->fullname);
1235         echo $OUTPUT->header();
1237         echo '<div class="usersubmissions">';
1239         //hook to allow plagiarism plugins to update status/print links.
1240         echo plagiarism_update_status($this->course, $this->cm);
1242         $course_context = get_context_instance(CONTEXT_COURSE, $course->id);
1243         if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
1244             echo '<div class="allcoursegrades"><a href="' . $CFG->wwwroot . '/grade/report/grader/index.php?id=' . $course->id . '">'
1245                 . get_string('seeallcoursegrades', 'grades') . '</a></div>';
1246         }
1248         if (!empty($message)) {
1249             echo $message;   // display messages here if any
1250         }
1252         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1254     /// Check to see if groups are being used in this assignment
1256         /// find out current groups mode
1257         $groupmode = groups_get_activity_groupmode($cm);
1258         $currentgroup = groups_get_activity_group($cm, true);
1259         groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/assignment/submissions.php?id=' . $this->cm->id);
1261         /// Print quickgrade form around the table
1262         if ($quickgrade) {
1263             $formattrs = array();
1264             $formattrs['action'] = new moodle_url('/mod/assignment/submissions.php');
1265             $formattrs['id'] = 'fastg';
1266             $formattrs['method'] = 'post';
1268             echo html_writer::start_tag('form', $formattrs);
1269             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'id',      'value'=> $this->cm->id));
1270             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'mode',    'value'=> 'fastgrade'));
1271             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'page',    'value'=> $page));
1272             echo html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=> sesskey()));
1273         }
1275         /// Get all ppl that are allowed to submit assignments
1276         list($esql, $params) = get_enrolled_sql($context, 'mod/assignment:submit', $currentgroup);
1278         if ($filter == self::FILTER_ALL) {
1279             $sql = "SELECT u.id FROM {user} u ".
1280                    "LEFT JOIN ($esql) eu ON eu.id=u.id ".
1281                    "WHERE u.deleted = 0 AND eu.id=u.id ";
1282         } else {
1283             $wherefilter = ' AND s.assignment = '. $this->assignment->id;
1284             $assignmentsubmission = "LEFT JOIN {assignment_submissions} s ON (u.id = s.userid) ";
1285             if($filter == self::FILTER_SUBMITTED) {
1286                 $wherefilter .= ' AND s.timemodified > 0 ';
1287             } else if($filter == self::FILTER_REQUIRE_GRADING && $assignment->assignmenttype != 'offline') {
1288                 $wherefilter .= ' AND s.timemarked < s.timemodified ';
1289             } else { // require grading for offline assignment
1290                 $assignmentsubmission = "";
1291                 $wherefilter = "";
1292             }
1294             $sql = "SELECT u.id FROM {user} u ".
1295                    "LEFT JOIN ($esql) eu ON eu.id=u.id ".
1296                    $assignmentsubmission.
1297                    "WHERE u.deleted = 0 AND eu.id=u.id ".
1298                    $wherefilter;
1299         }
1301         $users = $DB->get_records_sql($sql, $params);
1302         if (!empty($users)) {
1303             if($assignment->assignmenttype == 'offline' && $filter == self::FILTER_REQUIRE_GRADING) {
1304                 //remove users who has submitted their assignment
1305                 foreach ($this->get_submissions() as $submission) {
1306                     if (array_key_exists($submission->userid, $users)) {
1307                         unset($users[$submission->userid]);
1308                     }
1309                 }
1310             }
1311             $users = array_keys($users);
1312         }
1314         // if groupmembersonly used, remove users who are not in any group
1315         if ($users and !empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
1316             if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
1317                 $users = array_intersect($users, array_keys($groupingusers));
1318             }
1319         }
1321         $extrafields = get_extra_user_fields($context);
1322         $tablecolumns = array_merge(array('picture', 'fullname'), $extrafields,
1323                 array('grade', 'submissioncomment', 'timemodified', 'timemarked', 'status', 'finalgrade'));
1324         if ($uses_outcomes) {
1325             $tablecolumns[] = 'outcome'; // no sorting based on outcomes column
1326         }
1328         $extrafieldnames = array();
1329         foreach ($extrafields as $field) {
1330             $extrafieldnames[] = get_user_field_name($field);
1331         }
1332         $tableheaders = array_merge(
1333                 array('', get_string('fullnameuser')),
1334                 $extrafieldnames,
1335                 array(
1336                     get_string('grade'),
1337                     get_string('comment', 'assignment'),
1338                     get_string('lastmodified').' ('.get_string('submission', 'assignment').')',
1339                     get_string('lastmodified').' ('.get_string('grade').')',
1340                     get_string('status'),
1341                     get_string('finalgrade', 'grades'),
1342                 ));
1343         if ($uses_outcomes) {
1344             $tableheaders[] = get_string('outcome', 'grades');
1345         }
1347         require_once($CFG->libdir.'/tablelib.php');
1348         $table = new flexible_table('mod-assignment-submissions');
1350         $table->define_columns($tablecolumns);
1351         $table->define_headers($tableheaders);
1352         $table->define_baseurl($CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id.'&amp;currentgroup='.$currentgroup);
1354         $table->sortable(true, 'lastname');//sorted by lastname by default
1355         $table->collapsible(true);
1356         $table->initialbars(true);
1358         $table->column_suppress('picture');
1359         $table->column_suppress('fullname');
1361         $table->column_class('picture', 'picture');
1362         $table->column_class('fullname', 'fullname');
1363         foreach ($extrafields as $field) {
1364             $table->column_class($field, $field);
1365         }
1366         $table->column_class('grade', 'grade');
1367         $table->column_class('submissioncomment', 'comment');
1368         $table->column_class('timemodified', 'timemodified');
1369         $table->column_class('timemarked', 'timemarked');
1370         $table->column_class('status', 'status');
1371         $table->column_class('finalgrade', 'finalgrade');
1372         if ($uses_outcomes) {
1373             $table->column_class('outcome', 'outcome');
1374         }
1376         $table->set_attribute('cellspacing', '0');
1377         $table->set_attribute('id', 'attempts');
1378         $table->set_attribute('class', 'submissions');
1379         $table->set_attribute('width', '100%');
1381         $table->no_sorting('finalgrade');
1382         $table->no_sorting('outcome');
1384         // Start working -- this is necessary as soon as the niceties are over
1385         $table->setup();
1387         /// Construct the SQL
1388         list($where, $params) = $table->get_sql_where();
1389         if ($where) {
1390             $where .= ' AND ';
1391         }
1393         if ($filter == self::FILTER_SUBMITTED) {
1394            $where .= 's.timemodified > 0 AND ';
1395         } else if($filter == self::FILTER_REQUIRE_GRADING) {
1396             $where = '';
1397             if ($assignment->assignmenttype != 'offline') {
1398                $where .= 's.timemarked < s.timemodified AND ';
1399             }
1400         }
1402         if ($sort = $table->get_sql_sort()) {
1403             $sort = ' ORDER BY '.$sort;
1404         }
1406         $ufields = user_picture::fields('u', $extrafields);
1407         if (!empty($users)) {
1408             $select = "SELECT $ufields,
1409                               s.id AS submissionid, s.grade, s.submissioncomment,
1410                               s.timemodified, s.timemarked,
1411                               CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
1412                                    ELSE 0 END AS status ";
1414             $sql = 'FROM {user} u '.
1415                    'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
1416                     AND s.assignment = '.$this->assignment->id.' '.
1417                    'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
1419             $ausers = $DB->get_records_sql($select.$sql.$sort, $params, $table->get_page_start(), $table->get_page_size());
1421             $table->pagesize($perpage, count($users));
1423             ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1424             $offset = $page * $perpage;
1425             $strupdate = get_string('update');
1426             $strgrade  = get_string('grade');
1427             $strview  = get_string('view');
1428             $grademenu = make_grades_menu($this->assignment->grade);
1430             if ($ausers !== false) {
1431                 $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
1432                 $endposition = $offset + $perpage;
1433                 $currentposition = 0;
1434                 foreach ($ausers as $auser) {
1435                     if ($currentposition == $offset && $offset < $endposition) {
1436                         $rowclass = null;
1437                         $final_grade = $grading_info->items[0]->grades[$auser->id];
1438                         $grademax = $grading_info->items[0]->grademax;
1439                         $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
1440                         $locked_overridden = 'locked';
1441                         if ($final_grade->overridden) {
1442                             $locked_overridden = 'overridden';
1443                         }
1445                         // TODO add here code if advanced grading grade must be reviewed => $auser->status=0
1447                         $picture = $OUTPUT->user_picture($auser);
1449                         if (empty($auser->submissionid)) {
1450                             $auser->grade = -1; //no submission yet
1451                         }
1453                         if (!empty($auser->submissionid)) {
1454                             $hassubmission = true;
1455                         ///Prints student answer and student modified date
1456                         ///attach file or print link to student answer, depending on the type of the assignment.
1457                         ///Refer to print_student_answer in inherited classes.
1458                             if ($auser->timemodified > 0) {
1459                                 $studentmodifiedcontent = $this->print_student_answer($auser->id)
1460                                         . userdate($auser->timemodified);
1461                                 if ($assignment->timedue && $auser->timemodified > $assignment->timedue && $this->supports_lateness()) {
1462                                     $studentmodifiedcontent .= $this->display_lateness($auser->timemodified);
1463                                     $rowclass = 'late';
1464                                 }
1465                             } else {
1466                                 $studentmodifiedcontent = '&nbsp;';
1467                             }
1468                             $studentmodified = html_writer::tag('div', $studentmodifiedcontent, array('id' => 'ts' . $auser->id));
1469                         ///Print grade, dropdown or text
1470                             if ($auser->timemarked > 0) {
1471                                 $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
1473                                 if ($final_grade->locked or $final_grade->overridden) {
1474                                     $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1475                                 } else if ($quickgrade) {
1476                                     $attributes = array();
1477                                     $attributes['tabindex'] = $tabindex++;
1478                                     $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
1479                                     $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
1480                                 } else {
1481                                     $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1482                                 }
1484                             } else {
1485                                 $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1486                                 if ($final_grade->locked or $final_grade->overridden) {
1487                                     $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
1488                                 } else if ($quickgrade) {
1489                                     $attributes = array();
1490                                     $attributes['tabindex'] = $tabindex++;
1491                                     $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
1492                                     $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1493                                 } else {
1494                                     $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
1495                                 }
1496                             }
1497                         ///Print Comment
1498                             if ($final_grade->locked or $final_grade->overridden) {
1499                                 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
1501                             } else if ($quickgrade) {
1502                                 $comment = '<div id="com'.$auser->id.'">'
1503                                          . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1504                                          . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1505                             } else {
1506                                 $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
1507                             }
1508                         } else {
1509                             $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
1510                             $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
1511                             $status          = '<div id="st'.$auser->id.'">&nbsp;</div>';
1513                             if ($final_grade->locked or $final_grade->overridden) {
1514                                 $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
1515                                 $hassubmission = true;
1516                             } else if ($quickgrade) {   // allow editing
1517                                 $attributes = array();
1518                                 $attributes['tabindex'] = $tabindex++;
1519                                 $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
1520                                 $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
1521                                 $hassubmission = true;
1522                             } else {
1523                                 $grade = '<div id="g'.$auser->id.'">-</div>';
1524                             }
1526                             if ($final_grade->locked or $final_grade->overridden) {
1527                                 $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
1528                             } else if ($quickgrade) {
1529                                 $comment = '<div id="com'.$auser->id.'">'
1530                                          . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
1531                                          . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
1532                             } else {
1533                                 $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
1534                             }
1535                         }
1537                         if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
1538                             $auser->status = 0;
1539                         } else {
1540                             $auser->status = 1;
1541                         }
1543                         $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
1544                         if ($final_grade->locked or $final_grade->overridden) {
1545                             $buttontext = $strview;
1546                         }
1548                         ///No more buttons, we use popups ;-).
1549                         $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
1550                                    . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;filter='.$filter.'&amp;offset='.$offset++;
1552                         $button = $OUTPUT->action_link($popup_url, $buttontext);
1554                         $status  = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
1556                         $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
1558                         $outcomes = '';
1560                         if ($uses_outcomes) {
1562                             foreach($grading_info->outcomes as $n=>$outcome) {
1563                                 $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
1564                                 $options = make_grades_menu(-$outcome->scaleid);
1566                                 if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
1567                                     $options[0] = get_string('nooutcome', 'grades');
1568                                     $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
1569                                 } else {
1570                                     $attributes = array();
1571                                     $attributes['tabindex'] = $tabindex++;
1572                                     $attributes['id'] = 'outcome_'.$n.'_'.$auser->id;
1573                                     $outcomes .= ' '.html_writer::select($options, 'outcome_'.$n.'['.$auser->id.']', $outcome->grades[$auser->id]->grade, array(0=>get_string('nooutcome', 'grades')), $attributes);
1574                                 }
1575                                 $outcomes .= '</div>';
1576                             }
1577                         }
1579                         $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser, has_capability('moodle/site:viewfullnames', $this->context)) . '</a>';
1580                         $extradata = array();
1581                         foreach ($extrafields as $field) {
1582                             $extradata[] = $auser->{$field};
1583                         }
1584                         $row = array_merge(array($picture, $userlink), $extradata,
1585                                 array($grade, $comment, $studentmodified, $teachermodified,
1586                                 $status, $finalgrade));
1587                         if ($uses_outcomes) {
1588                             $row[] = $outcomes;
1589                         }
1590                         $table->add_data($row, $rowclass);
1591                     }
1592                     $currentposition++;
1593                 }
1594                 if ($hassubmission && method_exists('assignment_'.$this->assignment->assignmenttype, 'download_submissions')) {
1595                     echo html_writer::start_tag('div', array('class' => 'mod-assignment-download-link'));
1596                     echo html_writer::link(new moodle_url('/mod/assignment/submissions.php', array('id' => $this->cm->id, 'download' => 'zip')), get_string('downloadall', 'assignment'));
1597                     echo html_writer::end_tag('div');
1598                 }
1599                 $table->print_html();  /// Print the whole table
1600             } else {
1601                 if ($filter == self::FILTER_SUBMITTED) {
1602                     echo html_writer::tag('div', get_string('nosubmisson', 'assignment'), array('class'=>'nosubmisson'));
1603                 } else if ($filter == self::FILTER_REQUIRE_GRADING) {
1604                     echo html_writer::tag('div', get_string('norequiregrading', 'assignment'), array('class'=>'norequiregrading'));
1605                 }
1606             }
1607         }
1609         /// Print quickgrade form around the table
1610         if ($quickgrade && $table->started_output && !empty($users)){
1611             $mailinfopref = false;
1612             if (get_user_preferences('assignment_mailinfo', 1)) {
1613                 $mailinfopref = true;
1614             }
1615             $emailnotification =  html_writer::checkbox('mailinfo', 1, $mailinfopref, get_string('enablenotification','assignment'));
1617             $emailnotification .= $OUTPUT->help_icon('enablenotification', 'assignment');
1618             echo html_writer::tag('div', $emailnotification, array('class'=>'emailnotification'));
1620             $savefeedback = html_writer::empty_tag('input', array('type'=>'submit', 'name'=>'fastg', 'value'=>get_string('saveallfeedback', 'assignment')));
1621             echo html_writer::tag('div', $savefeedback, array('class'=>'fastgbutton'));
1623             echo html_writer::end_tag('form');
1624         } else if ($quickgrade) {
1625             echo html_writer::end_tag('form');
1626         }
1628         echo '</div>';
1629         /// End of fast grading form
1631         /// Mini form for setting user preference
1633         $formaction = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id));
1634         $mform = new MoodleQuickForm('optionspref', 'post', $formaction, '', array('class'=>'optionspref'));
1636         $mform->addElement('hidden', 'updatepref');
1637         $mform->setDefault('updatepref', 1);
1638         $mform->addElement('header', 'qgprefs', get_string('optionalsettings', 'assignment'));
1639         $mform->addElement('select', 'filter', get_string('show'),  $filters);
1641         $mform->setDefault('filter', $filter);
1643         $mform->addElement('text', 'perpage', get_string('pagesize', 'assignment'), array('size'=>1));
1644         $mform->setDefault('perpage', $perpage);
1646         if ($this->quickgrade_mode_allowed()) {
1647             $mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
1648             $mform->setDefault('quickgrade', $quickgrade);
1649             $mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
1650         }
1652         $mform->addElement('submit', 'savepreferences', get_string('savepreferences'));
1654         $mform->display();
1656         echo $OUTPUT->footer();
1657     }
1659     /**
1660      * If the form was cancelled ('Cancel' or 'Next' was pressed), call cancel method
1661      * from advanced grading (if applicable) and returns true
1662      * If the form was submitted, validates it and returns false if validation did not pass.
1663      * If validation passes, preprocess advanced grading (if applicable) and returns true.
1664      *
1665      * Note to the developers: This is NOT the correct way to implement advanced grading
1666      * in grading form. The assignment grading was written long time ago and unfortunately
1667      * does not fully use the mforms. Usually function is_validated() is called to
1668      * validate the form and get_data() is called to get the data from the form.
1669      *
1670      * Here we have to push the calculated grade to $_POST['xgrade'] because further processing
1671      * of the form gets the data not from form->get_data(), but from $_POST (using statement
1672      * like  $feedback = data_submitted() )
1673      */
1674     protected function validate_and_preprocess_feedback() {
1675         global $USER, $CFG;
1676         require_once($CFG->libdir.'/gradelib.php');
1677         if (!($feedback = data_submitted()) || !isset($feedback->userid) || !isset($feedback->offset)) {
1678             return true;      // No incoming data, nothing to validate
1679         }
1680         $userid = required_param('userid', PARAM_INT);
1681         $offset = required_param('offset', PARAM_INT);
1682         $gradinginfo = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($userid));
1683         $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
1684         if ($gradingdisabled) {
1685             return true;
1686         }
1687         $submissiondata = $this->display_submission($offset, $userid, false);
1688         $mform = $submissiondata->mform;
1689         $gradinginstance = $mform->use_advanced_grading();
1690         if (optional_param('cancel', false, PARAM_BOOL) || optional_param('next', false, PARAM_BOOL)) {
1691             // form was cancelled
1692             if ($gradinginstance) {
1693                 $gradinginstance->cancel();
1694             }
1695         } else if ($mform->is_submitted()) {
1696             // form was submitted (= a submit button other than 'cancel' or 'next' has been clicked)
1697             if (!$mform->is_validated()) {
1698                 return false;
1699             }
1700             // preprocess advanced grading here
1701             if ($gradinginstance) {
1702                 $data = $mform->get_data();
1703                 // create submission if it did not exist yet because we need submission->id for storing the grading instance
1704                 $submission = $this->get_submission($userid, true);
1705                 $_POST['xgrade'] = $gradinginstance->submit_and_get_grade($data->advancedgrading, $submission->id);
1706             }
1707         }
1708         return true;
1709     }
1711     /**
1712      *  Process teacher feedback submission
1713      *
1714      * This is called by submissions() when a grading even has taken place.
1715      * It gets its data from the submitted form.
1716      *
1717      * @global object
1718      * @global object
1719      * @global object
1720      * @return object|bool The updated submission object or false
1721      */
1722     function process_feedback($formdata=null) {
1723         global $CFG, $USER, $DB;
1724         require_once($CFG->libdir.'/gradelib.php');
1726         if (!$feedback = data_submitted() or !confirm_sesskey()) {      // No incoming data?
1727             return false;
1728         }
1730         ///For save and next, we need to know the userid to save, and the userid to go
1731         ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1732         ///as the userid to store
1733         if ((int)$feedback->saveuserid !== -1){
1734             $feedback->userid = $feedback->saveuserid;
1735         }
1737         if (!empty($feedback->cancel)) {          // User hit cancel button
1738             return false;
1739         }
1741         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
1743         // store outcomes if needed
1744         $this->process_outcomes($feedback->userid);
1746         $submission = $this->get_submission($feedback->userid, true);  // Get or make one
1748         if (!($grading_info->items[0]->grades[$feedback->userid]->locked ||
1749             $grading_info->items[0]->grades[$feedback->userid]->overridden) ) {
1751             $submission->grade      = $feedback->xgrade;
1752             $submission->submissioncomment    = $feedback->submissioncomment_editor['text'];
1753             $submission->teacher    = $USER->id;
1754             $mailinfo = get_user_preferences('assignment_mailinfo', 0);
1755             if (!$mailinfo) {
1756                 $submission->mailed = 1;       // treat as already mailed
1757             } else {
1758                 $submission->mailed = 0;       // Make sure mail goes out (again, even)
1759             }
1760             $submission->timemarked = time();
1762             unset($submission->data1);  // Don't need to update this.
1763             unset($submission->data2);  // Don't need to update this.
1765             if (empty($submission->timemodified)) {   // eg for offline assignments
1766                 // $submission->timemodified = time();
1767             }
1769             $DB->update_record('assignment_submissions', $submission);
1771             // triger grade event
1772             $this->update_grade($submission);
1774             add_to_log($this->course->id, 'assignment', 'update grades',
1775                        'submissions.php?id='.$this->cm->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
1776              if (!is_null($formdata)) {
1777                     if ($this->type == 'upload' || $this->type == 'uploadsingle') {
1778                         $mformdata = $formdata->mform->get_data();
1779                         $mformdata = file_postupdate_standard_filemanager($mformdata, 'files', $formdata->fileui_options, $this->context, 'mod_assignment', 'response', $submission->id);
1780                     }
1781              }
1782         }
1784         return $submission;
1786     }
1788     function process_outcomes($userid) {
1789         global $CFG, $USER;
1791         if (empty($CFG->enableoutcomes)) {
1792             return;
1793         }
1795         require_once($CFG->libdir.'/gradelib.php');
1797         if (!$formdata = data_submitted() or !confirm_sesskey()) {
1798             return;
1799         }
1801         $data = array();
1802         $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
1804         if (!empty($grading_info->outcomes)) {
1805             foreach($grading_info->outcomes as $n=>$old) {
1806                 $name = 'outcome_'.$n;
1807                 if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
1808                     $data[$n] = $formdata->{$name}[$userid];
1809                 }
1810             }
1811         }
1812         if (count($data) > 0) {
1813             grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
1814         }
1816     }
1818     /**
1819      * Load the submission object for a particular user
1820      *
1821      * @global object
1822      * @global object
1823      * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1824      * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1825      * @param bool $teachermodified student submission set if false
1826      * @return object The submission
1827      */
1828     function get_submission($userid=0, $createnew=false, $teachermodified=false) {
1829         global $USER, $DB;
1831         if (empty($userid)) {
1832             $userid = $USER->id;
1833         }
1835         $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1837         if ($submission || !$createnew) {
1838             return $submission;
1839         }
1840         $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
1841         $DB->insert_record("assignment_submissions", $newsubmission);
1843         return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
1844     }
1846     /**
1847      * Check the given submission is complete. Preliminary rows are often created in the assignment_submissions
1848      * table before a submission actually takes place. This function checks to see if the given submission has actually
1849      * been submitted.
1850      *
1851      * @param  stdClass $submission The submission we want to check for completion
1852      * @return bool                 Indicates if the submission was found to be complete
1853      */
1854     public function is_submitted_with_required_data($submission) {
1855         return $submission->timemodified;
1856     }
1858     /**
1859      * Instantiates a new submission object for a given user
1860      *
1861      * Sets the assignment, userid and times, everything else is set to default values.
1862      *
1863      * @param int $userid The userid for which we want a submission object
1864      * @param bool $teachermodified student submission set if false
1865      * @return object The submission
1866      */
1867     function prepare_new_submission($userid, $teachermodified=false) {
1868         $submission = new stdClass();
1869         $submission->assignment   = $this->assignment->id;
1870         $submission->userid       = $userid;
1871         $submission->timecreated = time();
1872         // teachers should not be modifying modified date, except offline assignments
1873         if ($teachermodified) {
1874             $submission->timemodified = 0;
1875         } else {
1876             $submission->timemodified = $submission->timecreated;
1877         }
1878         $submission->numfiles     = 0;
1879         $submission->data1        = '';
1880         $submission->data2        = '';
1881         $submission->grade        = -1;
1882         $submission->submissioncomment      = '';
1883         $submission->format       = 0;
1884         $submission->teacher      = 0;
1885         $submission->timemarked   = 0;
1886         $submission->mailed       = 0;
1887         return $submission;
1888     }
1890     /**
1891      * Return all assignment submissions by ENROLLED students (even empty)
1892      *
1893      * @param string $sort optional field names for the ORDER BY in the sql query
1894      * @param string $dir optional specifying the sort direction, defaults to DESC
1895      * @return array The submission objects indexed by id
1896      */
1897     function get_submissions($sort='', $dir='DESC') {
1898         return assignment_get_all_submissions($this->assignment, $sort, $dir);
1899     }
1901     /**
1902      * Counts all complete (real) assignment submissions by enrolled students
1903      *
1904      * @param  int $groupid (optional) If nonzero then count is restricted to this group
1905      * @return int          The number of submissions
1906      */
1907     function count_real_submissions($groupid=0) {
1908         global $CFG;
1909         global $DB;
1911         // Grab the context assocated with our course module
1912         $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
1914         // Get ids of users enrolled in the given course.
1915         list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
1916         $params['assignmentid'] = $this->cm->instance;
1918         // Get ids of users enrolled in the given course.
1919         return $DB->count_records_sql("SELECT COUNT('x')
1920                                          FROM {assignment_submissions} s
1921                                     LEFT JOIN {assignment} a ON a.id = s.assignment
1922                                    INNER JOIN ($enroledsql) u ON u.id = s.userid
1923                                         WHERE s.assignment = :assignmentid AND
1924                                               s.timemodified > 0", $params);
1925     }
1927     /**
1928      * Alerts teachers by email of new or changed assignments that need grading
1929      *
1930      * First checks whether the option to email teachers is set for this assignment.
1931      * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1932      * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1933      *
1934      * @global object
1935      * @global object
1936      * @param $submission object The submission that has changed
1937      * @return void
1938      */
1939     function email_teachers($submission) {
1940         global $CFG, $DB;
1942         if (empty($this->assignment->emailteachers)) {          // No need to do anything
1943             return;
1944         }
1946         $user = $DB->get_record('user', array('id'=>$submission->userid));
1948         if ($teachers = $this->get_graders($user)) {
1950             $strassignments = get_string('modulenameplural', 'assignment');
1951             $strassignment  = get_string('modulename', 'assignment');
1952             $strsubmitted  = get_string('submitted', 'assignment');
1954             foreach ($teachers as $teacher) {
1955                 $info = new stdClass();
1956                 $info->username = fullname($user, true);
1957                 $info->assignment = format_string($this->assignment->name,true);
1958                 $info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
1959                 $info->timeupdated = userdate($submission->timemodified, '%c', $teacher->timezone);
1961                 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
1962                 $posttext = $this->email_teachers_text($info);
1963                 $posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
1965                 $eventdata = new stdClass();
1966                 $eventdata->modulename       = 'assignment';
1967                 $eventdata->userfrom         = $user;
1968                 $eventdata->userto           = $teacher;
1969                 $eventdata->subject          = $postsubject;
1970                 $eventdata->fullmessage      = $posttext;
1971                 $eventdata->fullmessageformat = FORMAT_PLAIN;
1972                 $eventdata->fullmessagehtml  = $posthtml;
1973                 $eventdata->smallmessage     = $postsubject;
1975                 $eventdata->name            = 'assignment_updates';
1976                 $eventdata->component       = 'mod_assignment';
1977                 $eventdata->notification    = 1;
1978                 $eventdata->contexturl      = $info->url;
1979                 $eventdata->contexturlname  = $info->assignment;
1981                 message_send($eventdata);
1982             }
1983         }
1984     }
1986     /**
1987      * Sends a file
1988      *
1989      * @param string $filearea
1990      * @param array $args
1991      * @param bool $forcedownload whether or not force download
1992      * @param array $options additional options affecting the file serving
1993      * @return bool
1994      */
1995     function send_file($filearea, $args, $forcedownload, array $options=array()) {
1996         debugging('plugin does not implement file sending', DEBUG_DEVELOPER);
1997         return false;
1998     }
2000     /**
2001      * Returns a list of teachers that should be grading given submission
2002      *
2003      * @param object $user
2004      * @return array
2005      */
2006     function get_graders($user) {
2007         global $DB;
2009         //potential graders
2010         list($enrolledsql, $params) = get_enrolled_sql($this->context, 'mod/assignment:grade', 0, true);
2011         $sql = "SELECT u.*
2012                   FROM {user} u
2013                   JOIN ($enrolledsql) je ON je.id = u.id";
2014         $potgraders = $DB->get_records_sql($sql, $params);
2016         $graders = array();
2017         if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) {   // Separate groups are being used
2018             if ($groups = groups_get_all_groups($this->course->id, $user->id)) {  // Try to find all groups
2019                 foreach ($groups as $group) {
2020                     foreach ($potgraders as $t) {
2021                         if ($t->id == $user->id) {
2022                             continue; // do not send self
2023                         }
2024                         if (groups_is_member($group->id, $t->id)) {
2025                             $graders[$t->id] = $t;
2026                         }
2027                     }
2028                 }
2029             } else {
2030                 // user not in group, try to find graders without group
2031                 foreach ($potgraders as $t) {
2032                     if ($t->id == $user->id) {
2033                         continue; // do not send self
2034                     }
2035                     if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
2036                         $graders[$t->id] = $t;
2037                     }
2038                 }
2039             }
2040         } else {
2041             foreach ($potgraders as $t) {
2042                 if ($t->id == $user->id) {
2043                     continue; // do not send self
2044                 }
2045                 $graders[$t->id] = $t;
2046             }
2047         }
2048         return $graders;
2049     }
2051     /**
2052      * Creates the text content for emails to teachers
2053      *
2054      * @param $info object The info used by the 'emailteachermail' language string
2055      * @return string
2056      */
2057     function email_teachers_text($info) {
2058         $posttext  = format_string($this->course->shortname, true, array('context' => $this->coursecontext)).' -> '.
2059                      $this->strassignments.' -> '.
2060                      format_string($this->assignment->name, true, array('context' => $this->context))."\n";
2061         $posttext .= '---------------------------------------------------------------------'."\n";
2062         $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
2063         $posttext .= "\n---------------------------------------------------------------------\n";
2064         return $posttext;
2065     }
2067      /**
2068      * Creates the html content for emails to teachers
2069      *
2070      * @param $info object The info used by the 'emailteachermailhtml' language string
2071      * @return string
2072      */
2073     function email_teachers_html($info) {
2074         global $CFG;
2075         $posthtml  = '<p><font face="sans-serif">'.
2076                      '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->course->id.'">'.format_string($this->course->shortname, true, array('context' => $this->coursecontext)).'</a> ->'.
2077                      '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->course->id.'">'.$this->strassignments.'</a> ->'.
2078                      '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->cm->id.'">'.format_string($this->assignment->name, true, array('context' => $this->context)).'</a></font></p>';
2079         $posthtml .= '<hr /><font face="sans-serif">';
2080         $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
2081         $posthtml .= '</font><hr />';
2082         return $posthtml;
2083     }
2085     /**
2086      * Produces a list of links to the files uploaded by a user
2087      *
2088      * @param $userid int optional id of the user. If 0 then $USER->id is used.
2089      * @param $return boolean optional defaults to false. If true the list is returned rather than printed
2090      * @return string optional
2091      */
2092     function print_user_files($userid=0, $return=false) {
2093         global $CFG, $USER, $OUTPUT;
2095         if (!$userid) {
2096             if (!isloggedin()) {
2097                 return '';
2098             }
2099             $userid = $USER->id;
2100         }
2102         $output = '';
2104         $submission = $this->get_submission($userid);
2105         if (!$submission) {
2106             return $output;
2107         }
2109         $fs = get_file_storage();
2110         $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
2111         if (!empty($files)) {
2112             require_once($CFG->dirroot . '/mod/assignment/locallib.php');
2113             if ($CFG->enableportfolios) {
2114                 require_once($CFG->libdir.'/portfoliolib.php');
2115                 $button = new portfolio_add_button();
2116             }
2117             foreach ($files as $file) {
2118                 $filename = $file->get_filename();
2119                 $mimetype = $file->get_mimetype();
2120                 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
2121                 $output .= '<a href="'.$path.'" >'.$OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')).s($filename).'</a>';
2122                 if ($CFG->enableportfolios && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
2123                     $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
2124                     $button->set_format_by_file($file);
2125                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
2126                 }
2128                 if ($CFG->enableplagiarism) {
2129                     require_once($CFG->libdir.'/plagiarismlib.php');
2130                     $output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
2131                     $output .= '<br />';
2132                 }
2133             }
2134             if ($CFG->enableportfolios && count($files) > 1  && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
2135                 $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id), '/mod/assignment/locallib.php');
2136                 $output .= '<br />'  . $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
2137             }
2138         }
2140         $output = '<div class="files">'.$output.'</div>';
2142         if ($return) {
2143             return $output;
2144         }
2145         echo $output;
2146     }
2148     /**
2149      * Count the files uploaded by a given user
2150      *
2151      * @param $itemid int The submission's id as the file's itemid.
2152      * @return int
2153      */
2154     function count_user_files($itemid) {
2155         $fs = get_file_storage();
2156         $files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $itemid, "id", false);
2157         return count($files);
2158     }
2160     /**
2161      * Returns true if the student is allowed to submit
2162      *
2163      * Checks that the assignment has started and, if the option to prevent late
2164      * submissions is set, also checks that the assignment has not yet closed.
2165      * @return boolean
2166      */
2167     function isopen() {
2168         $time = time();
2169         if ($this->assignment->preventlate && $this->assignment->timedue) {
2170             return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
2171         } else {
2172             return ($this->assignment->timeavailable <= $time);
2173         }
2174     }
2177     /**
2178      * Return true if is set description is hidden till available date
2179      *
2180      * This is needed by calendar so that hidden descriptions do not
2181      * come up in upcoming events.
2182      *
2183      * Check that description is hidden till available date
2184      * By default return false
2185      * Assignments types should implement this method if needed
2186      * @return boolen
2187      */
2188     function description_is_hidden() {
2189         return false;
2190     }
2192     /**
2193      * Return an outline of the user's interaction with the assignment
2194      *
2195      * The default method prints the grade and timemodified
2196      * @param $grade object
2197      * @return object with properties ->info and ->time
2198      */
2199     function user_outline($grade) {
2201         $result = new stdClass();
2202         $result->info = get_string('grade').': '.$grade->str_long_grade;
2203         $result->time = $grade->dategraded;
2204         return $result;
2205     }
2207     /**
2208      * Print complete information about the user's interaction with the assignment
2209      *
2210      * @param $user object
2211      */
2212     function user_complete($user, $grade=null) {
2213         global $OUTPUT;
2215         if ($submission = $this->get_submission($user->id)) {
2217             $fs = get_file_storage();
2219             if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
2220                 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
2221                 foreach ($files as $file) {
2222                     $countfiles .= "; ".$file->get_filename();
2223                 }
2224             }
2226             echo $OUTPUT->box_start();
2227             echo get_string("lastmodified").": ";
2228             echo userdate($submission->timemodified);
2229             echo $this->display_lateness($submission->timemodified);
2231             $this->print_user_files($user->id);
2233             echo '<br />';
2235             $this->view_feedback($submission);
2237             echo $OUTPUT->box_end();
2239         } else {
2240             if ($grade) {
2241                 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
2242                 if ($grade->str_feedback) {
2243                     echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
2244                 }
2245             }
2246             print_string("notsubmittedyet", "assignment");
2247         }
2248     }
2250     /**
2251      * Return a string indicating how late a submission is
2252      *
2253      * @param $timesubmitted int
2254      * @return string
2255      */
2256     function display_lateness($timesubmitted) {
2257         return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
2258     }
2260     /**
2261      * Empty method stub for all delete actions.
2262      */
2263     function delete() {
2264         //nothing by default
2265         redirect('view.php?id='.$this->cm->id);
2266     }
2268     /**
2269      * Empty custom feedback grading form.
2270      */
2271     function custom_feedbackform($submission, $return=false) {
2272         //nothing by default
2273         return '';
2274     }
2276     /**
2277      * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2278      * for the course (see resource).
2279      *
2280      * Given a course_module object, this function returns any "extra" information that may be needed
2281      * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
2282      *
2283      * @param $coursemodule object The coursemodule object (record).
2284      * @return cached_cm_info Object used to customise appearance on course page
2285      */
2286     function get_coursemodule_info($coursemodule) {
2287         return null;
2288     }
2290     /**
2291      * Plugin cron method - do not use $this here, create new assignment instances if needed.
2292      * @return void
2293      */
2294     function cron() {
2295         //no plugin cron by default - override if needed
2296     }
2298     /**
2299      * Reset all submissions
2300      */
2301     function reset_userdata($data) {
2302         global $CFG, $DB;
2304         if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
2305             return array(); // no assignments of this type present
2306         }
2308         $componentstr = get_string('modulenameplural', 'assignment');
2309         $status = array();
2311         $typestr = get_string('type'.$this->type, 'assignment');
2312         // ugly hack to support pluggable assignment type titles...
2313         if($typestr === '[[type'.$this->type.']]'){
2314             $typestr = get_string('type'.$this->type, 'assignment_'.$this->type);
2315         }
2317         if (!empty($data->reset_assignment_submissions)) {
2318             $assignmentssql = "SELECT a.id
2319                                  FROM {assignment} a
2320                                 WHERE a.course=? AND a.assignmenttype=?";
2321             $params = array($data->courseid, $this->type);
2323             // now get rid of all submissions and responses
2324             $fs = get_file_storage();
2325             if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
2326                 foreach ($assignments as $assignmentid=>$unused) {
2327                     if (!$cm = get_coursemodule_from_instance('assignment', $assignmentid)) {
2328                         continue;
2329                     }
2330                     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2331                     $fs->delete_area_files($context->id, 'mod_assignment', 'submission');
2332                     $fs->delete_area_files($context->id, 'mod_assignment', 'response');
2333                 }
2334             }
2336             $DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
2338             $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
2340             if (empty($data->reset_gradebook_grades)) {
2341                 // remove all grades from gradebook
2342                 assignment_reset_gradebook($data->courseid, $this->type);
2343             }
2344         }
2346         /// updating dates - shift may be negative too
2347         if ($data->timeshift) {
2348             shift_course_mod_dates('assignment', array('timedue', 'timeavailable'), $data->timeshift, $data->courseid);
2349             $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged').': '.$typestr, 'error'=>false);
2350         }
2352         return $status;
2353     }
2356     function portfolio_exportable() {
2357         return false;
2358     }
2360     /**
2361      * base implementation for backing up subtype specific information
2362      * for one single module
2363      *
2364      * @param filehandle $bf file handle for xml file to write to
2365      * @param mixed $preferences the complete backup preference object
2366      *
2367      * @return boolean
2368      *
2369      * @static
2370      */
2371     static function backup_one_mod($bf, $preferences, $assignment) {
2372         return true;
2373     }
2375     /**
2376      * base implementation for backing up subtype specific information
2377      * for one single submission
2378      *
2379      * @param filehandle $bf file handle for xml file to write to
2380      * @param mixed $preferences the complete backup preference object
2381      * @param object $submission the assignment submission db record
2382      *
2383      * @return boolean
2384      *
2385      * @static
2386      */
2387     static function backup_one_submission($bf, $preferences, $assignment, $submission) {
2388         return true;
2389     }
2391     /**
2392      * base implementation for restoring subtype specific information
2393      * for one single module
2394      *
2395      * @param array  $info the array representing the xml
2396      * @param object $restore the restore preferences
2397      *
2398      * @return boolean
2399      *
2400      * @static
2401      */
2402     static function restore_one_mod($info, $restore, $assignment) {
2403         return true;
2404     }
2406     /**
2407      * base implementation for restoring subtype specific information
2408      * for one single submission
2409      *
2410      * @param object $submission the newly created submission
2411      * @param array  $info the array representing the xml
2412      * @param object $restore the restore preferences
2413      *
2414      * @return boolean
2415      *
2416      * @static
2417      */
2418     static function restore_one_submission($info, $restore, $assignment, $submission) {
2419         return true;
2420     }
2422 } ////// End of the assignment_base class
2425 class assignment_grading_form extends moodleform {
2426     /** @var stores the advaned grading instance (if used in grading) */
2427     private $advancegradinginstance;
2429     function definition() {
2430         global $OUTPUT;
2431         $mform =& $this->_form;
2433         if (isset($this->_customdata->advancedgradinginstance)) {
2434             $this->use_advanced_grading($this->_customdata->advancedgradinginstance);
2435         }
2437         $formattr = $mform->getAttributes();
2438         $formattr['id'] = 'submitform';
2439         $mform->setAttributes($formattr);
2440         // hidden params
2441         $mform->addElement('hidden', 'offset', ($this->_customdata->offset+1));
2442         $mform->setType('offset', PARAM_INT);
2443         $mform->addElement('hidden', 'userid', $this->_customdata->userid);
2444         $mform->setType('userid', PARAM_INT);
2445         $mform->addElement('hidden', 'nextid', $this->_customdata->nextid);
2446         $mform->setType('nextid', PARAM_INT);
2447         $mform->addElement('hidden', 'id', $this->_customdata->cm->id);
2448         $mform->setType('id', PARAM_INT);
2449         $mform->addElement('hidden', 'sesskey', sesskey());
2450         $mform->setType('sesskey', PARAM_ALPHANUM);
2451         $mform->addElement('hidden', 'mode', 'grade');
2452         $mform->setType('mode', PARAM_TEXT);
2453         $mform->addElement('hidden', 'menuindex', "0");
2454         $mform->setType('menuindex', PARAM_INT);
2455         $mform->addElement('hidden', 'saveuserid', "-1");
2456         $mform->setType('saveuserid', PARAM_INT);
2457         $mform->addElement('hidden', 'filter', "0");
2458         $mform->setType('filter', PARAM_INT);
2460         $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->user),
2461                                                 fullname($this->_customdata->user, true) . '<br/>' .
2462                                                 userdate($this->_customdata->submission->timemodified) .
2463                                                 $this->_customdata->lateness );
2465         $this->add_submission_content();
2466         $this->add_grades_section();
2468         $this->add_feedback_section();
2470         if ($this->_customdata->submission->timemarked) {
2471             $datestring = userdate($this->_customdata->submission->timemarked)."&nbsp; (".format_time(time() - $this->_customdata->submission->timemarked).")";
2472             $mform->addElement('header', 'Last Grade', get_string('lastgrade', 'assignment'));
2473             $mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->teacher) ,
2474                                                     fullname($this->_customdata->teacher,true).
2475                                                     '<br/>'.$datestring);
2476         }
2477         // buttons
2478         $this->add_action_buttons();
2480     }
2482     /**
2483      * Gets or sets the instance for advanced grading
2484      *
2485      * @param gradingform_instance $gradinginstance
2486      */
2487     public function use_advanced_grading($gradinginstance = false) {
2488         if ($gradinginstance !== false) {
2489             $this->advancegradinginstance = $gradinginstance;
2490         }
2491         return $this->advancegradinginstance;
2492     }
2494     /**
2495      * Add the grades configuration section to the assignment configuration form
2496      */
2497     function add_grades_section() {
2498         global $CFG;
2499         $mform =& $this->_form;
2500         $attributes = array();
2501         if ($this->_customdata->gradingdisabled) {
2502             $attributes['disabled'] ='disabled';
2503         }
2505         $mform->addElement('header', 'Grades', get_string('grades', 'grades'));
2507         $grademenu = make_grades_menu($this->_customdata->assignment->grade);
2508         if ($gradinginstance = $this->use_advanced_grading()) {
2509             $gradinginstance->get_controller()->set_grade_range($grademenu);
2510             $gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
2511             if ($this->_customdata->gradingdisabled) {
2512                 $gradingelement->freeze();
2513             } else {
2514                 $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
2515             }
2516         } else {
2517             // use simple direct grading
2518             $grademenu['-1'] = get_string('nograde');
2520             $mform->addElement('select', 'xgrade', get_string('grade').':', $grademenu, $attributes);
2521             $mform->setDefault('xgrade', $this->_customdata->submission->grade ); //@fixme some bug when element called 'grade' makes it break
2522             $mform->setType('xgrade', PARAM_INT);
2523         }
2525         if (!empty($this->_customdata->enableoutcomes)) {
2526             foreach($this->_customdata->grading_info->outcomes as $n=>$outcome) {
2527                 $options = make_grades_menu(-$outcome->scaleid);
2528                 if ($outcome->grades[$this->_customdata->submission->userid]->locked) {
2529                     $options[0] = get_string('nooutcome', 'grades');
2530                     $mform->addElement('static', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':',
2531                             $options[$outcome->grades[$this->_customdata->submission->userid]->grade]);
2532                 } else {
2533                     $options[''] = get_string('nooutcome', 'grades');
2534                     $attributes = array('id' => 'menuoutcome_'.$n );
2535                     $mform->addElement('select', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':', $options, $attributes );
2536                     $mform->setType('outcome_'.$n.'['.$this->_customdata->userid.']', PARAM_INT);
2537                     $mform->setDefault('outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->grades[$this->_customdata->submission->userid]->grade );
2538                 }
2539             }
2540         }
2541         $course_context = get_context_instance(CONTEXT_MODULE , $this->_customdata->cm->id);
2542         if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
2543             $grade = '<a href="'.$CFG->wwwroot.'/grade/report/grader/index.php?id='. $this->_customdata->courseid .'" >'.
2544                         $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade . '</a>';
2545         }else{
2546             $grade = $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade;
2547         }
2548         $mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assignment').':' ,$grade);
2549         $mform->setType('finalgrade', PARAM_INT);
2550     }
2552     /**
2553      *
2554      * @global core_renderer $OUTPUT
2555      */
2556     function add_feedback_section() {
2557         global $OUTPUT;
2558         $mform =& $this->_form;
2559         $mform->addElement('header', 'Feed Back', get_string('feedback', 'grades'));
2561         if ($this->_customdata->gradingdisabled) {
2562             $mform->addElement('static', 'disabledfeedback', $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_feedback );
2563         } else {
2564             // visible elements
2566             $mform->addElement('editor', 'submissioncomment_editor', get_string('feedback', 'assignment').':', null, $this->get_editor_options() );
2567             $mform->setType('submissioncomment_editor', PARAM_RAW); // to be cleaned before display
2568             $mform->setDefault('submissioncomment_editor', $this->_customdata->submission->submissioncomment);
2569             //$mform->addRule('submissioncomment', get_string('required'), 'required', null, 'client');
2570             switch ($this->_customdata->assignment->assignmenttype) {
2571                 case 'upload' :
2572                 case 'uploadsingle' :
2573                     $mform->addElement('filemanager', 'files_filemanager', get_string('responsefiles', 'assignment'). ':', null, $this->_customdata->fileui_options);
2574                     break;
2575                 default :
2576                     break;
2577             }
2578             $mform->addElement('hidden', 'mailinfo_h', "0");
2579             $mform->setType('mailinfo_h', PARAM_INT);
2580             $mform->addElement('checkbox', 'mailinfo',get_string('enablenotification','assignment').
2581             $OUTPUT->help_icon('enablenotification', 'assignment') .':' );
2582             $mform->setType('mailinfo', PARAM_INT);
2583         }
2584     }
2586     function add_action_buttons($cancel = true, $submitlabel = NULL) {
2587         $mform =& $this->_form;
2588         //if there are more to be graded.
2589         if ($this->_customdata->nextid>0) {
2590             $buttonarray=array();
2591             $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
2592             //@todo: fix accessibility: javascript dependency not necessary
2593             $buttonarray[] = &$mform->createElement('submit', 'saveandnext', get_string('saveandnext'));
2594             $buttonarray[] = &$mform->createElement('submit', 'next', get_string('next'));
2595             $buttonarray[] = &$mform->createElement('cancel');
2596         } else {
2597             $buttonarray=array();
2598             $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
2599             $buttonarray[] = &$mform->createElement('cancel');
2600         }
2601         $mform->addGroup($buttonarray, 'grading_buttonar', '', array(' '), false);
2602         $mform->closeHeaderBefore('grading_buttonar');
2603         $mform->setType('grading_buttonar', PARAM_RAW);
2604     }
2606     function add_submission_content() {
2607         $mform =& $this->_form;
2608         $mform->addElement('header', 'Submission', get_string('submission', 'assignment'));
2609         $mform->addElement('static', '', '' , $this->_customdata->submission_content );
2610     }
2612     protected function get_editor_options() {
2613         $editoroptions = array();
2614         $editoroptions['component'] = 'mod_assignment';
2615         $editoroptions['filearea'] = 'feedback';
2616         $editoroptions['noclean'] = false;
2617         $editoroptions['maxfiles'] = 0; //TODO: no files for now, we need to first implement assignment_feedback area, integration with gradebook, files support in quickgrading, etc. (skodak)
2618         $editoroptions['maxbytes'] = $this->_customdata->maxbytes;
2619         $editoroptions['context'] = $this->_customdata->context;
2620         return $editoroptions;
2621     }
2623     public function set_data($data) {
2624         $editoroptions = $this->get_editor_options();
2625         if (!isset($data->text)) {
2626             $data->text = '';
2627         }
2628         if (!isset($data->format)) {
2629             $data->textformat = FORMAT_HTML;
2630         } else {
2631             $data->textformat = $data->format;
2632         }
2634         if (!empty($this->_customdata->submission->id)) {
2635             $itemid = $this->_customdata->submission->id;
2636         } else {
2637             $itemid = null;
2638         }
2640         switch ($this->_customdata->assignment->assignmenttype) {
2641                 case 'upload' :
2642                 case 'uploadsingle' :
2643                     $data = file_prepare_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
2644                     break;
2645                 default :
2646                     break;
2647         }
2649         $data = file_prepare_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
2650         return parent::set_data($data);
2651     }
2653     public function get_data() {
2654         $data = parent::get_data();
2656         if (!empty($this->_customdata->submission->id)) {
2657             $itemid = $this->_customdata->submission->id;
2658         } else {
2659             $itemid = null; //TODO: this is wrong, itemid MUST be known when saving files!! (skodak)
2660         }
2662         if ($data) {
2663             $editoroptions = $this->get_editor_options();
2664             switch ($this->_customdata->assignment->assignmenttype) {
2665                 case 'upload' :
2666                 case 'uploadsingle' :
2667                     $data = file_postupdate_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
2668                     break;
2669                 default :
2670                     break;
2671             }
2672             $data = file_postupdate_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
2673         }
2675         if ($this->use_advanced_grading() && !isset($data->advancedgrading)) {
2676             $data->advancedgrading = null;
2677         }
2679         return $data;
2680     }
2683 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
2685 /**
2686  * Deletes an assignment instance
2687  *
2688  * This is done by calling the delete_instance() method of the assignment type class
2689  */
2690 function assignment_delete_instance($id){
2691     global $CFG, $DB;
2693     if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
2694         return false;
2695     }
2697     // fall back to base class if plugin missing
2698     $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
2699     if (file_exists($classfile)) {
2700         require_once($classfile);
2701         $assignmentclass = "assignment_$assignment->assignmenttype";
2703     } else {
2704         debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
2705         $assignmentclass = "assignment_base";
2706     }
2708     $ass = new $assignmentclass();
2709     return $ass->delete_instance($assignment);
2713 /**
2714  * Updates an assignment instance
2715  *
2716  * This is done by calling the update_instance() method of the assignment type class
2717  */
2718 function assignment_update_instance($assignment){
2719     global $CFG;
2721     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
2723     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2724     $assignmentclass = "assignment_$assignment->assignmenttype";
2725     $ass = new $assignmentclass();
2726     return $ass->update_instance($assignment);
2730 /**
2731  * Adds an assignment instance
2732  *
2733  * This is done by calling the add_instance() method of the assignment type class
2734  *
2735  * @param stdClass $assignment
2736  * @param mod_assignment_mod_form $mform
2737  * @return int intance id
2738  */
2739 function assignment_add_instance($assignment, $mform = null) {
2740     global $CFG;
2742     $assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
2744     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2745     $assignmentclass = "assignment_$assignment->assignmenttype";
2746     $ass = new $assignmentclass();
2747     return $ass->add_instance($assignment);
2751 /**
2752  * Returns an outline of a user interaction with an assignment
2753  *
2754  * This is done by calling the user_outline() method of the assignment type class
2755  */
2756 function assignment_user_outline($course, $user, $mod, $assignment) {
2757     global $CFG;
2759     require_once("$CFG->libdir/gradelib.php");
2760     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2761     $assignmentclass = "assignment_$assignment->assignmenttype";
2762     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2763     $grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
2764     if (!empty($grades->items[0]->grades)) {
2765         return $ass->user_outline(reset($grades->items[0]->grades));
2766     } else {
2767         return null;
2768     }
2771 /**
2772  * Prints the complete info about a user's interaction with an assignment
2773  *
2774  * This is done by calling the user_complete() method of the assignment type class
2775  */
2776 function assignment_user_complete($course, $user, $mod, $assignment) {
2777     global $CFG;
2779     require_once("$CFG->libdir/gradelib.php");
2780     require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2781     $assignmentclass = "assignment_$assignment->assignmenttype";
2782     $ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
2783     $grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
2784     if (empty($grades->items[0]->grades)) {
2785         $grade = false;
2786     } else {
2787         $grade = reset($grades->items[0]->grades);
2788     }
2789     return $ass->user_complete($user, $grade);
2792 /**
2793  * Function to be run periodically according to the moodle cron
2794  *
2795  * Finds all assignment notifications that have yet to be mailed out, and mails them
2796  */
2797 function assignment_cron () {
2798     global $CFG, $USER, $DB;
2800     /// first execute all crons in plugins
2801     if ($plugins = get_plugin_list('assignment')) {
2802         foreach ($plugins as $plugin=>$dir) {
2803             require_once("$dir/assignment.class.php");
2804             $assignmentclass = "assignment_$plugin";
2805             $ass = new $assignmentclass();
2806             $ass->cron();
2807         }
2808     }
2810     /// Notices older than 1 day will not be mailed.  This is to avoid the problem where
2811     /// cron has not been running for a long time, and then suddenly people are flooded
2812     /// with mail from the past few weeks or months
2814     $timenow   = time();
2815     $endtime   = $timenow - $CFG->maxeditingtime;
2816     $starttime = $endtime - 24 * 3600;   /// One day earlier
2818     if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
2820         $realuser = clone($USER);
2822         foreach ($submissions as $key => $submission) {
2823             $DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id));
2824         }
2826         $timenow = time();
2828         foreach ($submissions as $submission) {
2830             echo "Processing assignment submission $submission->id\n";
2832             if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
2833                 echo "Could not find user $user->id\n";
2834                 continue;
2835             }
2837             if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
2838                 echo "Could not find course $submission->course\n";
2839                 continue;
2840             }
2842             /// Override the language and timezone of the "current" user, so that
2843             /// mail is customised for the receiver.
2844             cron_setup_user($user, $course);
2846             $coursecontext = get_context_instance(CONTEXT_COURSE, $submission->course);
2847             $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
2848             if (!is_enrolled($coursecontext, $user->id)) {
2849                 echo fullname($user)." not an active participant in " . $courseshortname . "\n";
2850                 continue;
2851             }
2853             if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
2854                 echo "Could not find teacher $submission->teacher\n";
2855                 continue;
2856             }
2858             if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
2859                 echo "Could not find course module for assignment id $submission->assignment\n";
2860                 continue;
2861             }
2863             if (! $mod->visible) {    /// Hold mail notification for hidden assignments until later
2864                 continue;
2865             }
2867             $strassignments = get_string("modulenameplural", "assignment");
2868             $strassignment  = get_string("modulename", "assignment");
2870             $assignmentinfo = new stdClass();
2871             $assignmentinfo->teacher = fullname($teacher);
2872             $assignmentinfo->assignment = format_string($submission->name,true);
2873             $assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
2875             $postsubject = "$courseshortname: $strassignments: ".format_string($submission->name,true);
2876             $posttext  = "$courseshortname -> $strassignments -> ".format_string($submission->name,true)."\n";
2877             $posttext .= "---------------------------------------------------------------------\n";
2878             $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
2879             $posttext .= "---------------------------------------------------------------------\n";
2881             if ($user->mailformat == 1) {  // HTML
2882                 $posthtml = "<p><font face=\"sans-serif\">".
2883                 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$courseshortname</a> ->".
2884                 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2885                 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name,true)."</a></font></p>";
2886                 $posthtml .= "<hr /><font face=\"sans-serif\">";
2887                 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2888                 $posthtml .= "</font><hr />";
2889             } else {
2890                 $posthtml = "";
2891             }
2893             $eventdata = new stdClass();
2894             $eventdata->modulename       = 'assignment';
2895             $eventdata->userfrom         = $teacher;
2896             $eventdata->userto           = $user;
2897             $eventdata->subject          = $postsubject;
2898             $eventdata->fullmessage      = $posttext;
2899             $eventdata->fullmessageformat = FORMAT_PLAIN;
2900             $eventdata->fullmessagehtml  = $posthtml;
2901             $eventdata->smallmessage     = get_string('assignmentmailsmall', 'assignment', $assignmentinfo);
2903             $eventdata->name            = 'assignment_updates';
2904             $eventdata->component       = 'mod_assignment';
2905             $eventdata->notification    = 1;
2906             $eventdata->contexturl      = $assignmentinfo->url;
2907             $eventdata->contexturlname  = $assignmentinfo->assignment;
2909             message_send($eventdata);
2910         }
2912         cron_setup_user();
2913     }
2915     return true;
2918 /**
2919  * Return grade for given user or all users.
2920  *
2921  * @param stdClass $assignment An assignment instance
2922  * @param int $userid Optional user id, 0 means all users
2923  * @return array An array of grades, false if none
2924  */
2925 function assignment_get_user_grades($assignment, $userid=0) {
2926     global $CFG, $DB;
2928     if ($userid) {
2929         $user = "AND u.id = :userid";
2930         $params = array('userid'=>$userid);
2931     } else {
2932         $user = "";
2933     }
2934     $params['aid'] = $assignment->id;
2936     $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
2937                    s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
2938               FROM {user} u, {assignment_submissions} s
2939              WHERE u.id = s.userid AND s.assignment = :aid
2940                    $user";
2942     return $DB->get_records_sql($sql, $params);
2945 /**
2946  * Update activity grades
2947  *
2948  * @category grade
2949  * @param stdClass $assignment Assignment instance
2950  * @param int $userid specific user only, 0 means all
2951  * @param bool $nullifnone Not used
2952  */
2953 function assignment_update_grades($assignment, $userid=0, $nullifnone=true) {
2954     global $CFG, $DB;
2955     require_once($CFG->libdir.'/gradelib.php');
2957     if ($assignment->grade == 0) {
2958         assignment_grade_item_update($assignment);
2960     } else if ($grades = assignment_get_user_grades($assignment, $userid)) {
2961         foreach($grades as $k=>$v) {
2962             if ($v->rawgrade == -1) {
2963                 $grades[$k]->rawgrade = null;
2964             }
2965         }
2966         assignment_grade_item_update($assignment, $grades);
2968     } else {
2969         assignment_grade_item_update($assignment);
2970     }
2973 /**
2974  * Update all grades in gradebook.
2975  */
2976 function assignment_upgrade_grades() {
2977     global $DB;
2979     $sql = "SELECT COUNT('x')
2980               FROM {assignment} a, {course_modules} cm, {modules} m
2981              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2982     $count = $DB->count_records_sql($sql);
2984     $sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
2985               FROM {assignment} a, {course_modules} cm, {modules} m
2986              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2987     $rs = $DB->get_recordset_sql($sql);
2988     if ($rs->valid()) {
2989         // too much debug output
2990         $pbar = new progress_bar('assignmentupgradegrades', 500, true);
2991         $i=0;
2992         foreach ($rs as $assignment) {
2993             $i++;
2994             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
2995             assignment_update_grades($assignment);
2996             $pbar->update($i, $count, "Updating Assignment grades ($i/$count).");
2997         }
2998         upgrade_set_timeout(); // reset to default timeout
2999     }
3000     $rs->close();
3003 /**
3004  * Create grade item for given assignment
3005  *
3006  * @category grade
3007  * @param stdClass $assignment An assignment instance with extra cmidnumber property
3008  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
3009  * @return int 0 if ok, error code otherwise
3010  */
3011 function assignment_grade_item_update($assignment, $grades=NULL) {
3012     global $CFG;
3013     require_once($CFG->libdir.'/gradelib.php');
3015     if (!isset($assignment->courseid)) {
3016         $assignment->courseid = $assignment->course;
3017     }
3019     $params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
3021     if ($assignment->grade > 0) {
3022         $params['gradetype'] = GRADE_TYPE_VALUE;
3023         $params['grademax']  = $assignment->grade;
3024         $params['grademin']  = 0;
3026     } else if ($assignment->grade < 0) {
3027         $params['gradetype'] = GRADE_TYPE_SCALE;
3028         $params['scaleid']   = -$assignment->grade;
3030     } else {
3031         $params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
3032     }
3034     if ($grades  === 'reset') {
3035         $params['reset'] = true;
3036         $grades = NULL;
3037     }
3039     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
3042 /**
3043  * Delete grade item for given assignment
3044  *
3045  * @category grade
3046  * @param object $assignment object
3047  * @return object assignment
3048  */
3049 function assignment_grade_item_delete($assignment) {
3050     global $CFG;
3051     require_once($CFG->libdir.'/gradelib.php');
3053     if (!isset($assignment->courseid)) {
3054         $assignment->courseid = $assignment->course;
3055     }
3057     return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
3061 /**
3062  * Serves assignment submissions and other files.
3063  *
3064  * @package  mod_assignment
3065  * @category files
3066  * @param stdClass $course course object
3067  * @param stdClass $cm course module object
3068  * @param stdClass $context context object
3069  * @param string $filearea file area
3070  * @param array $args extra arguments
3071  * @param bool $forcedownload whether or not force download
3072  * @param array $options additional options affecting the file serving
3073  * @return bool false if file not found, does not return if found - just send the file
3074  */
3075 function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
3076     global $CFG, $DB;
3078     if ($context->contextlevel != CONTEXT_MODULE) {
3079         return false;
3080     }
3082     require_login($course, false, $cm);
3084     if (!$assignment = $DB->get_record('assignment', array('id'=>$cm->instance))) {
3085         return false;
3086     }
3088     require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
3089     $assignmentclass = 'assignment_'.$assignment->assignmenttype;
3090     $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
3092     return $assignmentinstance->send_file($filearea, $args, $forcedownload, $options);
3094 /**
3095  * Checks if a scale is being used by an assignment
3096  *
3097  * This is used by the backup code to decide whether to back up a scale
3098  * @param $assignmentid int
3099  * @param $scaleid int
3100  * @return boolean True if the scale is used by the assignment
3101  */
3102 function assignment_scale_used($assignmentid, $scaleid) {
3103     global $DB;
3105     $return = false;
3107     $rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
3109     if (!empty($rec) && !empty($scaleid)) {
3110         $return = true;
3111     }
3113     return $return;
3116 /**
3117  * Checks if scale is being used by any instance of assignment
3118  *
3119  * This is used to find out if scale used anywhere
3120  * @param $scaleid int
3121  * @return boolean True if the scale is used by any assignment
3122  */
3123 function assignment_scale_used_anywhere($scaleid) {
3124     global $DB;
3126     if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
3127         return true;
3128     } else {
3129         return false;
3130     }
3133 /**
3134  * Make sure up-to-date events are created for all assignment instances
3135  *
3136  * This standard function will check all instances of this module
3137  * and make sure there are up-to-date events created for each of them.
3138  * If courseid = 0, then every assignment event in the site is checked, else
3139  * only assignment events belonging to the course specified are checked.
3140  * This function is used, in its new format, by restore_refresh_events()
3141  *
3142  * @param $courseid int optional If zero then all assignments for all courses are covered
3143  * @return boolean Always returns true
3144  */
3145 function assignment_refresh_events($courseid = 0) {
3146     global $DB;
3148     if ($courseid == 0) {
3149         if (! $assignments = $DB->get_records("assignment")) {
3150             return true;
3151         }
3152     } else {
3153         if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
3154             return true;
3155         }
3156     }
3157     $moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
3159     foreach ($assignments as $assignment) {
3160         $cm = get_coursemodule_from_id('assignment', $assignment->id);
3161         $event = new stdClass();
3162         $event->name        = $assignment->name;
3163         $event->description = format_module_intro('assignment', $assignment, $cm->id);
3164         $event->timestart   = $assignment->timedue;
3166         if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
3167             update_event($event);
3169         } else {
3170             $event->courseid    = $assignment->course;
3171             $event->groupid     = 0;
3172             $event->userid      = 0;
3173             $event->modulename  = 'assignment';
3174             $event->instance    = $assignment->id;
3175             $event->eventtype   = 'due';
3176             $event->timeduration = 0;
3177             $event->visible     = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
3178             add_event($event);
3179         }
3181     }
3182     return true;
3185 /**
3186  * Print recent activity from all assignments in a given course
3187  *
3188  * This is used by the recent activity block
3189  */
3190 function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
3191     global $CFG, $USER, $DB, $OUTPUT;
3193     // do not use log table if possible, it may be huge
3195     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
3196                                                      u.firstname, u.lastname, u.email, u.picture
3197                                                 FROM {assignment_submissions} asb
3198                                                      JOIN {assignment} a      ON a.id = asb.assignment
3199                                                      JOIN {course_modules} cm ON cm.instance = a.id
3200                                                      JOIN {modules} md        ON md.id = cm.module
3201                                                      JOIN {user} u            ON u.id = asb.userid
3202                                                WHERE asb.timemodified > ? AND
3203                                                      a.course = ? AND
3204                                                      md.name = 'assignment'
3205                                             ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
3206          return false;
3207     }
3209     $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
3210     $show    = array();
3211     $grader  = array();
3213     foreach($submissions as $submission) {
3214         if (!array_key_exists($submission->cmid, $modinfo->cms)) {
3215             continue;
3216         }
3217         $cm = $modinfo->cms[$submission->cmid];
3218         if (!$cm->uservisible) {
3219             continue;
3220         }
3221         if ($submission->userid == $USER->id) {
3222             $show[] = $submission;
3223             continue;
3224         }
3226         // the act of sumbitting of assignment may be considered private - only graders will see it if specified
3227         if (empty($CFG->assignment_showrecentsubmissions)) {
3228             if (!array_key_exists($cm->id, $grader)) {
3229                 $grader[$cm->id] = has_capability('moodle/grade:viewall', get_context_instance(CONTEXT_MODULE, $cm->id));
3230             }
3231             if (!$grader[$cm->id]) {
3232                 continue;
3233             }
3234         }
3236         $groupmode = groups_get_activity_groupmode($cm, $course);
3238         if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
3239             if (isguestuser()) {
3240                 // shortcut - guest user does not belong into any group
3241                 continue;
3242             }
3244             if (is_null($modinfo->groups)) {
3245                 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
3246             }
3248             // this will be slow - show only users that share group with me in this cm
3249             if (empty($modinfo->groups[$cm->id])) {
3250                 continue;
3251             }
3252             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
3253             if (is_array($usersgroups)) {
3254                 $usersgroups = array_keys($usersgroups);
3255                 $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
3256                 if (empty($intersect)) {
3257                     continue;
3258                 }
3259             }
3260         }
3261         $show[] = $submission;
3262     }
3264     if (empty($show)) {
3265         return false;
3266     }
3268     echo $OUTPUT->heading(get_string('newsubmissions', 'assignment').':', 3);
3270     foreach ($show as $submission) {
3271         $cm = $modinfo->cms[$submission->cmid];
3272         $link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
3273         print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
3274     }
3276     return true;
3280 /**
3281  * Returns all assignments since a given time in specified forum.
3282  */
3283 function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0)  {
3284     global $CFG, $COURSE, $USER, $DB;
3286     if ($COURSE->id == $courseid) {
3287         $course = $COURSE;
3288     } else {
3289         $course = $DB->get_record('course', array('id'=>$courseid));
3290     }
3292     $modinfo = get_fast_modinfo($course);
3294     $cm = $modinfo->cms[$cmid];
3296     $params = array();
3297     if ($userid) {
3298         $userselect = "AND u.id = :userid";
3299         $params['userid'] = $userid;
3300     } else {
3301         $userselect = "";
3302     }
3304     if ($groupid) {
3305         $groupselect = "AND gm.groupid = :groupid";
3306         $groupjoin   = "JOIN {groups_members} gm ON  gm.userid=u.id";
3307         $params['groupid'] = $groupid;
3308     } else {
3309         $groupselect = "";
3310         $groupjoin   = "";
3311     }
3313     $params['cminstance'] = $cm->instance;
3314     $params['timestart'] = $timestart;
3316     $userfields = user_picture::fields('u', null, 'userid');
3318     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified,
3319                                                      $userfields
3320                                                 FROM {assignment_submissions} asb
3321                                                 JOIN {assignment} a      ON a.id = asb.assignment
3322                                                 JOIN {user} u            ON u.id = asb.userid
3323                                           $groupjoin
3324                                                WHERE asb.timemodified > :timestart AND a.id = :cminstance
3325                                                      $userselect $groupselect
3326                                             ORDER BY asb.timemodified ASC", $params)) {
3327          return;
3328     }
3330     $groupmode       = groups_get_activity_groupmode($cm, $course);
3331     $cm_context      = get_context_instance(CONTEXT_MODULE, $cm->id);
3332     $grader          = has_capability('moodle/grade:viewall', $cm_context);
3333     $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
3334     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cm_context);
3336     if (is_null($modinfo->groups)) {
3337         $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
3338     }
3340     $show = array();
3342     foreach($submissions as $submission) {
3343         if ($submission->userid == $USER->id) {
3344             $show[] = $submission;
3345             continue;
3346         }
3347         // the act of submitting of assignment may be considered private - only graders will see it if specified
3348         if (empty($CFG->assignment_showrecentsubmissions)) {
3349             if (!$grader) {
3350                 continue;
3351             }
3352         }
3354         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
3355             if (isguestuser()) {
3356                 // shortcut - guest user does not belong into any group
3357                 continue;
3358             }
3360             // this will be slow - show only users that share group with me in this cm
3361             if (empty($modinfo->groups[$cm->id])) {
3362                 continue;
3363             }
3364             $usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
3365             if (is_array($usersgroups)) {
3366                 $usersgroups = array_keys($usersgroups);
3367                 $intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
3368                 if (empty($intersect)) {
3369                     continue;
3370                 }
3371             }
3372         }
3373         $show[] = $submission;
3374     }
3376     if (empty($show)) {
3377         return;
3378     }
3380     if ($grader) {
3381         require_once($CFG->libdir.'/gradelib.php');
3382         $userids = array();
3383         foreach ($show as $id=>$submission) {
3384             $userids[] = $submission->userid;
3386         }
3387         $grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
3388     }
3390     $aname = format_string($cm->name,true);
3391     foreach ($show as $submission) {
3392         $tmpactivity = new stdClass();
3394         $tmpactivity->type         = 'assignment';
3395         $tmpactivity->cmid         = $cm->id;
3396         $tmpactivity->name         = $aname;
3397         $tmpactivity->sectionnum   = $cm->sectionnum;
3398         $tmpactivity->timestamp    = $submission->timemodified;
3400         if ($grader) {
3401             $tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
3402         }
3404         $userfields = explode(',', user_picture::fields());
3405         foreach ($userfields as $userfield) {
3406             if ($userfield == 'id') {
3407                 $tmpactivity->user->{$userfield} = $submission->userid; // aliased in SQL above
3408             } else {
3409                 $tmpactivity->user->{$userfield} = $submission->{$userfield};
3410             }
3411         }
3412         $tmpactivity->user->fullname = fullname($submission, $viewfullnames);
3414         $activities[$index++] = $tmpactivity;
3415     }
3417     return;
3420 /**
3421  * Print recent activity from all assignments in a given course
3422  *
3423  * This is used by course/recent.php
3424  */
3425 function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames)  {
3426     global $CFG, $OUTPUT;
3428     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
3430     echo "<tr><td class=\"userpicture\" valign=\"top\">";
3431     echo $OUTPUT->user_picture($activity->user);
3432     echo "</td><td>";
3434     if ($detail) {
3435         $modname = $modnames[$activity->type];
3436         echo '<div class="title">';
3437         echo "<img src=\"" . $OUTPUT->pix_url('icon', 'assignment') . "\" ".
3438              "class=\"icon\" alt=\"$modname\">";
3439         echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id={$activity->cmid}\">{$activity->name}</a>";
3440         echo '</div>';
3441     }
3443     if (isset($activity->grade)) {
3444         echo '<div class="grade">';
3445         echo get_string('grade').': ';
3446         echo $activity->grade;
3447         echo '</div>';
3448     }
3450     echo '<div class="user">';
3451     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
3452          ."{$activity->user->fullname}</a>  - ".userdate($activity->timestamp);
3453     echo '</div>';
3455     echo "</td></tr></table>";
3458 /// GENERIC SQL FUNCTIONS
3460 /**
3461  * Fetch info from logs
3462  *
3463  * @param $log object with properties ->info (the assignment id) and ->userid
3464  * @return array with assignment name and user firstname and lastname
3465  */
3466 function assignment_log_info($log) {
3467     global $CFG, $DB;
3469     return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
3470                                   FROM {assignment} a, {user} u
3471                                  WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
3474 /**
3475  * Return list of marked submissions that have not been mailed out for currently enrolled students
3476  *
3477  * @return array
3478  */
3479 function assignment_get_unmailed_submissions($starttime, $endtime) {
3480     global $CFG, $DB;
3482     return $DB->get_records_sql("SELECT s.*, a.course, a.name
3483                                    FROM {assignment_submissions} s,
3484                                         {assignment} a
3485                                   WHERE s.mailed = 0
3486                                         AND s.timemarked <= ?
3487                                         AND s.timemarked >= ?
3488                                         AND s.assignment = a.id", array($endtime, $starttime));
3491 /**
3492  * Counts all complete (real) assignment submissions by enrolled students for the given course modeule.
3493  *
3494  * @deprecated                         Since Moodle 2.2 MDL-abc - Please do not use this function any more.
3495  * @param  cm_info $cm                 The course module that we wish to perform the count on.
3496  * @param  int     $groupid (optional) If nonzero then count is restricted to this group
3497  * @return int                         The number of submissions
3498  */
3499 function assignment_count_real_submissions($cm, $groupid=0) {
3500     global $CFG, $DB;
3502     // Grab the assignment type for the given course module
3503     $assignmenttype = $DB->get_field($cm->modname, 'assignmenttype', array('id' => $cm->instance), MUST_EXIST);
3505     // Create the expected class file path and class name for the returned assignemnt type
3506     $filename = "{$CFG->dirroot}/mod/assignment/type/{$assignmenttype}/assignment.class.php";
3507     $classname = "assignment_{$assignmenttype}";
3509     // If the file exists and the class is not already loaded we require the class file
3510     if (file_exists($filename) && !class_exists($classname)) {
3511         require_once($filename);
3512     }
3513     // If the required class is still not loaded then we revert to assignment base
3514     if (!class_exists($classname)) {
3515         $classname = 'assignment_base';
3516     }
3517     $instance = new $classname;
3519     // Attach the course module to the assignment type instance and then call the method for counting submissions
3520     $instance->cm = $cm;
3521     return $instance->count_real_submissions($groupid);
3524 /**
3525  * Return all assignment submissions by ENROLLED students (even empty)
3526  *
3527  * There are also assignment type methods get_submissions() wich in the default
3528  * implementation simply call this function.
3529  * @param $sort string optional field names for the ORDER BY in the sql query
3530  * @param $dir string optional specifying the sort direction, defaults to DESC
3531  * @return array The submission objects indexed by id
3532  */
3533 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
3534 /// Return all assignment submissions by ENROLLED students (even empty)
3535     global $CFG, $DB;
3537     if ($sort == "lastname" or $sort == "firstname") {
3538         $sort = "u.$sort $dir";
3539     } else if (empty($sort)) {
3540         $sort = "a.timemodified DESC";
3541     } else {
3542         $sort = "a.$sort $dir";
3543     }
3545     /* not sure this is needed at all since assignment already has a course define, so this join?
3546     $select = "s.course = '$assignment->course' AND";
3547     if ($assignment->course == SITEID) {
3548         $select = '';
3549     }*/
3551     return $DB->get_records_sql("SELECT a.*
3552                                    FROM {assignment_submissions} a, {user} u
3553                                   WHERE u.id = a.userid
3554                                         AND a.assignment = ?
3555                                ORDER BY $sort", array($assignment->id));
3559 /**
3560  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
3561  * for the course (see resource).
3562  *
3563  * Given a course_module object, this function returns any "extra" information that may be needed
3564  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
3565  *
3566  * @param $coursemodule object The coursemodule object (record).
3567  * @return cached_cm_info An object on information that the courses will know about (most noticeably, an icon).
3568  */
3569 function assignment_get_coursemodule_info($coursemodule) {
3570     global $CFG, $DB;
3572     if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance),
3573             'id, assignmenttype, name, intro, introformat')) {
3574         return false;
3575     }
3577     $libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
3579     if (file_exists($libfile)) {
3580         require_once($libfile);
3581         $assignmentclass = "assignment_$assignment->assignmenttype";
3582         $ass = new $assignmentclass('staticonly');
3583         if (!($result = $ass->get_coursemodule_info($coursemodule))) {
3584             $result = new cached_cm_info();
3585             $result->name = $assignment->name;
3586         }
3587         if ($coursemodule->showdescription) {
3588             // Convert intro to html. Do not filter cached version, filters run at display time.
3589             $result->content = format_module_intro('assignment', $assignment, $coursemodule->id, false);
3590         }
3591         return $result;
3592     } else {
3593         debugging('Incorrect assignment type: '.$assignment->assignmenttype);
3594         return false;
3595     }
3600 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS  ///////////////////////////////////////
3602 /**
3603  * Returns an array of installed assignment types indexed and sorted by name
3604  *
3605  * @return array The index is the name of the assignment type, the value its full name from the language strings
3606  */
3607 function assignment_types() {
3608     $types = array();
3609     $names = get_plugin_list('assignment');
3610     foreach ($names as $name=>$dir) {
3611         $types[$name] = get_string('type'.$name, 'assignment');
3613         // ugly hack to support pluggable assignment type titles..
3614         if ($types[$name] == '[[type'.$name.']]') {
3615             $types[$name] = get_string('type'.$name, 'assignment_'.$name);
3616         }
3617     }
3618     asort($types);
3619     return $types;
3622 function assignment_print_overview($courses, &$htmlarray) {
3623     global $USER, $CFG, $DB;
3624     require_once($CFG->libdir.'/gradelib.php');
3626     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
3627         return array();
3628     }
3630     if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
3631         return;
3632     }
3634     $assignmentids = array();
3636     // Do assignment_base::isopen() here without loading the whole thing for speed
3637     foreach ($assignments as $key => $assignment) {
3638         $time = time();
3639         if ($assignment->timedue) {
3640             if ($assignment->preventlate) {
3641                 $isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
3642             } else {
3643                 $isopen = ($assignment->timeavailable <= $time);
3644             }
3645         }
3646         if (empty($isopen) || empty($assignment->timedue)) {
3647             unset($assignments[$key]);
3648         } else {
3649             $assignmentids[] = $assignment->id;
3650         }
3651     }
3653     if (empty($assignmentids)){
3654         // no assignments to look at - we're done
3655         return true;
3656     }
3658     $strduedate = get_string('duedate', 'assignment');
3659     $strduedateno = get_string('duedateno', 'assignment');
3660     $strgraded = get_string('graded', 'assignment');
3661     $strnotgradedyet = get_string('notgradedyet', 'assignment');
3662     $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
3663     $strsubmitted = get_string('submitted', 'assignment');
3664     $strassignment = get_string('modulename', 'assignment');
3665     $strreviewed = get_string('reviewed','assignment');
3668     // NOTE: we do all possible database work here *outside* of the loop to ensure this scales
3669     //
3670     list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
3672     // build up and array of unmarked submissions indexed by assignment id/ userid
3673     // for use where the user has grading rights on assignment
3674     $rs = $DB->get_recordset_sql("SELECT id, assignment, userid
3675                             FROM {assignment_submissions}
3676                             WHERE teacher = 0 AND timemarked = 0
3677                             AND assignment $sqlassignmentids", $assignmentidparams);
3679     $unmarkedsubmissions = array();
3680     foreach ($rs as $rd) {
3681         $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
3682     }
3683     $rs->close();
3686     // get all user submissions, indexed by assignment id
3687     $mysubmissions = $DB->get_records_sql("SELECT assignment, timemarked, teacher, grade
3688                                       FROM {assignment_submissions}
3689                                       WHERE userid = ? AND
3690                                       assignment $sqlassignmentids", array_merge(array($USER->id), $assignmentidparams));
3692     foreach ($assignments as $assignment) {
3693         $grading_info = grade_get_grades($assignment->course, 'mod', 'assignment', $assignment->id, $USER->id);
3694         $final_grade = $grading_info->items[0]->grades[$USER->id];
3696         $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
3697                '<a '.($assignment->visible ? '':' class="dimmed"').
3698                'title="'.$strassignment.'" href="'.$CFG->wwwroot.
3699                '/mod/assignment/view.php?id='.$assignment->coursemodule.'">'.
3700                $assignment->name.'</a></div>';
3701         if ($assignment->timedue) {
3702             $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue).'</div>';
3703         } else {
3704             $str .= '<div class="info">'.$strduedateno.'</div>';
3705         }
3706         $context = get_context_instance(CONTEXT_MODULE, $assignment->coursemodule);
3707         if (has_capability('mod/assignment:grade', $context)) {
3709             // count how many people can submit
3710             $submissions = 0; // init
3711             if ($students = get_enrolled_users($context, 'mod/assignment:view', 0, 'u.id')) {
3712                 foreach ($students as $student) {
3713                     if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
3714                         $submissions++;
3715                     }
3716                 }
3717             }
3719             if ($submissions) {
3720                 $link = new moodle_url('/mod/assignment/submissions.php', array('id'=>$assignment->coursemodule));
3721                 $str .= '<div class="details"><a href="'.$link.'">'.get_string('submissionsnotgraded', 'assignment', $submissions).'</a></div>';
3722             }
3723         } else {
3724             $str .= '<div class="details">';
3725             if (isset($mysubmissions[$assignment->id])) {
3727                 $submission = $mysubmissions[$assignment->id];
3729                 if ($submission->teacher == 0 && $submission->timemarked == 0 && !$final_grade->grade) {
3730                     $str .= $strsubmitted . ', ' . $strnotgradedyet;
3731                 } else if ($submission->grade <= 0 && !$final_grade->grade) {
3732                     $str .= $strsubmitted . ', ' . $strreviewed;
3733                 } else {
3734                     $str .= $strsubmitted . ', ' . $strgraded;
3735                 }
3736             } else {
3737                 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue);
3738             }
3739             $str .= '</div>';
3740         }
3741         $str .= '</div>';
3742         if (empty($htmlarray[$assignment->course]['assignment'])) {
3743             $htmlarray[$assignment->course]['assignment'] = $str;
3744         } else {
3745             $htmlarray[$assignment->course]['assignment'] .= $str;
3746         }
3747     }
3750 function assignment_display_lateness($timesubmitted, $timedue) {
3751     if (!$timedue) {
3752         return '';
3753     }
3754     $time = $timedue - $timesubmitted;
3755     if ($time < 0) {
3756         $timetext = get_string('late', 'assignment', format_time($time));
3757         return ' (<span class="late">'.$timetext.'</span>)';
3758     } else {
3759         $timetext = get_string('early', 'assignment', format_time($time));
3760         return ' (<span class="early">'.$timetext.'</span>)';
3761     }
3764 function assignment_get_view_actions() {
3765     return array('view');
3768 function assignment_get_post_actions() {
3769     return array('upload');
3772 function assignment_get_types() {
3773     global $CFG;
3774     $types = array();
3776     $type = new stdClass();
3777     $type->modclass = MOD_CLASS_ACTIVITY;
3778     $type->type = "assignment_group_start";
3779     $type->typestr = '--'.get_string('modulenameplural', 'assignment');
3780     $types[] = $type;
3782     $standardassignments = array('upload','online','uploadsingle','offline');
3783     foreach ($standardassignments as $assignmenttype) {
3784         $type = new stdClass();
3785         $type->modclass = MOD_CLASS_ACTIVITY;
3786         $type->type = "assignment&amp;type=$assignmenttype";
3787         $type->typestr = get_string("type$assignmenttype", 'assignment');
3788         $types[] = $type;
3789     }
3791     /// Drop-in extra assignment types
3792     $assignmenttypes = get_list_of_plugins('mod/assignment/type');
3793     foreach ($assignmenttypes as $assignmenttype) {
3794         if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) {  // Not wanted
3795             continue;
3796         }
3797         if (!in_array($assignmenttype, $standardassignments)) {
3798             $type = new stdClass();
3799             $type->modclass = MOD_CLASS_ACTIVITY;
3800             $type->type = "assignment&amp;type=$assignmenttype";
3801             $type->typestr = get_string("type$assignmenttype", 'assignment_'.$assignmenttype);
3802             $types[] = $type;
3803         }
3804     }
3806     $type = new stdClass();
3807     $type->modclass = MOD_CLASS_ACTIVITY;
3808     $type->type = "assignment_group_end";
3809     $type->typestr = '--';
3810     $types[] = $type;
3812     return $types;
3815 /**
3816  * Removes all grades from gradebook
3817  *
3818  * @param int $courseid The ID of the course to reset
3819  * @param string $type Optional type of assignment to limit the reset to a particular assignment type
3820  */
3821 function assignment_reset_gradebook($courseid, $type='') {
3822     global $CFG, $DB;
3824     $params = array('courseid'=>$courseid);
3825     if ($type) {
3826         $type = "AND a.assignmenttype= :type";
3827         $params['type'] = $type;
3828     }
3830     $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
3831               FROM {assignment} a, {course_modules} cm, {modules} m
3832              WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid $type";
3834     if ($assignments = $DB->get_records_sql($sql, $params)) {
3835         foreach ($assignments as $assignment) {
3836             assignment_grade_item_update($assignment, 'reset');
3837         }
3838     }
3841 /**
3842  * This function is used by the reset_course_userdata function in moodlelib.
3843  * This function will remove all posts from the specified assignment
3844  * and clean up any related data.
3845  * @param $data the data submitted from the reset course.
3846  * @return array status array
3847  */
3848 function assignment_reset_userdata($data) {
3849     global $CFG;
3851     $status = array();
3852     foreach (get_plugin_list('assignment') as $type=>$dir) {
3853         require_once("$dir/assignment.class.php");
3854         $assignmentclass = "assignment_$type";
3855         $ass = new $assignmentclass();
3856         $status = array_merge($status, $ass->reset_userdata($data));
3857     }
3859     return $status;
3862 /**
3863  * Implementation of the function for printing the form elements that control
3864  * whether the course reset functionality affects the assignment.
3865  * @param $mform form passed by reference
3866  */
3867 function assignment_reset_course_form_definition(&$mform) {
3868     $mform->addElement('header', 'assignmentheader', get_string('modulenameplural', 'assignment'));
3869     $mform->addElement('advcheckbox', 'reset_assignment_submissions', get_string('deleteallsubmissions','assignment'));
3872 /**
3873  * Course reset form defaults.
3874  */
3875 function assignment_reset_course_form_defaults($course) {
3876     return array('reset_assignment_submissions'=>1);
3879 /**
3880  * Returns all other caps used in module
3881  */
3882 function assignment_get_extra_capabilities() {
3883     return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/grade:managegradingforms');
3886 /**
3887  * @param string $feature FEATURE_xx constant for requested feature
3888  * @return mixed True if module supports feature, null if doesn't know
3889  */
3890 function assignment_supports($feature) {
3891     switch($feature) {
3892         case FEATURE_GROUPS:                  return true;
3893         case FEATURE_GROUPINGS:               return true;
3894         case FEATURE_GROUPMEMBERSONLY:        return true;
3895         case FEATURE_MOD_INTRO:               return true;
3896         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3897         case FEATURE_GRADE_HAS_GRADE:         return true;
3898         case FEATURE_GRADE_OUTCOMES:          return true;
3899         case FEATURE_GRADE_HA