a193b56e18914d0924d63f42d0b4d61f19f5e775
[moodle.git] / mod / assign / locallib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains the definition for the class assignment
19  *
20  * This class provides all the functionality for the new assign module.
21  *
22  * @package   mod_assign
23  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
24  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Assignment submission statuses
31  */
32 define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft'); // student thinks it is a draft
33 define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
35 /**
36  * Search filters for grading page
37  */
38 define('ASSIGN_FILTER_SUBMITTED', 'submitted');
39 define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
40 define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
42 /**
43  * File areas for assignment portfolio if enabled
44  */
45 define('ASSIGN_FILEAREA_PORTFOLIO_FILES', 'portfolio_files');
48 /** Include accesslib.php */
49 require_once($CFG->libdir.'/accesslib.php');
50 /** Include formslib.php */
51 require_once($CFG->libdir.'/formslib.php');
52 /** Include repository/lib.php */
53 require_once($CFG->dirroot . '/repository/lib.php');
54 /** Include local mod_form.php */
55 require_once($CFG->dirroot.'/mod/assign/mod_form.php');
56 /** Include portfoliolib.php */
57 require_once($CFG->libdir . '/portfoliolib.php');
58 /** gradelib.php */
59 require_once($CFG->libdir.'/gradelib.php');
60 /** grading lib.php */
61 require_once($CFG->dirroot.'/grade/grading/lib.php');
62 /** Include feedbackplugin.php */
63 require_once($CFG->dirroot.'/mod/assign/feedbackplugin.php');
64 /** Include submissionplugin.php */
65 require_once($CFG->dirroot.'/mod/assign/submissionplugin.php');
66 /** Include renderable.php */
67 require_once($CFG->dirroot.'/mod/assign/renderable.php');
68 /** Include gradingtable.php */
69 require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
70 /** Include eventslib.php */
71 require_once($CFG->libdir.'/eventslib.php');
74 /**
75  * Standard base class for mod_assign (assignment types).
76  *
77  * @package   mod_assign
78  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
79  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
80  */
81 class assign {
84     /** @var stdClass the assignment record that contains the global settings for this assign instance */
85     private $instance;
87     /** @var context the context of the course module for this assign instance (or just the course if we are
88         creating a new one) */
89     private $context;
91     /** @var stdClass the course this assign instance belongs to */
92     private $course;
94     /** @var assign_renderer the custom renderer for this module */
95     private $output;
97     /** @var stdClass the course module for this assign instance */
98     private $coursemodule;
100     /** @var array cache for things like the coursemodule name or the scale menu - only lives for a single
101         request */
102     private $cache;
104     /** @var array list of the installed submission plugins */
105     private $submissionplugins;
107     /** @var array list of the installed feedback plugins */
108     private $feedbackplugins;
110     /** @var string action to be used to return to this page (without repeating any form submissions etc.) */
111     private $returnaction = 'view';
113     /** @var array params to be used to return to this page */
114     private $returnparams = array();
116     /** @var string modulename prevents excessive calls to get_string */
117     private static $modulename = '';
119     /** @var string modulenameplural prevents excessive calls to get_string */
120     private static $modulenameplural = '';
122     /**
123      * Constructor for the base assign class
124      *
125      * @param mixed $coursemodulecontext context|null the course module context (or the course context if the coursemodule has not been created yet)
126      * @param mixed $coursemodule the current course module if it was already loaded - otherwise this class will load one from the context as required
127      * @param mixed $course the current course  if it was already loaded - otherwise this class will load one from the context as required
128      */
129     public function __construct($coursemodulecontext, $coursemodule, $course) {
130         global $PAGE;
132         $this->context = $coursemodulecontext;
133         $this->coursemodule = $coursemodule;
134         $this->course = $course;
135         $this->cache = array(); // temporary cache only lives for a single request - used to reduce db lookups
137         $this->submissionplugins = $this->load_plugins('assignsubmission');
138         $this->feedbackplugins = $this->load_plugins('assignfeedback');
139         $this->output = $PAGE->get_renderer('mod_assign');
140     }
142     /**
143      * Set the action and parameters that can be used to return to the current page
144      *
145      * @param string $action The action for the current page
146      * @param array $params An array of name value pairs which form the parameters to return to the current page
147      * @return void
148      */
149     public function register_return_link($action, $params) {
150         $this->returnaction = $action;
151         $this->returnparams = $params;
152     }
154     /**
155      * Return an action that can be used to get back to the current page
156      * @return string action
157      */
158     public function get_return_action() {
159         return $this->returnaction;
160     }
162     /**
163      * Based on the current assignment settings should we display the intro
164      * @return bool showintro
165      */
166     private function show_intro() {
167         if ($this->get_instance()->alwaysshowdescription ||
168                 time() > $this->get_instance()->allowsubmissionsfromdate) {
169             return true;
170         }
171         return false;
172     }
174     /**
175      * Return a list of parameters that can be used to get back to the current page
176      * @return array params
177      */
178     public function get_return_params() {
179         return $this->returnparams;
180     }
182     /**
183      * Set the submitted form data
184      * @param stdClass $data The form data (instance)
185      */
186     public function set_instance(stdClass $data) {
187         $this->instance = $data;
188     }
190     /**
191      * Set the context
192      * @param context $context The new context
193      */
194     public function set_context(context $context) {
195         $this->context = $context;
196     }
198     /**
199      * Set the course data
200      * @param stdClass $course The course data
201      */
202     public function set_course(stdClass $course) {
203         $this->course = $course;
204     }
206     /**
207      * get list of feedback plugins installed
208      * @return array
209      */
210     public function get_feedback_plugins() {
211         return $this->feedbackplugins;
212     }
214     /**
215      * get list of submission plugins installed
216      * @return array
217      */
218     public function get_submission_plugins() {
219         return $this->submissionplugins;
220     }
223     /**
224      * get a specific submission plugin by its type
225      * @param string $subtype assignsubmission | assignfeedback
226      * @param string $type
227      * @return mixed assign_plugin|null
228      */
229     private function get_plugin_by_type($subtype, $type) {
230         $shortsubtype = substr($subtype, strlen('assign'));
231         $name = $shortsubtype . 'plugins';
232         $pluginlist = $this->$name;
233         foreach ($pluginlist as $plugin) {
234             if ($plugin->get_type() == $type) {
235                 return $plugin;
236             }
237         }
238         return null;
239     }
241     /**
242      * Get a feedback plugin by type
243      * @param string $type - The type of plugin e.g comments
244      * @return mixed assign_feedback_plugin|null
245      */
246     public function get_feedback_plugin_by_type($type) {
247         return $this->get_plugin_by_type('assignfeedback', $type);
248     }
250     /**
251      * Get a submission plugin by type
252      * @param string $type - The type of plugin e.g comments
253      * @return mixed assign_submission_plugin|null
254      */
255     public function get_submission_plugin_by_type($type) {
256         return $this->get_plugin_by_type('assignsubmission', $type);
257     }
259     /**
260      * Load the plugins from the sub folders under subtype
261      * @param string $subtype - either submission or feedback
262      * @return array - The sorted list of plugins
263      */
264     private function load_plugins($subtype) {
265         global $CFG;
266         $result = array();
268         $names = get_plugin_list($subtype);
270         foreach ($names as $name => $path) {
271             if (file_exists($path . '/locallib.php')) {
272                 require_once($path . '/locallib.php');
274                 $shortsubtype = substr($subtype, strlen('assign'));
275                 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
277                 $plugin = new $pluginclass($this, $name);
279                 if ($plugin instanceof assign_plugin) {
280                     $idx = $plugin->get_sort_order();
281                     while (array_key_exists($idx, $result)) $idx +=1;
282                     $result[$idx] = $plugin;
283                 }
284             }
285         }
286         ksort($result);
287         return $result;
288     }
291     /**
292      * Display the assignment, used by view.php
293      *
294      * The assignment is displayed differently depending on your role,
295      * the settings for the assignment and the status of the assignment.
296      * @param string $action The current action if any.
297      * @return void
298      */
299     public function view($action='') {
301         $o = '';
302         $mform = null;
304         // handle form submissions first
305         if ($action == 'savesubmission') {
306             $action = 'editsubmission';
307             if ($this->process_save_submission($mform)) {
308                 $action = 'view';
309             }
310          } else if ($action == 'lock') {
311             $this->process_lock();
312             $action = 'grading';
313          } else if ($action == 'reverttodraft') {
314             $this->process_revert_to_draft();
315             $action = 'grading';
316          } else if ($action == 'unlock') {
317             $this->process_unlock();
318             $action = 'grading';
319          } else if ($action == 'confirmsubmit') {
320             $this->process_submit_for_grading();
321             // save and show next button
322          } else if ($action == 'batchgradingoperation') {
323             $this->process_batch_grading_operation();
324             $action = 'grading';
325          } else if ($action == 'submitgrade') {
326             if (optional_param('saveandshownext', null, PARAM_ALPHA)) {
327                 //save and show next
328                 $action = 'grade';
329                 if ($this->process_save_grade($mform)) {
330                     $action = 'nextgrade';
331                 }
332             } else if (optional_param('nosaveandprevious', null, PARAM_ALPHA)) {
333                 $action = 'previousgrade';
334             } else if (optional_param('nosaveandnext', null, PARAM_ALPHA)) {
335                 //show next button
336                 $action = 'nextgrade';
337             } else if (optional_param('savegrade', null, PARAM_ALPHA)) {
338                 //save changes button
339                 $action = 'grade';
340                 if ($this->process_save_grade($mform)) {
341                     $action = 'grading';
342                 }
343             } else {
344                 //cancel button
345                 $action = 'grading';
346             }
347         }else if ($action == 'saveoptions') {
348             $this->process_save_grading_options();
349             $action = 'grading';
350         }
352         $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
353         $this->register_return_link($action, $returnparams);
355         // now show the right view page
356         if ($action == 'previousgrade') {
357             $mform = null;
358             $o .= $this->view_single_grade_page($mform, -1);
359         } else if ($action == 'nextgrade') {
360             $mform = null;
361             $o .= $this->view_single_grade_page($mform, 1);
362         } else if ($action == 'redirect') {
363             redirect(required_param('url', PARAM_TEXT));
364         } else if ($action == 'grade') {
365             $o .= $this->view_single_grade_page($mform);
366         } else if ($action == 'viewpluginassignfeedback') {
367             $o .= $this->view_plugin_content('assignfeedback');
368         } else if ($action == 'viewpluginassignsubmission') {
369             $o .= $this->view_plugin_content('assignsubmission');
370         } else if ($action == 'editsubmission') {
371             $o .= $this->view_edit_submission_page($mform);
372         } else if ($action == 'grading') {
373             $o .= $this->view_grading_page();
374         } else if ($action == 'downloadall') {
375             $o .= $this->download_submissions();
376         } else if ($action == 'submit') {
377             $o .= $this->check_submit_for_grading();
378         } else {
379             $o .= $this->view_submission_page();
380         }
382         return $o;
383     }
386     /**
387      * Add this instance to the database
388      *
389      * @param stdClass $formdata The data submitted from the form
390      * @param bool $callplugins This is used to skip the plugin code
391      *             when upgrading an old assignment to a new one (the plugins get called manually)
392      * @return mixed false if an error occurs or the int id of the new instance
393      */
394     public function add_instance(stdClass $formdata, $callplugins) {
395         global $DB;
397         $err = '';
399         // add the database record
400         $update = new stdClass();
401         $update->name = $formdata->name;
402         $update->timemodified = time();
403         $update->timecreated = time();
404         $update->course = $formdata->course;
405         $update->courseid = $formdata->course;
406         $update->intro = $formdata->intro;
407         $update->introformat = $formdata->introformat;
408         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
409         $update->preventlatesubmissions = $formdata->preventlatesubmissions;
410         $update->submissiondrafts = $formdata->submissiondrafts;
411         $update->sendnotifications = $formdata->sendnotifications;
412         $update->duedate = $formdata->duedate;
413         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
414         $update->grade = $formdata->grade;
415         $returnid = $DB->insert_record('assign', $update);
416         $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
417         // cache the course record
418         $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
420         if ($callplugins) {
421             // call save_settings hook for submission plugins
422             foreach ($this->submissionplugins as $plugin) {
423                 if (!$this->update_plugin_instance($plugin, $formdata)) {
424                     print_error($plugin->get_error());
425                     return false;
426                 }
427             }
428             foreach ($this->feedbackplugins as $plugin) {
429                 if (!$this->update_plugin_instance($plugin, $formdata)) {
430                     print_error($plugin->get_error());
431                     return false;
432                 }
433             }
435             // in the case of upgrades the coursemodule has not been set so we need to wait before calling these two
436             // TODO: add event to the calendar
437             $this->update_calendar($formdata->coursemodule);
438             // TODO: add the item in the gradebook
439             $this->update_gradebook(false, $formdata->coursemodule);
441         }
443         $update = new stdClass();
444         $update->id = $this->get_instance()->id;
445         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
446         $DB->update_record('assign', $update);
448         return $returnid;
449     }
451     /**
452      * Delete all grades from the gradebook for this assignment
453      *
454      * @return bool
455      */
456     private function delete_grades() {
457         global $CFG;
459         return grade_update('mod/assign', $this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, 0, NULL, array('deleted'=>1)) == GRADE_UPDATE_OK;
460     }
462     /**
463      * Delete this instance from the database
464      *
465      * @return bool false if an error occurs
466      */
467     public function delete_instance() {
468         global $DB;
469         $result = true;
471         foreach ($this->submissionplugins as $plugin) {
472             if (!$plugin->delete_instance()) {
473                 print_error($plugin->get_error());
474                 $result = false;
475             }
476         }
477         foreach ($this->feedbackplugins as $plugin) {
478             if (!$plugin->delete_instance()) {
479                 print_error($plugin->get_error());
480                 $result = false;
481             }
482         }
484         // delete files associated with this assignment
485         $fs = get_file_storage();
486         if (! $fs->delete_area_files($this->context->id) ) {
487             $result = false;
488         }
490         // delete_records will throw an exception if it fails - so no need for error checking here
492         $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
493         $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
494         $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
496         // delete items from the gradebook
497         if (! $this->delete_grades()) {
498             $result = false;
499         }
501         // delete the instance
502         $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
504         return $result;
505     }
507     /**
508      * Update the settings for a single plugin
509      *
510      * @param assign_plugin $plugin The plugin to update
511      * @param stdClass $formdata The form data
512      * @return bool false if an error occurs
513      */
514     private function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
515         if ($plugin->is_visible()) {
516             $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
517             if ($formdata->$enabledname) {
518                 $plugin->enable();
519                 if (!$plugin->save_settings($formdata)) {
520                     print_error($plugin->get_error());
521                     return false;
522                 }
523             } else {
524                 $plugin->disable();
525             }
526         }
527         return true;
528     }
530     /**
531      * Update the gradebook information for this assignment
532      *
533      * @param bool $reset If true, will reset all grades in the gradbook for this assignment
534      * @param int $coursemoduleid This is required because it might not exist in the database yet
535      * @return bool
536      */
537     public function update_gradebook($reset, $coursemoduleid) {
538          global $CFG;
539         /** Include lib.php */
540         require_once($CFG->dirroot.'/mod/assign/lib.php');
541         $assign = clone $this->get_instance();
542         $assign->cmidnumber = $coursemoduleid;
543         $param = null;
544         if ($reset) {
545             $param = 'reset';
546         }
548         return assign_grade_item_update($assign, $param);
549     }
552     /**
553      * Update the calendar entries for this assignment
554      *
555      * @param int $coursemoduleid - Required to pass this in because it might not exist in the database yet
556      * @return bool
557      */
558     public function update_calendar($coursemoduleid) {
559         global $DB, $CFG;
560         require_once($CFG->dirroot.'/calendar/lib.php');
562         // special case for add_instance as the coursemodule has not been set yet.
564         if ($this->get_instance()->duedate) {
565             $event = new stdClass();
567             if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id))) {
569                 $event->name        = $this->get_instance()->name;
571                 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
572                 $event->timestart   = $this->get_instance()->duedate;
574                 $calendarevent = calendar_event::load($event->id);
575                 $calendarevent->update($event);
576             } else {
577                 $event = new stdClass();
578                 $event->name        = $this->get_instance()->name;
579                 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
580                 $event->courseid    = $this->get_instance()->course;
581                 $event->groupid     = 0;
582                 $event->userid      = 0;
583                 $event->modulename  = 'assign';
584                 $event->instance    = $this->get_instance()->id;
585                 $event->eventtype   = 'due';
586                 $event->timestart   = $this->get_instance()->duedate;
587                 $event->timeduration = 0;
589                 calendar_event::create($event);
590             }
591         } else {
592             $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id));
593         }
594     }
597     /**
598      * Update this instance in the database
599      *
600      * @param stdClass $formdata - the data submitted from the form
601      * @return bool false if an error occurs
602      */
603     public function update_instance($formdata) {
604         global $DB;
606         $update = new stdClass();
607         $update->id = $formdata->instance;
608         $update->name = $formdata->name;
609         $update->timemodified = time();
610         $update->course = $formdata->course;
611         $update->intro = $formdata->intro;
612         $update->introformat = $formdata->introformat;
613         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
614         $update->preventlatesubmissions = $formdata->preventlatesubmissions;
615         $update->submissiondrafts = $formdata->submissiondrafts;
616         $update->sendnotifications = $formdata->sendnotifications;
617         $update->duedate = $formdata->duedate;
618         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
619         $update->grade = $formdata->grade;
621         $result = $DB->update_record('assign', $update);
622         $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
624         // load the assignment so the plugins have access to it
626         // call save_settings hook for submission plugins
627         foreach ($this->submissionplugins as $plugin) {
628             if (!$this->update_plugin_instance($plugin, $formdata)) {
629                 print_error($plugin->get_error());
630                 return false;
631             }
632         }
633         foreach ($this->feedbackplugins as $plugin) {
634             if (!$this->update_plugin_instance($plugin, $formdata)) {
635                 print_error($plugin->get_error());
636                 return false;
637             }
638         }
641         // update the database record
644         // update all the calendar events
645         $this->update_calendar($this->get_course_module()->id);
647         $this->update_gradebook(false, $this->get_course_module()->id);
649         $update = new stdClass();
650         $update->id = $this->get_instance()->id;
651         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
652         $DB->update_record('assign', $update);
658         return $result;
659     }
661     /**
662      * add elements in grading plugin form
663      *
664      * @param mixed $grade stdClass|null
665      * @param MoodleQuickForm $mform
666      * @param stdClass $data
667      * @return void
668      */
669     private function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data) {
670         foreach ($this->feedbackplugins as $plugin) {
671             if ($plugin->is_enabled() && $plugin->is_visible()) {
672                 $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
673                 if (!$plugin->get_form_elements($grade, $mform, $data)) {
674                     $mform->removeElement('header_' . $plugin->get_type());
675                 }
676             }
677         }
678     }
682     /**
683      * Add one plugins settings to edit plugin form
684      *
685      * @param assign_plugin $plugin The plugin to add the settings from
686      * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
687      * @return void
688      */
689     private function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform) {
690         global $CFG;
691         if ($plugin->is_visible()) {
692             // enabled
693             //tied disableIf rule to this select element
694             $mform->addElement('selectyesno', $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $plugin->get_name());
695             $mform->addHelpButton($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', 'enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
697             $setting = $plugin->get_subtype() . '_' . $plugin->get_type() . '_default';
699             $default = $CFG->$setting;
700             if ($plugin->get_config('enabled') !== false) {
701                 $default = $plugin->is_enabled();
702             }
703             $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
705             $plugin->get_settings($mform);
707         }
709     }
712     /**
713      * Add settings to edit plugin form
714      *
715      * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
716      * @return void
717      */
718     public function add_all_plugin_settings(MoodleQuickForm $mform) {
719         $mform->addElement('header', 'general', get_string('submissionsettings', 'assign'));
721         foreach ($this->submissionplugins as $plugin) {
722             $this->add_plugin_settings($plugin, $mform);
724         }
725         $mform->addElement('header', 'general', get_string('feedbacksettings', 'assign'));
726         foreach ($this->feedbackplugins as $plugin) {
727             $this->add_plugin_settings($plugin, $mform);
728         }
729     }
731     /**
732      * Allow each plugin an opportunity to update the defaultvalues
733      * passed in to the settings form (needed to set up draft areas for
734      * editor and filemanager elements)
735      * @param array $defaultvalues
736      */
737     public function plugin_data_preprocessing(&$defaultvalues) {
738         foreach ($this->submissionplugins as $plugin) {
739             if ($plugin->is_visible()) {
740                 $plugin->data_preprocessing($defaultvalues);
741             }
742         }
743         foreach ($this->feedbackplugins as $plugin) {
744             if ($plugin->is_visible()) {
745                 $plugin->data_preprocessing($defaultvalues);
746             }
747         }
748     }
750     /**
751      * Get the name of the current module.
752      *
753      * @return string the module name (Assignment)
754      */
755     protected function get_module_name() {
756         if (isset(self::$modulename)) {
757             return self::$modulename;
758         }
759         self::$modulename = get_string('modulename', 'assign');
760         return self::$modulename;
761     }
763     /**
764      * Get the plural name of the current module.
765      *
766      * @return string the module name plural (Assignments)
767      */
768     protected function get_module_name_plural() {
769         if (isset(self::$modulenameplural)) {
770             return self::$modulenameplural;
771         }
772         self::$modulenameplural = get_string('modulenameplural', 'assign');
773         return self::$modulenameplural;
774     }
776     /**
777      * Has this assignment been constructed from an instance?
778      *
779      * @return bool
780      */
781     public function has_instance() {
782         return $this->instance || $this->get_course_module();
783     }
785     /**
786      * Get the settings for the current instance of this assignment
787      *
788      * @return stdClass The settings
789      */
790     public function get_instance() {
791         global $DB;
792         if ($this->instance) {
793             return $this->instance;
794         }
795         if ($this->get_course_module()) {
796             $this->instance = $DB->get_record('assign', array('id' => $this->get_course_module()->instance), '*', MUST_EXIST);
797         }
798         if (!$this->instance) {
799             throw new coding_exception('Improper use of the assignment class. Cannot load the assignment record.');
800         }
801         return $this->instance;
802     }
804     /**
805      * Get the context of the current course
806      * @return mixed context|null The course context
807      */
808     public function get_course_context() {
809         if (!$this->context && !$this->course) {
810             throw new coding_exception('Improper use of the assignment class. Cannot load the course context.');
811         }
812         if ($this->context) {
813             return $this->context->get_course_context();
814         } else {
815             return context_course::instance($this->course->id);
816         }
817     }
820     /**
821      * Get the current course module
822      *
823      * @return mixed stdClass|null The course module
824      */
825     public function get_course_module() {
826         if ($this->coursemodule) {
827             return $this->coursemodule;
828         }
829         if (!$this->context) {
830             return null;
831         }
833         if ($this->context->contextlevel == CONTEXT_MODULE) {
834             $this->coursemodule = get_coursemodule_from_id('assign', $this->context->instanceid, 0, false, MUST_EXIST);
835             return $this->coursemodule;
836         }
837         return null;
838     }
840     /**
841      * Get context module
842      *
843      * @return context
844      */
845     public function get_context() {
846         return $this->context;
847     }
849     /**
850      * Get the current course
851      * @return mixed stdClass|null The course
852      */
853     public function get_course() {
854         global $DB;
855         if ($this->course) {
856             return $this->course;
857         }
859         if (!$this->context) {
860             return null;
861         }
862         $this->course = $DB->get_record('course', array('id' => $this->get_course_context()->instanceid), '*', MUST_EXIST);
863         return $this->course;
864     }
866     /**
867      * Return a grade in user-friendly form, whether it's a scale or not
868      *
869      * @param mixed $grade int|null
870      * @return string User-friendly representation of grade
871      */
872     public function display_grade($grade) {
873         global $DB;
875         static $scalegrades = array();
879         if ($this->get_instance()->grade >= 0) {    // Normal number
880             if ($grade == -1 || $grade === null) {
881                 return '-';
882             } else {
883                 return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
884             }
886         } else {                                // Scale
887             if (empty($this->cache['scale'])) {
888                 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
889                     $this->cache['scale'] = make_menu_from_list($scale->scale);
890                 } else {
891                     return '-';
892                 }
893             }
894             $scaleid = (int)$grade;
895             if (isset($this->cache['scale'][$scaleid])) {
896                 return $this->cache['scale'][$scaleid];
897             }
898             return '-';
899         }
900     }
902     /**
903      * Load a list of users enrolled in the current course with the specified permission and group (0 for no group)
904      *
905      * @param int $currentgroup
906      * @param bool $idsonly
907      * @return array List of user records
908      */
909     public function list_participants($currentgroup, $idsonly) {
910         if ($idsonly) {
911             return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup, 'u.id');
912         } else {
913             return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
914         }
915     }
917     /**
918      * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
919      *
920      * @param int $currentgroup
921      * @return int number of matching users
922      */
923     public function count_participants($currentgroup) {
924         return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
925     }
927     /**
928      * Load a count of users enrolled in the current course with the specified permission and group (optional)
929      *
930      * @param string $status The submission status - should match one of the constants
931      * @return int number of matching submissions
932      */
933     public function count_submissions_with_status($status) {
934         global $DB;
935         return $DB->count_records_sql("SELECT COUNT('x')
936                                      FROM {assign_submission}
937                                     WHERE assignment = ? AND
938                                           status = ?", array($this->get_course_module()->instance, $status));
939     }
941     /**
942      * Utility function to get the userid for every row in the grading table
943      * so the order can be frozen while we iterate it
944      *
945      * @return array An array of userids
946      */
947     private function get_grading_userid_list(){
948         $filter = get_user_preferences('assign_filter', '');
949         $table = new assign_grading_table($this, 0, $filter);
951         $useridlist = $table->get_column_data('userid');
953         return $useridlist;
954     }
957     /**
958      * Utility function get the userid based on the row number of the grading table.
959      * This takes into account any active filters on the table.
960      *
961      * @param int $num The row number of the user
962      * @param bool $last This is set to true if this is the last user in the table
963      * @return mixed The user id of the matching user or false if there was an error
964      */
965     private function get_userid_for_row($num, $last){
966         if (!array_key_exists('userid_for_row', $this->cache)) {
967             $this->cache['userid_for_row'] = array();
968         }
969         if (array_key_exists($num, $this->cache['userid_for_row'])) {
970             list($userid, $last) = $this->cache['userid_for_row'][$num];
971             return $userid;
972         }
974         $filter = get_user_preferences('assign_filter', '');
975         $table = new assign_grading_table($this, 0, $filter);
977         $userid = $table->get_cell_data($num, 'userid', $last);
979         $this->cache['userid_for_row'][$num] = array($userid, $last);
980         return $userid;
981     }
983     /**
984      * Return all assignment submissions by ENROLLED students (even empty)
985      *
986      * @param string $sort optional field names for the ORDER BY in the sql query
987      * @param string $dir optional specifying the sort direction, defaults to DESC
988      * @return array The submission objects indexed by id
989      */
990     private function get_all_submissions( $sort="", $dir="DESC") {
991         global $CFG, $DB;
993         if ($sort == "lastname" or $sort == "firstname") {
994             $sort = "u.$sort $dir";
995         } else if (empty($sort)) {
996             $sort = "a.timemodified DESC";
997         } else {
998             $sort = "a.$sort $dir";
999         }
1001         return $DB->get_records_sql("SELECT a.*
1002                                        FROM {assign_submission} a, {user} u
1003                                       WHERE u.id = a.userid
1004                                             AND a.assignment = ?
1005                                    ORDER BY $sort", array($this->get_instance()->id));
1007     }
1009     /**
1010      * Generate zip file from array of given files
1011      *
1012      * @param array $filesforzipping - array of files to pass into archive_to_pathname - this array is indexed by the final file name and each element in the array is an instance of a stored_file object
1013      * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
1014      */
1015      private function pack_files($filesforzipping) {
1016          global $CFG;
1017          //create path for new zip file.
1018          $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
1019          //zip files
1020          $zipper = new zip_packer();
1021          if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1022              return $tempzip;
1023          }
1024          return false;
1025     }
1028     /**
1029      *  Cron function to be run periodically according to the moodle cron
1030      *  Finds all assignment notifications that have yet to be mailed out, and mails them
1031      *
1032      * @return bool
1033      */
1034     static function cron() {
1035         global $CFG, $USER, $DB;
1038         return true;
1039     }
1041     /**
1042      * Update a grade in the grade table for the assignment and in the gradebook
1043      *
1044      * @param stdClass $grade a grade record keyed on id
1045      * @return bool true for success
1046      */
1047     private function update_grade($grade) {
1048         global $DB;
1050         $grade->timemodified = time();
1052         if ($grade->grade && $grade->grade != -1) {
1053             if ($this->get_instance()->grade > 0) {
1054                 if (!is_numeric($grade->grade)) {
1055                     return false;
1056                 } else if ($grade->grade > $this->get_instance()->grade) {
1057                     return false;
1058                 } else if ($grade->grade < 0) {
1059                     return false;
1060                 }
1061             } else {
1062                 // this is a scale
1063                 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1064                     $scaleoptions = make_menu_from_list($scale->scale);
1065                     if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1066                         return false;
1067                     }
1068                 }
1069             }
1070         }
1072         $result = $DB->update_record('assign_grades', $grade);
1073         if ($result) {
1074             $this->gradebook_item_update(null, $grade);
1075         }
1076         return $result;
1077     }
1079     /**
1080      * display the submission that is used by a plugin
1081      * Uses url parameters 'sid', 'gid' and 'plugin'
1082      * @param string $pluginsubtype
1083      * @return string
1084      */
1085     private function view_plugin_content($pluginsubtype) {
1086         global $USER;
1088         $o = '';
1090         $submissionid = optional_param('sid', 0, PARAM_INT);
1091         $gradeid = optional_param('gid', 0, PARAM_INT);
1092         $plugintype = required_param('plugin', PARAM_TEXT);
1093         $item = null;
1094         if ($pluginsubtype == 'assignsubmission') {
1095             $plugin = $this->get_submission_plugin_by_type($plugintype);
1096             if ($submissionid <= 0) {
1097                 throw new coding_exception('Submission id should not be 0');
1098             }
1099             $item = $this->get_submission($submissionid);
1101             // permissions
1102             if ($item->userid != $USER->id) {
1103                 require_capability('mod/assign:grade', $this->context);
1104             }
1105             $o .= $this->output->render(new assign_header($this->get_instance(),
1106                                                               $this->get_context(),
1107                                                               $this->show_intro(),
1108                                                               $this->get_course_module()->id,
1109                                                               $plugin->get_name()));
1110             $o .= $this->output->render(new assign_submission_plugin_submission($plugin,
1111                                                               $item,
1112                                                               assign_submission_plugin_submission::FULL,
1113                                                               $this->get_course_module()->id,
1114                                                               $this->get_return_action(),
1115                                                               $this->get_return_params()));
1117             $this->add_to_log('view submission', get_string('viewsubmissionforuser', 'assign', $item->userid));
1118         } else {
1119             $plugin = $this->get_feedback_plugin_by_type($plugintype);
1120             if ($gradeid <= 0) {
1121                 throw new coding_exception('Grade id should not be 0');
1122             }
1123             $item = $this->get_grade($gradeid);
1124             // permissions
1125             if ($item->userid != $USER->id) {
1126                 require_capability('mod/assign:grade', $this->context);
1127             }
1128             $o .= $this->output->render(new assign_header($this->get_instance(),
1129                                                               $this->get_context(),
1130                                                               $this->show_intro(),
1131                                                               $this->get_course_module()->id,
1132                                                               $plugin->get_name()));
1133             $o .= $this->output->render(new assign_feedback_plugin_feedback($plugin,
1134                                                               $item,
1135                                                               assign_feedback_plugin_feedback::FULL,
1136                                                               $this->get_course_module()->id,
1137                                                               $this->get_return_action(),
1138                                                               $this->get_return_params()));
1139             $this->add_to_log('view feedback', get_string('viewfeedbackforuser', 'assign', $item->userid));
1140         }
1143         $o .= $this->view_return_links();
1145         $o .= $this->view_footer();
1146         return $o;
1147     }
1149     /**
1150      * render the content in editor that is often used by plugin
1151      *
1152      * @param string $filearea
1153      * @param int  $submissionid
1154      * @param string $plugintype
1155      * @param string $editor
1156      * @param string $component
1157      * @return string
1158      */
1159     public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
1160         global $CFG;
1162         $result = '';
1164         $plugin = $this->get_submission_plugin_by_type($plugintype);
1166         $text = $plugin->get_editor_text($editor, $submissionid);
1167         $format = $plugin->get_editor_format($editor, $submissionid);
1169         $finaltext = file_rewrite_pluginfile_urls($text, 'pluginfile.php', $this->get_context()->id, $component, $filearea, $submissionid);
1170         $result .= format_text($finaltext, $format, array('overflowdiv' => true, 'context' => $this->get_context()));
1174         if ($CFG->enableportfolios) {
1175             require_once($CFG->libdir . '/portfoliolib.php');
1177             $button = new portfolio_add_button();
1178             $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id, 'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea), '/mod/assign/portfolio_callback.php');
1179             $fs = get_file_storage();
1181             if ($files = $fs->get_area_files($this->context->id, $component,$filearea, $submissionid, "timemodified", false)) {
1182                 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
1183             } else {
1184                 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
1185             }
1186             $result .= $button->to_html();
1187         }
1188         return $result;
1189     }
1192     /**
1193      * Display the page footer
1194      *
1195      * @return None
1196      */
1197     private function view_footer() {
1198         return $this->output->render_footer();
1199     }
1201     /**
1202      * Does this user have grade permission for this assignment
1203      *
1204      * @return bool
1205      */
1206     private function can_grade() {
1207         // Permissions check
1208         if (!has_capability('mod/assign:grade', $this->context)) {
1209             return false;
1210         }
1212         return true;
1213     }
1215     /**
1216      * Download a zip file of all assignment submissions
1217      *
1218      * @return void
1219      */
1220     private function download_submissions() {
1221         global $CFG,$DB;
1223         // more efficient to load this here
1224         require_once($CFG->libdir.'/filelib.php');
1226         // load all submissions
1227         $submissions = $this->get_all_submissions('','');
1229         if (empty($submissions)) {
1230             print_error('errornosubmissions', 'assign');
1231             return;
1232         }
1234         // build a list of files to zip
1235         $filesforzipping = array();
1236         $fs = get_file_storage();
1238         $groupmode = groups_get_activity_groupmode($this->get_course_module());
1239         $groupid = 0;   // All users
1240         $groupname = '';
1241         if ($groupmode) {
1242             $groupid = groups_get_activity_group($this->get_course_module(), true);
1243             $groupname = groups_get_group_name($groupid).'-';
1244         }
1246         // construct the zip file name
1247         $filename = str_replace(' ', '_', clean_filename($this->get_course()->shortname.'-'.$this->get_instance()->name.'-'.$groupname.$this->get_course_module()->id.".zip")); //name of new zip file.
1249         // get all the files for each submission
1250         foreach ($submissions as $submission) {
1251             $userid = $submission->userid; //get userid
1252             if ((groups_is_member($groupid,$userid) or !$groupmode or !$groupid)) {
1253                 // get the plugins to add their own files to the zip
1255                 $user = $DB->get_record("user", array("id"=>$userid),'id,username,firstname,lastname', MUST_EXIST);
1257                 $prefix = clean_filename(fullname($user) . "_" .$userid . "_");
1259                 foreach ($this->submissionplugins as $plugin) {
1260                     if ($plugin->is_enabled() && $plugin->is_visible()) {
1261                         $pluginfiles = $plugin->get_files($submission);
1264                         foreach ($pluginfiles as $zipfilename => $file) {
1265                             $filesforzipping[$prefix . $zipfilename] = $file;
1266                         }
1267                     }
1268                 }
1270             }
1271         } // end of foreach loop
1272         if ($zipfile = $this->pack_files($filesforzipping)) {
1273             $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
1274             send_temp_file($zipfile, $filename); //send file and delete after sending.
1275         }
1276     }
1278     /**
1279      * Util function to add a message to the log
1280      *
1281      * @param string $action The current action
1282      * @param string $info A detailed description of the change. But no more than 255 characters.
1283      * @param string $url The url to the assign module instance.
1284      * @return void
1285      */
1286     public function add_to_log($action = '', $info = '', $url='') {
1287         global $USER;
1289         $fullurl = 'view.php?id=' . $this->get_course_module()->id;
1290         if ($url != '') {
1291             $fullurl .= '&' . $url;
1292         }
1294         add_to_log($this->get_course()->id, 'assign', $action, $fullurl, $info, $this->get_course_module()->id, $USER->id);
1295     }
1297     /**
1298      * Load the submission object for a particular user, optionally creating it if required
1299      *
1300      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
1301      * @param bool $create optional Defaults to false. If set to true a new submission object will be created in the database
1302      * @return stdClass The submission
1303      */
1304     private function get_user_submission($userid, $create) {
1305         global $DB, $USER;
1307         if (!$userid) {
1308             $userid = $USER->id;
1309         }
1310         // if the userid is not null then use userid
1311         $submission = $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
1313         if ($submission) {
1314             return $submission;
1315         }
1316         if ($create) {
1317             $submission = new stdClass();
1318             $submission->assignment   = $this->get_instance()->id;
1319             $submission->userid       = $userid;
1320             $submission->timecreated = time();
1321             $submission->timemodified = $submission->timecreated;
1323             if ($this->get_instance()->submissiondrafts) {
1324                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1325             } else {
1326                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1327             }
1328             $sid = $DB->insert_record('assign_submission', $submission);
1329             $submission->id = $sid;
1330             return $submission;
1331         }
1332         return false;
1333     }
1335     /**
1336      * Load the submission object from it's id
1337      *
1338      * @param int $submissionid The id of the submission we want
1339      * @return stdClass The submission
1340      */
1341     private function get_submission($submissionid) {
1342         global $DB;
1344         return $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid), '*', MUST_EXIST);
1345     }
1347     /**
1348      * This will retrieve a grade object from the db, optionally creating it if required
1349      *
1350      * @param int $userid The user we are grading
1351      * @param bool $create If true the grade will be created if it does not exist
1352      * @return stdClass The grade record
1353      */
1354     private function get_user_grade($userid, $create) {
1355         global $DB, $USER;
1357         if (!$userid) {
1358             $userid = $USER->id;
1359         }
1361         // if the userid is not null then use userid
1362         $grade = $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
1364         if ($grade) {
1365             return $grade;
1366         }
1367         if ($create) {
1368             $grade = new stdClass();
1369             $grade->assignment   = $this->get_instance()->id;
1370             $grade->userid       = $userid;
1371             $grade->timecreated = time();
1372             $grade->timemodified = $grade->timecreated;
1373             $grade->locked = 0;
1374             $grade->grade = -1;
1375             $grade->grader = $USER->id;
1376             $gid = $DB->insert_record('assign_grades', $grade);
1377             $grade->id = $gid;
1378             return $grade;
1379         }
1380         return false;
1381     }
1383     /**
1384      * This will retrieve a grade object from the db
1385      *
1386      * @param int $gradeid The id of the grade
1387      * @return stdClass The grade record
1388      */
1389     private function get_grade($gradeid) {
1390         global $DB;
1392         return $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid), '*', MUST_EXIST);
1393     }
1395     /**
1396      * Print the grading page for a single user submission
1397      *
1398      * @param moodleform $mform
1399      * @param int $offset
1400      * @return string
1401      */
1402     private function view_single_grade_page($mform, $offset=0) {
1403         global $DB, $CFG;
1405         $o = '';
1407         // Include grade form
1408         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1410         // Need submit permission to submit an assignment
1411         require_capability('mod/assign:grade', $this->context);
1413         $o .= $this->output->render(new assign_header($this->get_instance(),
1414                                                       $this->get_context(), false, $this->get_course_module()->id,get_string('grading', 'assign')));
1416         $rownum = required_param('rownum', PARAM_INT) + $offset;
1417         $useridlist = optional_param('useridlist', '', PARAM_TEXT);
1418         if ($useridlist) {
1419             $useridlist = explode(',', $useridlist);
1420         } else {
1421             $useridlist = $this->get_grading_userid_list();
1422         }
1423         $last = false;
1424         $userid = $useridlist[$rownum];
1425         if ($rownum == count($useridlist) - 1) {
1426             $last = true;
1427         }
1428         // the placement of this is important so can pass the list of userids above
1429         if ($offset) {
1430             $_POST = array();
1431         }
1432         if(!$userid){
1433             throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
1434         }
1435         $user = $DB->get_record('user', array('id' => $userid));
1436         if ($user) {
1437             $o .= $this->output->render(new assign_user_summary($user, $this->get_course()->id, has_capability('moodle/site:viewfullnames', $this->get_course_context())));
1438         }
1439         $submission = $this->get_user_submission($userid, false);
1440         // get the current grade
1441         $grade = $this->get_user_grade($userid, false);
1442         if ($this->can_view_submission($userid)) {
1443             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
1444             $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
1445                                                               $this->get_instance()->alwaysshowdescription,
1446                                                               $submission,
1447                                                               $this->is_any_submission_plugin_enabled(),
1448                                                               $gradelocked,
1449                                                               $this->is_graded($userid),
1450                                                               $this->get_instance()->duedate,
1451                                                               $this->get_submission_plugins(),
1452                                                               $this->get_return_action(),
1453                                                               $this->get_return_params(),
1454                                                               $this->get_course_module()->id,
1455                                                               assign_submission_status::GRADER_VIEW,
1456                                                               false,
1457                                                               false));
1458         }
1459         if ($grade) {
1460             $data = new stdClass();
1461             if ($grade->grade >= 0) {
1462                 $data->grade = format_float($grade->grade,2);
1463             }
1464         } else {
1465             $data = new stdClass();
1466             $data->grade = '';
1467         }
1469         // now show the grading form
1470         if (!$mform) {
1471             $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last)), 'post', '', array('class'=>'gradeform'));
1472         }
1473         $o .= $this->output->render(new assign_form('gradingform',$mform));
1475         $this->add_to_log('view grading form', get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
1477         $o .= $this->view_footer();
1478         return $o;
1479     }
1483     /**
1484      * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
1485      *
1486      * @return string
1487      */
1488     private function view_return_links() {
1490         $returnaction = optional_param('returnaction','', PARAM_ALPHA);
1491         $returnparams = optional_param('returnparams','', PARAM_TEXT);
1493         $params = array();
1494         parse_str($returnparams, $params);
1495         $params = array_merge( array('id' => $this->get_course_module()->id, 'action' => $returnaction), $params);
1497         return $this->output->single_button(new moodle_url('/mod/assign/view.php', $params), get_string('back'), 'get');
1499     }
1501     /**
1502      * View the grading table of all submissions for this assignment
1503      *
1504      * @return string
1505      */
1506     private function view_grading_table() {
1507         global $USER, $CFG;
1508         // Include grading options form
1509         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
1510         require_once($CFG->dirroot . '/mod/assign/gradingactionsform.php');
1511         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
1512         $o = '';
1514         $links = array();
1515         $selecturl = (string)(new moodle_url('/mod/assign/view.php',
1516                                              array('action'=>'grading', 'id'=>$this->get_course_module()->id)));
1517         $links[$selecturl] = get_string('selectlink', 'assign');
1518         if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
1519                 has_capability('moodle/grade:viewall', $this->get_course_context())) {
1520             $gradebookurl = (string) (new moodle_url('/grade/report/grader/index.php',
1521                                                      array('id' => $this->get_course()->id)));
1522             $links[$gradebookurl] = get_string('viewgradebook', 'assign');
1523         }
1524         if ($this->is_any_submission_plugin_enabled()) {
1525             $downloadurl = (string) (new moodle_url('/mod/assign/view.php',
1526                                                     array('id' => $this->get_course_module()->id,
1527                                                           'action' => 'downloadall')));
1528             $links[$downloadurl] = get_string('downloadall', 'assign');
1529         }
1530         $gradingactionsform = new mod_assign_grading_actions_form(null,
1531                                                                   array('links'=>$links,
1532                                                                         'cm'=>$this->get_course_module()->id),
1533                                                                   'post', '',
1534                                                                   array('class'=>'gradingactionsform'));
1536         $perpage = get_user_preferences('assign_perpage', 10);
1537         $filter = get_user_preferences('assign_filter', '');
1538         // print options  for changing the filter and changing the number of results per page
1539         $gradingoptionsform = new mod_assign_grading_options_form(null,
1540                                                                   array('cm'=>$this->get_course_module()->id,
1541                                                                         'contextid'=>$this->context->id,
1542                                                                         'userid'=>$USER->id),
1543                                                                   'post', '',
1544                                                                   array('class'=>'gradingoptionsform'));
1546         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
1547                                                                   array('cm'=>$this->get_course_module()->id,
1548                                                                         'submissiondrafts'=>$this->get_instance()->submissiondrafts),
1549                                                                   'post', '',
1550                                                                   array('class'=>'gradingbatchoperationsform'));
1552         $gradingoptionsdata = new stdClass();
1553         $gradingoptionsdata->perpage = $perpage;
1554         $gradingoptionsdata->filter = $filter;
1555         $gradingoptionsform->set_data($gradingoptionsdata);
1557         // plagiarism update status apearring in the grading book
1558         if (!empty($CFG->enableplagiarism)) {
1559             /** Include plagiarismlib.php */
1560             require_once($CFG->libdir . '/plagiarismlib.php');
1561             plagiarism_update_status($this->get_course(), $this->get_course_module());
1562         }
1564         $o .= $this->output->render(new assign_form('gradingactionsform', $gradingactionsform));
1565         $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform));
1567         // load and print the table of submissions
1568         $o .= $this->output->render(new assign_grading_table($this, $perpage, $filter));
1570         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1571         $users = array_keys($this->list_participants($currentgroup, true));
1572         if (count($users) != 0) {
1573             // if no enrolled user in a course then don't display the batch operations feature
1574             $o .= $this->output->render(new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform));
1575         }
1576         return $o;
1577     }
1579     /**
1580      * View entire grading page.
1581      *
1582      * @return string
1583      */
1584     private function view_grading_page() {
1585         global $CFG;
1587         $o = '';
1588         // Need submit permission to submit an assignment
1589         require_capability('mod/assign:grade', $this->context);
1590         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
1592         // only load this if it is
1594         $o .= $this->output->render(new assign_header($this->get_instance(),
1595                                                       $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign')));
1596         $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
1599         $o .= $this->view_grading_table();
1601         $o .= $this->view_footer();
1602         $this->add_to_log('view submission grading table', get_string('viewsubmissiongradingtable', 'assign'));
1603         return $o;
1604     }
1606     /**
1607      * Capture the output of the plagiarism plugins disclosures and return it as a string
1608      *
1609      * @return void
1610      */
1611     private function plagiarism_print_disclosure() {
1612         global $CFG;
1613         $o = '';
1615         if (!empty($CFG->enableplagiarism)) {
1616             /** Include plagiarismlib.php */
1617             require_once($CFG->libdir . '/plagiarismlib.php');
1618             ob_start();
1620             plagiarism_print_disclosure($this->get_course_module()->id);
1621             $o = ob_get_contents();
1622             ob_end_clean();
1623         }
1625         return $o;
1626     }
1628     /**
1629      * message for students when assignment submissions have been closed
1630      *
1631      * @return string
1632      */
1633     private function view_student_error_message() {
1634         global $CFG;
1636         $o = '';
1637         // Need submit permission to submit an assignment
1638         require_capability('mod/assign:submit', $this->context);
1640         $o .= $this->output->render(new assign_header($this->get_instance(),
1641                                                       $this->get_context(),
1642                                                       $this->show_intro(),
1643                                                       $this->get_course_module()->id,
1644                                                       get_string('editsubmission', 'assign')));
1646         $o .= $this->output->notification('This assignment is no longer accepting submissions');
1648         $o .= $this->view_footer();
1650         return $o;
1652     }
1654     /**
1655      * View edit submissions page.
1656      *
1657      * @param moodleform $mform
1658      * @return void
1659      */
1660     private function view_edit_submission_page($mform) {
1661         global $CFG;
1663         $o = '';
1664         // Include submission form
1665         require_once($CFG->dirroot . '/mod/assign/submission_form.php');
1666         // Need submit permission to submit an assignment
1667         require_capability('mod/assign:submit', $this->context);
1669         if (!$this->submissions_open()) {
1670             $subclosed  = '';
1671             $subclosed .= $this->view_student_error_message();
1672             return $subclosed;
1673         }
1674         $o .= $this->output->render(new assign_header($this->get_instance(),
1675                                                       $this->get_context(),
1676                                                       $this->show_intro(),
1677                                                       $this->get_course_module()->id,
1678                                                       get_string('editsubmission', 'assign')));
1679         $o .= $this->plagiarism_print_disclosure();
1680         $data = new stdClass();
1682         if (!$mform) {
1683             $mform = new mod_assign_submission_form(null, array($this, $data));
1684         }
1686         $o .= $this->output->render(new assign_form('editsubmissionform',$mform));
1688         $o .= $this->view_footer();
1689         $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
1691         return $o;
1692     }
1694     /**
1695      * See if this assignment has a grade yet
1696      *
1697      * @param int $userid
1698      * @return bool
1699      */
1700     private function is_graded($userid) {
1701         $grade = $this->get_user_grade($userid, false);
1702         if ($grade) {
1703             return ($grade->grade != '');
1704         }
1705         return false;
1706     }
1709     /**
1710      * Perform an access check to see if the current $USER can view this users submission
1711      *
1712      * @param int $userid
1713      * @return bool
1714      */
1715     public function can_view_submission($userid) {
1716         global $USER;
1718         if (!is_enrolled($this->get_course_context(), $userid)) {
1719             return false;
1720         }
1721         if ($userid == $USER->id && !has_capability('mod/assign:submit', $this->context)) {
1722             return false;
1723         }
1724         if ($userid != $USER->id && !has_capability('mod/assign:grade', $this->context)) {
1725             return false;
1726         }
1727         return true;
1728     }
1730     /**
1731      * Ask the user to confirm they want to perform this batch operation
1732      * @return string
1733      */
1734     private function process_batch_grading_operation() {
1735         global $CFG;
1736         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
1737         require_sesskey();
1739         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
1740                                                                   array('cm'=>$this->get_course_module()->id,
1741                                                                         'submissiondrafts'=>$this->get_instance()->submissiondrafts),
1742                                                                   'post', '',
1743                                                                   array('class'=>'gradingbatchoperationsform'));
1745         if ($data = $gradingbatchoperationsform->get_data()) {
1746             // get the list of users
1747             $users = $data->selectedusers;
1748             $userlist = explode(',', $users);
1750             foreach ($userlist as $userid) {
1751                 if ($data->operation == 'lock') {
1752                     $this->process_lock($userid);
1753                 } else if ($data->operation == 'unlock') {
1754                     $this->process_unlock($userid);
1755                 } else if ($data->operation == 'reverttodraft') {
1756                     $this->process_revert_to_draft($userid);
1757                 }
1758             }
1759         }
1761         return true;
1762     }
1764     /**
1765      * Ask the user to confirm they want to submit their work for grading
1766      * @return string
1767      */
1768     private function check_submit_for_grading() {
1769         global $USER;
1770         // Check that all of the submission plugins are ready for this submission
1771         $notifications = array();
1772         $submission = $this->get_user_submission($USER->id, false);
1773         $plugins = $this->get_submission_plugins();
1774         foreach ($plugins as $plugin) {
1775             if ($plugin->is_enabled() && $plugin->is_visible()) {
1776                 $check = $plugin->precheck_submission($submission);
1777                 if ($check !== true) {
1778                     $notifications[] = $check;
1779                 }
1780             }
1781         }
1783         $o = '';
1784         $o .= $this->output->header();
1785         $o .= $this->output->render(new assign_submit_for_grading_page($notifications, $this->get_course_module()->id));
1786         $o .= $this->view_footer();
1787         return $o;
1788     }
1790     /**
1791      * Print 2 tables of information with no action links -
1792      * the submission summary and the grading summary
1793      *
1794      * @param stdClass $user the user to print the report for
1795      * @param bool $showlinks - Return plain text or links to the profile
1796      * @return string - the html summary
1797      */
1798     public function view_student_summary($user, $showlinks) {
1799         global $CFG, $DB, $PAGE;
1801         $grade = $this->get_user_grade($user->id, false);
1802         $submission = $this->get_user_submission($user->id, false);
1803         $o = '';
1805         if ($this->can_view_submission($user->id)) {
1806             $showedit = has_capability('mod/assign:submit', $this->context) &&
1807                          $this->submissions_open() && ($this->is_any_submission_plugin_enabled()) && $showlinks;
1808             $showsubmit = $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT) && $showlinks;
1809             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
1811             $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
1812                                                               $this->get_instance()->alwaysshowdescription,
1813                                                               $submission,
1814                                                               $this->is_any_submission_plugin_enabled(),
1815                                                               $gradelocked,
1816                                                               $this->is_graded($user->id),
1817                                                               $this->get_instance()->duedate,
1818                                                               $this->get_submission_plugins(),
1819                                                               $this->get_return_action(),
1820                                                               $this->get_return_params(),
1821                                                               $this->get_course_module()->id,
1822                                                               assign_submission_status::STUDENT_VIEW,
1823                                                               $showedit,
1824                                                               $showsubmit));
1825             require_once($CFG->libdir.'/gradelib.php');
1826             require_once($CFG->dirroot.'/grade/grading/lib.php');
1828             $gradinginfo = grade_get_grades($this->get_course()->id,
1829                                         'mod',
1830                                         'assign',
1831                                         $this->get_instance()->id,
1832                                         $user->id);
1834             $gradingitem = $gradinginfo->items[0];
1835             $gradebookgrade = $gradingitem->grades[$user->id];
1837             // check to see if all feedback plugins are empty
1838             $emptyplugins = true;
1839             if ($grade) {
1840                 foreach ($this->get_feedback_plugins() as $plugin) {
1841                     if ($plugin->is_visible() && $plugin->is_enabled()) {
1842                         if (!$plugin->is_empty($grade)) {
1843                             $emptyplugins = false;
1844                         }
1845                     }
1846                 }
1847             }
1850             if (!($gradebookgrade->hidden) && ($gradebookgrade->grade !== null || !$emptyplugins)) {
1852                 $gradefordisplay = '';
1853                 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
1855                 if ($controller = $gradingmanager->get_active_controller()) {
1856                     $controller->set_grade_range(make_grades_menu($this->get_instance()->grade));
1857                     $gradefordisplay = $controller->render_grade($PAGE,
1858                                                                  $grade->id,
1859                                                                  $gradingitem,
1860                                                                  $gradebookgrade->str_long_grade,
1861                                                                  has_capability('mod/assign:grade', $this->get_context()));
1862                 } else {
1863                     $gradefordisplay = $this->display_grade($gradebookgrade->grade);
1864                 }
1866                 $gradeddate = $gradebookgrade->dategraded;
1867                 $grader = $DB->get_record('user', array('id'=>$gradebookgrade->usermodified));
1869                 $feedbackstatus = new assign_feedback_status($gradefordisplay,
1870                                                       $gradeddate,
1871                                                       $grader,
1872                                                       $this->get_feedback_plugins(),
1873                                                       $grade,
1874                                                       $this->get_course_module()->id,
1875                                                       $this->get_return_action(),
1876                                                       $this->get_return_params());
1878                 $o .= $this->output->render($feedbackstatus);
1879             }
1881         }
1882         return $o;
1883     }
1885     /**
1886      * View submissions page (contains details of current submission).
1887      *
1888      * @return string
1889      */
1890     private function view_submission_page() {
1891         global $CFG, $DB, $USER, $PAGE;
1893         $o = '';
1894         $o .= $this->output->render(new assign_header($this->get_instance(),
1895                                                       $this->get_context(),
1896                                                       $this->show_intro(),
1897                                                       $this->get_course_module()->id));
1899         if ($this->can_grade()) {
1900             $o .= $this->output->render(new assign_grading_summary($this->count_participants(0),
1901                                                             $this->get_instance()->submissiondrafts,
1902                                                             $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
1903                                                             $this->is_any_submission_plugin_enabled(),
1904                                                             $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
1905                                                             $this->get_instance()->duedate,
1906                                                             $this->get_course_module()->id
1907                                                             ));
1908         }
1909         $grade = $this->get_user_grade($USER->id, false);
1910         $submission = $this->get_user_submission($USER->id, false);
1912         if ($this->can_view_submission($USER->id)) {
1913             $o .= $this->view_student_summary($USER, true);
1914         }
1917         $o .= $this->view_footer();
1918         $this->add_to_log('view', get_string('viewownsubmissionstatus', 'assign'));
1919         return $o;
1920     }
1922     /**
1923      * convert the final raw grade(s) in the  grading table for the gradebook
1924      *
1925      * @param stdClass $grade
1926      * @return array
1927      */
1928     private function convert_grade_for_gradebook(stdClass $grade) {
1929         $gradebookgrade = array();
1930         // trying to match those array keys in grade update function in gradelib.php
1931         // with keys in th database table assign_grades
1932         // starting around line 262
1933         $gradebookgrade['rawgrade'] = $grade->grade;
1934         $gradebookgrade['userid'] = $grade->userid;
1935         $gradebookgrade['usermodified'] = $grade->grader;
1936         $gradebookgrade['datesubmitted'] = NULL;
1937         $gradebookgrade['dategraded'] = $grade->timemodified;
1938         if (isset($grade->feedbackformat)) {
1939             $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
1940         }
1941         if (isset($grade->feedbacktext)) {
1942             $gradebookgrade['feedback'] = $grade->feedbacktext;
1943         }
1945         return $gradebookgrade;
1946     }
1948     /**
1949      * convert submission details for the gradebook
1950      *
1951      * @param stdClass $submission
1952      * @return array
1953      */
1954     private function convert_submission_for_gradebook(stdClass $submission) {
1955         $gradebookgrade = array();
1958         $gradebookgrade['userid'] = $submission->userid;
1959         $gradebookgrade['usermodified'] = $submission->userid;
1960         $gradebookgrade['datesubmitted'] = $submission->timemodified;
1962         return $gradebookgrade;
1963     }
1965     /**
1966      * update grades in the gradebook
1967      *
1968      * @param mixed $submission stdClass|null
1969      * @param mixed $grade stdClass|null
1970      * @return bool
1971      */
1972     private function gradebook_item_update($submission=NULL, $grade=NULL) {
1974         if($submission != NULL){
1976             $gradebookgrade = $this->convert_submission_for_gradebook($submission);
1979         }else{
1982             $gradebookgrade = $this->convert_grade_for_gradebook($grade);
1983         }
1984         $assign = clone $this->get_instance();
1985         $assign->cmidnumber = $this->get_course_module()->id;
1987         return assign_grade_item_update($assign, $gradebookgrade);
1988     }
1990     /**
1991      * update grades in the gradebook based on submission time
1992      *
1993      * @param stdClass $submission
1994      * @param bool $updatetime
1995      * @return bool
1996      */
1997     private function update_submission(stdClass $submission, $updatetime=true) {
1998         global $DB;
2000         if ($updatetime) {
2001             $submission->timemodified = time();
2002         }
2003         $result= $DB->update_record('assign_submission', $submission);
2004         if ($result) {
2005             $this->gradebook_item_update($submission);
2006         }
2007         return $result;
2008     }
2010     /**
2011      * Is this assignment open for submissions?
2012      *
2013      * Check the due date,
2014      * prevent late submissions,
2015      * has this person already submitted,
2016      * is the assignment locked?
2017      *
2018      * @return bool
2019      */
2020     private function submissions_open() {
2021         global $USER;
2023         $time = time();
2024         $dateopen = true;
2025         if ($this->get_instance()->preventlatesubmissions && $this->get_instance()->duedate) {
2026             $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $this->get_instance()->duedate);
2027         } else {
2028             $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
2029         }
2031         if (!$dateopen) {
2032             return false;
2033         }
2035         // now check if this user has already submitted etc.
2036         if (!is_enrolled($this->get_course_context(), $USER)) {
2037             return false;
2038         }
2039         if ($submission = $this->get_user_submission($USER->id, false)) {
2040             if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2041                 // drafts are tracked and the student has submitted the assignment
2042                 return false;
2043             }
2044         }
2045         if ($grade = $this->get_user_grade($USER->id, false)) {
2046             if ($grade->locked) {
2047                 return false;
2048             }
2049         }
2051         if ($this->grading_disabled($USER->id)) {
2052             return false;
2053         }
2055         return true;
2056     }
2058     /**
2059      * render the files in file area
2060      * @param string $component
2061      * @param string $area
2062      * @param int $submissionid
2063      * @return string
2064      */
2065     public function render_area_files($component, $area, $submissionid) {
2066         global $USER;
2068         if (!$submissionid) {
2069             $submission = $this->get_user_submission($USER->id,false);
2070             $submissionid = $submission->id;
2071         }
2075         $fs = get_file_storage();
2076         $browser = get_file_browser();
2077         $files = $fs->get_area_files($this->get_context()->id, $component, $area , $submissionid , "timemodified", false);
2078         return $this->output->assign_files($this->context, $submissionid, $area, $component);
2080     }
2082     /**
2083      * Returns a list of teachers that should be grading given submission
2084      *
2085      * @param stdClass $user
2086      * @return array
2087      */
2088     private function get_graders(stdClass $user) {
2089         //potential graders
2090         $potentialgraders = get_users_by_capability($this->context, 'mod/assign:grade', '', '', '', '', '', '', false, false);
2092         $graders = array();
2093         if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {   // Separate groups are being used
2094             if ($groups = groups_get_all_groups($this->get_course()->id, $user->id)) {  // Try to find all groups
2095                 foreach ($groups as $group) {
2096                     foreach ($potentialgraders as $grader) {
2097                         if ($grader->id == $user->id) {
2098                             continue; // do not send self
2099                         }
2100                         if (groups_is_member($group->id, $grader->id)) {
2101                             $graders[$grader->id] = $grader;
2102                         }
2103                     }
2104                 }
2105             } else {
2106                 // user not in group, try to find graders without group
2107                 foreach ($potentialgraders as $grader) {
2108                     if ($grader->id == $user->id) {
2109                         continue; // do not send self
2110                     }
2111                     if (!groups_has_membership($this->get_course_module(), $grader->id)) {
2112                         $graders[$grader->id] = $grader;
2113                     }
2114                 }
2115             }
2116         } else {
2117             foreach ($potentialgraders as $grader) {
2118                 if ($grader->id == $user->id) {
2119                     continue; // do not send self
2120                 }
2121                 // must be enrolled
2122                 if (is_enrolled($this->get_course_context(), $grader->id)) {
2123                     $graders[$grader->id] = $grader;
2124                 }
2125             }
2126         }
2127         return $graders;
2128     }
2130     /**
2131      * Creates the text content for emails to grader
2132      *
2133      * @param array $info The info used by the 'emailgradermail' language string
2134      * @return string
2135      */
2136     private function format_email_grader_text($info) {
2137         $posttext  = format_string($this->get_course()->shortname, true, array('context' => $this->get_course_context())).' -> '.
2138                      $this->get_module_name().' -> '.
2139                      format_string($this->get_instance()->name, true, array('context' => $this->context))."\n";
2140         $posttext .= '---------------------------------------------------------------------'."\n";
2141         $posttext .= get_string("emailgradermail", "assign", $info)."\n";
2142         $posttext .= "\n---------------------------------------------------------------------\n";
2143         return $posttext;
2144     }
2146      /**
2147      * Creates the html content for emails to graders
2148      *
2149      * @param array $info The info used by the 'emailgradermailhtml' language string
2150      * @return string
2151      */
2152     private function format_email_grader_html($info) {
2153         global $CFG;
2154         $posthtml  = '<p><font face="sans-serif">'.
2155                      '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$this->get_course()->id.'">'.format_string($this->get_course()->shortname, true, array('context' => $this->get_course_context())).'</a> ->'.
2156                      '<a href="'.$CFG->wwwroot.'/mod/assignment/index.php?id='.$this->get_course()->id.'">'.$this->get_module_name().'</a> ->'.
2157                      '<a href="'.$CFG->wwwroot.'/mod/assignment/view.php?id='.$this->get_course_module()->id.'">'.format_string($this->get_instance()->name, true, array('context' => $this->context)).'</a></font></p>';
2158         $posthtml .= '<hr /><font face="sans-serif">';
2159         $posthtml .= '<p>'.get_string('emailgradermailhtml', 'assign', $info).'</p>';
2160         $posthtml .= '</font><hr />';
2161         return $posthtml;
2162     }
2164     /**
2165      * email graders upon student submissions
2166      *
2167      * @param stdClass $submission
2168      * @return void
2169      */
2170     private function email_graders(stdClass $submission) {
2171         global $CFG, $DB;
2173         if (empty($this->get_instance()->sendnotifications)) {          // No need to do anything
2174             return;
2175         }
2177         $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
2179         if ($teachers = $this->get_graders($user)) {
2181             $strassignments = $this->get_module_name_plural();
2182             $strassignment  = $this->get_module_name();
2183             $strsubmitted  = get_string('submitted', 'assign');
2185             foreach ($teachers as $teacher) {
2186                 $info = new stdClass();
2187                 $info->username = fullname($user, true);
2188                 $info->assignment = format_string($this->get_instance()->name,true, array('context'=>$this->get_context()));
2189                 $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$this->get_course_module()->id;
2190                 $info->timeupdated = strftime('%c',$submission->timemodified);
2192                 $postsubject = $strsubmitted.': '.$info->username.' -> '.$this->get_instance()->name;
2193                 $posttext = $this->format_email_grader_text($info);
2194                 $posthtml = ($teacher->mailformat == 1) ? $this->format_email_grader_html($info) : '';
2196                 $eventdata = new stdClass();
2197                 $eventdata->modulename       = 'assign';
2198                 $eventdata->userfrom         = $user;
2199                 $eventdata->userto           = $teacher;
2200                 $eventdata->subject          = $postsubject;
2201                 $eventdata->fullmessage      = $posttext;
2202                 $eventdata->fullmessageformat = FORMAT_PLAIN;
2203                 $eventdata->fullmessagehtml  = $posthtml;
2204                 $eventdata->smallmessage     = $postsubject;
2206                 $eventdata->name            = 'assign_updates';
2207                 $eventdata->component       = 'mod_assign';
2208                 $eventdata->notification    = 1;
2209                 $eventdata->contexturl      = $info->url;
2210                 $eventdata->contexturlname  = $info->assignment;
2212                 message_send($eventdata);
2213             }
2214         }
2215     }
2217     /**
2218      * assignment submission is processed before grading
2219      *
2220      * @return void
2221      */
2222     private function process_submit_for_grading() {
2223         global $USER;
2225         // Need submit permission to submit an assignment
2226         require_capability('mod/assign:submit', $this->context);
2227         require_sesskey();
2229         $submission = $this->get_user_submission($USER->id,true);
2230         if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2231             // Give each submission plugin a chance to process the submission
2232             $plugins = $this->get_submission_plugins();
2233             foreach ($plugins as $plugin) {
2234                 $plugin->submit_for_grading();
2235             }
2237             $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2238             $this->update_submission($submission);
2239             $this->add_to_log('submit for grading', $this->format_submission_for_log($submission));
2240             $this->email_graders($submission);
2241         }
2242     }
2244     /**
2245      * save grading options
2246      *
2247      * @return void
2248      */
2249     private function process_save_grading_options() {
2250         global $USER, $CFG;
2252         // Include grading options form
2253         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
2255         // Need submit permission to submit an assignment
2256         require_capability('mod/assign:grade', $this->context);
2260         $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id));
2262         if ($formdata = $mform->get_data()) {
2263             set_user_preference('assign_perpage', $formdata->perpage);
2264             set_user_preference('assign_filter', $formdata->filter);
2265         }
2266     }
2268    /**
2269     * Take a grade object and print a short summary for the log file.
2270     * The size limit for the log file is 255 characters, so be careful not
2271     * to include too much information.
2272     *
2273     * @param stdClass $grade
2274     * @return string
2275     */
2276     private function format_grade_for_log(stdClass $grade) {
2277         global $DB;
2279         $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
2281         $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
2282         if ($grade->grade != '') {
2283             $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade) . '. ';
2284         } else {
2285             $info .= get_string('nograde', 'assign');
2286         }
2287         if ($grade->locked) {
2288             $info .= get_string('submissionslocked', 'assign') . '. ';
2289         }
2290         return $info;
2291     }
2293     /**
2294      * Take a submission object and print a short summary for the log file.
2295      * The size limit for the log file is 255 characters, so be careful not
2296      * to include too much information.
2297      *
2298      * @param stdClass $submission
2299      * @return string
2300      */
2301     private function format_submission_for_log(stdClass $submission) {
2302         $info = '';
2303         $info .= get_string('submissionstatus', 'assign') . ': ' . get_string('submissionstatus_' . $submission->status, 'assign') . '. <br>';
2304         // format_for_log here iterating every single log INFO  from either submission or grade in every assignment plugin
2306         foreach ($this->submissionplugins as $plugin) {
2307             if ($plugin->is_enabled() && $plugin->is_visible()) {
2310                 $info .= "<br>" . $plugin->format_for_log($submission);
2311             }
2312         }
2315         return $info;
2316     }
2318     /**
2319      * save assignment submission
2320      *
2321      * @param  moodleform $mform
2322      * @return bool
2323      */
2324     private function process_save_submission(&$mform) {
2325         global $USER, $CFG;
2327         // Include submission form
2328         require_once($CFG->dirroot . '/mod/assign/submission_form.php');
2330         // Need submit permission to submit an assignment
2331         require_capability('mod/assign:submit', $this->context);
2332         require_sesskey();
2334         $data = new stdClass();
2335         $mform = new mod_assign_submission_form(null, array($this, $data));
2336         if ($mform->is_cancelled()) {
2337             return true;
2338         }
2339         if ($data = $mform->get_data()) {
2340             $submission = $this->get_user_submission($USER->id, true); //create the submission if needed & its id
2341             $grade = $this->get_user_grade($USER->id, false); // get the grade to check if it is locked
2342             if ($grade && $grade->locked) {
2343                 print_error('submissionslocked', 'assign');
2344                 return true;
2345             }
2348             foreach ($this->submissionplugins as $plugin) {
2349                 if ($plugin->is_enabled()) {
2350                     if (!$plugin->save($submission, $data)) {
2351                         print_error($plugin->get_error());
2352                     }
2353                 }
2354             }
2356             $this->update_submission($submission);
2358             // Logging
2359             $this->add_to_log('submit', $this->format_submission_for_log($submission));
2361             if (!$this->get_instance()->submissiondrafts) {
2362                 $this->email_graders($submission);
2363             }
2364             return true;
2365         }
2366         return false;
2367     }
2369     /**
2370      * count the number of files in the file area
2371      *
2372      * @param int $userid
2373      * @param string $area
2374      * @return int
2375      */
2376     private function count_files($userid = 0, $area = ASSIGN_FILEAREA_SUBMISSION_FILES) {
2377         global $USER;
2379         if (!$userid) {
2380             $userid = $USER->id;
2381         }
2383         $fs = get_file_storage();
2384         $files = $fs->get_area_files($this->context->id, 'mod_assign', $area, $userid, "id", false);
2386         return count($files);
2387     }
2389     /**
2390      * Determine if this users grade is locked or overridden
2391      *
2392      * @param int $userid - The student userid
2393      * @return bool $gradingdisabled
2394      */
2395     private function grading_disabled($userid) {
2396         global $CFG;
2398         $gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($userid));
2399         if (!$gradinginfo) {
2400             return false;
2401         }
2403         if (!isset($gradinginfo->items[0]->grades[$userid])) {
2404             return false;
2405         }
2406         $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
2407         return $gradingdisabled;
2408     }
2411     /**
2412      * Get an instance of a grading form if advanced grading is enabled
2413      * This is specific to the assignment, marker and student
2414      *
2415      * @param int $userid - The student userid
2416      * @param bool $gradingdisabled
2417      * @return mixed gradingform_instance|null $gradinginstance
2418      */
2419     private function get_grading_instance($userid, $gradingdisabled) {
2420         global $CFG, $USER;
2422         $grade = $this->get_user_grade($userid, false);
2423         $grademenu = make_grades_menu($this->get_instance()->grade);
2425         $advancedgradingwarning = false;
2426         $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
2427         $gradinginstance = null;
2428         if ($gradingmethod = $gradingmanager->get_active_method()) {
2429             $controller = $gradingmanager->get_controller($gradingmethod);
2430             if ($controller->is_form_available()) {
2431                 $itemid = null;
2432                 if ($grade) {
2433                     $itemid = $grade->id;
2434                 }
2435                 if ($gradingdisabled && $itemid) {
2436                     $gradinginstance = ($controller->get_current_instance($USER->id, $itemid));
2437                 } else if (!$gradingdisabled) {
2438                     $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
2439                     $gradinginstance = ($controller->get_or_create_instance($instanceid, $USER->id, $itemid));
2440                 }
2441             } else {
2442                 $advancedgradingwarning = $controller->form_unavailable_notification();
2443             }
2444         }
2445         if ($gradinginstance) {
2446             $gradinginstance->get_controller()->set_grade_range($grademenu);
2447         }
2448         return $gradinginstance;
2449     }
2451     /**
2452      * add elements to grade form
2453      *
2454      * @param MoodleQuickForm $mform
2455      * @param stdClass $data
2456      * @param array $params
2457      * @return void
2458      */
2459     public function add_grade_form_elements(MoodleQuickForm $mform, stdClass $data, $params) {
2460         global $USER, $CFG;
2461         $settings = $this->get_instance();
2463         $rownum = $params['rownum'];
2464         $last = $params['last'];
2465         $useridlist = $params['useridlist'];
2466         $userid = $useridlist[$rownum];
2467         $grade = $this->get_user_grade($userid, false);
2469         // add advanced grading
2470         $gradingdisabled = $this->grading_disabled($userid);
2471         $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
2473         if ($gradinginstance) {
2474             $gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
2475             if ($gradingdisabled) {
2476                 $gradingelement->freeze();
2477             } else {
2478                 $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
2479             }
2480         } else {
2481             // use simple direct grading
2482             if ($this->get_instance()->grade > 0) {
2483                 $mform->addElement('text', 'grade', get_string('gradeoutof', 'assign',$this->get_instance()->grade));
2484                 $mform->addHelpButton('grade', 'gradeoutofhelp', 'assign');
2485                 $mform->setType('grade', PARAM_TEXT);
2486             } else {
2487                 $grademenu = make_grades_menu($this->get_instance()->grade);
2488                 if (count($grademenu) > 0) {
2489                     $mform->addElement('select', 'grade', get_string('grade').':', $grademenu);
2490                     $mform->setType('grade', PARAM_INT);
2491                 }
2492             }
2493         }
2494         $mform->addElement('static', 'progress', '', get_string('gradingstudentprogress', 'assign', array('index'=>$rownum+1, 'count'=>count($useridlist))));
2497         // plugins
2498         $this->add_plugin_grade_elements($grade, $mform, $data);
2500         // hidden params
2501         $mform->addElement('hidden', 'id', $this->get_course_module()->id);
2502         $mform->setType('id', PARAM_INT);
2503         $mform->addElement('hidden', 'rownum', $rownum);
2504         $mform->setType('rownum', PARAM_INT);
2505         $mform->addElement('hidden', 'useridlist', implode(',', $useridlist));
2506         $mform->setType('useridlist', PARAM_TEXT);
2507         $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
2508         $mform->setType('ajax', PARAM_INT);
2510         $mform->addElement('hidden', 'action', 'submitgrade');
2511         $mform->setType('action', PARAM_ALPHA);
2514         $buttonarray=array();
2515         $buttonarray[] = $mform->createElement('submit', 'savegrade', get_string('savechanges', 'assign'));
2516         if (!$last){
2517             $buttonarray[] = $mform->createElement('submit', 'saveandshownext', get_string('savenext','assign'));
2518         }
2519         $buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
2520         $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
2521         $mform->closeHeaderBefore('buttonar');
2522         $buttonarray=array();
2524         if ($rownum > 0) {
2525             $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', get_string('previous','assign'));
2526         }
2528         if (!$last){
2529             $buttonarray[] = $mform->createElement('submit', 'nosaveandnext', get_string('nosavebutnext', 'assign'));
2530         }
2531         $mform->addGroup($buttonarray, 'navar', '', array(' '), false);
2532     }
2535     /**
2536      * add elements in submission plugin form
2537      *
2538      * @param mixed $submission stdClass|null
2539      * @param MoodleQuickForm $mform
2540      * @param stdClass $data
2541      * @return void
2542      */
2543     private function add_plugin_submission_elements($submission, MoodleQuickForm $mform, stdClass $data) {
2544         foreach ($this->submissionplugins as $plugin) {
2545             if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
2546                 $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
2547                 if (!$plugin->get_form_elements($submission, $mform, $data)) {
2548                     $mform->removeElement('header_' . $plugin->get_type());
2549                 }
2550             }
2551         }
2552     }
2554     /**
2555      * check if feedback plugins installed are enabled
2556      *
2557      * @return bool
2558      */
2559     public function is_any_feedback_plugin_enabled() {
2560         if (!isset($this->cache['any_feedback_plugin_enabled'])) {
2561             $this->cache['any_feedback_plugin_enabled'] = false;
2562             foreach ($this->feedbackplugins as $plugin) {
2563                 if ($plugin->is_enabled() && $plugin->is_visible()) {
2564                     $this->cache['any_feedback_plugin_enabled'] = true;
2565                     break;
2566                 }
2567             }
2568         }
2570         return $this->cache['any_feedback_plugin_enabled'];
2572     }
2574     /**
2575      * check if submission plugins installed are enabled
2576      *
2577      * @return bool
2578      */
2579     public function is_any_submission_plugin_enabled() {
2580         if (!isset($this->cache['any_submission_plugin_enabled'])) {
2581             $this->cache['any_submission_plugin_enabled'] = false;
2582             foreach ($this->submissionplugins as $plugin) {
2583                 if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
2584                     $this->cache['any_submission_plugin_enabled'] = true;
2585                     break;
2586                 }
2587             }
2588         }
2590         return $this->cache['any_submission_plugin_enabled'];
2592     }
2594     /**
2595      * add elements to submission form
2596      * @param MoodleQuickForm $mform
2597      * @param stdClass $data
2598      * @return void
2599      */
2600     public function add_submission_form_elements(MoodleQuickForm $mform, stdClass $data) {
2601         global $USER;
2603         // online text submissions
2605         $submission = $this->get_user_submission($USER->id, false);
2607         $this->add_plugin_submission_elements($submission, $mform, $data);
2609         // hidden params
2610         $mform->addElement('hidden', 'id', $this->get_course_module()->id);
2611         $mform->setType('id', PARAM_INT);
2613         $mform->addElement('hidden', 'action', 'savesubmission');
2614         $mform->setType('action', PARAM_TEXT);
2615         // buttons
2617     }
2619     /**
2620      * revert to draft
2621      * Uses url parameter userid
2622      *
2623      * @param int $userid
2624      * @return void
2625      */
2626     private function process_revert_to_draft($userid = 0) {
2627         global $USER, $DB;
2629         // Need grade permission
2630         require_capability('mod/assign:grade', $this->context);
2631         require_sesskey();
2633         if (!$userid) {
2634             $userid = required_param('userid', PARAM_INT);
2635         }
2637         $submission = $this->get_user_submission($userid, false);
2638         if (!$submission) {
2639             return;
2640         }
2641         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2642         $this->update_submission($submission, false);
2644         // update the modified time on the grade (grader modified)
2645         $grade = $this->get_user_grade($userid, true);
2646         $this->update_grade($grade);
2648         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2650         $this->add_to_log('revert submission to draft', get_string('reverttodraftforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
2652     }
2654     /**
2655      * lock  the process
2656      * Uses url parameter userid
2657      * @param int $userid
2658      * @return void
2659      */
2660     private function process_lock($userid = 0) {
2661         global $USER, $DB;
2663         // Need grade permission
2664         require_capability('mod/assign:grade', $this->context);
2665         require_sesskey();
2667         if (!$userid) {
2668             $userid = required_param('userid', PARAM_INT);
2669         }
2671         $grade = $this->get_user_grade($userid, true);
2672         $grade->locked = 1;
2673         $grade->grader = $USER->id;
2674         $this->update_grade($grade);
2676         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2678         $this->add_to_log('lock submission', get_string('locksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
2679     }
2681     /**
2682      * unlock the process
2683      *
2684      * @param int $userid
2685      * @return void
2686      */
2687     private function process_unlock($userid = 0) {
2688         global $USER, $DB;
2690         // Need grade permission
2691         require_capability('mod/assign:grade', $this->context);
2692         require_sesskey();
2694         if (!$userid) {
2695             $userid = required_param('userid', PARAM_INT);
2696         }
2698         $grade = $this->get_user_grade($userid, true);
2699         $grade->locked = 0;
2700         $grade->grader = $USER->id;
2701         $this->update_grade($grade);
2703         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2705         $this->add_to_log('unlock submission', get_string('unlocksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
2706     }
2708     /**
2709      * save grade
2710      *
2711      * @param  moodleform $mform
2712      * @return bool - was the grade saved
2713      */
2714     private function process_save_grade(&$mform) {
2715         global $USER, $DB, $CFG;
2716         // Include grade form
2717         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2719         // Need submit permission to submit an assignment
2720         require_capability('mod/assign:grade', $this->context);
2721         require_sesskey();
2723         $rownum = required_param('rownum', PARAM_INT);
2724         $useridlist = optional_param('useridlist', '', PARAM_TEXT);
2725         if ($useridlist) {
2726             $useridlist = explode(',', $useridlist);
2727         } else {
2728             $useridlist = $this->get_grading_userid_list();
2729         }
2730         $last = false;
2731         $userid = $useridlist[$rownum];
2732         if ($rownum == count($useridlist) - 1) {
2733             $last = true;
2734         }
2736         $data = new stdClass();
2737         $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>false)), 'post', '', array('class'=>'gradeform'));
2739         if ($formdata = $mform->get_data()) {
2740             $grade = $this->get_user_grade($userid, true);
2741             $gradingdisabled = $this->grading_disabled($userid);
2742             $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
2743             if ($gradinginstance) {
2744                 $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
2745             } else {
2746                 // handle the case when grade is set to No Grade
2747                 if (isset($formdata->grade)) {
2748                     $grade->grade= grade_floatval($formdata->grade);
2749                 }
2750             }
2751             $grade->grader= $USER->id;
2753             $gradebookplugin = $CFG->assign_feedback_plugin_for_gradebook;
2755             // call save in plugins
2756             foreach ($this->feedbackplugins as $plugin) {
2757                 if ($plugin->is_enabled() && $plugin->is_visible()) {
2758                     if (!$plugin->save($grade, $formdata)) {
2759                         $result = false;
2760                         print_error($plugin->get_error());
2761                     }
2762                     if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
2763                         // this is the feedback plugin chose to push comments to the gradebook
2764                         $grade->feedbacktext = $plugin->text_for_gradebook($grade);
2765                         $grade->feedbackformat = $plugin->format_for_gradebook($grade);
2766                     }
2767                 }
2768             }
2769             $this->update_grade($grade);
2771             $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2773             $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
2776         } else {
2777             return false;
2778         }
2779         return true;
2780     }
2782     /**
2783      * This function is a static wrapper around can_upgrade
2784      *
2785      * @param string $type The plugin type
2786      * @param int $version The plugin version
2787      * @return bool
2788      */
2789     public static function can_upgrade_assignment($type, $version) {
2790         $assignment = new assign(null, null, null);
2791         return $assignment->can_upgrade($type, $version);
2792     }
2794     /**
2795      * This function returns true if it can upgrade an assignment from the 2.2
2796      * module.
2797      * @param string $type The plugin type
2798      * @param int $version The plugin version
2799      * @return bool
2800      */
2801     public function can_upgrade($type, $version) {
2802         if ($type == 'offline' && $version >= 2011112900) {
2803             return true;
2804         }
2805         foreach ($this->submissionplugins as $plugin) {
2806             if ($plugin->can_upgrade($type, $version)) {
2807                 return true;
2808             }
2809         }
2810         foreach ($this->feedbackplugins as $plugin) {
2811             if ($plugin->can_upgrade($type, $version)) {
2812                 return true;
2813             }
2814         }
2815         return false;
2816     }
2818     /**
2819      * Copy all the files from the old assignment files area to the new one.
2820      * This is used by the plugin upgrade code.
2821      *
2822      * @param int $oldcontextid The old assignment context id
2823      * @param int $oldcomponent The old assignment component ('assignment')
2824      * @param int $oldfilearea The old assignment filearea ('submissions')
2825      * @param int $olditemid The old submissionid (can be null e.g. intro)
2826      * @param int $newcontextid The new assignment context id
2827      * @param int $newcomponent The new assignment component ('assignment')
2828      * @param int $newfilearea The new assignment filearea ('submissions')
2829      * @param int $newitemid The new submissionid (can be null e.g. intro)
2830      * @return int The number of files copied
2831      */
2832     public function copy_area_files_for_upgrade($oldcontextid, $oldcomponent, $oldfilearea, $olditemid, $newcontextid, $newcomponent, $newfilearea, $newitemid) {
2833         // Note, this code is based on some code in filestorage - but that code
2834         // deleted the old files (which we don't want)
2835         $count = 0;
2837         $fs = get_file_storage();
2839         $oldfiles = $fs->get_area_files($oldcontextid, $oldcomponent, $oldfilearea, $olditemid, 'id', false);
2840         foreach ($oldfiles as $oldfile) {
2841             $filerecord = new stdClass();
2842             $filerecord->contextid = $newcontextid;
2843             $filerecord->component = $newcomponent;
2844             $filerecord->filearea = $newfilearea;
2845             $filerecord->itemid = $newitemid;
2846             $fs->create_file_from_storedfile($filerecord, $oldfile);
2847             $count += 1;
2848         }
2850         return $count;
2851     }
2853     /**
2854      * Get an upto date list of user grades and feedback for the gradebook
2855      *
2856      * @param int $userid int or 0 for all users
2857      * @return array of grade data formated for the gradebook api
2858      *         The data required by the gradebook api is userid,
2859      *                                                   rawgrade,
2860      *                                                   feedback,
2861      *                                                   feedbackformat,
2862      *                                                   usermodified,
2863      *                                                   dategraded,
2864      *                                                   datesubmitted
2865      */
2866     public function get_user_grades_for_gradebook($userid) {
2867         global $DB, $CFG;
2868         $grades = array();
2869         $assignmentid = $this->get_instance()->id;
2871         $gradebookpluginname = $CFG->assign_feedback_plugin_for_gradebook;
2872         $gradebookplugin = null;
2874         // find the gradebook plugin
2875         foreach ($this->feedbackplugins as $plugin) {
2876             if ($plugin->is_enabled() && $plugin->is_visible()) {
2877                 if (('assignfeedback_' . $plugin->get_type()) == $gradebookpluginname) {
2878                     $gradebookplugin = $plugin;
2879                 }
2880             }
2881         }
2882         if ($userid) {
2883             $where = ' WHERE u.id = ? ';
2884         } else {
2885             $where = ' WHERE u.id != ? ';
2886         }
2888         $graderesults = $DB->get_recordset_sql('SELECT u.id as userid, s.timemodified as datesubmitted, g.grade as rawgrade, g.timemodified as dategraded, g.grader as usermodified
2889                             FROM {user} u
2890                             LEFT JOIN {assign_submission} s ON u.id = s.userid and s.assignment = ?
2891                             LEFT JOIN {assign_grades} g ON u.id = g.userid and g.assignment = ?
2892                             ' . $where, array($assignmentid, $assignmentid, $userid));
2895         foreach ($graderesults as $result) {
2896             $gradebookgrade = clone $result;
2897             // now get the feedback
2898             if ($gradebookplugin) {
2899                 $grade = $this->get_user_grade($result->userid, false);
2900                 $gradebookgrade->feedbacktext = $gradebookplugin->text_for_gradebook($grade);
2901                 $gradebookgrade->feedbackformat = $gradebookplugin->format_for_gradebook($grade);
2902             }
2903             $grades[$gradebookgrade->userid] = $gradebookgrade;
2904         }
2906         $graderesults->close();
2907         return $grades;
2908     }