Merge branch 'w08_MDL-38154_m25_delperf' of git://github.com/skodak/moodle
[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 // Assignment submission statuses.
30 define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
31 define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
33 // Search filters for grading page.
34 define('ASSIGN_FILTER_SUBMITTED', 'submitted');
35 define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
36 define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
38 require_once($CFG->libdir . '/accesslib.php');
39 require_once($CFG->libdir . '/formslib.php');
40 require_once($CFG->dirroot . '/repository/lib.php');
41 require_once($CFG->dirroot . '/mod/assign/mod_form.php');
42 require_once($CFG->libdir . '/gradelib.php');
43 require_once($CFG->dirroot . '/grade/grading/lib.php');
44 require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
45 require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
46 require_once($CFG->dirroot . '/mod/assign/renderable.php');
47 require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
48 require_once($CFG->libdir . '/eventslib.php');
49 require_once($CFG->libdir . '/portfolio/caller.php');
51 /**
52  * Standard base class for mod_assign (assignment types).
53  *
54  * @package   mod_assign
55  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
56  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
57  */
58 class assign {
60     /** @var stdClass the assignment record that contains the global settings for this assign instance */
61     private $instance;
63     /** @var context the context of the course module for this assign instance
64      *               (or just the course if we are creating a new one)
65      */
66     private $context;
68     /** @var stdClass the course this assign instance belongs to */
69     private $course;
71     /** @var stdClass the admin config for all assign instances  */
72     private $adminconfig;
74     /** @var assign_renderer the custom renderer for this module */
75     private $output;
77     /** @var stdClass the course module for this assign instance */
78     private $coursemodule;
80     /** @var array cache for things like the coursemodule name or the scale menu -
81      *             only lives for a single request.
82      */
83     private $cache;
85     /** @var array list of the installed submission plugins */
86     private $submissionplugins;
88     /** @var array list of the installed feedback plugins */
89     private $feedbackplugins;
91     /** @var string action to be used to return to this page
92      *              (without repeating any form submissions etc).
93      */
94     private $returnaction = 'view';
96     /** @var array params to be used to return to this page */
97     private $returnparams = array();
99     /** @var string modulename prevents excessive calls to get_string */
100     private static $modulename = null;
102     /** @var string modulenameplural prevents excessive calls to get_string */
103     private static $modulenameplural = null;
105     /**
106      * Constructor for the base assign class.
107      *
108      * @param mixed $coursemodulecontext context|null the course module context
109      *                                   (or the course context if the coursemodule has not been
110      *                                   created yet).
111      * @param mixed $coursemodule the current course module if it was already loaded,
112      *                            otherwise this class will load one from the context as required.
113      * @param mixed $course the current course  if it was already loaded,
114      *                      otherwise this class will load one from the context as required.
115      */
116     public function __construct($coursemodulecontext, $coursemodule, $course) {
117         global $PAGE;
119         $this->context = $coursemodulecontext;
120         $this->coursemodule = $coursemodule;
121         $this->course = $course;
123         // Temporary cache only lives for a single request - used to reduce db lookups.
124         $this->cache = array();
126         $this->submissionplugins = $this->load_plugins('assignsubmission');
127         $this->feedbackplugins = $this->load_plugins('assignfeedback');
128     }
130     /**
131      * Set the action and parameters that can be used to return to the current page.
132      *
133      * @param string $action The action for the current page
134      * @param array $params An array of name value pairs which form the parameters
135      *                      to return to the current page.
136      * @return void
137      */
138     public function register_return_link($action, $params) {
139         global $PAGE;
140         $params['action'] = $action;
141         $currenturl = $PAGE->url;
143         $currenturl->params($params);
144         $PAGE->set_url($currenturl);
145     }
147     /**
148      * Return an action that can be used to get back to the current page.
149      *
150      * @return string action
151      */
152     public function get_return_action() {
153         global $PAGE;
155         $params = $PAGE->url->params();
157         if (!empty($params['action'])) {
158             return $params['action'];
159         }
160         return '';
161     }
163     /**
164      * Based on the current assignment settings should we display the intro.
165      *
166      * @return bool showintro
167      */
168     protected function show_intro() {
169         if ($this->get_instance()->alwaysshowdescription ||
170                 time() > $this->get_instance()->allowsubmissionsfromdate) {
171             return true;
172         }
173         return false;
174     }
176     /**
177      * Return a list of parameters that can be used to get back to the current page.
178      *
179      * @return array params
180      */
181     public function get_return_params() {
182         global $PAGE;
184         $params = $PAGE->url->params();
185         unset($params['id']);
186         unset($params['action']);
187         return $params;
188     }
190     /**
191      * Set the submitted form data.
192      *
193      * @param stdClass $data The form data (instance)
194      */
195     public function set_instance(stdClass $data) {
196         $this->instance = $data;
197     }
199     /**
200      * Set the context.
201      *
202      * @param context $context The new context
203      */
204     public function set_context(context $context) {
205         $this->context = $context;
206     }
208     /**
209      * Set the course data.
210      *
211      * @param stdClass $course The course data
212      */
213     public function set_course(stdClass $course) {
214         $this->course = $course;
215     }
217     /**
218      * Get list of feedback plugins installed.
219      *
220      * @return array
221      */
222     public function get_feedback_plugins() {
223         return $this->feedbackplugins;
224     }
226     /**
227      * Get list of submission plugins installed.
228      *
229      * @return array
230      */
231     public function get_submission_plugins() {
232         return $this->submissionplugins;
233     }
235     /**
236      * Is blind marking enabled and reveal identities not set yet?
237      *
238      * @return bool
239      */
240     public function is_blind_marking() {
241         return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
242     }
244     /**
245      * Does an assignment have submission(s) or grade(s) already?
246      *
247      * @return bool
248      */
249     public function has_submissions_or_grades() {
250         $allgrades = $this->count_grades();
251         $allsubmissions = $this->count_submissions();
252         if (($allgrades == 0) && ($allsubmissions == 0)) {
253             return false;
254         }
255         return true;
256     }
258     /**
259      * Get a specific submission plugin by its type.
260      *
261      * @param string $subtype assignsubmission | assignfeedback
262      * @param string $type
263      * @return mixed assign_plugin|null
264      */
265     public function get_plugin_by_type($subtype, $type) {
266         $shortsubtype = substr($subtype, strlen('assign'));
267         $name = $shortsubtype . 'plugins';
268         if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
269             return null;
270         }
271         $pluginlist = $this->$name;
272         foreach ($pluginlist as $plugin) {
273             if ($plugin->get_type() == $type) {
274                 return $plugin;
275             }
276         }
277         return null;
278     }
280     /**
281      * Get a feedback plugin by type.
282      *
283      * @param string $type - The type of plugin e.g comments
284      * @return mixed assign_feedback_plugin|null
285      */
286     public function get_feedback_plugin_by_type($type) {
287         return $this->get_plugin_by_type('assignfeedback', $type);
288     }
290     /**
291      * Get a submission plugin by type.
292      *
293      * @param string $type - The type of plugin e.g comments
294      * @return mixed assign_submission_plugin|null
295      */
296     public function get_submission_plugin_by_type($type) {
297         return $this->get_plugin_by_type('assignsubmission', $type);
298     }
300     /**
301      * Load the plugins from the sub folders under subtype.
302      *
303      * @param string $subtype - either submission or feedback
304      * @return array - The sorted list of plugins
305      */
306     protected function load_plugins($subtype) {
307         global $CFG;
308         $result = array();
310         $names = get_plugin_list($subtype);
312         foreach ($names as $name => $path) {
313             if (file_exists($path . '/locallib.php')) {
314                 require_once($path . '/locallib.php');
316                 $shortsubtype = substr($subtype, strlen('assign'));
317                 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
319                 $plugin = new $pluginclass($this, $name);
321                 if ($plugin instanceof assign_plugin) {
322                     $idx = $plugin->get_sort_order();
323                     while (array_key_exists($idx, $result)) {
324                         $idx +=1;
325                     }
326                     $result[$idx] = $plugin;
327                 }
328             }
329         }
330         ksort($result);
331         return $result;
332     }
334     /**
335      * Display the assignment, used by view.php
336      *
337      * The assignment is displayed differently depending on your role,
338      * the settings for the assignment and the status of the assignment.
339      *
340      * @param string $action The current action if any.
341      * @return void
342      */
343     public function view($action='') {
345         $o = '';
346         $mform = null;
347         $notices = array();
349         $nextpageparams = array('id'=>$this->get_course_module()->id);
351         // Handle form submissions first.
352         if ($action == 'savesubmission') {
353             $action = 'editsubmission';
354             if ($this->process_save_submission($mform, $notices)) {
355                 $action = 'redirect';
356                 $nextpageparams['action'] = 'view';
357             }
358         } else if ($action == 'lock') {
359             $this->process_lock();
360             $action = 'redirect';
361             $nextpageparams['action'] = 'grading';
362         } else if ($action == 'reverttodraft') {
363             $this->process_revert_to_draft();
364             $action = 'redirect';
365             $nextpageparams['action'] = 'grading';
366         } else if ($action == 'unlock') {
367             $this->process_unlock();
368             $action = 'redirect';
369             $nextpageparams['action'] = 'grading';
370         } else if ($action == 'confirmsubmit') {
371             $action = 'submit';
372             if ($this->process_submit_for_grading($mform)) {
373                 $action = 'redirect';
374                 $nextpageparams['action'] = 'view';
375             }
376         } else if ($action == 'gradingbatchoperation') {
377             $action = $this->process_grading_batch_operation($mform);
378             if ($action == 'grading') {
379                 $action = 'redirect';
380                 $nextpageparams['action'] = 'grading';
381             }
382         } else if ($action == 'submitgrade') {
383             if (optional_param('saveandshownext', null, PARAM_RAW)) {
384                 // Save and show next.
385                 $action = 'grade';
386                 if ($this->process_save_grade($mform)) {
387                     $action = 'redirect';
388                     $nextpageparams['action'] = 'grade';
389                     $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
390                     $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
391                 }
392             } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
393                 $action = 'redirect';
394                 $nextpageparams['action'] = 'grade';
395                 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
396                 $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
397             } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
398                 $action = 'redirect';
399                 $nextpageparams['action'] = 'grade';
400                 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
401                 $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
402             } else if (optional_param('savegrade', null, PARAM_RAW)) {
403                 // Save changes button.
404                 $action = 'grade';
405                 if ($this->process_save_grade($mform)) {
406                     $action = 'redirect';
407                     $nextpageparams['action'] = 'grading';
408                 }
409             } else {
410                 // Cancel button.
411                 $action = 'redirect';
412                 $nextpageparams['action'] = 'grading';
413             }
414         } else if ($action == 'quickgrade') {
415             $message = $this->process_save_quick_grades();
416             $action = 'quickgradingresult';
417         } else if ($action == 'saveoptions') {
418             $this->process_save_grading_options();
419             $action = 'redirect';
420             $nextpageparams['action'] = 'grading';
421         } else if ($action == 'saveextension') {
422             $action = 'grantextension';
423             if ($this->process_save_extension($mform)) {
424                 $action = 'redirect';
425                 $nextpageparams['action'] = 'grading';
426             }
427         } else if ($action == 'revealidentitiesconfirm') {
428             $this->process_reveal_identities();
429             $action = 'redirect';
430             $nextpageparams['action'] = 'grading';
431         }
433         $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
434                               'useridlistid'=>optional_param('useridlistid', 0, PARAM_INT));
435         $this->register_return_link($action, $returnparams);
437         // Now show the right view page.
438         if ($action == 'redirect') {
439             $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
440             redirect($nextpageurl);
441             return;
442         } else if ($action == 'quickgradingresult') {
443             $mform = null;
444             $o .= $this->view_quickgrading_result($message);
445         } else if ($action == 'grade') {
446             $o .= $this->view_single_grade_page($mform);
447         } else if ($action == 'viewpluginassignfeedback') {
448             $o .= $this->view_plugin_content('assignfeedback');
449         } else if ($action == 'viewpluginassignsubmission') {
450             $o .= $this->view_plugin_content('assignsubmission');
451         } else if ($action == 'editsubmission') {
452             $o .= $this->view_edit_submission_page($mform, $notices);
453         } else if ($action == 'grading') {
454             $o .= $this->view_grading_page();
455         } else if ($action == 'downloadall') {
456             $o .= $this->download_submissions();
457         } else if ($action == 'submit') {
458             $o .= $this->check_submit_for_grading($mform);
459         } else if ($action == 'grantextension') {
460             $o .= $this->view_grant_extension($mform);
461         } else if ($action == 'revealidentities') {
462             $o .= $this->view_reveal_identities_confirm($mform);
463         } else if ($action == 'plugingradingbatchoperation') {
464             $o .= $this->view_plugin_grading_batch_operation($mform);
465         } else if ($action == 'viewpluginpage') {
466              $o .= $this->view_plugin_page();
467         } else if ($action == 'viewcourseindex') {
468              $o .= $this->view_course_index();
469         } else {
470             $o .= $this->view_submission_page();
471         }
473         return $o;
474     }
476     /**
477      * Add this instance to the database.
478      *
479      * @param stdClass $formdata The data submitted from the form
480      * @param bool $callplugins This is used to skip the plugin code
481      *             when upgrading an old assignment to a new one (the plugins get called manually)
482      * @return mixed false if an error occurs or the int id of the new instance
483      */
484     public function add_instance(stdClass $formdata, $callplugins) {
485         global $DB;
487         $err = '';
489         // Add the database record.
490         $update = new stdClass();
491         $update->name = $formdata->name;
492         $update->timemodified = time();
493         $update->timecreated = time();
494         $update->course = $formdata->course;
495         $update->courseid = $formdata->course;
496         $update->intro = $formdata->intro;
497         $update->introformat = $formdata->introformat;
498         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
499         $update->submissiondrafts = $formdata->submissiondrafts;
500         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
501         $update->sendnotifications = $formdata->sendnotifications;
502         $update->sendlatenotifications = $formdata->sendlatenotifications;
503         $update->duedate = $formdata->duedate;
504         $update->cutoffdate = $formdata->cutoffdate;
505         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
506         $update->grade = $formdata->grade;
507         $update->completionsubmit = !empty($formdata->completionsubmit);
508         $update->teamsubmission = $formdata->teamsubmission;
509         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
510         $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
511         $update->blindmarking = $formdata->blindmarking;
513         $returnid = $DB->insert_record('assign', $update);
514         $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
515         // Cache the course record.
516         $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
518         if ($callplugins) {
519             // Call save_settings hook for submission plugins.
520             foreach ($this->submissionplugins as $plugin) {
521                 if (!$this->update_plugin_instance($plugin, $formdata)) {
522                     print_error($plugin->get_error());
523                     return false;
524                 }
525             }
526             foreach ($this->feedbackplugins as $plugin) {
527                 if (!$this->update_plugin_instance($plugin, $formdata)) {
528                     print_error($plugin->get_error());
529                     return false;
530                 }
531             }
533             // In the case of upgrades the coursemodule has not been set,
534             // so we need to wait before calling these two.
535             $this->update_calendar($formdata->coursemodule);
536             $this->update_gradebook(false, $formdata->coursemodule);
538         }
540         $update = new stdClass();
541         $update->id = $this->get_instance()->id;
542         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
543         $DB->update_record('assign', $update);
545         return $returnid;
546     }
548     /**
549      * Delete all grades from the gradebook for this assignment.
550      *
551      * @return bool
552      */
553     protected function delete_grades() {
554         global $CFG;
556         $result = grade_update('mod/assign',
557                                $this->get_course()->id,
558                                'mod',
559                                'assign',
560                                $this->get_instance()->id,
561                                0,
562                                null,
563                                array('deleted'=>1));
564         return $result == GRADE_UPDATE_OK;
565     }
567     /**
568      * Delete this instance from the database.
569      *
570      * @return bool false if an error occurs
571      */
572     public function delete_instance() {
573         global $DB;
574         $result = true;
576         foreach ($this->submissionplugins as $plugin) {
577             if (!$plugin->delete_instance()) {
578                 print_error($plugin->get_error());
579                 $result = false;
580             }
581         }
582         foreach ($this->feedbackplugins as $plugin) {
583             if (!$plugin->delete_instance()) {
584                 print_error($plugin->get_error());
585                 $result = false;
586             }
587         }
589         // Delete files associated with this assignment.
590         $fs = get_file_storage();
591         if (! $fs->delete_area_files($this->context->id) ) {
592             $result = false;
593         }
595         // Delete_records will throw an exception if it fails - so no need for error checking here.
596         $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
597         $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
598         $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
600         // Delete items from the gradebook.
601         if (! $this->delete_grades()) {
602             $result = false;
603         }
605         // Delete the instance.
606         $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
608         return $result;
609     }
611     /**
612      * Actual implementation of the reset course functionality, delete all the
613      * assignment submissions for course $data->courseid.
614      *
615      * @param $data the data submitted from the reset course.
616      * @return array status array
617      */
618     public function reset_userdata($data) {
619         global $CFG, $DB;
621         $componentstr = get_string('modulenameplural', 'assign');
622         $status = array();
624         $fs = get_file_storage();
625         if (!empty($data->reset_assign_submissions)) {
626             // Delete files associated with this assignment.
627             foreach ($this->submissionplugins as $plugin) {
628                 $fileareas = array();
629                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
630                 $fileareas = $plugin->get_file_areas();
631                 foreach ($fileareas as $filearea) {
632                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
633                 }
635                 if (!$plugin->delete_instance()) {
636                     $status[] = array('component'=>$componentstr,
637                                       'item'=>get_string('deleteallsubmissions', 'assign'),
638                                       'error'=>$plugin->get_error());
639                 }
640             }
642             foreach ($this->feedbackplugins as $plugin) {
643                 $fileareas = array();
644                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
645                 $fileareas = $plugin->get_file_areas();
646                 foreach ($fileareas as $filearea) {
647                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
648                 }
650                 if (!$plugin->delete_instance()) {
651                     $status[] = array('component'=>$componentstr,
652                                       'item'=>get_string('deleteallsubmissions', 'assign'),
653                                       'error'=>$plugin->get_error());
654                 }
655             }
657             $assignssql = 'SELECT a.id
658                              FROM {assign} a
659                            WHERE a.course=:course';
660             $params = array('course'=>$data->courseid);
662             $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
664             $status[] = array('component'=>$componentstr,
665                               'item'=>get_string('deleteallsubmissions', 'assign'),
666                               'error'=>false);
668             if (!empty($data->reset_gradebook_grades)) {
669                 $DB->delete_records_select('assign_grades', "assignment IN ($assignssql)", $params);
670                 // Remove all grades from gradebook.
671                 require_once($CFG->dirroot.'/mod/assign/lib.php');
672                 assign_reset_gradebook($data->courseid);
673             }
674         }
675         // Updating dates - shift may be negative too.
676         if ($data->timeshift) {
677             shift_course_mod_dates('assign',
678                                     array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
679                                     $data->timeshift,
680                                     $data->courseid);
681             $status[] = array('component'=>$componentstr,
682                               'item'=>get_string('datechanged'),
683                               'error'=>false);
684         }
686         return $status;
687     }
689     /**
690      * Update the settings for a single plugin.
691      *
692      * @param assign_plugin $plugin The plugin to update
693      * @param stdClass $formdata The form data
694      * @return bool false if an error occurs
695      */
696     protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
697         if ($plugin->is_visible()) {
698             $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
699             if (!empty($formdata->$enabledname)) {
700                 $plugin->enable();
701                 if (!$plugin->save_settings($formdata)) {
702                     print_error($plugin->get_error());
703                     return false;
704                 }
705             } else {
706                 $plugin->disable();
707             }
708         }
709         return true;
710     }
712     /**
713      * Update the gradebook information for this assignment.
714      *
715      * @param bool $reset If true, will reset all grades in the gradbook for this assignment
716      * @param int $coursemoduleid This is required because it might not exist in the database yet
717      * @return bool
718      */
719     public function update_gradebook($reset, $coursemoduleid) {
720         global $CFG;
722         require_once($CFG->dirroot.'/mod/assign/lib.php');
723         $assign = clone $this->get_instance();
724         $assign->cmidnumber = $coursemoduleid;
725         $param = null;
726         if ($reset) {
727             $param = 'reset';
728         }
730         return assign_grade_item_update($assign, $param);
731     }
733     /**
734      * Load and cache the admin config for this module.
735      *
736      * @return stdClass the plugin config
737      */
738     public function get_admin_config() {
739         if ($this->adminconfig) {
740             return $this->adminconfig;
741         }
742         $this->adminconfig = get_config('assign');
743         return $this->adminconfig;
744     }
746     /**
747      * Update the calendar entries for this assignment.
748      *
749      * @param int $coursemoduleid - Required to pass this in because it might
750      *                              not exist in the database yet.
751      * @return bool
752      */
753     public function update_calendar($coursemoduleid) {
754         global $DB, $CFG;
755         require_once($CFG->dirroot.'/calendar/lib.php');
757         // Special case for add_instance as the coursemodule has not been set yet.
758         $instance = $this->get_instance();
760         if ($instance->duedate) {
761             $event = new stdClass();
763             $params = array('modulename'=>'assign', 'instance'=>$instance->id);
764             $event->id = $DB->get_field('event',
765                                         'id',
766                                         $params);
768             if ($event->id) {
769                 $event->name        = $instance->name;
770                 $event->description = format_module_intro('assign', $instance, $coursemoduleid);
771                 $event->timestart   = $instance->duedate;
773                 $calendarevent = calendar_event::load($event->id);
774                 $calendarevent->update($event);
775             } else {
776                 $event = new stdClass();
777                 $event->name        = $instance->name;
778                 $event->description = format_module_intro('assign', $instance, $coursemoduleid);
779                 $event->courseid    = $instance->course;
780                 $event->groupid     = 0;
781                 $event->userid      = 0;
782                 $event->modulename  = 'assign';
783                 $event->instance    = $instance->id;
784                 $event->eventtype   = 'due';
785                 $event->timestart   = $instance->duedate;
786                 $event->timeduration = 0;
788                 calendar_event::create($event);
789             }
790         } else {
791             $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$instance->id));
792         }
793     }
796     /**
797      * Update this instance in the database.
798      *
799      * @param stdClass $formdata - the data submitted from the form
800      * @return bool false if an error occurs
801      */
802     public function update_instance($formdata) {
803         global $DB;
805         $update = new stdClass();
806         $update->id = $formdata->instance;
807         $update->name = $formdata->name;
808         $update->timemodified = time();
809         $update->course = $formdata->course;
810         $update->intro = $formdata->intro;
811         $update->introformat = $formdata->introformat;
812         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
813         $update->submissiondrafts = $formdata->submissiondrafts;
814         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
815         $update->sendnotifications = $formdata->sendnotifications;
816         $update->sendlatenotifications = $formdata->sendlatenotifications;
817         $update->duedate = $formdata->duedate;
818         $update->cutoffdate = $formdata->cutoffdate;
819         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
820         $update->grade = $formdata->grade;
821         $update->completionsubmit = !empty($formdata->completionsubmit);
822         $update->teamsubmission = $formdata->teamsubmission;
823         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
824         $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
825         $update->blindmarking = $formdata->blindmarking;
827         $result = $DB->update_record('assign', $update);
828         $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
830         // Load the assignment so the plugins have access to it.
832         // Call save_settings hook for submission plugins.
833         foreach ($this->submissionplugins as $plugin) {
834             if (!$this->update_plugin_instance($plugin, $formdata)) {
835                 print_error($plugin->get_error());
836                 return false;
837             }
838         }
839         foreach ($this->feedbackplugins as $plugin) {
840             if (!$this->update_plugin_instance($plugin, $formdata)) {
841                 print_error($plugin->get_error());
842                 return false;
843             }
844         }
846         $this->update_calendar($this->get_course_module()->id);
847         $this->update_gradebook(false, $this->get_course_module()->id);
849         $update = new stdClass();
850         $update->id = $this->get_instance()->id;
851         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
852         $DB->update_record('assign', $update);
854         return $result;
855     }
857     /**
858      * Add elements in grading plugin form.
859      *
860      * @param mixed $grade stdClass|null
861      * @param MoodleQuickForm $mform
862      * @param stdClass $data
863      * @param int $userid - The userid we are grading
864      * @return void
865      */
866     protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
867         foreach ($this->feedbackplugins as $plugin) {
868             if ($plugin->is_enabled() && $plugin->is_visible()) {
869                 $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
870                 if (!$plugin->get_form_elements_for_user($grade, $mform, $data, $userid)) {
871                     $mform->removeElement('header_' . $plugin->get_type());
872                 }
873             }
874         }
875     }
879     /**
880      * Add one plugins settings to edit plugin form.
881      *
882      * @param assign_plugin $plugin The plugin to add the settings from
883      * @param MoodleQuickForm $mform The form to add the configuration settings to.
884      *                               This form is modified directly (not returned).
885      * @param array $pluginsenabled A list of form elements to be added to a group.
886      *                              The new element is added to this array by this function.
887      * @return void
888      */
889     protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
890         global $CFG;
891         if ($plugin->is_visible()) {
893             $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
894             $label = $plugin->get_name();
895             $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
896             $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
898             $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
899             if ($plugin->get_config('enabled') !== false) {
900                 $default = $plugin->is_enabled();
901             }
902             $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
904             $plugin->get_settings($mform);
906         }
907     }
909     /**
910      * Add settings to edit plugin form.
911      *
912      * @param MoodleQuickForm $mform The form to add the configuration settings to.
913      *                               This form is modified directly (not returned).
914      * @return void
915      */
916     public function add_all_plugin_settings(MoodleQuickForm $mform) {
917         $mform->addElement('header', 'submissiontypes', get_string('submissionsettings', 'assign'));
919         $submissionpluginsenabled = array();
920         $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
921         foreach ($this->submissionplugins as $plugin) {
922             $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
923         }
924         $group->setElements($submissionpluginsenabled);
926         $mform->addElement('header', 'feedbacktypes', get_string('feedbacksettings', 'assign'));
927         $feedbackpluginsenabled = array();
928         $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
929         foreach ($this->feedbackplugins as $plugin) {
930             $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
931         }
932         $group->setElements($feedbackpluginsenabled);
933         $mform->setExpanded('submissiontypes');
934     }
936     /**
937      * Allow each plugin an opportunity to update the defaultvalues
938      * passed in to the settings form (needed to set up draft areas for
939      * editor and filemanager elements)
940      *
941      * @param array $defaultvalues
942      */
943     public function plugin_data_preprocessing(&$defaultvalues) {
944         foreach ($this->submissionplugins as $plugin) {
945             if ($plugin->is_visible()) {
946                 $plugin->data_preprocessing($defaultvalues);
947             }
948         }
949         foreach ($this->feedbackplugins as $plugin) {
950             if ($plugin->is_visible()) {
951                 $plugin->data_preprocessing($defaultvalues);
952             }
953         }
954     }
956     /**
957      * Get the name of the current module.
958      *
959      * @return string the module name (Assignment)
960      */
961     protected function get_module_name() {
962         if (isset(self::$modulename)) {
963             return self::$modulename;
964         }
965         self::$modulename = get_string('modulename', 'assign');
966         return self::$modulename;
967     }
969     /**
970      * Get the plural name of the current module.
971      *
972      * @return string the module name plural (Assignments)
973      */
974     protected function get_module_name_plural() {
975         if (isset(self::$modulenameplural)) {
976             return self::$modulenameplural;
977         }
978         self::$modulenameplural = get_string('modulenameplural', 'assign');
979         return self::$modulenameplural;
980     }
982     /**
983      * Has this assignment been constructed from an instance?
984      *
985      * @return bool
986      */
987     public function has_instance() {
988         return $this->instance || $this->get_course_module();
989     }
991     /**
992      * Get the settings for the current instance of this assignment
993      *
994      * @return stdClass The settings
995      */
996     public function get_instance() {
997         global $DB;
998         if ($this->instance) {
999             return $this->instance;
1000         }
1001         if ($this->get_course_module()) {
1002             $params = array('id' => $this->get_course_module()->instance);
1003             $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
1004         }
1005         if (!$this->instance) {
1006             throw new coding_exception('Improper use of the assignment class. ' .
1007                                        'Cannot load the assignment record.');
1008         }
1009         return $this->instance;
1010     }
1012     /**
1013      * Get the context of the current course.
1014      *
1015      * @return mixed context|null The course context
1016      */
1017     public function get_course_context() {
1018         if (!$this->context && !$this->course) {
1019             throw new coding_exception('Improper use of the assignment class. ' .
1020                                        'Cannot load the course context.');
1021         }
1022         if ($this->context) {
1023             return $this->context->get_course_context();
1024         } else {
1025             return context_course::instance($this->course->id);
1026         }
1027     }
1030     /**
1031      * Get the current course module.
1032      *
1033      * @return mixed stdClass|null The course module
1034      */
1035     public function get_course_module() {
1036         if ($this->coursemodule) {
1037             return $this->coursemodule;
1038         }
1039         if (!$this->context) {
1040             return null;
1041         }
1043         if ($this->context->contextlevel == CONTEXT_MODULE) {
1044             $this->coursemodule = get_coursemodule_from_id('assign',
1045                                                            $this->context->instanceid,
1046                                                            0,
1047                                                            false,
1048                                                            MUST_EXIST);
1049             return $this->coursemodule;
1050         }
1051         return null;
1052     }
1054     /**
1055      * Get context module.
1056      *
1057      * @return context
1058      */
1059     public function get_context() {
1060         return $this->context;
1061     }
1063     /**
1064      * Get the current course.
1065      *
1066      * @return mixed stdClass|null The course
1067      */
1068     public function get_course() {
1069         global $DB;
1071         if ($this->course) {
1072             return $this->course;
1073         }
1075         if (!$this->context) {
1076             return null;
1077         }
1078         $params = array('id' => $this->get_course_context()->instanceid);
1079         $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
1081         return $this->course;
1082     }
1084     /**
1085      * Return a grade in user-friendly form, whether it's a scale or not.
1086      *
1087      * @param mixed $grade int|null
1088      * @param boolean $editing Are we allowing changes to this grade?
1089      * @param int $userid The user id the grade belongs to
1090      * @param int $modified Timestamp from when the grade was last modified
1091      * @return string User-friendly representation of grade
1092      */
1093     public function display_grade($grade, $editing, $userid=0, $modified=0) {
1094         global $DB;
1096         static $scalegrades = array();
1098         $o = '';
1100         if ($this->get_instance()->grade >= 0) {
1101             // Normal number.
1102             if ($editing && $this->get_instance()->grade > 0) {
1103                 if ($grade < 0) {
1104                     $displaygrade = '';
1105                 } else {
1106                     $displaygrade = format_float($grade);
1107                 }
1108                 $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
1109                        get_string('usergrade', 'assign') .
1110                        '</label>';
1111                 $o .= '<input type="text"
1112                               id="quickgrade_' . $userid . '"
1113                               name="quickgrade_' . $userid . '"
1114                               value="' .  $displaygrade . '"
1115                               size="6"
1116                               maxlength="10"
1117                               class="quickgrade"/>';
1118                 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1119                 $o .= '<input type="hidden"
1120                               name="grademodified_' . $userid . '"
1121                               value="' . $modified . '"/>';
1122                 return $o;
1123             } else {
1124                 $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
1125                 if ($grade == -1 || $grade === null) {
1126                     $o .= '-';
1127                     return $o;
1128                 } else {
1129                     $o .= format_float($grade, 2) .
1130                           '&nbsp;/&nbsp;' .
1131                           format_float($this->get_instance()->grade, 2);
1132                     return $o;
1133                 }
1134             }
1136         } else {
1137             // Scale.
1138             if (empty($this->cache['scale'])) {
1139                 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1140                     $this->cache['scale'] = make_menu_from_list($scale->scale);
1141                 } else {
1142                     $o .= '-';
1143                     return $o;
1144                 }
1145             }
1146             if ($editing) {
1147                 $o .= '<label class="accesshide"
1148                               for="quickgrade_' . $userid . '">' .
1149                       get_string('usergrade', 'assign') .
1150                       '</label>';
1151                 $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
1152                 $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1153                 foreach ($this->cache['scale'] as $optionid => $option) {
1154                     $selected = '';
1155                     if ($grade == $optionid) {
1156                         $selected = 'selected="selected"';
1157                     }
1158                     $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1159                 }
1160                 $o .= '</select>';
1161                 $o .= '<input type="hidden" ' .
1162                              'name="grademodified_' . $userid . '" ' .
1163                              'value="' . $modified . '"/>';
1164                 return $o;
1165             } else {
1166                 $scaleid = (int)$grade;
1167                 if (isset($this->cache['scale'][$scaleid])) {
1168                     $o .= $this->cache['scale'][$scaleid];
1169                     return $o;
1170                 }
1171                 $o .= '-';
1172                 return $o;
1173             }
1174         }
1175     }
1177     /**
1178      * Load a list of users enrolled in the current course with the specified permission and group.
1179      * 0 for no group.
1180      *
1181      * @param int $currentgroup
1182      * @param bool $idsonly
1183      * @return array List of user records
1184      */
1185     public function list_participants($currentgroup, $idsonly) {
1186         if ($idsonly) {
1187             return get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.id');
1188         } else {
1189             return get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup);
1190         }
1191     }
1193     /**
1194      * Load a count of valid teams for this assignment.
1195      *
1196      * @return int number of valid teams
1197      */
1198     public function count_teams() {
1200         $groups = groups_get_all_groups($this->get_course()->id,
1201                                         0,
1202                                         $this->get_instance()->teamsubmissiongroupingid,
1203                                         'g.id');
1204         $count = count($groups);
1206         // See if there are any users in the default group.
1207         $defaultusers = $this->get_submission_group_members(0, true);
1208         if (count($defaultusers) > 0) {
1209             $count += 1;
1210         }
1211         return $count;
1212     }
1214     /**
1215      * Load a count of users enrolled in the current course with the specified permission and group.
1216      * 0 for no group.
1217      *
1218      * @param int $currentgroup
1219      * @return int number of matching users
1220      */
1221     public function count_participants($currentgroup) {
1222         return count_enrolled_users($this->context, 'mod/assign:submit', $currentgroup);
1223     }
1225     /**
1226      * Load a count of users submissions in the current module that require grading
1227      * This means the submission modification time is more recent than the
1228      * grading modification time and the status is SUBMITTED.
1229      *
1230      * @return int number of matching submissions
1231      */
1232     public function count_submissions_need_grading() {
1233         global $DB;
1235         if ($this->get_instance()->teamsubmission) {
1236             // This does not make sense for group assignment because the submission is shared.
1237             return 0;
1238         }
1240         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1241         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, false);
1243         $params['assignid'] = $this->get_instance()->id;
1244         $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1246         $sql = 'SELECT COUNT(s.userid)
1247                    FROM {assign_submission} s
1248                    LEFT JOIN {assign_grades} g ON
1249                         s.assignment = g.assignment AND
1250                         s.userid = g.userid
1251                    JOIN(' . $esql . ') e ON e.id = s.userid
1252                    WHERE
1253                         s.assignment = :assignid AND
1254                         s.timemodified IS NOT NULL AND
1255                         s.status = :submitted AND
1256                         (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
1258         return $DB->count_records_sql($sql, $params);
1259     }
1261     /**
1262      * Load a count of grades.
1263      *
1264      * @return int number of grades
1265      */
1266     public function count_grades() {
1267         global $DB;
1269         if (!$this->has_instance()) {
1270             return 0;
1271         }
1273         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1274         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, false);
1276         $params['assignid'] = $this->get_instance()->id;
1278         $sql = 'SELECT COUNT(g.userid)
1279                    FROM {assign_grades} g
1280                    JOIN(' . $esql . ') e ON e.id = g.userid
1281                    WHERE g.assignment = :assignid';
1283         return $DB->count_records_sql($sql, $params);
1284     }
1286     /**
1287      * Load a count of submissions.
1288      *
1289      * @return int number of submissions
1290      */
1291     public function count_submissions() {
1292         global $DB;
1294         if (!$this->has_instance()) {
1295             return 0;
1296         }
1298         $params = array();
1300         if ($this->get_instance()->teamsubmission) {
1301             // We cannot join on the enrolment tables for group submissions (no userid).
1302             $sql = 'SELECT COUNT(s.groupid)
1303                         FROM {assign_submission} s
1304                         WHERE
1305                             s.assignment = :assignid AND
1306                             s.timemodified IS NOT NULL AND
1307                             s.userid = :groupuserid';
1309             $params['assignid'] = $this->get_instance()->id;
1310             $params['groupuserid'] = 0;
1311         } else {
1312             $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1313             list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, false);
1315             $params['assignid'] = $this->get_instance()->id;
1317             $sql = 'SELECT COUNT(s.userid)
1318                        FROM {assign_submission} s
1319                        JOIN(' . $esql . ') e ON e.id = s.userid
1320                        WHERE
1321                             s.assignment = :assignid AND
1322                             s.timemodified IS NOT NULL';
1323         }
1325         return $DB->count_records_sql($sql, $params);
1326     }
1328     /**
1329      * Load a count of submissions with a specified status.
1330      *
1331      * @param string $status The submission status - should match one of the constants
1332      * @return int number of matching submissions
1333      */
1334     public function count_submissions_with_status($status) {
1335         global $DB;
1337         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1338         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, false);
1340         $params['assignid'] = $this->get_instance()->id;
1341         $params['submissionstatus'] = $status;
1343         if ($this->get_instance()->teamsubmission) {
1344             $sql = 'SELECT COUNT(s.groupid)
1345                         FROM {assign_submission} s
1346                         WHERE
1347                             s.assignment = :assignid AND
1348                             s.timemodified IS NOT NULL AND
1349                             s.userid = :groupuserid AND
1350                             s.status = :submissionstatus';
1351             $params['groupuserid'] = 0;
1352         } else {
1353             $sql = 'SELECT COUNT(s.userid)
1354                         FROM {assign_submission} s
1355                         JOIN(' . $esql . ') e ON e.id = s.userid
1356                         WHERE
1357                             s.assignment = :assignid AND
1358                             s.timemodified IS NOT NULL AND
1359                             s.status = :submissionstatus';
1360         }
1362         return $DB->count_records_sql($sql, $params);
1363     }
1365     /**
1366      * Utility function to get the userid for every row in the grading table
1367      * so the order can be frozen while we iterate it.
1368      *
1369      * @return array An array of userids
1370      */
1371     protected function get_grading_userid_list() {
1372         $filter = get_user_preferences('assign_filter', '');
1373         $table = new assign_grading_table($this, 0, $filter, 0, false);
1375         $useridlist = $table->get_column_data('userid');
1377         return $useridlist;
1378     }
1380     /**
1381      * Generate zip file from array of given files.
1382      *
1383      * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1384      *                                 This array is indexed by the final file name and each
1385      *                                 element in the array is an instance of a stored_file object.
1386      * @return path of temp file - note this returned file does
1387      *         not have a .zip extension - it is a temp file.
1388      */
1389     protected function pack_files($filesforzipping) {
1390         global $CFG;
1391         // Create path for new zip file.
1392         $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1393         // Zip files.
1394         $zipper = new zip_packer();
1395         if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1396             return $tempzip;
1397         }
1398         return false;
1399     }
1401     /**
1402      * Finds all assignment notifications that have yet to be mailed out, and mails them.
1403      *
1404      * Cron function to be run periodically according to the moodle cron.
1405      *
1406      * @return bool
1407      */
1408     public static function cron() {
1409         global $DB;
1411         // Only ever send a max of one days worth of updates.
1412         $yesterday = time() - (24 * 3600);
1413         $timenow   = time();
1415         // Collect all submissions from the past 24 hours that require mailing.
1416         $sql = 'SELECT s.*, a.course, a.name, a.blindmarking, a.revealidentities,
1417                        g.*, g.id as gradeid, g.timemodified as lastmodified
1418                  FROM {assign} a
1419                  JOIN {assign_grades} g ON g.assignment = a.id
1420             LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
1421                 WHERE g.timemodified >= :yesterday AND
1422                       g.timemodified <= :today AND
1423                       g.mailed = 0';
1425         $params = array('yesterday' => $yesterday, 'today' => $timenow);
1426         $submissions = $DB->get_records_sql($sql, $params);
1428         if (empty($submissions)) {
1429             return true;
1430         }
1432         mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1434         // Preload courses we are going to need those.
1435         $courseids = array();
1436         foreach ($submissions as $submission) {
1437             $courseids[] = $submission->course;
1438         }
1440         // Filter out duplicates.
1441         $courseids = array_unique($courseids);
1442         $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1443         list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1444         $sql = 'SELECT c.*, ' . $ctxselect .
1445                   ' FROM {course} c
1446              LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1447                  WHERE c.id ' . $courseidsql;
1449         $params['contextlevel'] = CONTEXT_COURSE;
1450         $courses = $DB->get_records_sql($sql, $params);
1452         // Clean up... this could go on for a while.
1453         unset($courseids);
1454         unset($ctxselect);
1455         unset($courseidsql);
1456         unset($params);
1458         // Simple array we'll use for caching modules.
1459         $modcache = array();
1461         // Message students about new feedback.
1462         foreach ($submissions as $submission) {
1464             mtrace("Processing assignment submission $submission->id ...");
1466             // Do not cache user lookups - could be too many.
1467             if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1468                 mtrace('Could not find user ' . $submission->userid);
1469                 continue;
1470             }
1472             // Use a cache to prevent the same DB queries happening over and over.
1473             if (!array_key_exists($submission->course, $courses)) {
1474                 mtrace('Could not find course ' . $submission->course);
1475                 continue;
1476             }
1477             $course = $courses[$submission->course];
1478             if (isset($course->ctxid)) {
1479                 // Context has not yet been preloaded. Do so now.
1480                 context_helper::preload_from_record($course);
1481             }
1483             // Override the language and timezone of the "current" user, so that
1484             // mail is customised for the receiver.
1485             cron_setup_user($user, $course);
1487             // Context lookups are already cached.
1488             $coursecontext = context_course::instance($course->id);
1489             if (!is_enrolled($coursecontext, $user->id)) {
1490                 $courseshortname = format_string($course->shortname,
1491                                                  true,
1492                                                  array('context' => $coursecontext));
1493                 mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1494                 continue;
1495             }
1497             if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1498                 mtrace('Could not find grader ' . $submission->grader);
1499                 continue;
1500             }
1502             if (!array_key_exists($submission->assignment, $modcache)) {
1503                 $mod = get_coursemodule_from_instance('assign', $submission->assignment, $course->id);
1504                 if (empty($mod)) {
1505                     mtrace('Could not find course module for assignment id ' . $submission->assignment);
1506                     continue;
1507                 }
1508                 $modcache[$submission->assignment] = $mod;
1509             } else {
1510                 $mod = $modcache[$submission->assignment];
1511             }
1512             // Context lookups are already cached.
1513             $contextmodule = context_module::instance($mod->id);
1515             if (!$mod->visible) {
1516                 // Hold mail notification for hidden assignments until later.
1517                 continue;
1518             }
1520             // Need to send this to the student.
1521             $messagetype = 'feedbackavailable';
1522             $eventtype = 'assign_notification';
1523             $updatetime = $submission->lastmodified;
1524             $modulename = get_string('modulename', 'assign');
1526             $uniqueid = 0;
1527             if ($submission->blindmarking && !$submission->revealidentities) {
1528                 $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1529             }
1530             $showusers = $submission->blindmarking && !$submission->revealidentities;
1531             self::send_assignment_notification($grader,
1532                                                $user,
1533                                                $messagetype,
1534                                                $eventtype,
1535                                                $updatetime,
1536                                                $mod,
1537                                                $contextmodule,
1538                                                $course,
1539                                                $modulename,
1540                                                $submission->name,
1541                                                $showusers,
1542                                                $uniqueid);
1544             $grade = new stdClass();
1545             $grade->id = $submission->gradeid;
1546             $grade->mailed = 1;
1547             $DB->update_record('assign_grades', $grade);
1549             mtrace('Done');
1550         }
1551         mtrace('Done processing ' . count($submissions) . ' assignment submissions');
1553         cron_setup_user();
1555         // Free up memory just to be sure.
1556         unset($courses);
1557         unset($modcache);
1559         return true;
1560     }
1562     /**
1563      * Mark in the database that this grade record should have an update notification sent by cron.
1564      *
1565      * @param stdClass $grade a grade record keyed on id
1566      * @return bool true for success
1567      */
1568     public function notify_grade_modified($grade) {
1569         global $DB;
1571         $grade->timemodified = time();
1572         if ($grade->mailed != 1) {
1573             $grade->mailed = 0;
1574         }
1576         return $DB->update_record('assign_grades', $grade);
1577     }
1579     /**
1580      * Update a grade in the grade table for the assignment and in the gradebook.
1581      *
1582      * @param stdClass $grade a grade record keyed on id
1583      * @return bool true for success
1584      */
1585     public function update_grade($grade) {
1586         global $DB;
1588         $grade->timemodified = time();
1590         if ($grade->grade && $grade->grade != -1) {
1591             if ($this->get_instance()->grade > 0) {
1592                 if (!is_numeric($grade->grade)) {
1593                     return false;
1594                 } else if ($grade->grade > $this->get_instance()->grade) {
1595                     return false;
1596                 } else if ($grade->grade < 0) {
1597                     return false;
1598                 }
1599             } else {
1600                 // This is a scale.
1601                 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1602                     $scaleoptions = make_menu_from_list($scale->scale);
1603                     if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1604                         return false;
1605                     }
1606                 }
1607             }
1608         }
1610         $result = $DB->update_record('assign_grades', $grade);
1611         if ($result) {
1612             $this->gradebook_item_update(null, $grade);
1613         }
1614         return $result;
1615     }
1617     /**
1618      * View the grant extension date page.
1619      *
1620      * Uses url parameters 'userid'
1621      * or from parameter 'selectedusers'
1622      *
1623      * @param moodleform $mform - Used for validation of the submitted data
1624      * @return string
1625      */
1626     protected function view_grant_extension($mform) {
1627         global $DB, $CFG;
1628         require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1630         $o = '';
1631         $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
1632         $data = new stdClass();
1633         $data->extensionduedate = null;
1634         $userid = 0;
1635         if (!$batchusers) {
1636             $userid = required_param('userid', PARAM_INT);
1638             $grade = $this->get_user_grade($userid, false);
1640             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
1642             if ($grade) {
1643                 $data->extensionduedate = $grade->extensionduedate;
1644             }
1645             $data->userid = $userid;
1646         } else {
1647             $data->batchusers = $batchusers;
1648         }
1649         $header = new assign_header($this->get_instance(),
1650                                     $this->get_context(),
1651                                     $this->show_intro(),
1652                                     $this->get_course_module()->id,
1653                                     get_string('grantextension', 'assign'));
1654         $o .= $this->get_renderer()->render($header);
1656         if (!$mform) {
1657             $formparams = array($this->get_course_module()->id,
1658                                 $userid,
1659                                 $batchusers,
1660                                 $this->get_instance(),
1661                                 $data);
1662             $mform = new mod_assign_extension_form(null, $formparams);
1663         }
1664         $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
1665         $o .= $this->view_footer();
1666         return $o;
1667     }
1669     /**
1670      * Get a list of the users in the same group as this user.
1671      *
1672      * @param int $groupid The id of the group whose members we want or 0 for the default group
1673      * @param bool $onlyids Whether to retrieve only the user id's
1674      * @return array The users (possibly id's only)
1675      */
1676     public function get_submission_group_members($groupid, $onlyids) {
1677         $members = array();
1678         if ($groupid != 0) {
1679             if ($onlyids) {
1680                 $allusers = groups_get_members($groupid, 'u.id');
1681             } else {
1682                 $allusers = groups_get_members($groupid);
1683             }
1684             foreach ($allusers as $user) {
1685                 if ($this->get_submission_group($user->id)) {
1686                     $members[] = $user;
1687                 }
1688             }
1689         } else {
1690             $allusers = $this->list_participants(null, $onlyids);
1691             foreach ($allusers as $user) {
1692                 if ($this->get_submission_group($user->id) == null) {
1693                     $members[] = $user;
1694                 }
1695             }
1696         }
1697         return $members;
1698     }
1700     /**
1701      * Get a list of the users in the same group as this user that have not submitted the assignment.
1702      *
1703      * @param int $groupid The id of the group whose members we want or 0 for the default group
1704      * @param bool $onlyids Whether to retrieve only the user id's
1705      * @return array The users (possibly id's only)
1706      */
1707     public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
1708         $instance = $this->get_instance();
1709         if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
1710             return array();
1711         }
1712         $members = $this->get_submission_group_members($groupid, $onlyids);
1714         foreach ($members as $id => $member) {
1715             $submission = $this->get_user_submission($member->id, false);
1716             if ($submission && $submission->status != ASSIGN_SUBMISSION_STATUS_DRAFT) {
1717                 unset($members[$id]);
1718             } else {
1719                 if ($this->is_blind_marking()) {
1720                     $members[$id]->alias = get_string('hiddenuser', 'assign') .
1721                                            $this->get_uniqueid_for_user($id);
1722                 }
1723             }
1724         }
1725         return $members;
1726     }
1728     /**
1729      * Load the group submission object for a particular user, optionally creating it if required.
1730      *
1731      * @param int $userid The id of the user whose submission we want
1732      * @param int $groupid The id of the group for this user - may be 0 in which
1733      *                     case it is determined from the userid.
1734      * @param bool $create If set to true a new submission object will be created in the database
1735      * @return stdClass The submission
1736      */
1737     public function get_group_submission($userid, $groupid, $create) {
1738         global $DB;
1740         if ($groupid == 0) {
1741             $group = $this->get_submission_group($userid);
1742             if ($group) {
1743                 $groupid = $group->id;
1744             }
1745         }
1747         if ($create) {
1748             // Make sure there is a submission for this user.
1749             $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>0, 'userid'=>$userid);
1750             $submission = $DB->get_record('assign_submission', $params);
1752             if (!$submission) {
1753                 $submission = new stdClass();
1754                 $submission->assignment = $this->get_instance()->id;
1755                 $submission->userid = $userid;
1756                 $submission->groupid = 0;
1757                 $submission->timecreated = time();
1758                 $submission->timemodified = $submission->timecreated;
1760                 if ($this->get_instance()->submissiondrafts) {
1761                     $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1762                 } else {
1763                     $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1764                 }
1765                 $DB->insert_record('assign_submission', $submission);
1766             }
1767         }
1768         // Now get the group submission.
1769         $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
1770         $submission = $DB->get_record('assign_submission', $params);
1772         if ($submission) {
1773             return $submission;
1774         }
1775         if ($create) {
1776             $submission = new stdClass();
1777             $submission->assignment = $this->get_instance()->id;
1778             $submission->userid = 0;
1779             $submission->groupid = $groupid;
1780             $submission->timecreated = time();
1781             $submission->timemodified = $submission->timecreated;
1783             if ($this->get_instance()->submissiondrafts) {
1784                 $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
1785             } else {
1786                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1787             }
1788             $sid = $DB->insert_record('assign_submission', $submission);
1789             $submission->id = $sid;
1790             return $submission;
1791         }
1792         return false;
1793     }
1795     /**
1796      * View a summary listing of all assignments in the current course.
1797      *
1798      * @return string
1799      */
1800     private function view_course_index() {
1801         global $USER;
1803         $o = '';
1805         $course = $this->get_course();
1806         $strplural = get_string('modulenameplural', 'assign');
1808         if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
1809             $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
1810             $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
1811             return $o;
1812         }
1814         $strsectionname  = get_string('sectionname', 'format_'.$course->format);
1815         $usesections = course_format_uses_sections($course->format);
1816         $modinfo = get_fast_modinfo($course);
1818         if ($usesections) {
1819             $sections = $modinfo->get_section_info_all();
1820         }
1821         $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
1823         $timenow = time();
1825         $currentsection = '';
1826         foreach ($modinfo->instances['assign'] as $cm) {
1827             if (!$cm->uservisible) {
1828                 continue;
1829             }
1831             $timedue = $cms[$cm->id]->duedate;
1833             $sectionname = '';
1834             if ($usesections && $cm->sectionnum) {
1835                 $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
1836             }
1838             $submitted = '';
1839             $context = context_module::instance($cm->id);
1841             $assignment = new assign($context, $cm, $course);
1843             if (has_capability('mod/assign:grade', $context)) {
1844                 $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
1846             } else if (has_capability('mod/assign:submit', $context)) {
1847                 $usersubmission = $assignment->get_user_submission($USER->id, false);
1849                 if (!empty($usersubmission->status)) {
1850                     $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
1851                 } else {
1852                     $submitted = get_string('submissionstatus_', 'assign');
1853                 }
1854             }
1855             $grading_info = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
1856             if (isset($grading_info->items[0]->grades[$USER->id]) &&
1857                     !$grading_info->items[0]->grades[$USER->id]->hidden ) {
1858                 $grade = $grading_info->items[0]->grades[$USER->id]->str_grade;
1859             } else {
1860                 $grade = '-';
1861             }
1863             $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
1865         }
1867         $o .= $this->get_renderer()->render($courseindexsummary);
1868         $o .= $this->view_footer();
1870         return $o;
1871     }
1873     /**
1874      * View a page rendered by a plugin.
1875      *
1876      * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
1877      *
1878      * @return string
1879      */
1880     protected function view_plugin_page() {
1881         global $USER;
1883         $o = '';
1885         $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
1886         $plugintype = required_param('plugin', PARAM_TEXT);
1887         $pluginaction = required_param('pluginaction', PARAM_ALPHA);
1889         $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
1890         if (!$plugin) {
1891             print_error('invalidformdata', '');
1892             return;
1893         }
1895         $o .= $plugin->view_page($pluginaction);
1897         return $o;
1898     }
1901     /**
1902      * This is used for team assignments to get the group for the specified user.
1903      * If the user is a member of multiple or no groups this will return false
1904      *
1905      * @param int $userid The id of the user whose submission we want
1906      * @return mixed The group or false
1907      */
1908     public function get_submission_group($userid) {
1909         $grouping = $this->get_instance()->teamsubmissiongroupingid;
1910         $groups = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
1911         if (count($groups) != 1) {
1912             return false;
1913         }
1914         return array_pop($groups);
1915     }
1918     /**
1919      * Display the submission that is used by a plugin.
1920      *
1921      * Uses url parameters 'sid', 'gid' and 'plugin'.
1922      *
1923      * @param string $pluginsubtype
1924      * @return string
1925      */
1926     protected function view_plugin_content($pluginsubtype) {
1927         global $USER;
1929         $o = '';
1931         $submissionid = optional_param('sid', 0, PARAM_INT);
1932         $gradeid = optional_param('gid', 0, PARAM_INT);
1933         $plugintype = required_param('plugin', PARAM_TEXT);
1934         $item = null;
1935         if ($pluginsubtype == 'assignsubmission') {
1936             $plugin = $this->get_submission_plugin_by_type($plugintype);
1937             if ($submissionid <= 0) {
1938                 throw new coding_exception('Submission id should not be 0');
1939             }
1940             $item = $this->get_submission($submissionid);
1942             // Check permissions.
1943             if ($item->userid != $USER->id) {
1944                 require_capability('mod/assign:grade', $this->context);
1945             }
1946             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1947                                                               $this->get_context(),
1948                                                               $this->show_intro(),
1949                                                               $this->get_course_module()->id,
1950                                                               $plugin->get_name()));
1951             $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
1952                                                               $item,
1953                                                               assign_submission_plugin_submission::FULL,
1954                                                               $this->get_course_module()->id,
1955                                                               $this->get_return_action(),
1956                                                               $this->get_return_params()));
1958             $logmessage = get_string('viewsubmissionforuser', 'assign', $item->userid);
1959             $this->add_to_log('view submission', $logmessage);
1960         } else {
1961             $plugin = $this->get_feedback_plugin_by_type($plugintype);
1962             if ($gradeid <= 0) {
1963                 throw new coding_exception('Grade id should not be 0');
1964             }
1965             $item = $this->get_grade($gradeid);
1966             // Check permissions.
1967             if ($item->userid != $USER->id) {
1968                 require_capability('mod/assign:grade', $this->context);
1969             }
1970             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
1971                                                               $this->get_context(),
1972                                                               $this->show_intro(),
1973                                                               $this->get_course_module()->id,
1974                                                               $plugin->get_name()));
1975             $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
1976                                                               $item,
1977                                                               assign_feedback_plugin_feedback::FULL,
1978                                                               $this->get_course_module()->id,
1979                                                               $this->get_return_action(),
1980                                                               $this->get_return_params()));
1981             $logmessage = get_string('viewfeedbackforuser', 'assign', $item->userid);
1982             $this->add_to_log('view feedback', $logmessage);
1983         }
1985         $o .= $this->view_return_links();
1987         $o .= $this->view_footer();
1988         return $o;
1989     }
1991     /**
1992      * Rewrite plugin file urls so they resolve correctly in an exported zip.
1993      *
1994      * @param stdClass $user - The user record
1995      * @param assign_plugin $plugin - The assignment plugin
1996      */
1997     public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
1998         $groupmode = groups_get_activity_groupmode($this->get_course_module());
1999         $groupname = '';
2000         if ($groupmode) {
2001             $groupid = groups_get_activity_group($this->get_course_module(), true);
2002             $groupname = groups_get_group_name($groupid).'-';
2003         }
2005         if ($this->is_blind_marking()) {
2006             $prefix = $groupname . get_string('participant', 'assign');
2007             $prefix = str_replace('_', ' ', $prefix);
2008             $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2009         } else {
2010             $prefix = $groupname . fullname($user);
2011             $prefix = str_replace('_', ' ', $prefix);
2012             $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2013         }
2015         $subtype = $plugin->get_subtype();
2016         $type = $plugin->get_type();
2017         $prefix = $prefix . $subtype . '_' . $type . '_';
2019         $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2021         return $result;
2022     }
2024     /**
2025      * Render the content in editor that is often used by plugin.
2026      *
2027      * @param string $filearea
2028      * @param int  $submissionid
2029      * @param string $plugintype
2030      * @param string $editor
2031      * @param string $component
2032      * @return string
2033      */
2034     public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2035         global $CFG;
2037         $result = '';
2039         $plugin = $this->get_submission_plugin_by_type($plugintype);
2041         $text = $plugin->get_editor_text($editor, $submissionid);
2042         $format = $plugin->get_editor_format($editor, $submissionid);
2044         $finaltext = file_rewrite_pluginfile_urls($text,
2045                                                   'pluginfile.php',
2046                                                   $this->get_context()->id,
2047                                                   $component,
2048                                                   $filearea,
2049                                                   $submissionid);
2050         $params = array('overflowdiv' => true, 'context' => $this->get_context());
2051         $result .= format_text($finaltext, $format, $params);
2053         if ($CFG->enableportfolios) {
2054             require_once($CFG->libdir . '/portfoliolib.php');
2056             $button = new portfolio_add_button();
2057             $portfolioparams = array('cmid' => $this->get_course_module()->id,
2058                                      'sid' => $submissionid,
2059                                      'plugin' => $plugintype,
2060                                      'editor' => $editor,
2061                                      'area'=>$filearea);
2062             $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
2063             $fs = get_file_storage();
2065             if ($files = $fs->get_area_files($this->context->id,
2066                                              $component,
2067                                              $filearea,
2068                                              $submissionid,
2069                                              'timemodified',
2070                                              false)) {
2071                 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2072             } else {
2073                 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2074             }
2075             $result .= $button->to_html();
2076         }
2077         return $result;
2078     }
2080     /**
2081      * Display a grading error.
2082      *
2083      * @param string $message - The description of the result
2084      * @return string
2085      */
2086     protected function view_quickgrading_result($message) {
2087         $o = '';
2088         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2089                                                       $this->get_context(),
2090                                                       $this->show_intro(),
2091                                                       $this->get_course_module()->id,
2092                                                       get_string('quickgradingresult', 'assign')));
2093         $gradingresult = new assign_quickgrading_result($message, $this->get_course_module()->id);
2094         $o .= $this->get_renderer()->render($gradingresult);
2095         $o .= $this->view_footer();
2096         return $o;
2097     }
2099     /**
2100      * Display the page footer.
2101      *
2102      * @return string
2103      */
2104     protected function view_footer() {
2105         return $this->get_renderer()->render_footer();
2106     }
2108     /**
2109      * Does this user have grade permission for this assignment?
2110      *
2111      * @return bool
2112      */
2113     protected function can_grade() {
2114         // Permissions check.
2115         if (!has_capability('mod/assign:grade', $this->context)) {
2116             return false;
2117         }
2119         return true;
2120     }
2122     /**
2123      * Download a zip file of all assignment submissions.
2124      *
2125      * @return void
2126      */
2127     protected function download_submissions() {
2128         global $CFG, $DB;
2130         // More efficient to load this here.
2131         require_once($CFG->libdir.'/filelib.php');
2133         // Load all users with submit.
2134         $students = get_enrolled_users($this->context, "mod/assign:submit");
2136         // Build a list of files to zip.
2137         $filesforzipping = array();
2138         $fs = get_file_storage();
2140         $groupmode = groups_get_activity_groupmode($this->get_course_module());
2141         // All users.
2142         $groupid = 0;
2143         $groupname = '';
2144         if ($groupmode) {
2145             $groupid = groups_get_activity_group($this->get_course_module(), true);
2146             $groupname = groups_get_group_name($groupid).'-';
2147         }
2149         // Construct the zip file name.
2150         $filename = clean_filename($this->get_course()->shortname . '-' .
2151                                    $this->get_instance()->name . '-' .
2152                                    $groupname.$this->get_course_module()->id . '.zip');
2154         // Get all the files for each student.
2155         foreach ($students as $student) {
2156             $userid = $student->id;
2158             if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
2159                 // Get the plugins to add their own files to the zip.
2161                 $submissiongroup = false;
2162                 $groupname = '';
2163                 if ($this->get_instance()->teamsubmission) {
2164                     $submission = $this->get_group_submission($userid, 0, false);
2165                     $submissiongroup = $this->get_submission_group($userid);
2166                     if ($submissiongroup) {
2167                         $groupname = $submissiongroup->name . '-';
2168                     } else {
2169                         $groupname = get_string('defaultteam', 'assign') . '-';
2170                     }
2171                 } else {
2172                     $submission = $this->get_user_submission($userid, false);
2173                 }
2175                 if ($this->is_blind_marking()) {
2176                     $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2177                     $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2178                 } else {
2179                     $prefix = str_replace('_', ' ', $groupname . fullname($student));
2180                     $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2181                 }
2183                 if ($submission) {
2184                     foreach ($this->submissionplugins as $plugin) {
2185                         if ($plugin->is_enabled() && $plugin->is_visible()) {
2186                             $pluginfiles = $plugin->get_files($submission, $student);
2187                             foreach ($pluginfiles as $zipfilename => $file) {
2188                                 $subtype = $plugin->get_subtype();
2189                                 $type = $plugin->get_type();
2190                                 $prefixedfilename = clean_filename($prefix .
2191                                                                    $subtype .
2192                                                                    '_' .
2193                                                                    $type .
2194                                                                    '_' .
2195                                                                    $zipfilename);
2196                                 $filesforzipping[$prefixedfilename] = $file;
2197                             }
2198                         }
2199                     }
2200                 }
2201             }
2202         }
2203         $result = '';
2204         if (count($filesforzipping) == 0) {
2205             $header = new assign_header($this->get_instance(),
2206                                         $this->get_context(),
2207                                         '',
2208                                         $this->get_course_module()->id,
2209                                         get_string('downloadall', 'assign'));
2210             $result .= $this->get_renderer()->render($header);
2211             $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
2212             $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2213                                                                     'action'=>'grading'));
2214             $result .= $this->get_renderer()->continue_button($url);
2215             $result .= $this->view_footer();
2216         } else if ($zipfile = $this->pack_files($filesforzipping)) {
2217             $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
2218             // Send file and delete after sending.
2219             send_temp_file($zipfile, $filename);
2220             // We will not get here - send_temp_file calls exit.
2221         }
2222         return $result;
2223     }
2225     /**
2226      * Util function to add a message to the log.
2227      *
2228      * @param string $action The current action
2229      * @param string $info A detailed description of the change. But no more than 255 characters.
2230      * @param string $url The url to the assign module instance.
2231      * @return void
2232      */
2233     public function add_to_log($action = '', $info = '', $url='') {
2234         global $USER;
2236         $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2237         if ($url != '') {
2238             $fullurl .= '&' . $url;
2239         }
2241         add_to_log($this->get_course()->id,
2242                    'assign',
2243                    $action,
2244                    $fullurl,
2245                    $info,
2246                    $this->get_course_module()->id,
2247                    $USER->id);
2248     }
2250     /**
2251      * Lazy load the page renderer and expose the renderer to plugins.
2252      *
2253      * @return assign_renderer
2254      */
2255     public function get_renderer() {
2256         global $PAGE;
2257         if ($this->output) {
2258             return $this->output;
2259         }
2260         $this->output = $PAGE->get_renderer('mod_assign');
2261         return $this->output;
2262     }
2264     /**
2265      * Load the submission object for a particular user, optionally creating it if required.
2266      *
2267      * For team assignments there are 2 submissions - the student submission and the team submission
2268      * All files are associated with the team submission but the status of the students contribution is
2269      * recorded separately.
2270      *
2271      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
2272      * @param bool $create optional - defaults to false. If set to true a new submission object
2273      *                     will be created in the database.
2274      * @return stdClass The submission
2275      */
2276     public function get_user_submission($userid, $create) {
2277         global $DB, $USER;
2279         if (!$userid) {
2280             $userid = $USER->id;
2281         }
2282         // If the userid is not null then use userid.
2283         $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2284         $submission = $DB->get_record('assign_submission', $params);
2286         if ($submission) {
2287             return $submission;
2288         }
2289         if ($create) {
2290             $submission = new stdClass();
2291             $submission->assignment   = $this->get_instance()->id;
2292             $submission->userid       = $userid;
2293             $submission->timecreated = time();
2294             $submission->timemodified = $submission->timecreated;
2295             $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
2296             $sid = $DB->insert_record('assign_submission', $submission);
2297             $submission->id = $sid;
2298             return $submission;
2299         }
2300         return false;
2301     }
2303     /**
2304      * Load the submission object from it's id.
2305      *
2306      * @param int $submissionid The id of the submission we want
2307      * @return stdClass The submission
2308      */
2309     protected function get_submission($submissionid) {
2310         global $DB;
2312         $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2313         return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
2314     }
2316     /**
2317      * This will retrieve a grade object from the db, optionally creating it if required.
2318      *
2319      * @param int $userid The user we are grading
2320      * @param bool $create If true the grade will be created if it does not exist
2321      * @return stdClass The grade record
2322      */
2323     public function get_user_grade($userid, $create) {
2324         global $DB, $USER;
2326         if (!$userid) {
2327             $userid = $USER->id;
2328         }
2330         // If the userid is not null then use userid.
2331         $grade = $DB->get_record('assign_grades', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
2333         if ($grade) {
2334             return $grade;
2335         }
2336         if ($create) {
2337             $grade = new stdClass();
2338             $grade->assignment   = $this->get_instance()->id;
2339             $grade->userid       = $userid;
2340             $grade->timecreated = time();
2341             $grade->timemodified = $grade->timecreated;
2342             $grade->locked = 0;
2343             $grade->grade = -1;
2344             $grade->grader = $USER->id;
2345             $grade->extensionduedate = 0;
2347             // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2348             // This is because students only want to be notified about certain types of update (grades and feedback).
2349             $grade->mailed = 2;
2350             $gid = $DB->insert_record('assign_grades', $grade);
2351             $grade->id = $gid;
2352             return $grade;
2353         }
2354         return false;
2355     }
2357     /**
2358      * This will retrieve a grade object from the db.
2359      *
2360      * @param int $gradeid The id of the grade
2361      * @return stdClass The grade record
2362      */
2363     protected function get_grade($gradeid) {
2364         global $DB;
2366         $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
2367         return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
2368     }
2370     /**
2371      * Print the grading page for a single user submission.
2372      *
2373      * @param moodleform $mform
2374      * @return string
2375      */
2376     protected function view_single_grade_page($mform) {
2377         global $DB, $CFG;
2379         $o = '';
2380         $instance = $this->get_instance();
2382         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2384         // Need submit permission to submit an assignment.
2385         require_capability('mod/assign:grade', $this->context);
2387         $header = new assign_header($instance,
2388                                     $this->get_context(),
2389                                     false,
2390                                     $this->get_course_module()->id,
2391                                     get_string('grading', 'assign'));
2392         $o .= $this->get_renderer()->render($header);
2394         $rownum = required_param('rownum', PARAM_INT);
2395         $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
2396         $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
2397         if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
2398             $useridlist = $this->get_grading_userid_list();
2399             $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
2400         }
2402         if ($rownum < 0 || $rownum > count($useridlist)) {
2403             throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
2404         }
2406         $last = false;
2407         $userid = $useridlist[$rownum];
2408         if ($rownum == count($useridlist) - 1) {
2409             $last = true;
2410         }
2411         $user = $DB->get_record('user', array('id' => $userid));
2412         if ($user) {
2413             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2414             $usersummary = new assign_user_summary($user,
2415                                                    $this->get_course()->id,
2416                                                    $viewfullnames,
2417                                                    $this->is_blind_marking(),
2418                                                    $this->get_uniqueid_for_user($user->id),
2419                                                    get_extra_user_fields($this->get_context()));
2420             $o .= $this->get_renderer()->render($usersummary);
2421         }
2422         $submission = $this->get_user_submission($userid, false);
2423         $submissiongroup = null;
2424         $submissiongroupmemberswhohavenotsubmitted = array();
2425         $teamsubmission = null;
2426         $notsubmitted = array();
2427         if ($instance->teamsubmission) {
2428             $teamsubmission = $this->get_group_submission($userid, 0, false);
2429             $submissiongroup = $this->get_submission_group($userid);
2430             $groupid = 0;
2431             if ($submissiongroup) {
2432                 $groupid = $submissiongroup->id;
2433             }
2434             $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2436         }
2438         // Get the current grade.
2439         $grade = $this->get_user_grade($userid, false);
2440         if ($this->can_view_submission($userid)) {
2441             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
2442             $extensionduedate = null;
2443             if ($grade) {
2444                 $extensionduedate = $grade->extensionduedate;
2445             }
2446             $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
2448             if ($teamsubmission) {
2449                 $showsubmit = $showedit &&
2450                               $teamsubmission &&
2451                               ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
2452             } else {
2453                 $showsubmit = $showedit &&
2454                               $submission &&
2455                               ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
2456             }
2457             if (!$this->get_instance()->submissiondrafts) {
2458                 $showsubmit = false;
2459             }
2460             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2462             $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
2463                                                              $instance->alwaysshowdescription,
2464                                                              $submission,
2465                                                              $instance->teamsubmission,
2466                                                              $teamsubmission,
2467                                                              $submissiongroup,
2468                                                              $notsubmitted,
2469                                                              $this->is_any_submission_plugin_enabled(),
2470                                                              $gradelocked,
2471                                                              $this->is_graded($userid),
2472                                                              $instance->duedate,
2473                                                              $instance->cutoffdate,
2474                                                              $this->get_submission_plugins(),
2475                                                              $this->get_return_action(),
2476                                                              $this->get_return_params(),
2477                                                              $this->get_course_module()->id,
2478                                                              $this->get_course()->id,
2479                                                              assign_submission_status::GRADER_VIEW,
2480                                                              $showedit,
2481                                                              $showsubmit,
2482                                                              $viewfullnames,
2483                                                              $extensionduedate,
2484                                                              $this->get_context(),
2485                                                              $this->is_blind_marking(),
2486                                                              '');
2487             $o .= $this->get_renderer()->render($submissionstatus);
2488         }
2489         if ($grade) {
2490             $data = new stdClass();
2491             if ($grade->grade !== null && $grade->grade >= 0) {
2492                 $data->grade = format_float($grade->grade, 2);
2493             }
2494         } else {
2495             $data = new stdClass();
2496             $data->grade = '';
2497         }
2499         // Now show the grading form.
2500         if (!$mform) {
2501             $pagination = array( 'rownum'=>$rownum, 'useridlistid'=>$useridlistid, 'last'=>$last);
2502             $formparams = array($this, $data, $pagination);
2503             $mform = new mod_assign_grade_form(null,
2504                                                $formparams,
2505                                                'post',
2506                                                '',
2507                                                array('class'=>'gradeform'));
2508         }
2509         $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
2511         $msg = get_string('viewgradingformforstudent',
2512                           'assign',
2513                           array('id'=>$user->id, 'fullname'=>fullname($user)));
2514         $this->add_to_log('view grading form', $msg);
2516         $o .= $this->view_footer();
2517         return $o;
2518     }
2520     /**
2521      * Show a confirmation page to make sure they want to release student identities.
2522      *
2523      * @return string
2524      */
2525     protected function view_reveal_identities_confirm() {
2526         global $CFG, $USER;
2528         require_capability('mod/assign:revealidentities', $this->get_context());
2530         $o = '';
2531         $header = new assign_header($this->get_instance(),
2532                                     $this->get_context(),
2533                                     false,
2534                                     $this->get_course_module()->id);
2535         $o .= $this->get_renderer()->render($header);
2537         $urlparams = array('id'=>$this->get_course_module()->id,
2538                            'action'=>'revealidentitiesconfirm',
2539                            'sesskey'=>sesskey());
2540         $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
2542         $urlparams = array('id'=>$this->get_course_module()->id,
2543                            'action'=>'grading');
2544         $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
2546         $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
2547                                              $confirmurl,
2548                                              $cancelurl);
2549         $o .= $this->view_footer();
2550         $this->add_to_log('view', get_string('viewrevealidentitiesconfirm', 'assign'));
2551         return $o;
2552     }
2554     /**
2555      * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
2556      *
2557      * @return string
2558      */
2559     protected function view_return_links() {
2560         $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
2561         $returnparams = optional_param('returnparams', '', PARAM_TEXT);
2563         $params = array();
2564         $returnparams = str_replace('&amp;', '&', $returnparams);
2565         parse_str($returnparams, $params);
2566         $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
2567         $params = array_merge($newparams, $params);
2569         $url = new moodle_url('/mod/assign/view.php', $params);
2570         return $this->get_renderer()->single_button($url, get_string('back'), 'get');
2571     }
2573     /**
2574      * View the grading table of all submissions for this assignment.
2575      *
2576      * @return string
2577      */
2578     protected function view_grading_table() {
2579         global $USER, $CFG;
2581         // Include grading options form.
2582         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
2583         require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
2584         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2585         $o = '';
2586         $cmid = $this->get_course_module()->id;
2588         $links = array();
2589         if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
2590                 has_capability('moodle/grade:viewall', $this->get_course_context())) {
2591             $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
2592             $links[$gradebookurl] = get_string('viewgradebook', 'assign');
2593         }
2594         if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
2595             $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
2596             $links[$downloadurl] = get_string('downloadall', 'assign');
2597         }
2598         if ($this->is_blind_marking() &&
2599                 has_capability('mod/assign:revealidentities', $this->get_context())) {
2600             $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
2601             $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
2602         }
2603         foreach ($this->get_feedback_plugins() as $plugin) {
2604             if ($plugin->is_enabled() && $plugin->is_visible()) {
2605                 foreach ($plugin->get_grading_actions() as $action => $description) {
2606                     $url = '/mod/assign/view.php' .
2607                            '?id=' .  $cmid .
2608                            '&plugin=' . $plugin->get_type() .
2609                            '&pluginsubtype=assignfeedback' .
2610                            '&action=viewpluginpage&pluginaction=' . $action;
2611                     $links[$url] = $description;
2612                 }
2613             }
2614         }
2616         $gradingactions = new url_select($links);
2617         $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
2619         $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
2621         $perpage = get_user_preferences('assign_perpage', 10);
2622         $filter = get_user_preferences('assign_filter', '');
2623         $controller = $gradingmanager->get_active_controller();
2624         $showquickgrading = empty($controller);
2625         if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
2626             $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
2627             set_user_preference('assign_quickgrading', $quickgrading);
2628         }
2629         $quickgrading = get_user_preferences('assign_quickgrading', false);
2631         // Print options for changing the filter and changing the number of results per page.
2632         $gradingoptionsformparams = array('cm'=>$cmid,
2633                                           'contextid'=>$this->context->id,
2634                                           'userid'=>$USER->id,
2635                                           'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
2636                                           'showquickgrading'=>$showquickgrading,
2637                                           'quickgrading'=>$quickgrading);
2639         $classoptions = array('class'=>'gradingoptionsform');
2640         $gradingoptionsform = new mod_assign_grading_options_form(null,
2641                                                                   $gradingoptionsformparams,
2642                                                                   'post',
2643                                                                   '',
2644                                                                   $classoptions);
2646         $batchformparams = array('cm'=>$cmid,
2647                                  'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2648                                  'duedate'=>$this->get_instance()->duedate,
2649                                  'feedbackplugins'=>$this->get_feedback_plugins());
2650         $classoptions = array('class'=>'gradingbatchoperationsform');
2652         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
2653                                                                                    $batchformparams,
2654                                                                                    'post',
2655                                                                                    '',
2656                                                                                    $classoptions);
2658         $gradingoptionsdata = new stdClass();
2659         $gradingoptionsdata->perpage = $perpage;
2660         $gradingoptionsdata->filter = $filter;
2661         $gradingoptionsform->set_data($gradingoptionsdata);
2663         $actionformtext = $this->get_renderer()->render($gradingactions);
2664         $header = new assign_header($this->get_instance(),
2665                                     $this->get_context(),
2666                                     false,
2667                                     $this->get_course_module()->id,
2668                                     get_string('grading', 'assign'),
2669                                     $actionformtext);
2670         $o .= $this->get_renderer()->render($header);
2672         $currenturl = $CFG->wwwroot .
2673                       '/mod/assign/view.php?id=' .
2674                       $this->get_course_module()->id .
2675                       '&action=grading';
2677         $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
2679         // Plagiarism update status apearring in the grading book.
2680         if (!empty($CFG->enableplagiarism)) {
2681             require_once($CFG->libdir . '/plagiarismlib.php');
2682             $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
2683         }
2685         // Load and print the table of submissions.
2686         if ($showquickgrading && $quickgrading) {
2687             $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, true);
2688             $table = $this->get_renderer()->render($gradingtable);
2689             $quickformparams = array('cm'=>$this->get_course_module()->id, 'gradingtable'=>$table);
2690             $quickgradingform = new mod_assign_quick_grading_form(null, $quickformparams);
2692             $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
2693         } else {
2694             $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, false);
2695             $o .= $this->get_renderer()->render($gradingtable);
2696         }
2698         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
2699         $users = array_keys($this->list_participants($currentgroup, true));
2700         if (count($users) != 0) {
2701             // If no enrolled user in a course then don't display the batch operations feature.
2702             $assignform = new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform);
2703             $o .= $this->get_renderer()->render($assignform);
2704         }
2705         $assignform = new assign_form('gradingoptionsform',
2706                                       $gradingoptionsform,
2707                                       'M.mod_assign.init_grading_options');
2708         $o .= $this->get_renderer()->render($assignform);
2709         return $o;
2710     }
2712     /**
2713      * View entire grading page.
2714      *
2715      * @return string
2716      */
2717     protected function view_grading_page() {
2718         global $CFG;
2720         $o = '';
2721         // Need submit permission to submit an assignment.
2722         require_capability('mod/assign:grade', $this->context);
2723         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2725         // Only load this if it is.
2727         $o .= $this->view_grading_table();
2729         $o .= $this->view_footer();
2731         $logmessage = get_string('viewsubmissiongradingtable', 'assign');
2732         $this->add_to_log('view submission grading table', $logmessage);
2733         return $o;
2734     }
2736     /**
2737      * Capture the output of the plagiarism plugins disclosures and return it as a string.
2738      *
2739      * @return void
2740      */
2741     protected function plagiarism_print_disclosure() {
2742         global $CFG;
2743         $o = '';
2745         if (!empty($CFG->enableplagiarism)) {
2746             require_once($CFG->libdir . '/plagiarismlib.php');
2748             $o .= plagiarism_print_disclosure($this->get_course_module()->id);
2749         }
2751         return $o;
2752     }
2754     /**
2755      * Message for students when assignment submissions have been closed.
2756      *
2757      * @return string
2758      */
2759     protected function view_student_error_message() {
2760         global $CFG;
2762         $o = '';
2763         // Need submit permission to submit an assignment.
2764         require_capability('mod/assign:submit', $this->context);
2766         $header = new assign_header($this->get_instance(),
2767                                     $this->get_context(),
2768                                     $this->show_intro(),
2769                                     $this->get_course_module()->id,
2770                                     get_string('editsubmission', 'assign'));
2771         $o .= $this->get_renderer()->render($header);
2773         $o .= $this->get_renderer()->notification(get_string('submissionsclosed', 'assign'));
2775         $o .= $this->view_footer();
2777         return $o;
2779     }
2781     /**
2782      * View edit submissions page.
2783      *
2784      * @param moodleform $mform
2785      * @param array $notices A list of notices to display at the top of the
2786      *                       edit submission form (e.g. from plugins).
2787      * @return void
2788      */
2789     protected function view_edit_submission_page($mform, $notices) {
2790         global $CFG;
2792         $o = '';
2793         require_once($CFG->dirroot . '/mod/assign/submission_form.php');
2794         // Need submit permission to submit an assignment.
2795         require_capability('mod/assign:submit', $this->context);
2797         if (!$this->submissions_open()) {
2798             return $this->view_student_error_message();
2799         }
2800         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2801                                                       $this->get_context(),
2802                                                       $this->show_intro(),
2803                                                       $this->get_course_module()->id,
2804                                                       get_string('editsubmission', 'assign')));
2805         $o .= $this->plagiarism_print_disclosure();
2806         $data = new stdClass();
2808         if (!$mform) {
2809             $mform = new mod_assign_submission_form(null, array($this, $data));
2810         }
2812         foreach ($notices as $notice) {
2813             $o .= $this->get_renderer()->notification($notice);
2814         }
2816         $o .= $this->get_renderer()->render(new assign_form('editsubmissionform', $mform));
2818         $o .= $this->view_footer();
2819         $this->add_to_log('view submit assignment form', get_string('viewownsubmissionform', 'assign'));
2821         return $o;
2822     }
2824     /**
2825      * See if this assignment has a grade yet.
2826      *
2827      * @param int $userid
2828      * @return bool
2829      */
2830     protected function is_graded($userid) {
2831         $grade = $this->get_user_grade($userid, false);
2832         if ($grade) {
2833             return ($grade->grade !== null && $grade->grade >= 0);
2834         }
2835         return false;
2836     }
2838     /**
2839      * Perform an access check to see if the current $USER can view this group submission.
2840      *
2841      * @param int $groupid
2842      * @return bool
2843      */
2844     public function can_view_group_submission($groupid) {
2845         global $USER;
2847         if (!is_enrolled($this->get_course_context(), $USER->id)) {
2848             return false;
2849         }
2850         if (has_capability('mod/assign:grade', $this->context)) {
2851             return true;
2852         }
2853         $members = $this->get_submission_group_members($groupid, true);
2854         foreach ($members as $member) {
2855             if ($member->id == $USER->id) {
2856                 return true;
2857             }
2858         }
2859         return false;
2860     }
2862     /**
2863      * Perform an access check to see if the current $USER can view this users submission.
2864      *
2865      * @param int $userid
2866      * @return bool
2867      */
2868     public function can_view_submission($userid) {
2869         global $USER;
2871         if (!is_enrolled($this->get_course_context(), $userid)) {
2872             return false;
2873         }
2874         if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
2875             return true;
2876         }
2877         if (has_capability('mod/assign:grade', $this->context)) {
2878             return true;
2879         }
2880         return false;
2881     }
2883     /**
2884      * Allows the plugin to show a batch grading operation page.
2885      *
2886      * @return none
2887      */
2888     protected function view_plugin_grading_batch_operation($mform) {
2889         require_capability('mod/assign:grade', $this->context);
2890         $prefix = 'plugingradingbatchoperation_';
2892         if ($data = $mform->get_data()) {
2893             $tail = substr($data->operation, strlen($prefix));
2894             list($plugintype, $action) = explode('_', $tail, 2);
2896             $plugin = $this->get_feedback_plugin_by_type($plugintype);
2897             if ($plugin) {
2898                 $users = $data->selectedusers;
2899                 $userlist = explode(',', $users);
2900                 echo $plugin->grading_batch_operation($action, $userlist);
2901                 return;
2902             }
2903         }
2904         print_error('invalidformdata', '');
2905     }
2907     /**
2908      * Ask the user to confirm they want to perform this batch operation
2909      *
2910      * @param moodleform $mform Set to a grading batch operations form
2911      * @return string - the page to view after processing these actions
2912      */
2913     protected function process_grading_batch_operation(& $mform) {
2914         global $CFG;
2915         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
2916         require_sesskey();
2918         $batchformparams = array('cm'=>$this->get_course_module()->id,
2919                                  'submissiondrafts'=>$this->get_instance()->submissiondrafts,
2920                                  'duedate'=>$this->get_instance()->duedate,
2921                                  'feedbackplugins'=>$this->get_feedback_plugins());
2922         $formclasses = array('class'=>'gradingbatchoperationsform');
2923         $mform = new mod_assign_grading_batch_operations_form(null,
2924                                                               $batchformparams,
2925                                                               'post',
2926                                                               '',
2927                                                               $formclasses);
2929         if ($data = $mform->get_data()) {
2930             // Get the list of users.
2931             $users = $data->selectedusers;
2932             $userlist = explode(',', $users);
2934             $prefix = 'plugingradingbatchoperation_';
2936             if ($data->operation == 'grantextension') {
2937                 // Reset the form so the grant extension page will create the extension form.
2938                 $mform = null;
2939                 return 'grantextension';
2940             } else if (strpos($data->operation, $prefix) === 0) {
2941                 $tail = substr($data->operation, strlen($prefix));
2942                 list($plugintype, $action) = explode('_', $tail, 2);
2944                 $plugin = $this->get_feedback_plugin_by_type($plugintype);
2945                 if ($plugin) {
2946                     return 'plugingradingbatchoperation';
2947                 }
2948             }
2950             foreach ($userlist as $userid) {
2951                 if ($data->operation == 'lock') {
2952                     $this->process_lock($userid);
2953                 } else if ($data->operation == 'unlock') {
2954                     $this->process_unlock($userid);
2955                 } else if ($data->operation == 'reverttodraft') {
2956                     $this->process_revert_to_draft($userid);
2957                 }
2958             }
2959         }
2961         return 'grading';
2962     }
2964     /**
2965      * Ask the user to confirm they want to submit their work for grading.
2966      *
2967      * @param $mform moodleform - null unless form validation has failed
2968      * @return string
2969      */
2970     protected function check_submit_for_grading($mform) {
2971         global $USER, $CFG;
2973         require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
2975         // Check that all of the submission plugins are ready for this submission.
2976         $notifications = array();
2977         $submission = $this->get_user_submission($USER->id, false);
2978         $plugins = $this->get_submission_plugins();
2979         foreach ($plugins as $plugin) {
2980             if ($plugin->is_enabled() && $plugin->is_visible()) {
2981                 $check = $plugin->precheck_submission($submission);
2982                 if ($check !== true) {
2983                     $notifications[] = $check;
2984                 }
2985             }
2986         }
2988         $data = new stdClass();
2989         $adminconfig = $this->get_admin_config();
2990         $requiresubmissionstatement = (!empty($adminconfig->requiresubmissionstatement) ||
2991                                        $this->get_instance()->requiresubmissionstatement) &&
2992                                        !empty($adminconfig->submissionstatement);
2994         $submissionstatement = '';
2995         if (!empty($adminconfig->submissionstatement)) {
2996             $submissionstatement = $adminconfig->submissionstatement;
2997         }
2999         if ($mform == null) {
3000             $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
3001                                                                         $submissionstatement,
3002                                                                         $this->get_course_module()->id,
3003                                                                         $data));
3004         }
3005         $o = '';
3006         $o .= $this->get_renderer()->header();
3007         $submitforgradingpage = new assign_submit_for_grading_page($notifications,
3008                                                                    $this->get_course_module()->id,
3009                                                                    $mform);
3010         $o .= $this->get_renderer()->render($submitforgradingpage);
3011         $o .= $this->view_footer();
3013         $logmessage = get_string('viewownsubmissionform', 'assign');
3014         $this->add_to_log('view confirm submit assignment form', $logmessage);
3016         return $o;
3017     }
3019     /**
3020      * Print 2 tables of information with no action links -
3021      * the submission summary and the grading summary.
3022      *
3023      * @param stdClass $user the user to print the report for
3024      * @param bool $showlinks - Return plain text or links to the profile
3025      * @return string - the html summary
3026      */
3027     public function view_student_summary($user, $showlinks) {
3028         global $CFG, $DB, $PAGE;
3030         $instance = $this->get_instance();
3031         $grade = $this->get_user_grade($user->id, false);
3032         $submission = $this->get_user_submission($user->id, false);
3033         $o = '';
3035         $teamsubmission = null;
3036         $submissiongroup = null;
3037         $notsubmitted = array();
3038         if ($instance->teamsubmission) {
3039             $teamsubmission = $this->get_group_submission($user->id, 0, false);
3040             $submissiongroup = $this->get_submission_group($user->id);
3041             $groupid = 0;
3042             if ($submissiongroup) {
3043                 $groupid = $submissiongroup->id;
3044             }
3045             $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3046         }
3048         if ($this->can_view_submission($user->id)) {
3049             $showedit = has_capability('mod/assign:submit', $this->context) &&
3050                         $this->submissions_open($user->id) &&
3051                         ($this->is_any_submission_plugin_enabled()) &&
3052                         $showlinks;
3054             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
3056             // Grading criteria preview.
3057             $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
3058             $gradingcontrollerpreview = '';
3059             if ($gradingmethod = $gradingmanager->get_active_method()) {
3060                 $controller = $gradingmanager->get_controller($gradingmethod);
3061                 if ($controller->is_form_defined()) {
3062                     $gradingcontrollerpreview = $controller->render_preview($PAGE);
3063                 }
3064             }
3066             $showsubmit = ($submission || $teamsubmission) && $showlinks;
3067             if ($teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
3068                 $showsubmit = false;
3069             }
3070             if ($submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
3071                 $showsubmit = false;
3072             }
3073             if (!$this->get_instance()->submissiondrafts) {
3074                 $showsubmit = false;
3075             }
3076             $extensionduedate = null;
3077             if ($grade) {
3078                 $extensionduedate = $grade->extensionduedate;
3079             }
3080             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3082             $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
3083                                                               $instance->alwaysshowdescription,
3084                                                               $submission,
3085                                                               $instance->teamsubmission,
3086                                                               $teamsubmission,
3087                                                               $submissiongroup,
3088                                                               $notsubmitted,
3089                                                               $this->is_any_submission_plugin_enabled(),
3090                                                               $gradelocked,
3091                                                               $this->is_graded($user->id),
3092                                                               $instance->duedate,
3093                                                               $instance->cutoffdate,
3094                                                               $this->get_submission_plugins(),
3095                                                               $this->get_return_action(),
3096                                                               $this->get_return_params(),
3097                                                               $this->get_course_module()->id,
3098                                                               $this->get_course()->id,
3099                                                               assign_submission_status::STUDENT_VIEW,
3100                                                               $showedit,
3101                                                               $showsubmit,
3102                                                               $viewfullnames,
3103                                                               $extensionduedate,
3104                                                               $this->get_context(),
3105                                                               $this->is_blind_marking(),
3106                                                               $gradingcontrollerpreview);
3107             $o .= $this->get_renderer()->render($submissionstatus);
3109             require_once($CFG->libdir.'/gradelib.php');
3110             require_once($CFG->dirroot.'/grade/grading/lib.php');
3112             $gradinginfo = grade_get_grades($this->get_course()->id,
3113                                         'mod',
3114                                         'assign',
3115                                         $instance->id,
3116                                         $user->id);
3118             $gradingitem = null;
3119             $gradebookgrade = null;
3120             if (isset($gradinginfo->items[0])) {
3121                 $gradingitem = $gradinginfo->items[0];
3122                 $gradebookgrade = $gradingitem->grades[$user->id];
3123             }
3125             // Check to see if all feedback plugins are empty.
3126             $emptyplugins = true;
3127             if ($grade) {
3128                 foreach ($this->get_feedback_plugins() as $plugin) {
3129                     if ($plugin->is_visible() && $plugin->is_enabled()) {
3130                         if (!$plugin->is_empty($grade)) {
3131                             $emptyplugins = false;
3132                         }
3133                     }
3134                 }
3135             }
3137             $cangrade = has_capability('mod/assign:grade', $this->get_context());
3138             // If there is feedback or a visible grade, show the summary.
3139             if ((!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) ||
3140                     !$emptyplugins) {
3142                 $gradefordisplay = null;
3143                 $gradeddate = null;
3144                 $grader = null;
3145                 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3147                 // Only show the grade if it is not hidden in gradebook.
3148                 if (!empty($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) {
3149                     if ($controller = $gradingmanager->get_active_controller()) {
3150                         $controller->set_grade_range(make_grades_menu($this->get_instance()->grade));
3151                         $gradefordisplay = $controller->render_grade($PAGE,
3152                                                                      $grade->id,
3153                                                                      $gradingitem,
3154                                                                      $gradebookgrade->str_long_grade,
3155                                                                      $cangrade);
3156                     } else {
3157                         $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
3158                     }
3159                     $gradeddate = $gradebookgrade->dategraded;
3160                     if (isset($grade->grader)) {
3161                         $grader = $DB->get_record('user', array('id'=>$grade->grader));
3162                     }
3163                 }
3165                 $feedbackstatus = new assign_feedback_status($gradefordisplay,
3166                                                       $gradeddate,
3167                                                       $grader,
3168                                                       $this->get_feedback_plugins(),
3169                                                       $grade,
3170                                                       $this->get_course_module()->id,
3171                                                       $this->get_return_action(),
3172                                                       $this->get_return_params());
3174                 $o .= $this->get_renderer()->render($feedbackstatus);
3175             }
3177         }
3178         return $o;
3179     }
3181     /**
3182      * View submissions page (contains details of current submission).
3183      *
3184      * @return string
3185      */
3186     protected function view_submission_page() {
3187         global $CFG, $DB, $USER, $PAGE;
3189         $instance = $this->get_instance();
3191         $o = '';
3192         $o .= $this->get_renderer()->render(new assign_header($instance,
3193                                                       $this->get_context(),
3194                                                       $this->show_intro(),
3195                                                       $this->get_course_module()->id));
3197         if ($this->can_grade()) {
3198             $draft = ASSIGN_SUBMISSION_STATUS_DRAFT;
3199             $submitted = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
3200             if ($instance->teamsubmission) {
3201                 $summary = new assign_grading_summary($this->count_teams(),
3202                                                       $instance->submissiondrafts,
3203                                                       $this->count_submissions_with_status($draft),
3204                                                       $this->is_any_submission_plugin_enabled(),
3205                                                       $this->count_submissions_with_status($submitted),
3206                                                       $instance->cutoffdate,
3207                                                       $instance->duedate,
3208                                                       $this->get_course_module()->id,
3209                                                       $this->count_submissions_need_grading(),
3210                                                       $instance->teamsubmission);
3211                 $o .= $this->get_renderer()->render($summary);
3212             } else {
3213                 $summary = new assign_grading_summary($this->count_participants(0),
3214                                                       $instance->submissiondrafts,
3215                                                       $this->count_submissions_with_status($draft),
3216                                                       $this->is_any_submission_plugin_enabled(),
3217                                                       $this->count_submissions_with_status($submitted),
3218                                                       $instance->cutoffdate,
3219                                                       $instance->duedate,
3220                                                       $this->get_course_module()->id,
3221                                                       $this->count_submissions_need_grading(),
3222                                                       $instance->teamsubmission);
3223                 $o .= $this->get_renderer()->render($summary);