MDL-33791 Portfolio: Fixed security issue with passing file paths.
[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 /** Include accesslib.php */
43 require_once($CFG->libdir.'/accesslib.php');
44 /** Include formslib.php */
45 require_once($CFG->libdir.'/formslib.php');
46 /** Include repository/lib.php */
47 require_once($CFG->dirroot . '/repository/lib.php');
48 /** Include local mod_form.php */
49 require_once($CFG->dirroot.'/mod/assign/mod_form.php');
50 /** gradelib.php */
51 require_once($CFG->libdir.'/gradelib.php');
52 /** grading lib.php */
53 require_once($CFG->dirroot.'/grade/grading/lib.php');
54 /** Include feedbackplugin.php */
55 require_once($CFG->dirroot.'/mod/assign/feedbackplugin.php');
56 /** Include submissionplugin.php */
57 require_once($CFG->dirroot.'/mod/assign/submissionplugin.php');
58 /** Include renderable.php */
59 require_once($CFG->dirroot.'/mod/assign/renderable.php');
60 /** Include gradingtable.php */
61 require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
62 /** Include eventslib.php */
63 require_once($CFG->libdir.'/eventslib.php');
64 /** Include portfolio caller.php */
65 require_once($CFG->libdir . '/portfolio/caller.php');
67 /**
68  * Standard base class for mod_assign (assignment types).
69  *
70  * @package   mod_assign
71  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
72  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
73  */
74 class assign {
77     /** @var stdClass the assignment record that contains the global settings for this assign instance */
78     private $instance;
80     /** @var context the context of the course module for this assign instance (or just the course if we are
81         creating a new one) */
82     private $context;
84     /** @var stdClass the course this assign instance belongs to */
85     private $course;
87     /** @var stdClass the admin config for all assign instances  */
88     private $adminconfig;
91     /** @var assign_renderer the custom renderer for this module */
92     private $output;
94     /** @var stdClass the course module for this assign instance */
95     private $coursemodule;
97     /** @var array cache for things like the coursemodule name or the scale menu - only lives for a single
98         request */
99     private $cache;
101     /** @var array list of the installed submission plugins */
102     private $submissionplugins;
104     /** @var array list of the installed feedback plugins */
105     private $feedbackplugins;
107     /** @var string action to be used to return to this page (without repeating any form submissions etc.) */
108     private $returnaction = 'view';
110     /** @var array params to be used to return to this page */
111     private $returnparams = array();
113     /** @var string modulename prevents excessive calls to get_string */
114     private static $modulename = null;
116     /** @var string modulenameplural prevents excessive calls to get_string */
117     private static $modulenameplural = null;
119     /**
120      * Constructor for the base assign class
121      *
122      * @param mixed $coursemodulecontext context|null the course module context (or the course context if the coursemodule has not been created yet)
123      * @param mixed $coursemodule the current course module if it was already loaded - otherwise this class will load one from the context as required
124      * @param mixed $course the current course  if it was already loaded - otherwise this class will load one from the context as required
125      */
126     public function __construct($coursemodulecontext, $coursemodule, $course) {
127         global $PAGE;
129         $this->context = $coursemodulecontext;
130         $this->coursemodule = $coursemodule;
131         $this->course = $course;
132         $this->cache = array(); // temporary cache only lives for a single request - used to reduce db lookups
134         $this->submissionplugins = $this->load_plugins('assignsubmission');
135         $this->feedbackplugins = $this->load_plugins('assignfeedback');
136     }
138     /**
139      * Set the action and parameters that can be used to return to the current page
140      *
141      * @param string $action The action for the current page
142      * @param array $params An array of name value pairs which form the parameters to return to the current page
143      * @return void
144      */
145     public function register_return_link($action, $params) {
146         $this->returnaction = $action;
147         $this->returnparams = $params;
148     }
150     /**
151      * Return an action that can be used to get back to the current page
152      * @return string action
153      */
154     public function get_return_action() {
155         return $this->returnaction;
156     }
158     /**
159      * Based on the current assignment settings should we display the intro
160      * @return bool showintro
161      */
162     private function show_intro() {
163         if ($this->get_instance()->alwaysshowdescription ||
164                 time() > $this->get_instance()->allowsubmissionsfromdate) {
165             return true;
166         }
167         return false;
168     }
170     /**
171      * Return a list of parameters that can be used to get back to the current page
172      * @return array params
173      */
174     public function get_return_params() {
175         return $this->returnparams;
176     }
178     /**
179      * Set the submitted form data
180      * @param stdClass $data The form data (instance)
181      */
182     public function set_instance(stdClass $data) {
183         $this->instance = $data;
184     }
186     /**
187      * Set the context
188      * @param context $context The new context
189      */
190     public function set_context(context $context) {
191         $this->context = $context;
192     }
194     /**
195      * Set the course data
196      * @param stdClass $course The course data
197      */
198     public function set_course(stdClass $course) {
199         $this->course = $course;
200     }
202     /**
203      * get list of feedback plugins installed
204      * @return array
205      */
206     public function get_feedback_plugins() {
207         return $this->feedbackplugins;
208     }
210     /**
211      * get list of submission plugins installed
212      * @return array
213      */
214     public function get_submission_plugins() {
215         return $this->submissionplugins;
216     }
218     /**
219      * Is blind marking enabled and reveal identities not set yet?
220      *
221      * @return bool
222      */
223     public function is_blind_marking() {
224         return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
225     }
227     /**
228      * Does an assignment have submission(s) or grade(s) already?
229      *
230      * @return bool
231      */
232     public function has_submissions_or_grades() {
233         $allgrades = $this->count_grades();
234         $allsubmissions = $this->count_submissions();
235         if (($allgrades == 0) && ($allsubmissions == 0)) {
236             return false;
237         }
238         return true;
239     }
241     /**
242      * get a specific submission plugin by its type
243      * @param string $subtype assignsubmission | assignfeedback
244      * @param string $type
245      * @return mixed assign_plugin|null
246      */
247     public function get_plugin_by_type($subtype, $type) {
248         $shortsubtype = substr($subtype, strlen('assign'));
249         $name = $shortsubtype . 'plugins';
250         if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
251             return null;
252         }
253         $pluginlist = $this->$name;
254         foreach ($pluginlist as $plugin) {
255             if ($plugin->get_type() == $type) {
256                 return $plugin;
257             }
258         }
259         return null;
260     }
262     /**
263      * Get a feedback plugin by type
264      * @param string $type - The type of plugin e.g comments
265      * @return mixed assign_feedback_plugin|null
266      */
267     public function get_feedback_plugin_by_type($type) {
268         return $this->get_plugin_by_type('assignfeedback', $type);
269     }
271     /**
272      * Get a submission plugin by type
273      * @param string $type - The type of plugin e.g comments
274      * @return mixed assign_submission_plugin|null
275      */
276     public function get_submission_plugin_by_type($type) {
277         return $this->get_plugin_by_type('assignsubmission', $type);
278     }
280     /**
281      * Load the plugins from the sub folders under subtype
282      * @param string $subtype - either submission or feedback
283      * @return array - The sorted list of plugins
284      */
285     private function load_plugins($subtype) {
286         global $CFG;
287         $result = array();
289         $names = get_plugin_list($subtype);
291         foreach ($names as $name => $path) {
292             if (file_exists($path . '/locallib.php')) {
293                 require_once($path . '/locallib.php');
295                 $shortsubtype = substr($subtype, strlen('assign'));
296                 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
298                 $plugin = new $pluginclass($this, $name);
300                 if ($plugin instanceof assign_plugin) {
301                     $idx = $plugin->get_sort_order();
302                     while (array_key_exists($idx, $result)) $idx +=1;
303                     $result[$idx] = $plugin;
304                 }
305             }
306         }
307         ksort($result);
308         return $result;
309     }
311     /**
312      * Display the assignment, used by view.php
313      *
314      * The assignment is displayed differently depending on your role,
315      * the settings for the assignment and the status of the assignment.
316      * @param string $action The current action if any.
317      * @return void
318      */
319     public function view($action='') {
321         $o = '';
322         $mform = null;
324         // handle form submissions first
325         if ($action == 'savesubmission') {
326             $action = 'editsubmission';
327             if ($this->process_save_submission($mform)) {
328                 $action = 'view';
329             }
330         } else if ($action == 'lock') {
331             $this->process_lock();
332             $action = 'grading';
333         } else if ($action == 'reverttodraft') {
334             $this->process_revert_to_draft();
335             $action = 'grading';
336         } else if ($action == 'unlock') {
337             $this->process_unlock();
338             $action = 'grading';
339         } else if ($action == 'confirmsubmit') {
340             $action = 'submit';
341             if ($this->process_submit_for_grading($mform)) {
342                 $action = 'view';
343             }
344         } else if ($action == 'gradingbatchoperation') {
345             $action = $this->process_grading_batch_operation($mform);
346         } else if ($action == 'submitgrade') {
347             if (optional_param('saveandshownext', null, PARAM_RAW)) {
348                 //save and show next
349                 $action = 'grade';
350                 if ($this->process_save_grade($mform)) {
351                     $action = 'nextgrade';
352                 }
353             } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
354                 $action = 'previousgrade';
355             } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
356                 //show next button
357                 $action = 'nextgrade';
358             } else if (optional_param('savegrade', null, PARAM_RAW)) {
359                 //save changes button
360                 $action = 'grade';
361                 if ($this->process_save_grade($mform)) {
362                     $action = 'grading';
363                 }
364             } else {
365                 //cancel button
366                 $action = 'grading';
367             }
368         } else if ($action == 'quickgrade') {
369             $message = $this->process_save_quick_grades();
370             $action = 'quickgradingresult';
371         } else if ($action == 'saveoptions') {
372             $this->process_save_grading_options();
373             $action = 'grading';
374         } else if ($action == 'saveextension') {
375             $action = 'grantextension';
376             if ($this->process_save_extension($mform)) {
377                 $action = 'grading';
378             }
379         } else if ($action == 'revealidentitiesconfirm') {
380             $this->process_reveal_identities();
381             $action = 'grading';
382         }
384         $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
385         $this->register_return_link($action, $returnparams);
387         // now show the right view page
388         if ($action == 'previousgrade') {
389             $mform = null;
390             $o .= $this->view_single_grade_page($mform, -1);
391         } else if ($action == 'quickgradingresult') {
392             $mform = null;
393             $o .= $this->view_quickgrading_result($message);
394         } else if ($action == 'nextgrade') {
395             $mform = null;
396             $o .= $this->view_single_grade_page($mform, 1);
397         } else if ($action == 'grade') {
398             $o .= $this->view_single_grade_page($mform);
399         } else if ($action == 'viewpluginassignfeedback') {
400             $o .= $this->view_plugin_content('assignfeedback');
401         } else if ($action == 'viewpluginassignsubmission') {
402             $o .= $this->view_plugin_content('assignsubmission');
403         } else if ($action == 'editsubmission') {
404             $o .= $this->view_edit_submission_page($mform);
405         } else if ($action == 'grading') {
406             $o .= $this->view_grading_page();
407         } else if ($action == 'downloadall') {
408             $o .= $this->download_submissions();
409         } else if ($action == 'submit') {
410             $o .= $this->check_submit_for_grading($mform);
411         } else if ($action == 'grantextension') {
412             $o .= $this->view_grant_extension($mform);
413         } else if ($action == 'revealidentities') {
414             $o .= $this->view_reveal_identities_confirm($mform);
415         } else if ($action == 'plugingradingbatchoperation') {
416             $o .= $this->view_plugin_grading_batch_operation($mform);
417         } else if ($action == 'viewpluginpage') {
418              $o .= $this->view_plugin_page();
419         } else {
420             $o .= $this->view_submission_page();
421         }
423         return $o;
424     }
427     /**
428      * Add this instance to the database
429      *
430      * @param stdClass $formdata The data submitted from the form
431      * @param bool $callplugins This is used to skip the plugin code
432      *             when upgrading an old assignment to a new one (the plugins get called manually)
433      * @return mixed false if an error occurs or the int id of the new instance
434      */
435     public function add_instance(stdClass $formdata, $callplugins) {
436         global $DB;
438         $err = '';
440         // add the database record
441         $update = new stdClass();
442         $update->name = $formdata->name;
443         $update->timemodified = time();
444         $update->timecreated = time();
445         $update->course = $formdata->course;
446         $update->courseid = $formdata->course;
447         $update->intro = $formdata->intro;
448         $update->introformat = $formdata->introformat;
449         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
450         $update->submissiondrafts = $formdata->submissiondrafts;
451         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
452         $update->sendnotifications = $formdata->sendnotifications;
453         $update->sendlatenotifications = $formdata->sendlatenotifications;
454         $update->duedate = $formdata->duedate;
455         $update->cutoffdate = $formdata->cutoffdate;
456         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
457         $update->grade = $formdata->grade;
458         $update->completionsubmit = !empty($formdata->completionsubmit);
459         $update->teamsubmission = $formdata->teamsubmission;
460         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
461         $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
462         $update->blindmarking = $formdata->blindmarking;
464         $returnid = $DB->insert_record('assign', $update);
465         $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
466         // cache the course record
467         $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
469         if ($callplugins) {
470             // call save_settings hook for submission plugins
471             foreach ($this->submissionplugins as $plugin) {
472                 if (!$this->update_plugin_instance($plugin, $formdata)) {
473                     print_error($plugin->get_error());
474                     return false;
475                 }
476             }
477             foreach ($this->feedbackplugins as $plugin) {
478                 if (!$this->update_plugin_instance($plugin, $formdata)) {
479                     print_error($plugin->get_error());
480                     return false;
481                 }
482             }
484             // in the case of upgrades the coursemodule has not been set so we need to wait before calling these two
485             // TODO: add event to the calendar
486             $this->update_calendar($formdata->coursemodule);
487             // TODO: add the item in the gradebook
488             $this->update_gradebook(false, $formdata->coursemodule);
490         }
492         $update = new stdClass();
493         $update->id = $this->get_instance()->id;
494         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
495         $DB->update_record('assign', $update);
497         return $returnid;
498     }
500     /**
501      * Delete all grades from the gradebook for this assignment
502      *
503      * @return bool
504      */
505     private function delete_grades() {
506         global $CFG;
508         return grade_update('mod/assign', $this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, 0, NULL, array('deleted'=>1)) == GRADE_UPDATE_OK;
509     }
511     /**
512      * Delete this instance from the database
513      *
514      * @return bool false if an error occurs
515      */
516     public function delete_instance() {
517         global $DB;
518         $result = true;
520         foreach ($this->submissionplugins as $plugin) {
521             if (!$plugin->delete_instance()) {
522                 print_error($plugin->get_error());
523                 $result = false;
524             }
525         }
526         foreach ($this->feedbackplugins as $plugin) {
527             if (!$plugin->delete_instance()) {
528                 print_error($plugin->get_error());
529                 $result = false;
530             }
531         }
533         // delete files associated with this assignment
534         $fs = get_file_storage();
535         if (! $fs->delete_area_files($this->context->id) ) {
536             $result = false;
537         }
539         // delete_records will throw an exception if it fails - so no need for error checking here
541         $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
542         $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
543         $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
545         // delete items from the gradebook
546         if (! $this->delete_grades()) {
547             $result = false;
548         }
550         // delete the instance
551         $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
553         return $result;
554     }
556     /**
557     * Actual implementation of the reset course functionality, delete all the
558     * assignment submissions for course $data->courseid.
559     *
560     * @param $data the data submitted from the reset course.
561     * @return array status array
562     */
563     public function reset_userdata($data) {
564         global $CFG,$DB;
566         $componentstr = get_string('modulenameplural', 'assign');
567         $status = array();
569         $fs = get_file_storage();
570         if (!empty($data->reset_assign_submissions)) {
571             // Delete files associated with this assignment.
572             foreach ($this->submissionplugins as $plugin) {
573                 $fileareas = array();
574                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
575                 $fileareas = $plugin->get_file_areas();
576                 foreach ($fileareas as $filearea) {
577                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
578                 }
580                 if (!$plugin->delete_instance()) {
581                     $status[] = array('component'=>$componentstr,
582                                       'item'=>get_string('deleteallsubmissions','assign'),
583                                       'error'=>$plugin->get_error());
584                 }
585             }
587             foreach ($this->feedbackplugins as $plugin) {
588                 $fileareas = array();
589                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
590                 $fileareas = $plugin->get_file_areas();
591                 foreach ($fileareas as $filearea) {
592                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
593                 }
595                 if (!$plugin->delete_instance()) {
596                     $status[] = array('component'=>$componentstr,
597                                       'item'=>get_string('deleteallsubmissions','assign'),
598                                       'error'=>$plugin->get_error());
599                 }
600             }
602             $assignssql = "SELECT a.id
603                              FROM {assign} a
604                            WHERE a.course=:course";
605             $params = array ("course" => $data->courseid);
607             $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
608             $status[] = array('component'=>$componentstr,
609                               'item'=>get_string('deleteallsubmissions','assign'),
610                               'error'=>false);
612             if (empty($data->reset_gradebook_grades)) {
613                 // Remove all grades from gradebook.
614                 require_once($CFG->dirroot.'/mod/assign/lib.php');
615                 assign_reset_gradebook($data->courseid);
616             }
617         }
618         // Updating dates - shift may be negative too.
619         if ($data->timeshift) {
620             shift_course_mod_dates('assign',
621                                     array('duedate', 'allowsubmissionsfromdate','cutoffdate'),
622                                     $data->timeshift,
623                                     $data->courseid);
624             $status[] = array('component'=>$componentstr,
625                               'item'=>get_string('datechanged'),
626                               'error'=>false);
627         }
629         return $status;
630     }
632     /**
633      * Update the settings for a single plugin
634      *
635      * @param assign_plugin $plugin The plugin to update
636      * @param stdClass $formdata The form data
637      * @return bool false if an error occurs
638      */
639     private function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
640         if ($plugin->is_visible()) {
641             $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
642             if ($formdata->$enabledname) {
643                 $plugin->enable();
644                 if (!$plugin->save_settings($formdata)) {
645                     print_error($plugin->get_error());
646                     return false;
647                 }
648             } else {
649                 $plugin->disable();
650             }
651         }
652         return true;
653     }
655     /**
656      * Update the gradebook information for this assignment
657      *
658      * @param bool $reset If true, will reset all grades in the gradbook for this assignment
659      * @param int $coursemoduleid This is required because it might not exist in the database yet
660      * @return bool
661      */
662     public function update_gradebook($reset, $coursemoduleid) {
663          global $CFG;
664         /** Include lib.php */
665         require_once($CFG->dirroot.'/mod/assign/lib.php');
666         $assign = clone $this->get_instance();
667         $assign->cmidnumber = $coursemoduleid;
668         $param = null;
669         if ($reset) {
670             $param = 'reset';
671         }
673         return assign_grade_item_update($assign, $param);
674     }
676     /** Load and cache the admin config for this module
677      *
678      * @return stdClass the plugin config
679      */
680     public function get_admin_config() {
681         if ($this->adminconfig) {
682             return $this->adminconfig;
683         }
684         $this->adminconfig = get_config('assign');
685         return $this->adminconfig;
686     }
689     /**
690      * Update the calendar entries for this assignment
691      *
692      * @param int $coursemoduleid - Required to pass this in because it might not exist in the database yet
693      * @return bool
694      */
695     public function update_calendar($coursemoduleid) {
696         global $DB, $CFG;
697         require_once($CFG->dirroot.'/calendar/lib.php');
699         // special case for add_instance as the coursemodule has not been set yet.
701         if ($this->get_instance()->duedate) {
702             $event = new stdClass();
704             if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id))) {
706                 $event->name        = $this->get_instance()->name;
708                 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
709                 $event->timestart   = $this->get_instance()->duedate;
711                 $calendarevent = calendar_event::load($event->id);
712                 $calendarevent->update($event);
713             } else {
714                 $event = new stdClass();
715                 $event->name        = $this->get_instance()->name;
716                 $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
717                 $event->courseid    = $this->get_instance()->course;
718                 $event->groupid     = 0;
719                 $event->userid      = 0;
720                 $event->modulename  = 'assign';
721                 $event->instance    = $this->get_instance()->id;
722                 $event->eventtype   = 'due';
723                 $event->timestart   = $this->get_instance()->duedate;
724                 $event->timeduration = 0;
726                 calendar_event::create($event);
727             }
728         } else {
729             $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id));
730         }
731     }
734     /**
735      * Update this instance in the database
736      *
737      * @param stdClass $formdata - the data submitted from the form
738      * @return bool false if an error occurs
739      */
740     public function update_instance($formdata) {
741         global $DB;
743         $update = new stdClass();
744         $update->id = $formdata->instance;
745         $update->name = $formdata->name;
746         $update->timemodified = time();
747         $update->course = $formdata->course;
748         $update->intro = $formdata->intro;
749         $update->introformat = $formdata->introformat;
750         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
751         $update->submissiondrafts = $formdata->submissiondrafts;
752         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
753         $update->sendnotifications = $formdata->sendnotifications;
754         $update->sendlatenotifications = $formdata->sendlatenotifications;
755         $update->duedate = $formdata->duedate;
756         $update->cutoffdate = $formdata->cutoffdate;
757         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
758         $update->grade = $formdata->grade;
759         $update->completionsubmit = !empty($formdata->completionsubmit);
760         $update->teamsubmission = $formdata->teamsubmission;
761         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
762         $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
763         $update->blindmarking = $formdata->blindmarking;
765         $result = $DB->update_record('assign', $update);
766         $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
768         // load the assignment so the plugins have access to it
770         // call save_settings hook for submission plugins
771         foreach ($this->submissionplugins as $plugin) {
772             if (!$this->update_plugin_instance($plugin, $formdata)) {
773                 print_error($plugin->get_error());
774                 return false;
775             }
776         }
777         foreach ($this->feedbackplugins as $plugin) {
778             if (!$this->update_plugin_instance($plugin, $formdata)) {
779                 print_error($plugin->get_error());
780                 return false;
781             }
782         }
785         // update the database record
788         // update all the calendar events
789         $this->update_calendar($this->get_course_module()->id);
791         $this->update_gradebook(false, $this->get_course_module()->id);
793         $update = new stdClass();
794         $update->id = $this->get_instance()->id;
795         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
796         $DB->update_record('assign', $update);
802         return $result;
803     }
805     /**
806      * add elements in grading plugin form
807      *
808      * @param mixed $grade stdClass|null
809      * @param MoodleQuickForm $mform
810      * @param stdClass $data
811      * @return void
812      */
813     private function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data) {
814         foreach ($this->feedbackplugins as $plugin) {
815             if ($plugin->is_enabled() && $plugin->is_visible()) {
816                 $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
817                 if (!$plugin->get_form_elements($grade, $mform, $data)) {
818                     $mform->removeElement('header_' . $plugin->get_type());
819                 }
820             }
821         }
822     }
826     /**
827      * Add one plugins settings to edit plugin form
828      *
829      * @param assign_plugin $plugin The plugin to add the settings from
830      * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
831      * @return void
832      */
833     private function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform) {
834         global $CFG;
835         if ($plugin->is_visible()) {
836             // enabled
837             //tied disableIf rule to this select element
838             $mform->addElement('selectyesno', $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $plugin->get_name());
839             $mform->addHelpButton($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', 'enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
842             $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
843             if ($plugin->get_config('enabled') !== false) {
844                 $default = $plugin->is_enabled();
845             }
846             $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
848             $plugin->get_settings($mform);
850         }
852     }
855     /**
856      * Add settings to edit plugin form
857      *
858      * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
859      * @return void
860      */
861     public function add_all_plugin_settings(MoodleQuickForm $mform) {
862         $mform->addElement('header', 'general', get_string('submissionsettings', 'assign'));
864         foreach ($this->submissionplugins as $plugin) {
865             $this->add_plugin_settings($plugin, $mform);
867         }
868         $mform->addElement('header', 'general', get_string('feedbacksettings', 'assign'));
869         foreach ($this->feedbackplugins as $plugin) {
870             $this->add_plugin_settings($plugin, $mform);
871         }
872     }
874     /**
875      * Allow each plugin an opportunity to update the defaultvalues
876      * passed in to the settings form (needed to set up draft areas for
877      * editor and filemanager elements)
878      * @param array $defaultvalues
879      */
880     public function plugin_data_preprocessing(&$defaultvalues) {
881         foreach ($this->submissionplugins as $plugin) {
882             if ($plugin->is_visible()) {
883                 $plugin->data_preprocessing($defaultvalues);
884             }
885         }
886         foreach ($this->feedbackplugins as $plugin) {
887             if ($plugin->is_visible()) {
888                 $plugin->data_preprocessing($defaultvalues);
889             }
890         }
891     }
893     /**
894      * Get the name of the current module.
895      *
896      * @return string the module name (Assignment)
897      */
898     protected function get_module_name() {
899         if (isset(self::$modulename)) {
900             return self::$modulename;
901         }
902         self::$modulename = get_string('modulename', 'assign');
903         return self::$modulename;
904     }
906     /**
907      * Get the plural name of the current module.
908      *
909      * @return string the module name plural (Assignments)
910      */
911     protected function get_module_name_plural() {
912         if (isset(self::$modulenameplural)) {
913             return self::$modulenameplural;
914         }
915         self::$modulenameplural = get_string('modulenameplural', 'assign');
916         return self::$modulenameplural;
917     }
919     /**
920      * Has this assignment been constructed from an instance?
921      *
922      * @return bool
923      */
924     public function has_instance() {
925         return $this->instance || $this->get_course_module();
926     }
928     /**
929      * Get the settings for the current instance of this assignment
930      *
931      * @return stdClass The settings
932      */
933     public function get_instance() {
934         global $DB;
935         if ($this->instance) {
936             return $this->instance;
937         }
938         if ($this->get_course_module()) {
939             $this->instance = $DB->get_record('assign', array('id' => $this->get_course_module()->instance), '*', MUST_EXIST);
940         }
941         if (!$this->instance) {
942             throw new coding_exception('Improper use of the assignment class. Cannot load the assignment record.');
943         }
944         return $this->instance;
945     }
947     /**
948      * Get the context of the current course
949      * @return mixed context|null The course context
950      */
951     public function get_course_context() {
952         if (!$this->context && !$this->course) {
953             throw new coding_exception('Improper use of the assignment class. Cannot load the course context.');
954         }
955         if ($this->context) {
956             return $this->context->get_course_context();
957         } else {
958             return context_course::instance($this->course->id);
959         }
960     }
963     /**
964      * Get the current course module
965      *
966      * @return mixed stdClass|null The course module
967      */
968     public function get_course_module() {
969         if ($this->coursemodule) {
970             return $this->coursemodule;
971         }
972         if (!$this->context) {
973             return null;
974         }
976         if ($this->context->contextlevel == CONTEXT_MODULE) {
977             $this->coursemodule = get_coursemodule_from_id('assign', $this->context->instanceid, 0, false, MUST_EXIST);
978             return $this->coursemodule;
979         }
980         return null;
981     }
983     /**
984      * Get context module
985      *
986      * @return context
987      */
988     public function get_context() {
989         return $this->context;
990     }
992     /**
993      * Get the current course
994      * @return mixed stdClass|null The course
995      */
996     public function get_course() {
997         global $DB;
998         if ($this->course) {
999             return $this->course;
1000         }
1002         if (!$this->context) {
1003             return null;
1004         }
1005         $this->course = $DB->get_record('course', array('id' => $this->get_course_context()->instanceid), '*', MUST_EXIST);
1006         return $this->course;
1007     }
1009     /**
1010      * Return a grade in user-friendly form, whether it's a scale or not
1011      *
1012      * @param mixed $grade int|null
1013      * @param boolean $editing Are we allowing changes to this grade?
1014      * @param int $userid The user id the grade belongs to
1015      * @param int $modified Timestamp from when the grade was last modified
1016      * @return string User-friendly representation of grade
1017      */
1018     public function display_grade($grade, $editing, $userid=0, $modified=0) {
1019         global $DB;
1021         static $scalegrades = array();
1023         if ($this->get_instance()->grade >= 0) {
1024             // Normal number
1025             if ($editing && $this->get_instance()->grade > 0) {
1026                 if ($grade < 0) {
1027                     $displaygrade = '';
1028                 } else {
1029                     $displaygrade = format_float($grade);
1030                 }
1031                 $o = '<label class="accesshide" for="quickgrade_' . $userid . '">' . get_string('usergrade', 'assign') . '</label>';
1032                 $o .= '<input type="text" id="quickgrade_' . $userid . '" name="quickgrade_' . $userid . '" value="' . $displaygrade
1033                         . '" size="6" maxlength="10" class="quickgrade"/>';
1034                 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
1035                 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
1036                 return $o;
1037             } else {
1038                 if ($grade == -1 || $grade === null) {
1039                     return '-';
1040                 } else {
1041                     return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
1042                 }
1043             }
1045         } else {
1046             // Scale
1047             if (empty($this->cache['scale'])) {
1048                 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1049                     $this->cache['scale'] = make_menu_from_list($scale->scale);
1050                 } else {
1051                     return '-';
1052                 }
1053             }
1054             if ($editing) {
1055                 $o = '<label class="accesshide" for="quickgrade_' . $userid . '">' . get_string('usergrade', 'assign') . '</label>';
1056                 $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
1057                 $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1058                 foreach ($this->cache['scale'] as $optionid => $option) {
1059                     $selected = '';
1060                     if ($grade == $optionid) {
1061                         $selected = 'selected="selected"';
1062                     }
1063                     $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1064                 }
1065                 $o .= '</select>';
1066                 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
1067                 return $o;
1068             } else {
1069                 $scaleid = (int)$grade;
1070                 if (isset($this->cache['scale'][$scaleid])) {
1071                     return $this->cache['scale'][$scaleid];
1072                 }
1073                 return '-';
1074             }
1075         }
1076     }
1078     /**
1079      * Load a list of users enrolled in the current course with the specified permission and group (0 for no group)
1080      *
1081      * @param int $currentgroup
1082      * @param bool $idsonly
1083      * @return array List of user records
1084      */
1085     public function list_participants($currentgroup, $idsonly) {
1086         if ($idsonly) {
1087             return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup, 'u.id');
1088         } else {
1089             return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
1090         }
1091     }
1093     /**
1094      * Load a count of valid teams for this assignment
1095      *
1096      * @return int number of valid teams
1097      */
1098     public function count_teams() {
1100         $groups = groups_get_all_groups($this->get_course()->id, 0, $this->get_instance()->teamsubmissiongroupingid, 'g.id');
1101         $count = count($groups);
1103         // See if there are any users in the default group.
1104         $defaultusers = $this->get_submission_group_members(0, true);
1105         if (count($defaultusers) > 0) {
1106             $count += 1;
1107         }
1108         return $count;
1109     }
1111     /**
1112      * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
1113      *
1114      * @param int $currentgroup
1115      * @return int number of matching users
1116      */
1117     public function count_participants($currentgroup) {
1118         return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
1119     }
1121     /**
1122      * Load a count of users submissions in the current module that require grading
1123      * This means the submission modification time is more recent than the
1124      * grading modification time.
1125      *
1126      * @return int number of matching submissions
1127      */
1128     public function count_submissions_need_grading() {
1129         global $DB;
1131         $params = array($this->get_course_module()->instance);
1133         return $DB->count_records_sql("SELECT COUNT('x')
1134                                        FROM {assign_submission} s
1135                                        LEFT JOIN {assign_grades} g ON s.assignment = g.assignment AND s.userid = g.userid
1136                                        WHERE s.assignment = ?
1137                                            AND s.timemodified IS NOT NULL
1138                                            AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)",
1139                                        $params);
1140     }
1142     /**
1143      * Load a count of grades
1144      *
1145      * @return int number of grades
1146      */
1147     public function count_grades() {
1148         global $DB;
1150         if (!$this->has_instance()) {
1151             return 0;
1152         }
1154         $sql = 'SELECT COUNT(id) FROM {assign_grades} WHERE assignment = ?';
1155         $params = array($this->get_course_module()->instance);
1157         return $DB->count_records_sql($sql, $params);
1158     }
1160     /**
1161      * Load a count of submissions
1162      *
1163      * @return int number of submissions
1164      */
1165     public function count_submissions() {
1166         global $DB;
1168         if (!$this->has_instance()) {
1169             return 0;
1170         }
1172         $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ?';
1173         $params = array($this->get_course_module()->instance);
1175         if ($this->get_instance()->teamsubmission) {
1176             // only look at team submissions
1177             $sql .= ' AND userid = ?';
1178             $params[] = 0;
1179         }
1180         return $DB->count_records_sql($sql, $params);
1181     }
1183     /**
1184      * Load a count of submissions with a specified status
1185      *
1186      * @param string $status The submission status - should match one of the constants
1187      * @return int number of matching submissions
1188      */
1189     public function count_submissions_with_status($status) {
1190         global $DB;
1191         $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ? AND status = ?';
1192         $params = array($this->get_course_module()->instance, $status);
1194         if ($this->get_instance()->teamsubmission) {
1195             // only look at team submissions
1196             $sql .= ' AND userid = ?';
1197             $params[] = 0;
1198         }
1199         return $DB->count_records_sql($sql, $params);
1200     }
1202     /**
1203      * Utility function to get the userid for every row in the grading table
1204      * so the order can be frozen while we iterate it
1205      *
1206      * @return array An array of userids
1207      */
1208     private function get_grading_userid_list() {
1209         $filter = get_user_preferences('assign_filter', '');
1210         $table = new assign_grading_table($this, 0, $filter, 0, false);
1212         $useridlist = $table->get_column_data('userid');
1214         return $useridlist;
1215     }
1218     /**
1219      * Utility function get the userid based on the row number of the grading table.
1220      * This takes into account any active filters on the table.
1221      *
1222      * @param int $num The row number of the user
1223      * @param bool $last This is set to true if this is the last user in the table
1224      * @return mixed The user id of the matching user or false if there was an error
1225      */
1226     private function get_userid_for_row($num, $last) {
1227         if (!array_key_exists('userid_for_row', $this->cache)) {
1228             $this->cache['userid_for_row'] = array();
1229         }
1230         if (array_key_exists($num, $this->cache['userid_for_row'])) {
1231             list($userid, $last) = $this->cache['userid_for_row'][$num];
1232             return $userid;
1233         }
1235         $filter = get_user_preferences('assign_filter', '');
1236         $table = new assign_grading_table($this, 0, $filter, 0, false);
1238         $userid = $table->get_cell_data($num, 'userid', $last);
1240         $this->cache['userid_for_row'][$num] = array($userid, $last);
1241         return $userid;
1242     }
1244     /**
1245      * Generate zip file from array of given files
1246      *
1247      * @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
1248      * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
1249      */
1250      private function pack_files($filesforzipping) {
1251          global $CFG;
1252          //create path for new zip file.
1253          $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
1254          //zip files
1255          $zipper = new zip_packer();
1256          if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1257              return $tempzip;
1258          }
1259          return false;
1260     }
1262     /**
1263      * Finds all assignment notifications that have yet to be mailed out, and mails them.
1264      *
1265      * Cron function to be run periodically according to the moodle cron
1266      *
1267      * @return bool
1268      */
1269     static function cron() {
1270         global $DB;
1272         // only ever send a max of one days worth of updates
1273         $yesterday = time() - (24 * 3600);
1274         $timenow   = time();
1276         // Collect all submissions from the past 24 hours that require mailing.
1277         $sql = "SELECT s.*, a.course, a.name, a.blindmarking, a.revealidentities,
1278                        g.*, g.id as gradeid, g.timemodified as lastmodified
1279                  FROM {assign} a
1280                  JOIN {assign_grades} g ON g.assignment = a.id
1281             LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
1282                 WHERE g.timemodified >= :yesterday AND
1283                       g.timemodified <= :today AND
1284                       g.mailed = 0";
1285         $params = array('yesterday' => $yesterday, 'today' => $timenow);
1286         $submissions = $DB->get_records_sql($sql, $params);
1288         if (empty($submissions)) {
1289             mtrace('done.');
1290             return true;
1291         }
1293         mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1295         // Preload courses we are going to need those.
1296         $courseids = array();
1297         foreach ($submissions as $submission) {
1298             $courseids[] = $submission->course;
1299         }
1300         // Filter out duplicates
1301         $courseids = array_unique($courseids);
1302         $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1303         list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1304         $sql = "SELECT c.*, {$ctxselect}
1305                   FROM {course} c
1306              LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1307                  WHERE c.id {$courseidsql}";
1308         $params['contextlevel'] = CONTEXT_COURSE;
1309         $courses = $DB->get_records_sql($sql, $params);
1310         // Clean up... this could go on for a while.
1311         unset($courseids);
1312         unset($ctxselect);
1313         unset($courseidsql);
1314         unset($params);
1316         // Simple array we'll use for caching modules.
1317         $modcache = array();
1319         // Message students about new feedback
1320         foreach ($submissions as $submission) {
1322             mtrace("Processing assignment submission $submission->id ...");
1324             // do not cache user lookups - could be too many
1325             if (!$user = $DB->get_record("user", array("id"=>$submission->userid))) {
1326                 mtrace("Could not find user $submission->userid");
1327                 continue;
1328             }
1330             // use a cache to prevent the same DB queries happening over and over
1331             if (!array_key_exists($submission->course, $courses)) {
1332                 mtrace("Could not find course $submission->course");
1333                 continue;
1334             }
1335             $course = $courses[$submission->course];
1336             if (isset($course->ctxid)) {
1337                 // Context has not yet been preloaded. Do so now.
1338                 context_helper::preload_from_record($course);
1339             }
1341             // Override the language and timezone of the "current" user, so that
1342             // mail is customised for the receiver.
1343             cron_setup_user($user, $course);
1345             // context lookups are already cached
1346             $coursecontext = context_course::instance($course->id);
1347             if (!is_enrolled($coursecontext, $user->id)) {
1348                 $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
1349                 mtrace(fullname($user)." not an active participant in " . $courseshortname);
1350                 continue;
1351             }
1353             if (!$grader = $DB->get_record("user", array("id"=>$submission->grader))) {
1354                 mtrace("Could not find grader $submission->grader");
1355                 continue;
1356             }
1358             if (!array_key_exists($submission->assignment, $modcache)) {
1359                 if (! $mod = get_coursemodule_from_instance("assign", $submission->assignment, $course->id)) {
1360                     mtrace("Could not find course module for assignment id $submission->assignment");
1361                     continue;
1362                 }
1363                 $modcache[$submission->assignment] = $mod;
1364             } else {
1365                 $mod = $modcache[$submission->assignment];
1366             }
1367             // context lookups are already cached
1368             $contextmodule = context_module::instance($mod->id);
1370             if (!$mod->visible) {
1371                 // Hold mail notification for hidden assignments until later
1372                 continue;
1373             }
1375             // need to send this to the student
1376             $messagetype = 'feedbackavailable';
1377             $eventtype = 'assign_notification';
1378             $updatetime = $submission->lastmodified;
1379             $modulename = get_string('modulename', 'assign');
1381             $uniqueid = 0;
1382             if ($submission->blindmarking && !$submission->revealidentities) {
1383                 $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1384             }
1385             self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime,
1386                                                $mod, $contextmodule, $course, $modulename, $submission->name,
1387                                                $submission->blindmarking && !$submission->revealidentities,
1388                                                $uniqueid);
1390             $grade = new stdClass();
1391             $grade->id = $submission->gradeid;
1392             $grade->mailed = 1;
1393             $DB->update_record('assign_grades', $grade);
1395             mtrace('Done');
1396         }
1397         mtrace('Done processing ' . count($submissions) . ' assignment submissions');
1399         cron_setup_user();
1401         // Free up memory just to be sure
1402         unset($courses);
1403         unset($modcache);
1405         return true;
1406     }
1408     /**
1409      * Update a grade in the grade table for the assignment and in the gradebook
1410      *
1411      * @param stdClass $grade a grade record keyed on id
1412      * @return bool true for success
1413      */
1414     public function update_grade($grade) {
1415         global $DB;
1417         $grade->timemodified = time();
1419         if ($grade->grade && $grade->grade != -1) {
1420             if ($this->get_instance()->grade > 0) {
1421                 if (!is_numeric($grade->grade)) {
1422                     return false;
1423                 } else if ($grade->grade > $this->get_instance()->grade) {
1424                     return false;
1425                 } else if ($grade->grade < 0) {
1426                     return false;
1427                 }
1428             } else {
1429                 // this is a scale
1430                 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1431                     $scaleoptions = make_menu_from_list($scale->scale);
1432                     if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1433                         return false;
1434                     }
1435                 }
1436             }
1437         }
1439         $result = $DB->update_record('assign_grades', $grade);
1440         if ($result) {
1441             $this->gradebook_item_update(null, $grade);
1442         }
1443         return $result;
1444     }
1446     /**
1447      * View the grant extension date page
1448      *
1449      * Uses url parameters 'userid'
1450      * or from parameter 'selectedusers'
1451      * @param moodleform $mform - Used for validation of the submitted data
1452      * @return string
1453      */
1454     private function view_grant_extension($mform) {
1455         global $DB, $CFG;
1456         require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1458         $o = '';
1459         $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
1460         $data = new stdClass();
1461         $data->extensionduedate = null;
1462         $userid = 0;
1463         if (!$batchusers) {
1464             $userid = required_param('userid', PARAM_INT);
1466             $grade = $this->get_user_grade($userid, false);
1468             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
1470             if ($grade) {
1471                 $data->extensionduedate = $grade->extensionduedate;
1472             }
1473             $data->userid = $userid;
1474         } else {
1475             $data->batchusers = $batchusers;
1476         }
1477         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1478                                                       $this->get_context(),
1479                                                       $this->show_intro(),
1480                                                       $this->get_course_module()->id,
1481                                                       get_string('grantextension', 'assign')));
1483         if (!$mform) {
1484             $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
1485                                                                $userid,
1486                                                                $batchusers,
1487                                                                $this->get_instance(),
1488                                                                $data));
1489         }
1490         $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
1491         $o .= $this->view_footer();
1492         return $o;
1493     }
1495     /**
1496      * Get a list of the users in the same group as this user
1497      *
1498      * @param int $groupid The id of the group whose members we want or 0 for the default group
1499      * @param bool $onlyids Whether to retrieve only the user id's
1500      * @return array The users (possibly id's only)
1501      */
1502     public function get_submission_group_members($groupid, $onlyids) {
1503         $members = array();
1504         if ($groupid != 0) {
1505             if ($onlyids) {
1506                 $allusers = groups_get_members($groupid, 'u.id');
1507             } else {
1508                 $allusers = groups_get_members($groupid);
1509             }
1510             foreach ($allusers as $user) {
1511                 if ($this->get_submission_group($user->id)) {
1512                     $members[] = $user;
1513                 }
1514             }
1515         } else {
1516             $allusers = $this->list_participants(null, $onlyids);
1517             foreach ($allusers as $user) {
1518                 if ($this->get_submission_group($user->id) == null) {
1519                     $members[] = $user;
1520                 }
1521             }
1522         }
1523         return $members;
1524     }
1526     /**
1527      * Get a list of the users in the same group as this user that have not submitted the assignment
1528      *
1529      * @param int $groupid The id of the group whose members we want or 0 for the default group
1530      * @param bool $onlyids Whether to retrieve only the user id's
1531      * @return array The users (possibly id's only)
1532      */
1533     public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
1534         if (!$this->get_instance()->teamsubmission || !$this->get_instance()->requireallteammemberssubmit) {
1535             return array();
1536         }
1537         $members = $this->get_submission_group_members($groupid, $onlyids);
1539         foreach ($members as $id => $member) {
1540             $submission = $this->get_user_submission($member->id, false);
1541             if ($submission && $submission->status != ASSIGN_SUBMISSION_STATUS_DRAFT) {
1542                 unset($members[$id]);
1543             } else {
1544                 if ($this->is_blind_marking()) {
1545                     $members[$id]->alias = get_string('hiddenuser', 'assign') . $this->get_uniqueid_for_user($id);
1546                 }
1547             }
1548         }
1549         return $members;
1550     }
1552     /**
1553      * Load the group submission object for a particular user, optionally creating it if required
1554      *
1555      * This will create the user submission and the group submission if required
1556      *
1557      * @param int $userid The id of the user whose submission we want
1558      * @param int $groupid The id of the group for this user - may be 0 in which case it is determined from the userid
1559      * @param bool $create If set to true a new submission object will be created in the database
1560      * @return stdClass The submission
1561      */
1562     public function get_group_submission($userid, $groupid, $create) {
1563         global $DB;
1565         if ($groupid == 0) {
1566             $group = $this->get_submission_group($userid);
1567             if ($group) {
1568                 $groupid = $group->id;
1569             }
1570         }
1572         if ($create) {
1573             // Make sure there is a submission for this user.
1574             $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>0, 'userid'=>$userid);
1575             $submission = $DB->get_record('assign_submission', $params);
1577             if (!$submission) {
1578                 $submission = new stdClass();
1579                 $submission->assignment   = $this->get_instance()->id;
1580                 $submission->userid       = $userid;
1581                 $submission->groupid      = 0;
1582                 $submission->timecreated  = time();
1583                 $submission->timemodified = $submission->timecreated;
1585                 if ($this->get_instance()->submissiondrafts) {
1586                     $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1587                 } else {
1588                     $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1589                 }
1590                 $DB->insert_record('assign_submission', $submission);
1591             }
1592         }
1593         // Now get the group submission.
1594         $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
1595         $submission = $DB->get_record('assign_submission', $params);
1597         if ($submission) {
1598             return $submission;
1599         }
1600         if ($create) {
1601             $submission = new stdClass();
1602             $submission->assignment   = $this->get_instance()->id;
1603             $submission->userid       = 0;
1604             $submission->groupid       = $groupid;
1605             $submission->timecreated = time();
1606             $submission->timemodified = $submission->timecreated;
1608             if ($this->get_instance()->submissiondrafts) {
1609                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1610             } else {
1611                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1612             }
1613             $sid = $DB->insert_record('assign_submission', $submission);
1614             $submission->id = $sid;
1615             return $submission;
1616         }
1617         return false;
1618     }
1620     /**
1621      * View a page rendered by a plugin
1622      *
1623      * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'
1624      *
1625      * @return string
1626      */
1627     private function view_plugin_page() {
1628         global $USER;
1630         $o = '';
1632         $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
1633         $plugintype = required_param('plugin', PARAM_TEXT);
1634         $pluginaction = required_param('pluginaction', PARAM_ALPHA);
1636         $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
1637         if (!$plugin) {
1638             print_error('invalidformdata', '');
1639             return;
1640         }
1642         $o .= $plugin->view_page($pluginaction);
1644         return $o;
1645     }
1648     /**
1649      * This is used for team assignments to get the group for the specified user.
1650      * If the user is a member of multiple or no groups this will return false
1651      *
1652      * @param int $userid The id of the user whose submission we want
1653      * @return mixed The group or false
1654      */
1655     public function get_submission_group($userid) {
1656         $groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_instance()->teamsubmissiongroupingid);
1657         if (count($groups) != 1) {
1658             return false;
1659         }
1660         return array_pop($groups);
1661     }
1664     /**
1665      * display the submission that is used by a plugin
1666      * Uses url parameters 'sid', 'gid' and 'plugin'
1667      * @param string $pluginsubtype
1668      * @return string
1669      */
1670     private function view_plugin_content($pluginsubtype) {
1671         global $USER;
1673         $o = '';
1675         $submissionid = optional_param('sid', 0, PARAM_INT);
1676         $gradeid = optional_param('gid', 0, PARAM_INT);
1677         $plugintype = required_param('plugin', PARAM_TEXT);
1678         $item = null;
1679         if ($pluginsubtype == 'assignsubmission') {
1680             $plugin = $this->get_submission_plugin_by_type($plugintype);
1681             if ($submissionid <= 0) {
1682                 throw new coding_exception('Submission id should not be 0');
1683             }
1684             $item = $this->get_submission($submissionid);
1686             // permissions
1687             if ($item->userid != $USER->id) {
1688                 require_capability('mod/assign:grade', $this->context);
1689             }
1690             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1691                                                               $this->get_context(),
1692                                                               $this->show_intro(),
1693                                                               $this->get_course_module()->id,
1694                                                               $plugin->get_name()));
1695             $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
1696                                                               $item,
1697                                                               assign_submission_plugin_submission::FULL,
1698                                                               $this->get_course_module()->id,
1699                                                               $this->get_return_action(),
1700                                                               $this->get_return_params()));
1702             $this->add_to_log('view submission', get_string('viewsubmissionforuser', 'assign', $item->userid));
1703         } else {
1704             $plugin = $this->get_feedback_plugin_by_type($plugintype);
1705             if ($gradeid <= 0) {
1706                 throw new coding_exception('Grade id should not be 0');
1707             }
1708             $item = $this->get_grade($gradeid);
1709             // permissions
1710             if ($item->userid != $USER->id) {
1711                 require_capability('mod/assign:grade', $this->context);
1712             }
1713             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1714                                                               $this->get_context(),
1715                                                               $this->show_intro(),
1716                                                               $this->get_course_module()->id,
1717                                                               $plugin->get_name()));
1718             $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
1719                                                               $item,
1720                                                               assign_feedback_plugin_feedback::FULL,
1721                                                               $this->get_course_module()->id,
1722                                                               $this->get_return_action(),
1723                                                               $this->get_return_params()));
1724             $this->add_to_log('view feedback', get_string('viewfeedbackforuser', 'assign', $item->userid));
1725         }
1728         $o .= $this->view_return_links();
1730         $o .= $this->view_footer();
1731         return $o;
1732     }
1734     /**
1735      * render the content in editor that is often used by plugin
1736      *
1737      * @param string $filearea
1738      * @param int  $submissionid
1739      * @param string $plugintype
1740      * @param string $editor
1741      * @param string $component
1742      * @return string
1743      */
1744     public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
1745         global $CFG;
1747         $result = '';
1749         $plugin = $this->get_submission_plugin_by_type($plugintype);
1751         $text = $plugin->get_editor_text($editor, $submissionid);
1752         $format = $plugin->get_editor_format($editor, $submissionid);
1754         $finaltext = file_rewrite_pluginfile_urls($text, 'pluginfile.php', $this->get_context()->id, $component, $filearea, $submissionid);
1755         $result .= format_text($finaltext, $format, array('overflowdiv' => true, 'context' => $this->get_context()));
1759         if ($CFG->enableportfolios) {
1760             require_once($CFG->libdir . '/portfoliolib.php');
1762             $button = new portfolio_add_button();
1763             $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id,
1764                                           'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea),
1765                                           'mod_assign');
1766             $fs = get_file_storage();
1768             if ($files = $fs->get_area_files($this->context->id, $component,$filearea, $submissionid, "timemodified", false)) {
1769                 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
1770             } else {
1771                 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
1772             }
1773             $result .= $button->to_html();
1774         }
1775         return $result;
1776     }
1778     /**
1779      * Display a grading error
1780      *
1781      * @param string $message - The description of the result
1782      * @return string
1783      */
1784     private function view_quickgrading_result($message) {
1785         $o = '';
1786         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1787                                                       $this->get_context(),
1788                                                       $this->show_intro(),
1789                                                       $this->get_course_module()->id,
1790                                                       get_string('quickgradingresult', 'assign')));
1791         $o .= $this->get_renderer()->render(new assign_quickgrading_result($message, $this->get_course_module()->id));
1792         $o .= $this->view_footer();
1793         return $o;
1794     }
1796     /**
1797      * Display the page footer
1798      *
1799      * @return string
1800      */
1801     private function view_footer() {
1802         return $this->get_renderer()->render_footer();
1803     }
1805     /**
1806      * Does this user have grade permission for this assignment
1807      *
1808      * @return bool
1809      */
1810     private function can_grade() {
1811         // Permissions check
1812         if (!has_capability('mod/assign:grade', $this->context)) {
1813             return false;
1814         }
1816         return true;
1817     }
1819     /**
1820      * Download a zip file of all assignment submissions
1821      *
1822      * @return void
1823      */
1824     private function download_submissions() {
1825         global $CFG,$DB;
1827         // More efficient to load this here.
1828         require_once($CFG->libdir.'/filelib.php');
1830         // Load all users with submit.
1831         $students = get_enrolled_users($this->context, "mod/assign:submit");
1833         // Build a list of files to zip.
1834         $filesforzipping = array();
1835         $fs = get_file_storage();
1837         $groupmode = groups_get_activity_groupmode($this->get_course_module());
1838         // All users.
1839         $groupid = 0;
1840         $groupname = '';
1841         if ($groupmode) {
1842             $groupid = groups_get_activity_group($this->get_course_module(), true);
1843             $groupname = groups_get_group_name($groupid).'-';
1844         }
1846         // Construct the zip file name.
1847         $filename = clean_filename($this->get_course()->shortname.'-'.
1848                                    $this->get_instance()->name.'-'.
1849                                    $groupname.$this->get_course_module()->id.".zip");
1851         // Get all the files for each student.
1852         foreach ($students as $student) {
1853             $userid = $student->id;
1855             if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
1856                 // Get the plugins to add their own files to the zip.
1858                 $submissiongroup = false;
1859                 $groupname = '';
1860                 if ($this->get_instance()->teamsubmission) {
1861                     $submission = $this->get_group_submission($userid, 0, false);
1862                     $submissiongroup = $this->get_submission_group($userid);
1863                     if ($submissiongroup) {
1864                         $groupname = $submissiongroup->name . '-';
1865                     } else {
1866                         $groupname = get_string('defaultteam', 'assign') . '-';
1867                     }
1868                 } else {
1869                     $submission = $this->get_user_submission($userid, false);
1870                 }
1872                 if ($this->is_blind_marking()) {
1873                     $prefix = clean_filename(str_replace('_', ' ', $groupname . get_string('participant', 'assign')) .
1874                                              "_" . $this->get_uniqueid_for_user($userid) . "_");
1875                 } else {
1876                     $prefix = clean_filename(str_replace('_', ' ', $groupname . fullname($student)) .
1877                                              "_" . $this->get_uniqueid_for_user($userid) . "_");
1878                 }
1880                 if ($submission) {
1881                     foreach ($this->submissionplugins as $plugin) {
1882                         if ($plugin->is_enabled() && $plugin->is_visible()) {
1883                             $pluginfiles = $plugin->get_files($submission);
1884                             foreach ($pluginfiles as $zipfilename => $file) {
1885                                 $subtype = $plugin->get_subtype();
1886                                 $type = $plugin->get_type();
1887                                 $prefixedfilename = $prefix . $subtype . '_' . $type . '_' . $zipfilename;
1888                                 $filesforzipping[$prefixedfilename] = $file;
1889                             }
1890                         }
1891                     }
1892                 }
1893             }
1894         }
1895         if ($zipfile = $this->pack_files($filesforzipping)) {
1896             $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
1897             // Send file and delete after sending.
1898             send_temp_file($zipfile, $filename);
1899         }
1900     }
1902     /**
1903      * Util function to add a message to the log
1904      *
1905      * @param string $action The current action
1906      * @param string $info A detailed description of the change. But no more than 255 characters.
1907      * @param string $url The url to the assign module instance.
1908      * @return void
1909      */
1910     public function add_to_log($action = '', $info = '', $url='') {
1911         global $USER;
1913         $fullurl = 'view.php?id=' . $this->get_course_module()->id;
1914         if ($url != '') {
1915             $fullurl .= '&' . $url;
1916         }
1918         add_to_log($this->get_course()->id, 'assign', $action, $fullurl, $info, $this->get_course_module()->id, $USER->id);
1919     }
1921     /**
1922      * Lazy load the page renderer and expose the renderer to plugins
1923      *
1924      * @return assign_renderer
1925      */
1926     public function get_renderer() {
1927         global $PAGE;
1928         if ($this->output) {
1929             return $this->output;
1930         }
1931         $this->output = $PAGE->get_renderer('mod_assign');
1932         return $this->output;
1933     }
1935     /**
1936      * Load the submission object for a particular user, optionally creating it if required
1937      *
1938      * For team assignments there are 2 submissions - the student submission and the team submission
1939      * All files are associated with the team submission but the status of the students contribution is
1940      * recorded separately.
1941      *
1942      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
1943      * @param bool $create optional Defaults to false. If set to true a new submission object will be created in the database
1944      * @return stdClass The submission
1945      */
1946     public function get_user_submission($userid, $create) {
1947         global $DB, $USER;
1949         if (!$userid) {
1950             $userid = $USER->id;
1951         }
1952         // If the userid is not null then use userid.
1953         $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
1954         $submission = $DB->get_record('assign_submission', $params);
1956         if ($submission) {
1957             return $submission;
1958         }
1959         if ($create) {
1960             $submission = new stdClass();
1961             $submission->assignment   = $this->get_instance()->id;
1962             $submission->userid       = $userid;
1963             $submission->timecreated = time();
1964             $submission->timemodified = $submission->timecreated;
1966             if ($this->get_instance()->submissiondrafts) {
1967                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1968             } else {
1969                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1970             }
1971             $sid = $DB->insert_record('assign_submission', $submission);
1972             $submission->id = $sid;
1973             return $submission;
1974         }
1975         return false;
1976     }
1978     /**
1979      * Load the submission object from it's id
1980      *
1981      * @param int $submissionid The id of the submission we want
1982      * @return stdClass The submission
1983      */
1984     private function get_submission($submissionid) {
1985         global $DB;
1987         return $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid), '*', MUST_EXIST);
1988     }
1990     /**
1991      * This will retrieve a grade object from the db, optionally creating it if required
1992      *
1993      * @param int $userid The user we are grading
1994      * @param bool $create If true the grade will be created if it does not exist
1995      * @return stdClass The grade record
1996      */
1997     public function get_user_grade($userid, $create) {
1998         global $DB, $USER;
2000         if (!$userid) {
2001             $userid = $USER->id;
2002         }
2004         // if the userid is not null then use userid
2005         $grade = $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
2007         if ($grade) {
2008             return $grade;
2009         }
2010         if ($create) {
2011             $grade = new stdClass();
2012             $grade->assignment   = $this->get_instance()->id;
2013             $grade->userid       = $userid;
2014             $grade->timecreated = time();
2015             $grade->timemodified = $grade->timecreated;
2016             $grade->locked = 0;
2017             $grade->grade = -1;
2018             $grade->grader = $USER->id;
2019             $grade->extensionduedate = 0;
2020             $gid = $DB->insert_record('assign_grades', $grade);
2021             $grade->id = $gid;
2022             return $grade;
2023         }
2024         return false;
2025     }
2027     /**
2028      * This will retrieve a grade object from the db
2029      *
2030      * @param int $gradeid The id of the grade
2031      * @return stdClass The grade record
2032      */
2033     private function get_grade($gradeid) {
2034         global $DB;
2036         return $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid), '*', MUST_EXIST);
2037     }
2039     /**
2040      * Print the grading page for a single user submission
2041      *
2042      * @param moodleform $mform
2043      * @param int $offset
2044      * @return string
2045      */
2046     private function view_single_grade_page($mform, $offset=0) {
2047         global $DB, $CFG;
2049         $o = '';
2051         // Include grade form
2052         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2054         // Need submit permission to submit an assignment
2055         require_capability('mod/assign:grade', $this->context);
2057         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2058                                                       $this->get_context(), false, $this->get_course_module()->id,get_string('grading', 'assign')));
2060         $rownum = required_param('rownum', PARAM_INT) + $offset;
2061         $useridlist = optional_param('useridlist', '', PARAM_TEXT);
2062         if ($useridlist) {
2063             $useridlist = explode(',', $useridlist);
2064         } else {
2065             $useridlist = $this->get_grading_userid_list();
2066         }
2067         $last = false;
2068         $userid = $useridlist[$rownum];
2069         if ($rownum == count($useridlist) - 1) {
2070             $last = true;
2071         }
2072         if (!$userid) {
2073             throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
2074         }
2075         $user = $DB->get_record('user', array('id' => $userid));
2076         if ($user) {
2077             $o .= $this->get_renderer()->render(new assign_user_summary($user,
2078                                                                 $this->get_course()->id,
2079                                                                 has_capability('moodle/site:viewfullnames',
2080                                                                 $this->get_course_context()),
2081                                                                 $this->is_blind_marking(),
2082                                                                 $this->get_uniqueid_for_user($user->id)));
2083         }
2084         $submission = $this->get_user_submission($userid, false);
2085         $submissiongroup = null;
2086         $submissiongroupmemberswhohavenotsubmitted = array();
2087         $teamsubmission = null;
2088         $notsubmitted = array();
2089         if ($this->get_instance()->teamsubmission) {
2090             $teamsubmission = $this->get_group_submission($userid, 0, false);
2091             $submissiongroup = $this->get_submission_group($userid);
2092             $groupid = 0;
2093             if ($submissiongroup) {
2094                 $groupid = $submissiongroup->id;
2095             }
2096             $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2098         }
2100         // get the current grade
2101         $grade = $this->get_user_grade($userid, false);
2102         if ($this->can_view_submission($userid)) {
2103             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
2104             $extensionduedate = null;
2105             if ($grade) {
2106                 $extensionduedate = $grade->extensionduedate;
2107             }
2108             $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
2110             if ($teamsubmission) {
2111                 $showsubmit = $showedit && $teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
2112             } else {
2113                 $showsubmit = $showedit && $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
2114             }
2115             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2117             $o .= $this->get_renderer()->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
2118                                                               $this->get_instance()->alwaysshowdescription,
2119                                                               $submission,
2120                                                               $this->get_instance()->teamsubmission,
2121                                                               $teamsubmission,
2122                                                               $submissiongroup,
2123                                                               $notsubmitted,
2124                                                               $this->is_any_submission_plugin_enabled(),
2125                                                               $gradelocked,
2126                                                               $this->is_graded($userid),
2127                                                               $this->get_instance()->duedate,
2128                                                               $this->get_instance()->cutoffdate,
2129                                                               $this->get_submission_plugins(),
2130                                                               $this->get_return_action(),
2131                                                               $this->get_return_params(),
2132                                                               $this->get_course_module()->id,
2133                                                               $this->get_course()->id,
2134                                                               assign_submission_status::GRADER_VIEW,
2135                                                               $showedit,
2136                                                               $showsubmit,
2137                                                               $viewfullnames,
2138                                                               $extensionduedate,
2139                                                               $this->get_context(),
2140                                                               $this->is_blind_marking(),
2141                                                               ''));
2142         }
2143         if ($grade) {
2144             $data = new stdClass();
2145             if ($grade->grade !== NULL && $grade->grade >= 0) {
2146                 $data->grade = format_float($grade->grade,2);
2147             }
2148         } else {
2149             $data = new stdClass();
2150             $data->grade = '';
2151         }
2153         // now show the grading form
2154         if (!$mform) {
2155             $pagination = array( 'rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last);
2156             $formparams = array($this, $data, $pagination);
2157             $mform = new mod_assign_grade_form(null,
2158                                                $formparams,
2159                                                'post',
2160                                                '',
2161                                                array('class'=>'gradeform'));
2162         }
2163         $o .= $this->get_renderer()->render(new assign_form('gradingform',$mform));
2165         $msg = get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
2166         $this->add_to_log('view grading form', $msg);
2168         $o .= $this->view_footer();
2169         return $o;
2170     }
2172     /**
2173      * Show a confirmation page to make sure they want to release student identities
2174      *
2175      * @return string
2176      */
2177     private function view_reveal_identities_confirm() {
2178         global $CFG, $USER;
2180         require_capability('mod/assign:revealidentities', $this->get_context());
2182         $o = '';
2183         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2184                                                       $this->get_context(), false, $this->get_course_module()->id));
2186         $confirmurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2187                                                                     'action'=>'revealidentitiesconfirm',
2188                                                                     'sesskey'=>sesskey()));
2190         $cancelurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2191                                                                     'action'=>'grading'));
2193         $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'), $confirmurl, $cancelurl);
2194         $o .= $this->view_footer();
2195         $this->add_to_log('view', get_string('viewrevealidentitiesconfirm', 'assign'));
2196         return $o;
2197     }
2202     /**
2203      * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
2204      *
2205      * @return string
2206      */
2207     private function view_return_links() {
2209         $returnaction = optional_param('returnaction','', PARAM_ALPHA);
2210         $returnparams = optional_param('returnparams','', PARAM_TEXT);
2212         $params = array();
2213         parse_str($returnparams, $params);
2214         $params = array_merge( array('id' => $this->get_course_module()->id, 'action' => $returnaction), $params);
2216         return $this->get_renderer()->single_button(new moodle_url('/mod/assign/view.php', $params), get_string('back'), 'get');
2218     }
2220     /**
2221      * View the grading table of all submissions for this assignment
2222      *
2223      * @return string
2224      */
2225     private function view_grading_table() {
2226         global $USER, $CFG;
2227         // Include grading options form
2228         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
2229         require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
2230         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2231         $o = '';
2233         $links = array();
2234         if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
2235                 has_capability('moodle/grade:viewall', $this->get_course_context())) {
2236             $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
2237             $links[$gradebookurl] = get_string('viewgradebook', 'assign');
2238         }
2239         if ($this->is_any_submission_plugin_enabled()) {
2240             $downloadurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=downloadall';
2241             $links[$downloadurl] = get_string('downloadall', 'assign');
2242         }
2243         if ($this->is_blind_marking() && has_capability('mod/assign:revealidentities', $this->get_context())) {
2244             $revealidentitiesurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=revealidentities';
2245             $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
2246         }
2247         foreach ($this->get_feedback_plugins() as $plugin) {
2248             if ($plugin->is_enabled() && $plugin->is_visible()) {
2249                 foreach ($plugin->get_grading_actions() as $action => $description) {
2250                     $url = '/mod/assign/view.php' .
2251                            '?id=' .  $this->get_course_module()->id .
2252                            '&plugin=' . $plugin->get_type() .
2253                            '&pluginsubtype=assignfeedback' .
2254                            '&action=viewpluginpage&pluginaction=' . $action;
2255                     $links[$url] = $description;
2256                 }
2257             }
2258         }
2260         $gradingactions = new url_select($links);
2262         $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
2264         $perpage = get_user_preferences('assign_perpage', 10);
2265         $filter = get_user_preferences('assign_filter', '');
2266         $controller = $gradingmanager->get_active_controller();
2267         $showquickgrading = empty($controller);
2268         if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
2269             $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
2270             set_user_preference('assign_quickgrading', $quickgrading);
2271         }
2272         $quickgrading = get_user_preferences('assign_quickgrading', false);
2274         // print options  for changing the filter and changing the number of results per page
2275         $gradingoptionsform = new mod_assign_grading_options_form(null,
2276                                                                   array('cm'=>$this->get_course_module()->id,
2277                                                                         'contextid'=>$this->context->id,
2278                                                                         'userid'=>$USER->id,
2279                                                                         'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
2280                                                                         'showquickgrading'=>$showquickgrading,
2281                                                                         'quickgrading'=>$quickgrading),
2282                                                                   'post', '',
2283                                                                   array('class'=>'gradingoptionsform'));
2285         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
2286                                                                   array('cm'=>$this->get_course_module()->id,
2287                                                                         'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2288                                                                         'duedate'=>$this->get_instance()->duedate,
2289                                                                         'feedbackplugins'=>$this->get_feedback_plugins()),
2290                                                                   'post', '',
2291                                                                   array('class'=>'gradingbatchoperationsform'));
2293         $gradingoptionsdata = new stdClass();
2294         $gradingoptionsdata->perpage = $perpage;
2295         $gradingoptionsdata->filter = $filter;
2296         $gradingoptionsform->set_data($gradingoptionsdata);
2298         $actionformtext = $this->get_renderer()->render($gradingactions);
2299         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2300                                                       $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
2301         $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
2303         // plagiarism update status apearring in the grading book
2304         if (!empty($CFG->enableplagiarism)) {
2305             /** Include plagiarismlib.php */
2306             require_once($CFG->libdir . '/plagiarismlib.php');
2307             $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
2308         }
2310         // load and print the table of submissions
2311         if ($showquickgrading && $quickgrading) {
2312             $table = $this->get_renderer()->render(new assign_grading_table($this, $perpage, $filter, 0, true));
2313             $quickgradingform = new mod_assign_quick_grading_form(null,
2314                                                                   array('cm'=>$this->get_course_module()->id,
2315                                                                         'gradingtable'=>$table));
2316             $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
2317         } else {
2318             $o .= $this->get_renderer()->render(new assign_grading_table($this, $perpage, $filter, 0, false));
2319         }
2321         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
2322         $users = array_keys($this->list_participants($currentgroup, true));
2323         if (count($users) != 0) {
2324             // if no enrolled user in a course then don't display the batch operations feature
2325             $o .= $this->get_renderer()->render(new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform));
2326         }
2327         $o .= $this->get_renderer()->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
2328         return $o;
2329     }
2331     /**
2332      * View entire grading page.
2333      *
2334      * @return string
2335      */
2336     private function view_grading_page() {
2337         global $CFG;
2339         $o = '';
2340         // Need submit permission to submit an assignment
2341         require_capability('mod/assign:grade', $this->context);
2342         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2344         // only load this if it is
2346         $o .= $this->view_grading_table();
2348         $o .= $this->view_footer();
2349         $this->add_to_log('view submission grading table', get_string('viewsubmissiongradingtable', 'assign'));
2350         return $o;
2351     }
2353     /**
2354      * Capture the output of the plagiarism plugins disclosures and return it as a string
2355      *
2356      * @return void
2357      */
2358     private function plagiarism_print_disclosure() {
2359         global $CFG;
2360         $o = '';
2362         if (!empty($CFG->enableplagiarism)) {
2363             /** Include plagiarismlib.php */
2364             require_once($CFG->libdir . '/plagiarismlib.php');
2366             $o .= plagiarism_print_disclosure($this->get_course_module()->id);
2367         }
2369         return $o;
2370     }
2372     /**
2373      * message for students when assignment submissions have been closed
2374      *
2375      * @return string
2376      */
2377     private function view_student_error_message() {
2378         global $CFG;
2380         $o = '';
2381         // Need submit permission to submit an assignment
2382         require_capability('mod/assign:submit', $this->context);
2384         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2385                                                       $this->get_context(),
2386                                                       $this->show_intro(),
2387                                                       $this->get_course_module()->id,
2388                                                       get_string('editsubmission', 'assign')));
2390         $o .= $this->get_renderer()->notification(get_string('submissionsclosed', 'assign'));
2392         $o .= $this->view_footer();
2394         return $o;
2396     }
2398     /**
2399      * View edit submissions page.
2400      *
2401      * @param moodleform $mform
2402      * @return void
2403      */
2404     private function view_edit_submission_page($mform) {
2405         global $CFG;
2407         $o = '';
2408         // Include submission form
2409         require_once($CFG->dirroot . '/mod/assign/submission_form.php');
2410         // Need submit permission to submit an assignment
2411         require_capability('mod/assign:submit', $this->context);
2413         if (!$this->submissions_open()) {
2414             return $this->view_student_error_message();
2415         }
2416         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2417                                                       $this->get_context(),
2418                                                       $this->show_intro(),
2419                                                       $this->get_course_module()->id,
2420                                                       get_string('editsubmission', 'assign')));
2421         $o .= $this->plagiarism_print_disclosure();
2422         $data = new stdClass();
2424         if (!$mform) {
2425             $mform = new mod_assign_submission_form(null, array($this, $data));
2426         }
2428         $o .= $this->get_renderer()->render(new assign_form('editsubmissionform',$mform));
2430         $o .= $this->view_footer();
2431         $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
2433         return $o;
2434     }
2436     /**
2437      * See if this assignment has a grade yet
2438      *
2439      * @param int $userid
2440      * @return bool
2441      */
2442     private function is_graded($userid) {
2443         $grade = $this->get_user_grade($userid, false);
2444         if ($grade) {
2445             return ($grade->grade !== NULL && $grade->grade >= 0);
2446         }
2447         return false;
2448     }
2451     /**
2452      * Perform an access check to see if the current $USER can view this users submission
2453      *
2454      * @param int $userid
2455      * @return bool
2456      */
2457     public function can_view_submission($userid) {
2458         global $USER;
2460         if (!is_enrolled($this->get_course_context(), $userid)) {
2461             return false;
2462         }
2463         if ($userid == $USER->id && !has_capability('mod/assign:submit', $this->context)) {
2464             return false;
2465         }
2466         if ($userid != $USER->id && !has_capability('mod/assign:grade', $this->context)) {
2467             return false;
2468         }
2469         return true;
2470     }
2472     /**
2473      * Allows the plugin to show a batch grading operation page.
2474      *
2475      * @return none
2476      */
2477     private function view_plugin_grading_batch_operation($mform) {
2478         require_capability('mod/assign:grade', $this->context);
2479         $prefix = 'plugingradingbatchoperation_';
2481         if ($data = $mform->get_data()) {
2482             $tail = substr($data->operation, strlen($prefix));
2483             list($plugintype, $action) = explode('_', $tail, 2);
2485             $plugin = $this->get_feedback_plugin_by_type($plugintype);
2486             if ($plugin) {
2487                 $users = $data->selectedusers;
2488                 $userlist = explode(',', $users);
2489                 echo $plugin->grading_batch_operation($action, $userlist);
2490                 return;
2491             }
2492         }
2493         print_error('invalidformdata', '');
2494     }
2496     /**
2497      * Ask the user to confirm they want to perform this batch operation
2498      * @param moodleform $mform Set to a grading batch operations form
2499      * @return string - the page to view after processing these actions
2500      */
2501     private function process_grading_batch_operation(& $mform) {
2502         global $CFG;
2503         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2504         require_sesskey();
2506         $mform = new mod_assign_grading_batch_operations_form(null,
2507                                                               array('cm'=>$this->get_course_module()->id,
2508                                                                     'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2509                                                                     'duedate'=>$this->get_instance()->duedate,
2510                                                                     'feedbackplugins'=>$this->get_feedback_plugins()),
2511                                                               'post',
2512                                                               '',
2513                                                               array('class'=>'gradingbatchoperationsform'));
2515         if ($data = $mform->get_data()) {
2516             // get the list of users
2517             $users = $data->selectedusers;
2518             $userlist = explode(',', $users);
2520             $prefix = 'plugingradingbatchoperation_';
2522             if ($data->operation == 'grantextension') {
2523                 // Reset the form so the grant extension page will create the extension form.
2524                 $mform = null;
2525                 return 'grantextension';
2526             } else if (strpos($data->operation, $prefix) === 0) {
2527                 $tail = substr($data->operation, strlen($prefix));
2528                 list($plugintype, $action) = explode('_', $tail, 2);
2530                 $plugin = $this->get_feedback_plugin_by_type($plugintype);
2531                 if ($plugin) {
2532                     return 'plugingradingbatchoperation';
2533                 }
2534             }
2536             foreach ($userlist as $userid) {
2537                 if ($data->operation == 'lock') {
2538                     $this->process_lock($userid);
2539                 } else if ($data->operation == 'unlock') {
2540                     $this->process_unlock($userid);
2541                 } else if ($data->operation == 'reverttodraft') {
2542                     $this->process_revert_to_draft($userid);
2543                 }
2544             }
2545         }
2547         return 'grading';
2548     }
2550     /**
2551      * Ask the user to confirm they want to submit their work for grading
2552      * @param $mform moodleform - null unless form validation has failed
2553      * @return string
2554      */
2555     private function check_submit_for_grading($mform) {
2556         global $USER, $CFG;
2558         require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
2560         // Check that all of the submission plugins are ready for this submission
2561         $notifications = array();
2562         $submission = $this->get_user_submission($USER->id, false);
2563         $plugins = $this->get_submission_plugins();
2564         foreach ($plugins as $plugin) {
2565             if ($plugin->is_enabled() && $plugin->is_visible()) {
2566                 $check = $plugin->precheck_submission($submission);
2567                 if ($check !== true) {
2568                     $notifications[] = $check;
2569                 }
2570             }
2571         }
2573         $data = new stdClass();
2574         $adminconfig = $this->get_admin_config();
2575         $requiresubmissionstatement = (!empty($adminconfig->requiresubmissionstatement) ||
2576                                        $this->get_instance()->requiresubmissionstatement) &&
2577                                        !empty($adminconfig->submissionstatement);
2579         $submissionstatement = '';
2580         if (!empty($adminconfig->submissionstatement)) {
2581             $submissionstatement = $adminconfig->submissionstatement;
2582         }
2584         if ($mform == null) {
2585             $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
2586                                                                         $submissionstatement,
2587                                                                         $this->get_course_module()->id,
2588                                                                         $data));
2589         }
2590         $o = '';
2591         $o .= $this->get_renderer()->header();
2592         $o .= $this->get_renderer()->render(new assign_submit_for_grading_page($notifications, $this->get_course_module()->id, $mform));
2593         $o .= $this->view_footer();
2595         $this->add_to_log('view confirm submit assignment form', get_string('viewownsubmissionform', 'assign'));
2597         return $o;
2598     }
2600     /**
2601      * Print 2 tables of information with no action links -
2602      * the submission summary and the grading summary
2603      *
2604      * @param stdClass $user the user to print the report for
2605      * @param bool $showlinks - Return plain text or links to the profile
2606      * @return string - the html summary
2607      */
2608     public function view_student_summary($user, $showlinks) {
2609         global $CFG, $DB, $PAGE;
2611         $grade = $this->get_user_grade($user->id, false);
2612         $submission = $this->get_user_submission($user->id, false);
2613         $o = '';
2615         $teamsubmission = null;
2616         $submissiongroup = null;
2617         $notsubmitted = array();
2618         if ($this->get_instance()->teamsubmission) {
2619             $teamsubmission = $this->get_group_submission($user->id, 0, false);
2620             $submissiongroup = $this->get_submission_group($user->id);
2621             $groupid = 0;
2622             if ($submissiongroup) {
2623                 $groupid = $submissiongroup->id;
2624             }
2625             $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2626         }
2628         if ($this->can_view_submission($user->id)) {
2629             $showedit = has_capability('mod/assign:submit', $this->context) &&
2630                          $this->submissions_open($user->id) && ($this->is_any_submission_plugin_enabled()) && $showlinks;
2631             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
2632             // Grading criteria preview.
2633             $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
2634             $gradingcontrollerpreview = '';
2635             if ($gradingmethod = $gradingmanager->get_active_method()) {
2636                 $controller = $gradingmanager->get_controller($gradingmethod);
2637                 if ($controller->is_form_defined()) {
2638                     $gradingcontrollerpreview = $controller->render_preview($PAGE);
2639                 }
2640             }
2642             $showsubmit = ($submission || $teamsubmission) && $showlinks;
2643             if ($teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
2644                 $showsubmit = false;
2645             }
2646             if ($submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
2647                 $showsubmit = false;
2648             }
2649             $extensionduedate = null;
2650             if ($grade) {
2651                 $extensionduedate = $grade->extensionduedate;
2652             }
2653             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2654             $o .= $this->get_renderer()->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
2655                                                               $this->get_instance()->alwaysshowdescription,
2656                                                               $submission,
2657                                                               $this->get_instance()->teamsubmission,
2658                                                               $teamsubmission,
2659                                                               $submissiongroup,
2660                                                               $notsubmitted,
2661                                                               $this->is_any_submission_plugin_enabled(),
2662                                                               $gradelocked,
2663                                                               $this->is_graded($user->id),
2664                                                               $this->get_instance()->duedate,
2665                                                               $this->get_instance()->cutoffdate,
2666                                                               $this->get_submission_plugins(),
2667                                                               $this->get_return_action(),
2668                                                               $this->get_return_params(),
2669                                                               $this->get_course_module()->id,
2670                                                               $this->get_course()->id,
2671                                                               assign_submission_status::STUDENT_VIEW,
2672                                                               $showedit,
2673                                                               $showsubmit,
2674                                                               $viewfullnames,
2675                                                               $extensionduedate,
2676                                                               $this->get_context(),
2677                                                               $this->is_blind_marking(),
2678                                                               $gradingcontrollerpreview));
2680             require_once($CFG->libdir.'/gradelib.php');
2681             require_once($CFG->dirroot.'/grade/grading/lib.php');
2683             $gradinginfo = grade_get_grades($this->get_course()->id,
2684                                         'mod',
2685                                         'assign',
2686                                         $this->get_instance()->id,
2687                                         $user->id);
2689             $gradingitem = $gradinginfo->items[0];
2690             $gradebookgrade = $gradingitem->grades[$user->id];
2692             // check to see if all feedback plugins are empty
2693             $emptyplugins = true;
2694             if ($grade) {
2695                 foreach ($this->get_feedback_plugins() as $plugin) {
2696                     if ($plugin->is_visible() && $plugin->is_enabled()) {
2697                         if (!$plugin->is_empty($grade)) {
2698                             $emptyplugins = false;
2699                         }
2700                     }
2701                 }
2702             }
2705             if (!($gradebookgrade->hidden) && ($gradebookgrade->grade !== null || !$emptyplugins)) {
2707                 $gradefordisplay = '';
2708                 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
2710                 if ($controller = $gradingmanager->get_active_controller()) {
2711                     $controller->set_grade_range(make_grades_menu($this->get_instance()->grade));
2712                     $gradefordisplay = $controller->render_grade($PAGE,
2713                                                                  $grade->id,
2714                                                                  $gradingitem,
2715                                                                  $gradebookgrade->str_long_grade,
2716                                                                  has_capability('mod/assign:grade', $this->get_context()));
2717                 } else {
2718                     $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
2719                 }
2721                 $gradeddate = $gradebookgrade->dategraded;
2722                 $grader = $DB->get_record('user', array('id'=>$gradebookgrade->usermodified));
2724                 $feedbackstatus = new assign_feedback_status($gradefordisplay,
2725                                                       $gradeddate,
2726                                                       $grader,
2727                                                       $this->get_feedback_plugins(),
2728                                                       $grade,
2729                                                       $this->get_course_module()->id,
2730                                                       $this->get_return_action(),
2731                                                       $this->get_return_params());
2733                 $o .= $this->get_renderer()->render($feedbackstatus);
2734             }
2736         }
2737         return $o;
2738     }
2740     /**
2741      * View submissions page (contains details of current submission).
2742      *
2743      * @return string
2744      */
2745     private function view_submission_page() {
2746         global $CFG, $DB, $USER, $PAGE;
2748         $o = '';
2749         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2750                                                       $this->get_context(),
2751                                                       $this->show_intro(),
2752                                                       $this->get_course_module()->id));
2754         if ($this->can_grade()) {
2755             if ($this->get_instance()->teamsubmission) {
2756                 $summary = new assign_grading_summary($this->count_teams(),
2757                                                       $this->get_instance()->submissiondrafts,
2758                                                       $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
2759                                                       $this->is_any_submission_plugin_enabled(),
2760                                                       $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
2761                                                       $this->get_instance()->cutoffdate,
2762                                                       $this->get_instance()->duedate,
2763                                                       $this->get_course_module()->id,
2764                                                       $this->count_submissions_need_grading(),
2765                                                       $this->get_instance()->teamsubmission);
2766                 $o .= $this->get_renderer()->render($summary);
2767             } else {
2768                 $summary = new assign_grading_summary($this->count_participants(0),
2769                                                       $this->get_instance()->submissiondrafts,
2770                                                       $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
2771                                                       $this->is_any_submission_plugin_enabled(),
2772                                                       $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
2773                                                       $this->get_instance()->cutoffdate,
2774                                                       $this->get_instance()->duedate,
2775                                                       $this->get_course_module()->id,
2776                                                       $this->count_submissions_need_grading(),
2777                                                       $this->get_instance()->teamsubmission);
2778                 $o .= $this->get_renderer()->render($summary);
2779             }
2780         }
2781         $grade = $this->get_user_grade($USER->id, false);
2782         $submission = $this->get_user_submission($USER->id, false);
2784         if ($this->can_view_submission($USER->id)) {
2785             $o .= $this->view_student_summary($USER, true);
2786         }
2789         $o .= $this->view_footer();
2790         $this->add_to_log('view', get_string('viewownsubmissionstatus', 'assign'));
2791         return $o;
2792     }
2794     /**
2795      * convert the final raw grade(s) in the  grading table for the gradebook
2796      *
2797      * @param stdClass $grade
2798      * @return array
2799      */
2800     private function convert_grade_for_gradebook(stdClass $grade) {
2801         $gradebookgrade = array();
2802         // trying to match those array keys in grade update function in gradelib.php
2803         // with keys in th database table assign_grades
2804         // starting around line 262
2805         if ($grade->grade >= 0) {
2806             $gradebookgrade['rawgrade'] = $grade->grade;
2807         }
2808         $gradebookgrade['userid'] = $grade->userid;
2809         $gradebookgrade['usermodified'] = $grade->grader;
2810         $gradebookgrade['datesubmitted'] = NULL;
2811         $gradebookgrade['dategraded'] = $grade->timemodified;
2812         if (isset($grade->feedbackformat)) {
2813             $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
2814         }
2815         if (isset($grade->feedbacktext)) {
2816             $gradebookgrade['feedback'] = $grade->feedbacktext;
2817         }
2819         return $gradebookgrade;
2820     }
2822     /**
2823      * convert submission details for the gradebook
2824      *
2825      * @param stdClass $submission
2826      * @return array
2827      */
2828     private function convert_submission_for_gradebook(stdClass $submission) {
2829         $gradebookgrade = array();
2832         $gradebookgrade['userid'] = $submission->userid;
2833         $gradebookgrade['usermodified'] = $submission->userid;
2834         $gradebookgrade['datesubmitted'] = $submission->timemodified;
2836         return $gradebookgrade;
2837     }
2839     /**
2840      * update grades in the gradebook
2841      *
2842      * @param mixed $submission stdClass|null
2843      * @param mixed $grade stdClass|null
2844      * @return bool
2845      */
2846     private function gradebook_item_update($submission=NULL, $grade=NULL) {
2848         // Do not push grade to gradebook if blind marking is active as the gradebook would reveal the students.
2849         if ($this->is_blind_marking()) {
2850             return false;
2851         }
2852         if ($submission != NULL) {
2853             if ($submission->userid == 0) {
2854                 // This is a group submission update.
2855                 $team = groups_get_members($submission->groupid, 'u.id');
2857                 foreach ($team as $member) {
2858                     $submission->groupid = 0;
2859                     $submission->userid = $member->id;
2860                     $this->gradebook_item_update($submission, null);
2861                 }
2862                 return;
2863             }
2865             $gradebookgrade = $this->convert_submission_for_gradebook($submission);
2867         } else {
2868             $gradebookgrade = $this->convert_grade_for_gradebook($grade);
2869         }
2870         // Grading is disabled, return.
2871         if ($this->grading_disabled($gradebookgrade['userid'])) {
2872             return false;
2873         }
2874         $assign = clone $this->get_instance();
2875         $assign->cmidnumber = $this->get_course_module()->id;
2877         return assign_grade_item_update($assign, $gradebookgrade);
2878     }
2880     /**
2881      * update team submission
2882      *
2883      * @param stdClass $submission
2884      * @param int $userid
2885      * @param bool $updatetime
2886      * @return bool
2887      */
2888     private function update_team_submission(stdClass $submission, $userid, $updatetime) {
2889         global $DB;
2891         if ($updatetime) {
2892             $submission->timemodified = time();
2893         }
2895         // First update the submission for the current user.
2896         $mysubmission = $this->get_user_submission($userid, true);
2897         $mysubmission->status = $submission->status;
2899         $this->update_submission($mysubmission, 0, $updatetime, false);
2901         // Now check the team settings to see if this assignment qualifies as submitted or draft.
2902         $team = $this->get_submission_group_members($submission->groupid, true);
2904         $allsubmitted = true;
2905         $anysubmitted = false;
2906         foreach ($team as $member) {
2907             $membersubmission = $this->get_user_submission($member->id, false);
2909             if (!$membersubmission || $membersubmission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2910                 $allsubmitted = false;
2911                 if ($anysubmitted) {
2912                     break;
2913                 }
2914             } else {
2915                 $anysubmitted = true;
2916             }
2917         }
2918         if ($this->get_instance()->requireallteammemberssubmit) {
2919             if ($allsubmitted) {
2920                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2921             } else {
2922                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2923             }
2924             $result= $DB->update_record('assign_submission', $submission);
2925         } else {
2926             if ($anysubmitted) {
2927                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2928             } else {
2929                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2930             }
2931             $result= $DB->update_record('assign_submission', $submission);
2932         }
2934         $this->gradebook_item_update($submission);
2935         return $result;
2936     }
2939     /**
2940      * update grades in the gradebook based on submission time
2941      *
2942      * @param stdClass $submission
2943      * @param int $userid
2944      * @param bool $updatetime
2945      * @param bool $teamsubmission
2946      * @return bool
2947      */
2948     private function update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
2949         global $DB;
2951         if ($teamsubmission) {
2952             return $this->update_team_submission($submission, $userid, $updatetime);
2953         }
2955         if ($updatetime) {
2956             $submission->timemodified = time();
2957         }
2958         $result= $DB->update_record('assign_submission', $submission);
2959         if ($result) {
2960             $this->gradebook_item_update($submission);
2961         }
2962         return $result;
2963     }
2965     /**
2966      * Is this assignment open for submissions?
2967      *
2968      * Check the due date,
2969      * prevent late submissions,
2970      * has this person already submitted,
2971      * is the assignment locked?
2972      *
2973      * @param int $userid - Optional userid so we can see if a different user can submit
2974      * @return bool
2975      */
2976     private function submissions_open($userid = 0) {
2977         global $USER;
2979         if (!$userid) {
2980             $userid = $USER->id;
2981         }
2983         $time = time();
2984         $dateopen = true;
2985         $finaldate = false;
2986         if ($this->get_instance()->cutoffdate) {
2987             $finaldate = $this->get_instance()->cutoffdate;
2988         }
2989         // User extensions.
2990         if ($finaldate) {
2991             $grade = $this->get_user_grade($userid, false);
2992             if ($grade && $grade->extensionduedate) {
2993                 // Extension can be before cut off date.
2994                 if ($grade->extensionduedate > $finaldate) {
2995                     $finaldate = $grade->extensionduedate;
2996                 }
2997             }
2998         }
3000         if ($finaldate) {
3001             $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $finaldate);
3002         } else {
3003             $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
3004         }
3006         if (!$dateopen) {
3007             return false;
3008         }
3010         // Now check if this user has already submitted etc.
3011         if (!is_enrolled($this->get_course_context(), $userid)) {
3012             return false;
3013         }
3014         $submission = false;
3015         if ($this->get_instance()->teamsubmission) {
3016             $submission = $this->get_group_submission($userid, 0, false);
3017         } else {
3018             $submission = $this->get_user_submission($userid, false);
3019         }
3020         if ($submission) {
3022             if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
3023                 // drafts are tracked and the student has submitted the assignment
3024                 return false;
3025             }
3026         }
3027         if ($grade = $this->get_user_grade($userid, false)) {
3028             if ($grade->locked) {
3029                 return false;
3030             }
3031         }
3033         if ($this->grading_disabled($userid)) {
3034             return false;
3035         }
3037         return true;
3038     }
3040     /**
3041      * render the files in file area
3042      * @param string $component
3043      * @param string $area
3044      * @param int $submissionid
3045      * @return string
3046      */
3047     public function render_area_files($component, $area, $submissionid) {
3048         global $USER;
3050         $fs = get_file_storage();
3051         $browser = get_file_browser();
3052         $files = $fs->get_area_files($this->get_context()->id, $component, $area , $submissionid , "timemodified", false);
3053         return $this->get_renderer()->assign_files($this->context, $submissionid, $area, $component);
3055     }
3057     /**
3058      * Returns a list of teachers that should be grading given submission
3059      *
3060      * @param int $userid
3061      * @return array
3062      */
3063     private function get_graders($userid) {
3064         //potential graders
3065         $potentialgraders = get_enrolled_users($this->context, "mod/assign:grade");
3067         $graders = array();
3068         if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {   // Separate groups are being used
3069             if ($groups = groups_get_all_groups($this->get_course()->id, $userid)) {  // Try to find all groups
3070                 foreach ($groups as $group) {
3071                     foreach ($potentialgraders as $grader) {
3072                         if ($grader->id == $userid) {
3073                             continue; // do not send self
3074                         }
3075                         if (groups_is_member($group->id, $grader->id)) {
3076                             $graders[$grader->id] = $grader;
3077                         }
3078                     }
3079                 }
3080             } else {
3081                 // user not in group, try to find graders without group
3082                 foreach ($potentialgraders as $grader) {
3083                     if ($grader->id == $userid) {
3084                         continue; // do not send self
3085                     }
3086                     if (!groups_has_membership($this->get_course_module(), $grader->id)) {
3087                         $graders[$grader->id] = $grader;
3088                     }
3089                 }
3090             }
3091         } else {
3092             foreach ($potentialgraders as $grader) {
3093                 if ($grader->id == $userid) {
3094                     continue; // do not send self
3095                 }
3096                 // must be enrolled
3097                 if (is_enrolled($this->get_course_context(), $grader->id)) {
3098                     $graders[$grader->id] = $grader;
3099                 }
3100             }
3101         }
3102         return $graders;
3103     }
3105     /**
3106      * Format a notification for plain text
3107      *
3108      * @param string $messagetype
3109      * @param stdClass $info
3110      * @param stdClass $course
3111      * @param stdClass $context
3112      * @param string $modulename
3113      * @param string $assignmentname
3114      */
3115     private static function format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname) {
3116         $posttext  = format_string($course->shortname, true, array('context' => $context->get_course_context())).' -> '.
3117                      $modulename.' -> '.
3118                      format_string($assignmentname, true, array('context' => $context))."\n";
3119         $posttext .= '---------------------------------------------------------------------'."\n";
3120         $posttext .= get_string($messagetype . 'text', "assign", $info)."\n";
3121         $posttext .= "\n---------------------------------------------------------------------\n";
3122         return $posttext;
3123     }
3125     /**
3126      * Format a notification for HTML
3127      *
3128      * @param string $messagetype
3129      * @param stdClass $info
3130      * @param stdClass $course
3131      * @param stdClass $context
3132      * @param string $modulename
3133      * @param stdClass $coursemodule
3134      * @param string $assignmentname
3135      * @param stdClass $info
3136      */
3137     private static function format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) {
3138         global $CFG;
3139         $posthtml  = '<p><font face="sans-serif">'.
3140                      '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.format_string($course->shortname, true, array('context' => $context->get_course_context())).'</a> ->'.
3141                      '<a href="'.$CFG->wwwroot.'/mod/assign/index.php?id='.$course->id.'">'.$modulename.'</a> ->'.
3142                      '<a href="'.$CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id.'">'.format_string($assignmentname, true, array('context' => $context)).'</a></font></p>';
3143         $posthtml .= '<hr /><font face="sans-serif">';
3144         $posthtml .= '<p>'.get_string($messagetype . 'html', 'assign', $info).'</p>';
3145         $posthtml .= '</font><hr />';
3146         return $posthtml;
3147     }
3149     /**
3150      * Message someone about something (static so it can be called from cron)
3151      *
3152      * @param stdClass $userfrom
3153      * @param stdClass $userto
3154      * @param string $messagetype
3155      * @param string $eventtype
3156      * @param int $updatetime
3157      * @param stdClass $coursemodule
3158      * @param stdClass $context
3159      * @param stdClass $course
3160      * @param string $modulename
3161      * @param string $assignmentname
3162      * @return void
3163      */
3164     public static function send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
3165                                                         $updatetime, $coursemodule, $context, $course,
3166                                                         $modulename, $assignmentname, $blindmarking,
3167                                                         $uniqueidforuser) {
3168         global $CFG;
3170         $info = new stdClass();
3171         if ($blindmarking) {
3172             $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
3173         } else {
3174             $info->username = fullname($userfrom, true);
3175         }
3176         $info->assignment = format_string($assignmentname,true, array('context'=>$context));
3177         $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
3178         $info->timeupdated = strftime('%c',$updatetime);
3180         $postsubject = get_string($messagetype . 'small', 'assign', $info);
3181         $posttext = self::format_notification_message_text($messagetype, $info, $course, $context, $modulename, $assignmentname);
3182         $posthtml = ($userto->mailformat == 1) ? self::format_notification_message_html($messagetype, $info, $course, $context, $modulename, $coursemodule, $assignmentname) : '';
3184         $eventdata = new stdClass();
3185         $eventdata->modulename       = 'assign';
3186         $eventdata->userfrom         = $userfrom;
3187         $eventdata->userto           = $userto;
3188         $eventdata->subject          = $postsubject;
3189         $eventdata->fullmessage      = $posttext;
3190         $eventdata->fullmessageformat = FORMAT_PLAIN;
3191         $eventdata->fullmessagehtml  = $posthtml;
3192         $eventdata->smallmessage     = $postsubject;
3194         $eventdata->name            = $eventtype;
3195         $eventdata->component       = 'mod_assign';
3196         $eventdata->notification    = 1;
3197         $eventdata->contexturl      = $info->url;
3198         $eventdata->contexturlname  = $info->assignment;
3200         message_send($eventdata);
3201     }
3203     /**
3204      * Message someone about something
3205      *
3206      * @param stdClass $userfrom
3207      * @param stdClass $userto
3208      * @param string $messagetype
3209      * @param string $eventtype
3210      * @param int $updatetime
3211      * @return void
3212      */
3213     public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
3214         self::send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
3215                                            $updatetime, $this->get_course_module(), $this->get_context(),
3216                                            $this->get_course(), $this->get_module_name(),
3217                                            $this->get_instance()->name, $this->is_blind_marking(),
3218                                            $this->get_uniqueid_for_user($userfrom->id));
3219     }
3221     /**
3222      * Notify student upon successful submission
3223      *
3224      * @param stdClass $submission
3225      * @return void
3226      */
3227     private function notify_student_submission_receipt(stdClass $submission) {
3228         global $DB, $USER;
3230         $adminconfig = $this->get_admin_config();
3231         if (empty($adminconfig->submissionreceipts)) {
3232             // No need to do anything
3233             return;
3234         }
3235         if ($submission->userid) {
3236             $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
3237         } else {
3238             $user = $USER;
3239         }
3240         $this->send_notification($user, $user, 'submissionreceipt', 'assign_notification', $submission->timemodified);
3241     }
3243     /**
3244      * Send notifications to graders upon student submissions
3245      *
3246      * @param stdClass $submission
3247      * @return void
3248      */
3249     private function notify_graders(stdClass $submission) {
3250         global $DB, $USER;
3252         $late = $this->get_instance()->duedate && ($this->get_instance()->duedate < time());
3254         if (!$this->get_instance()->sendnotifications && !($late && $this->get_instance()->sendlatenotifications)) {          // No need to do anything
3255             return;
3256         }
3258         if ($submission->userid) {
3259             $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
3260         } else {
3261             $user = $USER;
3262         }
3263         if ($teachers = $this->get_graders($user->id)) {
3264             foreach ($teachers as $teacher) {
3265                 $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_notification', $submission->timemodified);
3266             }
3267         }
3268     }
3270     /**
3271      * assignment submission is processed before grading