f3539480110286ecd49f2e11f2c8d00782d79a04
[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_NEW', 'new');
31 define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
32 define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
33 define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
35 // Search filters for grading page.
36 define('ASSIGN_FILTER_SUBMITTED', 'submitted');
37 define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted');
38 define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
39 define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
41 // Marker filter for grading page.
42 define('ASSIGN_MARKER_FILTER_NO_MARKER', -1);
44 // Reopen attempt methods.
45 define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
46 define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
47 define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
49 // Special value means allow unlimited attempts.
50 define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
52 // Grading states.
53 define('ASSIGN_GRADING_STATUS_GRADED', 'graded');
54 define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded');
56 // Marking workflow states.
57 define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
58 define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
59 define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
60 define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
61 define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
62 define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
64 // Name of file area for intro attachments.
65 define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment');
67 require_once($CFG->libdir . '/accesslib.php');
68 require_once($CFG->libdir . '/formslib.php');
69 require_once($CFG->dirroot . '/repository/lib.php');
70 require_once($CFG->dirroot . '/mod/assign/mod_form.php');
71 require_once($CFG->libdir . '/gradelib.php');
72 require_once($CFG->dirroot . '/grade/grading/lib.php');
73 require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
74 require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
75 require_once($CFG->dirroot . '/mod/assign/renderable.php');
76 require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
77 require_once($CFG->libdir . '/eventslib.php');
78 require_once($CFG->libdir . '/portfolio/caller.php');
80 /**
81  * Standard base class for mod_assign (assignment types).
82  *
83  * @package   mod_assign
84  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
85  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
86  */
87 class assign {
89     /** @var stdClass the assignment record that contains the global settings for this assign instance */
90     private $instance;
92     /** @var stdClass the grade_item record for this assign instance's primary grade item. */
93     private $gradeitem;
95     /** @var context the context of the course module for this assign instance
96      *               (or just the course if we are creating a new one)
97      */
98     private $context;
100     /** @var stdClass the course this assign instance belongs to */
101     private $course;
103     /** @var stdClass the admin config for all assign instances  */
104     private $adminconfig;
106     /** @var assign_renderer the custom renderer for this module */
107     private $output;
109     /** @var cm_info the course module for this assign instance */
110     private $coursemodule;
112     /** @var array cache for things like the coursemodule name or the scale menu -
113      *             only lives for a single request.
114      */
115     private $cache;
117     /** @var array list of the installed submission plugins */
118     private $submissionplugins;
120     /** @var array list of the installed feedback plugins */
121     private $feedbackplugins;
123     /** @var string action to be used to return to this page
124      *              (without repeating any form submissions etc).
125      */
126     private $returnaction = 'view';
128     /** @var array params to be used to return to this page */
129     private $returnparams = array();
131     /** @var string modulename prevents excessive calls to get_string */
132     private static $modulename = null;
134     /** @var string modulenameplural prevents excessive calls to get_string */
135     private static $modulenameplural = null;
137     /** @var array of marking workflow states for the current user */
138     private $markingworkflowstates = null;
140     /** @var bool whether to exclude users with inactive enrolment */
141     private $showonlyactiveenrol = null;
143     /** @var string A key used to identify userlists created by this object. */
144     private $useridlistid = null;
146     /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
147     private $participants = array();
149     /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */
150     private $usersubmissiongroups = array();
152     /** @var array cached list of user groups. The cache key will be the user. */
153     private $usergroups = array();
155     /** @var array cached list of IDs of users who share group membership with the user. The cache key will be the user. */
156     private $sharedgroupmembers = array();
158     /**
159      * Constructor for the base assign class.
160      *
161      * Note: For $coursemodule you can supply a stdclass if you like, but it
162      * will be more efficient to supply a cm_info object.
163      *
164      * @param mixed $coursemodulecontext context|null the course module context
165      *                                   (or the course context if the coursemodule has not been
166      *                                   created yet).
167      * @param mixed $coursemodule the current course module if it was already loaded,
168      *                            otherwise this class will load one from the context as required.
169      * @param mixed $course the current course  if it was already loaded,
170      *                      otherwise this class will load one from the context as required.
171      */
172     public function __construct($coursemodulecontext, $coursemodule, $course) {
173         global $SESSION;
175         $this->context = $coursemodulecontext;
176         $this->course = $course;
178         // Ensure that $this->coursemodule is a cm_info object (or null).
179         $this->coursemodule = cm_info::create($coursemodule);
181         // Temporary cache only lives for a single request - used to reduce db lookups.
182         $this->cache = array();
184         $this->submissionplugins = $this->load_plugins('assignsubmission');
185         $this->feedbackplugins = $this->load_plugins('assignfeedback');
187         // Extra entropy is required for uniqid() to work on cygwin.
188         $this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM);
190         if (!isset($SESSION->mod_assign_useridlist)) {
191             $SESSION->mod_assign_useridlist = [];
192         }
193     }
195     /**
196      * Set the action and parameters that can be used to return to the current page.
197      *
198      * @param string $action The action for the current page
199      * @param array $params An array of name value pairs which form the parameters
200      *                      to return to the current page.
201      * @return void
202      */
203     public function register_return_link($action, $params) {
204         global $PAGE;
205         $params['action'] = $action;
206         $currenturl = $PAGE->url;
208         $currenturl->params($params);
209         $PAGE->set_url($currenturl);
210     }
212     /**
213      * Return an action that can be used to get back to the current page.
214      *
215      * @return string action
216      */
217     public function get_return_action() {
218         global $PAGE;
220         $params = $PAGE->url->params();
222         if (!empty($params['action'])) {
223             return $params['action'];
224         }
225         return '';
226     }
228     /**
229      * Based on the current assignment settings should we display the intro.
230      *
231      * @return bool showintro
232      */
233     public function show_intro() {
234         if ($this->get_instance()->alwaysshowdescription ||
235                 time() > $this->get_instance()->allowsubmissionsfromdate) {
236             return true;
237         }
238         return false;
239     }
241     /**
242      * Return a list of parameters that can be used to get back to the current page.
243      *
244      * @return array params
245      */
246     public function get_return_params() {
247         global $PAGE;
249         $params = $PAGE->url->params();
250         unset($params['id']);
251         unset($params['action']);
252         return $params;
253     }
255     /**
256      * Set the submitted form data.
257      *
258      * @param stdClass $data The form data (instance)
259      */
260     public function set_instance(stdClass $data) {
261         $this->instance = $data;
262     }
264     /**
265      * Set the context.
266      *
267      * @param context $context The new context
268      */
269     public function set_context(context $context) {
270         $this->context = $context;
271     }
273     /**
274      * Set the course data.
275      *
276      * @param stdClass $course The course data
277      */
278     public function set_course(stdClass $course) {
279         $this->course = $course;
280     }
282     /**
283      * Get list of feedback plugins installed.
284      *
285      * @return array
286      */
287     public function get_feedback_plugins() {
288         return $this->feedbackplugins;
289     }
291     /**
292      * Get list of submission plugins installed.
293      *
294      * @return array
295      */
296     public function get_submission_plugins() {
297         return $this->submissionplugins;
298     }
300     /**
301      * Is blind marking enabled and reveal identities not set yet?
302      *
303      * @return bool
304      */
305     public function is_blind_marking() {
306         return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
307     }
309     /**
310      * Does an assignment have submission(s) or grade(s) already?
311      *
312      * @return bool
313      */
314     public function has_submissions_or_grades() {
315         $allgrades = $this->count_grades();
316         $allsubmissions = $this->count_submissions();
317         if (($allgrades == 0) && ($allsubmissions == 0)) {
318             return false;
319         }
320         return true;
321     }
323     /**
324      * Get a specific submission plugin by its type.
325      *
326      * @param string $subtype assignsubmission | assignfeedback
327      * @param string $type
328      * @return mixed assign_plugin|null
329      */
330     public function get_plugin_by_type($subtype, $type) {
331         $shortsubtype = substr($subtype, strlen('assign'));
332         $name = $shortsubtype . 'plugins';
333         if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
334             return null;
335         }
336         $pluginlist = $this->$name;
337         foreach ($pluginlist as $plugin) {
338             if ($plugin->get_type() == $type) {
339                 return $plugin;
340             }
341         }
342         return null;
343     }
345     /**
346      * Get a feedback plugin by type.
347      *
348      * @param string $type - The type of plugin e.g comments
349      * @return mixed assign_feedback_plugin|null
350      */
351     public function get_feedback_plugin_by_type($type) {
352         return $this->get_plugin_by_type('assignfeedback', $type);
353     }
355     /**
356      * Get a submission plugin by type.
357      *
358      * @param string $type - The type of plugin e.g comments
359      * @return mixed assign_submission_plugin|null
360      */
361     public function get_submission_plugin_by_type($type) {
362         return $this->get_plugin_by_type('assignsubmission', $type);
363     }
365     /**
366      * Load the plugins from the sub folders under subtype.
367      *
368      * @param string $subtype - either submission or feedback
369      * @return array - The sorted list of plugins
370      */
371     protected function load_plugins($subtype) {
372         global $CFG;
373         $result = array();
375         $names = core_component::get_plugin_list($subtype);
377         foreach ($names as $name => $path) {
378             if (file_exists($path . '/locallib.php')) {
379                 require_once($path . '/locallib.php');
381                 $shortsubtype = substr($subtype, strlen('assign'));
382                 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
384                 $plugin = new $pluginclass($this, $name);
386                 if ($plugin instanceof assign_plugin) {
387                     $idx = $plugin->get_sort_order();
388                     while (array_key_exists($idx, $result)) {
389                         $idx +=1;
390                     }
391                     $result[$idx] = $plugin;
392                 }
393             }
394         }
395         ksort($result);
396         return $result;
397     }
399     /**
400      * Display the assignment, used by view.php
401      *
402      * The assignment is displayed differently depending on your role,
403      * the settings for the assignment and the status of the assignment.
404      *
405      * @param string $action The current action if any.
406      * @return string - The page output.
407      */
408     public function view($action='') {
410         $o = '';
411         $mform = null;
412         $notices = array();
413         $nextpageparams = array();
415         if (!empty($this->get_course_module()->id)) {
416             $nextpageparams['id'] = $this->get_course_module()->id;
417         }
419         // Handle form submissions first.
420         if ($action == 'savesubmission') {
421             $action = 'editsubmission';
422             if ($this->process_save_submission($mform, $notices)) {
423                 $action = 'redirect';
424                 $nextpageparams['action'] = 'view';
425             }
426         } else if ($action == 'editprevioussubmission') {
427             $action = 'editsubmission';
428             if ($this->process_copy_previous_attempt($notices)) {
429                 $action = 'redirect';
430                 $nextpageparams['action'] = 'editsubmission';
431             }
432         } else if ($action == 'lock') {
433             $this->process_lock_submission();
434             $action = 'redirect';
435             $nextpageparams['action'] = 'grading';
436         } else if ($action == 'addattempt') {
437             $this->process_add_attempt(required_param('userid', PARAM_INT));
438             $action = 'redirect';
439             $nextpageparams['action'] = 'grading';
440         } else if ($action == 'reverttodraft') {
441             $this->process_revert_to_draft();
442             $action = 'redirect';
443             $nextpageparams['action'] = 'grading';
444         } else if ($action == 'unlock') {
445             $this->process_unlock_submission();
446             $action = 'redirect';
447             $nextpageparams['action'] = 'grading';
448         } else if ($action == 'setbatchmarkingworkflowstate') {
449             $this->process_set_batch_marking_workflow_state();
450             $action = 'redirect';
451             $nextpageparams['action'] = 'grading';
452         } else if ($action == 'setbatchmarkingallocation') {
453             $this->process_set_batch_marking_allocation();
454             $action = 'redirect';
455             $nextpageparams['action'] = 'grading';
456         } else if ($action == 'confirmsubmit') {
457             $action = 'submit';
458             if ($this->process_submit_for_grading($mform, $notices)) {
459                 $action = 'redirect';
460                 $nextpageparams['action'] = 'view';
461             } else if ($notices) {
462                 $action = 'viewsubmitforgradingerror';
463             }
464         } else if ($action == 'submitotherforgrading') {
465             if ($this->process_submit_other_for_grading($mform, $notices)) {
466                 $action = 'redirect';
467                 $nextpageparams['action'] = 'grading';
468             } else {
469                 $action = 'viewsubmitforgradingerror';
470             }
471         } else if ($action == 'gradingbatchoperation') {
472             $action = $this->process_grading_batch_operation($mform);
473             if ($action == 'grading') {
474                 $action = 'redirect';
475                 $nextpageparams['action'] = 'grading';
476             }
477         } else if ($action == 'submitgrade') {
478             if (optional_param('saveandshownext', null, PARAM_RAW)) {
479                 // Save and show next.
480                 $action = 'grade';
481                 if ($this->process_save_grade($mform)) {
482                     $action = 'redirect';
483                     $nextpageparams['action'] = 'grade';
484                     $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
485                     $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
486                 }
487             } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
488                 $action = 'redirect';
489                 $nextpageparams['action'] = 'grade';
490                 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
491                 $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
492             } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
493                 $action = 'redirect';
494                 $nextpageparams['action'] = 'grade';
495                 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
496                 $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
497             } else if (optional_param('savegrade', null, PARAM_RAW)) {
498                 // Save changes button.
499                 $action = 'grade';
500                 if ($this->process_save_grade($mform)) {
501                     $action = 'redirect';
502                     $nextpageparams['action'] = 'savegradingresult';
503                 }
504             } else {
505                 // Cancel button.
506                 $action = 'redirect';
507                 $nextpageparams['action'] = 'grading';
508             }
509         } else if ($action == 'quickgrade') {
510             $message = $this->process_save_quick_grades();
511             $action = 'quickgradingresult';
512         } else if ($action == 'saveoptions') {
513             $this->process_save_grading_options();
514             $action = 'redirect';
515             $nextpageparams['action'] = 'grading';
516         } else if ($action == 'saveextension') {
517             $action = 'grantextension';
518             if ($this->process_save_extension($mform)) {
519                 $action = 'redirect';
520                 $nextpageparams['action'] = 'grading';
521             }
522         } else if ($action == 'revealidentitiesconfirm') {
523             $this->process_reveal_identities();
524             $action = 'redirect';
525             $nextpageparams['action'] = 'grading';
526         }
528         $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
529                               'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM));
530         $this->register_return_link($action, $returnparams);
532         // Now show the right view page.
533         if ($action == 'redirect') {
534             $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
535             redirect($nextpageurl);
536             return;
537         } else if ($action == 'savegradingresult') {
538             $message = get_string('gradingchangessaved', 'assign');
539             $o .= $this->view_savegrading_result($message);
540         } else if ($action == 'quickgradingresult') {
541             $mform = null;
542             $o .= $this->view_quickgrading_result($message);
543         } else if ($action == 'grade') {
544             $o .= $this->view_single_grade_page($mform);
545         } else if ($action == 'viewpluginassignfeedback') {
546             $o .= $this->view_plugin_content('assignfeedback');
547         } else if ($action == 'viewpluginassignsubmission') {
548             $o .= $this->view_plugin_content('assignsubmission');
549         } else if ($action == 'editsubmission') {
550             $o .= $this->view_edit_submission_page($mform, $notices);
551         } else if ($action == 'grading') {
552             $o .= $this->view_grading_page();
553         } else if ($action == 'downloadall') {
554             $o .= $this->download_submissions();
555         } else if ($action == 'submit') {
556             $o .= $this->check_submit_for_grading($mform);
557         } else if ($action == 'grantextension') {
558             $o .= $this->view_grant_extension($mform);
559         } else if ($action == 'revealidentities') {
560             $o .= $this->view_reveal_identities_confirm($mform);
561         } else if ($action == 'plugingradingbatchoperation') {
562             $o .= $this->view_plugin_grading_batch_operation($mform);
563         } else if ($action == 'viewpluginpage') {
564              $o .= $this->view_plugin_page();
565         } else if ($action == 'viewcourseindex') {
566              $o .= $this->view_course_index();
567         } else if ($action == 'viewbatchsetmarkingworkflowstate') {
568              $o .= $this->view_batch_set_workflow_state($mform);
569         } else if ($action == 'viewbatchmarkingallocation') {
570             $o .= $this->view_batch_markingallocation($mform);
571         } else if ($action == 'viewsubmitforgradingerror') {
572             $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices);
573         } else {
574             $o .= $this->view_submission_page();
575         }
577         return $o;
578     }
580     /**
581      * Add this instance to the database.
582      *
583      * @param stdClass $formdata The data submitted from the form
584      * @param bool $callplugins This is used to skip the plugin code
585      *             when upgrading an old assignment to a new one (the plugins get called manually)
586      * @return mixed false if an error occurs or the int id of the new instance
587      */
588     public function add_instance(stdClass $formdata, $callplugins) {
589         global $DB;
590         $adminconfig = $this->get_admin_config();
592         $err = '';
594         // Add the database record.
595         $update = new stdClass();
596         $update->name = $formdata->name;
597         $update->timemodified = time();
598         $update->timecreated = time();
599         $update->course = $formdata->course;
600         $update->courseid = $formdata->course;
601         $update->intro = $formdata->intro;
602         $update->introformat = $formdata->introformat;
603         $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
604         $update->submissiondrafts = $formdata->submissiondrafts;
605         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
606         $update->sendnotifications = $formdata->sendnotifications;
607         $update->sendlatenotifications = $formdata->sendlatenotifications;
608         $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
609         if (isset($formdata->sendstudentnotifications)) {
610             $update->sendstudentnotifications = $formdata->sendstudentnotifications;
611         }
612         $update->duedate = $formdata->duedate;
613         $update->cutoffdate = $formdata->cutoffdate;
614         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
615         $update->grade = $formdata->grade;
616         $update->completionsubmit = !empty($formdata->completionsubmit);
617         $update->teamsubmission = $formdata->teamsubmission;
618         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
619         if (isset($formdata->teamsubmissiongroupingid)) {
620             $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
621         }
622         $update->blindmarking = $formdata->blindmarking;
623         $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
624         if (!empty($formdata->attemptreopenmethod)) {
625             $update->attemptreopenmethod = $formdata->attemptreopenmethod;
626         }
627         if (!empty($formdata->maxattempts)) {
628             $update->maxattempts = $formdata->maxattempts;
629         }
630         if (isset($formdata->preventsubmissionnotingroup)) {
631             $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
632         }
633         $update->markingworkflow = $formdata->markingworkflow;
634         $update->markingallocation = $formdata->markingallocation;
635         if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
636             $update->markingallocation = 0;
637         }
639         $returnid = $DB->insert_record('assign', $update);
640         $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
641         // Cache the course record.
642         $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
644         $this->save_intro_draft_files($formdata);
646         if ($callplugins) {
647             // Call save_settings hook for submission plugins.
648             foreach ($this->submissionplugins as $plugin) {
649                 if (!$this->update_plugin_instance($plugin, $formdata)) {
650                     print_error($plugin->get_error());
651                     return false;
652                 }
653             }
654             foreach ($this->feedbackplugins as $plugin) {
655                 if (!$this->update_plugin_instance($plugin, $formdata)) {
656                     print_error($plugin->get_error());
657                     return false;
658                 }
659             }
661             // In the case of upgrades the coursemodule has not been set,
662             // so we need to wait before calling these two.
663             $this->update_calendar($formdata->coursemodule);
664             $this->update_gradebook(false, $formdata->coursemodule);
666         }
668         $update = new stdClass();
669         $update->id = $this->get_instance()->id;
670         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
671         $DB->update_record('assign', $update);
673         return $returnid;
674     }
676     /**
677      * Delete all grades from the gradebook for this assignment.
678      *
679      * @return bool
680      */
681     protected function delete_grades() {
682         global $CFG;
684         $result = grade_update('mod/assign',
685                                $this->get_course()->id,
686                                'mod',
687                                'assign',
688                                $this->get_instance()->id,
689                                0,
690                                null,
691                                array('deleted'=>1));
692         return $result == GRADE_UPDATE_OK;
693     }
695     /**
696      * Delete this instance from the database.
697      *
698      * @return bool false if an error occurs
699      */
700     public function delete_instance() {
701         global $DB;
702         $result = true;
704         foreach ($this->submissionplugins as $plugin) {
705             if (!$plugin->delete_instance()) {
706                 print_error($plugin->get_error());
707                 $result = false;
708             }
709         }
710         foreach ($this->feedbackplugins as $plugin) {
711             if (!$plugin->delete_instance()) {
712                 print_error($plugin->get_error());
713                 $result = false;
714             }
715         }
717         // Delete files associated with this assignment.
718         $fs = get_file_storage();
719         if (! $fs->delete_area_files($this->context->id) ) {
720             $result = false;
721         }
723         // Delete_records will throw an exception if it fails - so no need for error checking here.
724         $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id));
725         $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id));
726         $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id));
727         $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id));
728         $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id));
730         // Delete items from the gradebook.
731         if (! $this->delete_grades()) {
732             $result = false;
733         }
735         // Delete the instance.
736         $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
738         return $result;
739     }
741     /**
742      * Actual implementation of the reset course functionality, delete all the
743      * assignment submissions for course $data->courseid.
744      *
745      * @param stdClass $data the data submitted from the reset course.
746      * @return array status array
747      */
748     public function reset_userdata($data) {
749         global $CFG, $DB;
751         $componentstr = get_string('modulenameplural', 'assign');
752         $status = array();
754         $fs = get_file_storage();
755         if (!empty($data->reset_assign_submissions)) {
756             // Delete files associated with this assignment.
757             foreach ($this->submissionplugins as $plugin) {
758                 $fileareas = array();
759                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
760                 $fileareas = $plugin->get_file_areas();
761                 foreach ($fileareas as $filearea => $notused) {
762                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
763                 }
765                 if (!$plugin->delete_instance()) {
766                     $status[] = array('component'=>$componentstr,
767                                       'item'=>get_string('deleteallsubmissions', 'assign'),
768                                       'error'=>$plugin->get_error());
769                 }
770             }
772             foreach ($this->feedbackplugins as $plugin) {
773                 $fileareas = array();
774                 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
775                 $fileareas = $plugin->get_file_areas();
776                 foreach ($fileareas as $filearea => $notused) {
777                     $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
778                 }
780                 if (!$plugin->delete_instance()) {
781                     $status[] = array('component'=>$componentstr,
782                                       'item'=>get_string('deleteallsubmissions', 'assign'),
783                                       'error'=>$plugin->get_error());
784                 }
785             }
787             $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id');
788             list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids));
790             $DB->delete_records_select('assign_submission', "assignment $sql", $params);
791             $DB->delete_records_select('assign_user_flags', "assignment $sql", $params);
793             $status[] = array('component'=>$componentstr,
794                               'item'=>get_string('deleteallsubmissions', 'assign'),
795                               'error'=>false);
797             if (!empty($data->reset_gradebook_grades)) {
798                 $DB->delete_records_select('assign_grades', "assignment $sql", $params);
799                 // Remove all grades from gradebook.
800                 require_once($CFG->dirroot.'/mod/assign/lib.php');
801                 assign_reset_gradebook($data->courseid);
803                 // Reset revealidentities if both submissions and grades have been reset.
804                 if ($this->get_instance()->blindmarking && $this->get_instance()->revealidentities) {
805                     $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id));
806                 }
807             }
808         }
809         // Updating dates - shift may be negative too.
810         if ($data->timeshift) {
811             shift_course_mod_dates('assign',
812                                     array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
813                                     $data->timeshift,
814                                     $data->courseid, $this->get_instance()->id);
815             $status[] = array('component'=>$componentstr,
816                               'item'=>get_string('datechanged'),
817                               'error'=>false);
818         }
820         return $status;
821     }
823     /**
824      * Update the settings for a single plugin.
825      *
826      * @param assign_plugin $plugin The plugin to update
827      * @param stdClass $formdata The form data
828      * @return bool false if an error occurs
829      */
830     protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
831         if ($plugin->is_visible()) {
832             $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
833             if (!empty($formdata->$enabledname)) {
834                 $plugin->enable();
835                 if (!$plugin->save_settings($formdata)) {
836                     print_error($plugin->get_error());
837                     return false;
838                 }
839             } else {
840                 $plugin->disable();
841             }
842         }
843         return true;
844     }
846     /**
847      * Update the gradebook information for this assignment.
848      *
849      * @param bool $reset If true, will reset all grades in the gradbook for this assignment
850      * @param int $coursemoduleid This is required because it might not exist in the database yet
851      * @return bool
852      */
853     public function update_gradebook($reset, $coursemoduleid) {
854         global $CFG;
856         require_once($CFG->dirroot.'/mod/assign/lib.php');
857         $assign = clone $this->get_instance();
858         $assign->cmidnumber = $coursemoduleid;
860         // Set assign gradebook feedback plugin status (enabled and visible).
861         $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
863         $param = null;
864         if ($reset) {
865             $param = 'reset';
866         }
868         return assign_grade_item_update($assign, $param);
869     }
871     /**
872      * Load and cache the admin config for this module.
873      *
874      * @return stdClass the plugin config
875      */
876     public function get_admin_config() {
877         if ($this->adminconfig) {
878             return $this->adminconfig;
879         }
880         $this->adminconfig = get_config('assign');
881         return $this->adminconfig;
882     }
884     /**
885      * Update the calendar entries for this assignment.
886      *
887      * @param int $coursemoduleid - Required to pass this in because it might
888      *                              not exist in the database yet.
889      * @return bool
890      */
891     public function update_calendar($coursemoduleid) {
892         global $DB, $CFG;
893         require_once($CFG->dirroot.'/calendar/lib.php');
895         // Special case for add_instance as the coursemodule has not been set yet.
896         $instance = $this->get_instance();
898         $eventtype = 'due';
900         if ($instance->duedate) {
901             $event = new stdClass();
903             $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype);
904             $event->id = $DB->get_field('event', 'id', $params);
905             $event->name = $instance->name;
906             $event->timestart = $instance->duedate;
908             // Convert the links to pluginfile. It is a bit hacky but at this stage the files
909             // might not have been saved in the module area yet.
910             $intro = $instance->intro;
911             if ($draftid = file_get_submitted_draft_itemid('introeditor')) {
912                 $intro = file_rewrite_urls_to_pluginfile($intro, $draftid);
913             }
915             // We need to remove the links to files as the calendar is not ready
916             // to support module events with file areas.
917             $intro = strip_pluginfile_content($intro);
918             if ($this->show_intro()) {
919                 $event->description = array(
920                     'text' => $intro,
921                     'format' => $instance->introformat
922                 );
923             } else {
924                 $event->description = array(
925                     'text' => '',
926                     'format' => $instance->introformat
927                 );
928             }
930             if ($event->id) {
931                 $calendarevent = calendar_event::load($event->id);
932                 $calendarevent->update($event);
933             } else {
934                 unset($event->id);
935                 $event->courseid    = $instance->course;
936                 $event->groupid     = 0;
937                 $event->userid      = 0;
938                 $event->modulename  = 'assign';
939                 $event->instance    = $instance->id;
940                 $event->eventtype   = $eventtype;
941                 $event->timeduration = 0;
942                 calendar_event::create($event);
943             }
944         } else {
945             $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype));
946         }
947     }
950     /**
951      * Update this instance in the database.
952      *
953      * @param stdClass $formdata - the data submitted from the form
954      * @return bool false if an error occurs
955      */
956     public function update_instance($formdata) {
957         global $DB;
958         $adminconfig = $this->get_admin_config();
960         $update = new stdClass();
961         $update->id = $formdata->instance;
962         $update->name = $formdata->name;
963         $update->timemodified = time();
964         $update->course = $formdata->course;
965         $update->intro = $formdata->intro;
966         $update->introformat = $formdata->introformat;
967         $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
968         $update->submissiondrafts = $formdata->submissiondrafts;
969         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
970         $update->sendnotifications = $formdata->sendnotifications;
971         $update->sendlatenotifications = $formdata->sendlatenotifications;
972         $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
973         if (isset($formdata->sendstudentnotifications)) {
974             $update->sendstudentnotifications = $formdata->sendstudentnotifications;
975         }
976         $update->duedate = $formdata->duedate;
977         $update->cutoffdate = $formdata->cutoffdate;
978         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
979         $update->grade = $formdata->grade;
980         if (!empty($formdata->completionunlocked)) {
981             $update->completionsubmit = !empty($formdata->completionsubmit);
982         }
983         $update->teamsubmission = $formdata->teamsubmission;
984         $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
985         if (isset($formdata->teamsubmissiongroupingid)) {
986             $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
987         }
988         $update->blindmarking = $formdata->blindmarking;
989         $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
990         if (!empty($formdata->attemptreopenmethod)) {
991             $update->attemptreopenmethod = $formdata->attemptreopenmethod;
992         }
993         if (!empty($formdata->maxattempts)) {
994             $update->maxattempts = $formdata->maxattempts;
995         }
996         if (isset($formdata->preventsubmissionnotingroup)) {
997             $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
998         }
999         $update->markingworkflow = $formdata->markingworkflow;
1000         $update->markingallocation = $formdata->markingallocation;
1001         if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
1002             $update->markingallocation = 0;
1003         }
1005         $result = $DB->update_record('assign', $update);
1006         $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
1008         $this->save_intro_draft_files($formdata);
1010         // Load the assignment so the plugins have access to it.
1012         // Call save_settings hook for submission plugins.
1013         foreach ($this->submissionplugins as $plugin) {
1014             if (!$this->update_plugin_instance($plugin, $formdata)) {
1015                 print_error($plugin->get_error());
1016                 return false;
1017             }
1018         }
1019         foreach ($this->feedbackplugins as $plugin) {
1020             if (!$this->update_plugin_instance($plugin, $formdata)) {
1021                 print_error($plugin->get_error());
1022                 return false;
1023             }
1024         }
1026         $this->update_calendar($this->get_course_module()->id);
1027         $this->update_gradebook(false, $this->get_course_module()->id);
1029         $update = new stdClass();
1030         $update->id = $this->get_instance()->id;
1031         $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
1032         $DB->update_record('assign', $update);
1034         return $result;
1035     }
1037     /**
1038      * Save the attachments in the draft areas.
1039      *
1040      * @param stdClass $formdata
1041      */
1042     protected function save_intro_draft_files($formdata) {
1043         if (isset($formdata->introattachments)) {
1044             file_save_draft_area_files($formdata->introattachments, $this->get_context()->id,
1045                                        'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
1046         }
1047     }
1049     /**
1050      * Add elements in grading plugin form.
1051      *
1052      * @param mixed $grade stdClass|null
1053      * @param MoodleQuickForm $mform
1054      * @param stdClass $data
1055      * @param int $userid - The userid we are grading
1056      * @return void
1057      */
1058     protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
1059         foreach ($this->feedbackplugins as $plugin) {
1060             if ($plugin->is_enabled() && $plugin->is_visible()) {
1061                 $plugin->get_form_elements_for_user($grade, $mform, $data, $userid);
1062             }
1063         }
1064     }
1068     /**
1069      * Add one plugins settings to edit plugin form.
1070      *
1071      * @param assign_plugin $plugin The plugin to add the settings from
1072      * @param MoodleQuickForm $mform The form to add the configuration settings to.
1073      *                               This form is modified directly (not returned).
1074      * @param array $pluginsenabled A list of form elements to be added to a group.
1075      *                              The new element is added to this array by this function.
1076      * @return void
1077      */
1078     protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
1079         global $CFG;
1080         if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
1081             $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1082             $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
1083             $mform->setType($name, PARAM_BOOL);
1084             $plugin->get_settings($mform);
1085         } else if ($plugin->is_visible() && $plugin->is_configurable()) {
1086             $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1087             $label = $plugin->get_name();
1088             $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
1089             $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
1091             $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
1092             if ($plugin->get_config('enabled') !== false) {
1093                 $default = $plugin->is_enabled();
1094             }
1095             $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
1097             $plugin->get_settings($mform);
1099         }
1100     }
1102     /**
1103      * Add settings to edit plugin form.
1104      *
1105      * @param MoodleQuickForm $mform The form to add the configuration settings to.
1106      *                               This form is modified directly (not returned).
1107      * @return void
1108      */
1109     public function add_all_plugin_settings(MoodleQuickForm $mform) {
1110         $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));
1112         $submissionpluginsenabled = array();
1113         $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
1114         foreach ($this->submissionplugins as $plugin) {
1115             $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
1116         }
1117         $group->setElements($submissionpluginsenabled);
1119         $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign'));
1120         $feedbackpluginsenabled = array();
1121         $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
1122         foreach ($this->feedbackplugins as $plugin) {
1123             $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
1124         }
1125         $group->setElements($feedbackpluginsenabled);
1126         $mform->setExpanded('submissiontypes');
1127     }
1129     /**
1130      * Allow each plugin an opportunity to update the defaultvalues
1131      * passed in to the settings form (needed to set up draft areas for
1132      * editor and filemanager elements)
1133      *
1134      * @param array $defaultvalues
1135      */
1136     public function plugin_data_preprocessing(&$defaultvalues) {
1137         foreach ($this->submissionplugins as $plugin) {
1138             if ($plugin->is_visible()) {
1139                 $plugin->data_preprocessing($defaultvalues);
1140             }
1141         }
1142         foreach ($this->feedbackplugins as $plugin) {
1143             if ($plugin->is_visible()) {
1144                 $plugin->data_preprocessing($defaultvalues);
1145             }
1146         }
1147     }
1149     /**
1150      * Get the name of the current module.
1151      *
1152      * @return string the module name (Assignment)
1153      */
1154     protected function get_module_name() {
1155         if (isset(self::$modulename)) {
1156             return self::$modulename;
1157         }
1158         self::$modulename = get_string('modulename', 'assign');
1159         return self::$modulename;
1160     }
1162     /**
1163      * Get the plural name of the current module.
1164      *
1165      * @return string the module name plural (Assignments)
1166      */
1167     protected function get_module_name_plural() {
1168         if (isset(self::$modulenameplural)) {
1169             return self::$modulenameplural;
1170         }
1171         self::$modulenameplural = get_string('modulenameplural', 'assign');
1172         return self::$modulenameplural;
1173     }
1175     /**
1176      * Has this assignment been constructed from an instance?
1177      *
1178      * @return bool
1179      */
1180     public function has_instance() {
1181         return $this->instance || $this->get_course_module();
1182     }
1184     /**
1185      * Get the settings for the current instance of this assignment
1186      *
1187      * @return stdClass The settings
1188      */
1189     public function get_instance() {
1190         global $DB;
1191         if ($this->instance) {
1192             return $this->instance;
1193         }
1194         if ($this->get_course_module()) {
1195             $params = array('id' => $this->get_course_module()->instance);
1196             $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
1197         }
1198         if (!$this->instance) {
1199             throw new coding_exception('Improper use of the assignment class. ' .
1200                                        'Cannot load the assignment record.');
1201         }
1202         return $this->instance;
1203     }
1205     /**
1206      * Get the primary grade item for this assign instance.
1207      *
1208      * @return stdClass The grade_item record
1209      */
1210     public function get_grade_item() {
1211         if ($this->gradeitem) {
1212             return $this->gradeitem;
1213         }
1214         $instance = $this->get_instance();
1215         $params = array('itemtype' => 'mod',
1216                         'itemmodule' => 'assign',
1217                         'iteminstance' => $instance->id,
1218                         'courseid' => $instance->course,
1219                         'itemnumber' => 0);
1220         $this->gradeitem = grade_item::fetch($params);
1221         if (!$this->gradeitem) {
1222             throw new coding_exception('Improper use of the assignment class. ' .
1223                                        'Cannot load the grade item.');
1224         }
1225         return $this->gradeitem;
1226     }
1228     /**
1229      * Get the context of the current course.
1230      *
1231      * @return mixed context|null The course context
1232      */
1233     public function get_course_context() {
1234         if (!$this->context && !$this->course) {
1235             throw new coding_exception('Improper use of the assignment class. ' .
1236                                        'Cannot load the course context.');
1237         }
1238         if ($this->context) {
1239             return $this->context->get_course_context();
1240         } else {
1241             return context_course::instance($this->course->id);
1242         }
1243     }
1246     /**
1247      * Get the current course module.
1248      *
1249      * @return cm_info|null The course module or null if not known
1250      */
1251     public function get_course_module() {
1252         if ($this->coursemodule) {
1253             return $this->coursemodule;
1254         }
1255         if (!$this->context) {
1256             return null;
1257         }
1259         if ($this->context->contextlevel == CONTEXT_MODULE) {
1260             $modinfo = get_fast_modinfo($this->get_course());
1261             $this->coursemodule = $modinfo->get_cm($this->context->instanceid);
1262             return $this->coursemodule;
1263         }
1264         return null;
1265     }
1267     /**
1268      * Get context module.
1269      *
1270      * @return context
1271      */
1272     public function get_context() {
1273         return $this->context;
1274     }
1276     /**
1277      * Get the current course.
1278      *
1279      * @return mixed stdClass|null The course
1280      */
1281     public function get_course() {
1282         global $DB;
1284         if ($this->course) {
1285             return $this->course;
1286         }
1288         if (!$this->context) {
1289             return null;
1290         }
1291         $params = array('id' => $this->get_course_context()->instanceid);
1292         $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
1294         return $this->course;
1295     }
1297     /**
1298      * Count the number of intro attachments.
1299      *
1300      * @return int
1301      */
1302     protected function count_attachments() {
1304         $fs = get_file_storage();
1305         $files = $fs->get_area_files($this->get_context()->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA,
1306                         0, 'id', false);
1308         return count($files);
1309     }
1311     /**
1312      * Are there any intro attachments to display?
1313      *
1314      * @return boolean
1315      */
1316     protected function has_visible_attachments() {
1317         return ($this->count_attachments() > 0);
1318     }
1320     /**
1321      * Return a grade in user-friendly form, whether it's a scale or not.
1322      *
1323      * @param mixed $grade int|null
1324      * @param boolean $editing Are we allowing changes to this grade?
1325      * @param int $userid The user id the grade belongs to
1326      * @param int $modified Timestamp from when the grade was last modified
1327      * @return string User-friendly representation of grade
1328      */
1329     public function display_grade($grade, $editing, $userid=0, $modified=0) {
1330         global $DB;
1332         static $scalegrades = array();
1334         $o = '';
1336         if ($this->get_instance()->grade >= 0) {
1337             // Normal number.
1338             if ($editing && $this->get_instance()->grade > 0) {
1339                 if ($grade < 0) {
1340                     $displaygrade = '';
1341                 } else {
1342                     $displaygrade = format_float($grade, 2);
1343                 }
1344                 $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
1345                        get_string('usergrade', 'assign') .
1346                        '</label>';
1347                 $o .= '<input type="text"
1348                               id="quickgrade_' . $userid . '"
1349                               name="quickgrade_' . $userid . '"
1350                               value="' .  $displaygrade . '"
1351                               size="6"
1352                               maxlength="10"
1353                               class="quickgrade"/>';
1354                 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1355                 return $o;
1356             } else {
1357                 if ($grade == -1 || $grade === null) {
1358                     $o .= '-';
1359                 } else {
1360                     $item = $this->get_grade_item();
1361                     $o .= grade_format_gradevalue($grade, $item);
1362                     if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
1363                         // If displaying the raw grade, also display the total value.
1364                         $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1365                     }
1366                 }
1367                 return $o;
1368             }
1370         } else {
1371             // Scale.
1372             if (empty($this->cache['scale'])) {
1373                 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1374                     $this->cache['scale'] = make_menu_from_list($scale->scale);
1375                 } else {
1376                     $o .= '-';
1377                     return $o;
1378                 }
1379             }
1380             if ($editing) {
1381                 $o .= '<label class="accesshide"
1382                               for="quickgrade_' . $userid . '">' .
1383                       get_string('usergrade', 'assign') .
1384                       '</label>';
1385                 $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
1386                 $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1387                 foreach ($this->cache['scale'] as $optionid => $option) {
1388                     $selected = '';
1389                     if ($grade == $optionid) {
1390                         $selected = 'selected="selected"';
1391                     }
1392                     $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1393                 }
1394                 $o .= '</select>';
1395                 return $o;
1396             } else {
1397                 $scaleid = (int)$grade;
1398                 if (isset($this->cache['scale'][$scaleid])) {
1399                     $o .= $this->cache['scale'][$scaleid];
1400                     return $o;
1401                 }
1402                 $o .= '-';
1403                 return $o;
1404             }
1405         }
1406     }
1408     /**
1409      * Load a list of users enrolled in the current course with the specified permission and group.
1410      * 0 for no group.
1411      *
1412      * @param int $currentgroup
1413      * @param bool $idsonly
1414      * @return array List of user records
1415      */
1416     public function list_participants($currentgroup, $idsonly) {
1418         if (empty($currentgroup)) {
1419             $currentgroup = 0;
1420         }
1422         $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
1423         if (!isset($this->participants[$key])) {
1424             $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
1425                     $this->show_only_active_users());
1427             $cm = $this->get_course_module();
1428             $info = new \core_availability\info_module($cm);
1429             $users = $info->filter_user_list($users);
1431             $this->participants[$key] = $users;
1432         }
1434         if ($idsonly) {
1435             $idslist = array();
1436             foreach ($this->participants[$key] as $id => $user) {
1437                 $idslist[$id] = new stdClass();
1438                 $idslist[$id]->id = $id;
1439             }
1440             return $idslist;
1441         }
1442         return $this->participants[$key];
1443     }
1445     /**
1446      * Load a count of valid teams for this assignment.
1447      *
1448      * @param int $activitygroup Activity active group
1449      * @return int number of valid teams
1450      */
1451     public function count_teams($activitygroup = 0) {
1453         $count = 0;
1455         $participants = $this->list_participants($activitygroup, true);
1457         // If a team submission grouping id is provided all good as all returned groups
1458         // are the submission teams, but if no team submission grouping was specified
1459         // $groups will contain all participants groups.
1460         if ($this->get_instance()->teamsubmissiongroupingid) {
1462             // We restrict the users to the selected group ones.
1463             $groups = groups_get_all_groups($this->get_course()->id,
1464                                             array_keys($participants),
1465                                             $this->get_instance()->teamsubmissiongroupingid,
1466                                             'DISTINCT g.id, g.name');
1468             $count = count($groups);
1470             // When a specific group is selected we don't count the default group users.
1471             if ($activitygroup == 0) {
1472                 if (empty($this->get_instance()->preventsubmissionnotingroup)) {
1473                     // See if there are any users in the default group.
1474                     $defaultusers = $this->get_submission_group_members(0, true);
1475                     if (count($defaultusers) > 0) {
1476                         $count += 1;
1477                     }
1478                 }
1479             }
1480         } else {
1481             // It is faster to loop around participants if no grouping was specified.
1482             $groups = array();
1483             foreach ($participants as $participant) {
1484                 if ($group = $this->get_submission_group($participant->id)) {
1485                     $groups[$group->id] = true;
1486                 } else if (empty($this->get_instance()->preventsubmissionnotingroup)) {
1487                     $groups[0] = true;
1488                 }
1489             }
1491             $count = count($groups);
1492         }
1494         return $count;
1495     }
1497     /**
1498      * Load a count of active users enrolled in the current course with the specified permission and group.
1499      * 0 for no group.
1500      *
1501      * @param int $currentgroup
1502      * @return int number of matching users
1503      */
1504     public function count_participants($currentgroup) {
1505         return count($this->list_participants($currentgroup, true));
1506     }
1508     /**
1509      * Load a count of active users submissions in the current module that require grading
1510      * This means the submission modification time is more recent than the
1511      * grading modification time and the status is SUBMITTED.
1512      *
1513      * @return int number of matching submissions
1514      */
1515     public function count_submissions_need_grading() {
1516         global $DB;
1518         if ($this->get_instance()->teamsubmission) {
1519             // This does not make sense for group assignment because the submission is shared.
1520             return 0;
1521         }
1523         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1524         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1526         $params['assignid'] = $this->get_instance()->id;
1527         $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1529         $sql = 'SELECT COUNT(s.userid)
1530                    FROM {assign_submission} s
1531                    LEFT JOIN {assign_grades} g ON
1532                         s.assignment = g.assignment AND
1533                         s.userid = g.userid AND
1534                         g.attemptnumber = s.attemptnumber
1535                    JOIN(' . $esql . ') e ON e.id = s.userid
1536                    WHERE
1537                         s.latest = 1 AND
1538                         s.assignment = :assignid AND
1539                         s.timemodified IS NOT NULL AND
1540                         s.status = :submitted AND
1541                         (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
1543         return $DB->count_records_sql($sql, $params);
1544     }
1546     /**
1547      * Load a count of grades.
1548      *
1549      * @return int number of grades
1550      */
1551     public function count_grades() {
1552         global $DB;
1554         if (!$this->has_instance()) {
1555             return 0;
1556         }
1558         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1559         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1561         $params['assignid'] = $this->get_instance()->id;
1563         $sql = 'SELECT COUNT(g.userid)
1564                    FROM {assign_grades} g
1565                    JOIN(' . $esql . ') e ON e.id = g.userid
1566                    WHERE g.assignment = :assignid';
1568         return $DB->count_records_sql($sql, $params);
1569     }
1571     /**
1572      * Load a count of submissions.
1573      *
1574      * @param bool $includenew When true, also counts the submissions with status 'new'.
1575      * @return int number of submissions
1576      */
1577     public function count_submissions($includenew = false) {
1578         global $DB;
1580         if (!$this->has_instance()) {
1581             return 0;
1582         }
1584         $params = array();
1585         $sqlnew = '';
1587         if (!$includenew) {
1588             $sqlnew = ' AND s.status <> :status ';
1589             $params['status'] = ASSIGN_SUBMISSION_STATUS_NEW;
1590         }
1592         if ($this->get_instance()->teamsubmission) {
1593             // We cannot join on the enrolment tables for group submissions (no userid).
1594             $sql = 'SELECT COUNT(DISTINCT s.groupid)
1595                         FROM {assign_submission} s
1596                         WHERE
1597                             s.assignment = :assignid AND
1598                             s.timemodified IS NOT NULL AND
1599                             s.userid = :groupuserid' .
1600                             $sqlnew;
1602             $params['assignid'] = $this->get_instance()->id;
1603             $params['groupuserid'] = 0;
1604         } else {
1605             $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1606             list($esql, $enrolparams) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1608             $params = array_merge($params, $enrolparams);
1609             $params['assignid'] = $this->get_instance()->id;
1611             $sql = 'SELECT COUNT(DISTINCT s.userid)
1612                        FROM {assign_submission} s
1613                        JOIN(' . $esql . ') e ON e.id = s.userid
1614                        WHERE
1615                             s.assignment = :assignid AND
1616                             s.timemodified IS NOT NULL ' .
1617                             $sqlnew;
1619         }
1621         return $DB->count_records_sql($sql, $params);
1622     }
1624     /**
1625      * Load a count of submissions with a specified status.
1626      *
1627      * @param string $status The submission status - should match one of the constants
1628      * @return int number of matching submissions
1629      */
1630     public function count_submissions_with_status($status) {
1631         global $DB;
1633         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1634         list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1636         $params['assignid'] = $this->get_instance()->id;
1637         $params['assignid2'] = $this->get_instance()->id;
1638         $params['submissionstatus'] = $status;
1640         if ($this->get_instance()->teamsubmission) {
1642             $groupsstr = '';
1643             if ($currentgroup != 0) {
1644                 // If there is an active group we should only display the current group users groups.
1645                 $participants = $this->list_participants($currentgroup, true);
1646                 $groups = groups_get_all_groups($this->get_course()->id,
1647                                                 array_keys($participants),
1648                                                 $this->get_instance()->teamsubmissiongroupingid,
1649                                                 'DISTINCT g.id, g.name');
1650                 list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
1651                 $groupsstr = 's.groupid ' . $groupssql . ' AND';
1652                 $params = $params + $groupsparams;
1653             }
1654             $sql = 'SELECT COUNT(s.groupid)
1655                         FROM {assign_submission} s
1656                         WHERE
1657                             s.latest = 1 AND
1658                             s.assignment = :assignid AND
1659                             s.timemodified IS NOT NULL AND
1660                             s.userid = :groupuserid AND '
1661                             . $groupsstr . '
1662                             s.status = :submissionstatus';
1663             $params['groupuserid'] = 0;
1664         } else {
1665             $sql = 'SELECT COUNT(s.userid)
1666                         FROM {assign_submission} s
1667                         JOIN(' . $esql . ') e ON e.id = s.userid
1668                         WHERE
1669                             s.latest = 1 AND
1670                             s.assignment = :assignid AND
1671                             s.timemodified IS NOT NULL AND
1672                             s.status = :submissionstatus';
1674         }
1676         return $DB->count_records_sql($sql, $params);
1677     }
1679     /**
1680      * Utility function to get the userid for every row in the grading table
1681      * so the order can be frozen while we iterate it.
1682      *
1683      * @return array An array of userids
1684      */
1685     protected function get_grading_userid_list() {
1686         $filter = get_user_preferences('assign_filter', '');
1687         $table = new assign_grading_table($this, 0, $filter, 0, false);
1689         $useridlist = $table->get_column_data('userid');
1691         return $useridlist;
1692     }
1694     /**
1695      * Generate zip file from array of given files.
1696      *
1697      * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1698      *                                 This array is indexed by the final file name and each
1699      *                                 element in the array is an instance of a stored_file object.
1700      * @return path of temp file - note this returned file does
1701      *         not have a .zip extension - it is a temp file.
1702      */
1703     protected function pack_files($filesforzipping) {
1704         global $CFG;
1705         // Create path for new zip file.
1706         $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1707         // Zip files.
1708         $zipper = new zip_packer();
1709         if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1710             return $tempzip;
1711         }
1712         return false;
1713     }
1715     /**
1716      * Finds all assignment notifications that have yet to be mailed out, and mails them.
1717      *
1718      * Cron function to be run periodically according to the moodle cron.
1719      *
1720      * @return bool
1721      */
1722     public static function cron() {
1723         global $DB;
1725         // Only ever send a max of one days worth of updates.
1726         $yesterday = time() - (24 * 3600);
1727         $timenow   = time();
1728         $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign'));
1730         // Collect all submissions that require mailing.
1731         // Submissions are included if all are true:
1732         //   - The assignment is visible in the gradebook.
1733         //   - No previous notification has been sent.
1734         //   - If marking workflow is not enabled, the grade was updated in the past 24 hours, or
1735         //     if marking workflow is enabled, the workflow state is at 'released'.
1736         $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
1737                        g.*, g.timemodified as lastmodified, cm.id as cmid
1738                  FROM {assign} a
1739                  JOIN {assign_grades} g ON g.assignment = a.id
1740             LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
1741                  JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
1742                  JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
1743                  JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
1744                  WHERE ((a.markingworkflow = 0 AND g.timemodified >= :yesterday AND g.timemodified <= :today) OR
1745                         (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND
1746                        uf.mailed = 0 AND gri.hidden = 0
1747               ORDER BY a.course, cm.id";
1749         $params = array(
1750             'yesterday' => $yesterday,
1751             'today' => $timenow,
1752             'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
1753         );
1754         $submissions = $DB->get_records_sql($sql, $params);
1756         if (!empty($submissions)) {
1758             mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1760             // Preload courses we are going to need those.
1761             $courseids = array();
1762             foreach ($submissions as $submission) {
1763                 $courseids[] = $submission->course;
1764             }
1766             // Filter out duplicates.
1767             $courseids = array_unique($courseids);
1768             $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1769             list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1770             $sql = 'SELECT c.*, ' . $ctxselect .
1771                       ' FROM {course} c
1772                  LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1773                      WHERE c.id ' . $courseidsql;
1775             $params['contextlevel'] = CONTEXT_COURSE;
1776             $courses = $DB->get_records_sql($sql, $params);
1778             // Clean up... this could go on for a while.
1779             unset($courseids);
1780             unset($ctxselect);
1781             unset($courseidsql);
1782             unset($params);
1784             // Message students about new feedback.
1785             foreach ($submissions as $submission) {
1787                 mtrace("Processing assignment submission $submission->id ...");
1789                 // Do not cache user lookups - could be too many.
1790                 if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1791                     mtrace('Could not find user ' . $submission->userid);
1792                     continue;
1793                 }
1795                 // Use a cache to prevent the same DB queries happening over and over.
1796                 if (!array_key_exists($submission->course, $courses)) {
1797                     mtrace('Could not find course ' . $submission->course);
1798                     continue;
1799                 }
1800                 $course = $courses[$submission->course];
1801                 if (isset($course->ctxid)) {
1802                     // Context has not yet been preloaded. Do so now.
1803                     context_helper::preload_from_record($course);
1804                 }
1806                 // Override the language and timezone of the "current" user, so that
1807                 // mail is customised for the receiver.
1808                 cron_setup_user($user, $course);
1810                 // Context lookups are already cached.
1811                 $coursecontext = context_course::instance($course->id);
1812                 if (!is_enrolled($coursecontext, $user->id)) {
1813                     $courseshortname = format_string($course->shortname,
1814                                                      true,
1815                                                      array('context' => $coursecontext));
1816                     mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1817                     continue;
1818                 }
1820                 if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1821                     mtrace('Could not find grader ' . $submission->grader);
1822                     continue;
1823                 }
1825                 $modinfo = get_fast_modinfo($course, $user->id);
1826                 $cm = $modinfo->get_cm($submission->cmid);
1827                 // Context lookups are already cached.
1828                 $contextmodule = context_module::instance($cm->id);
1830                 if (!$cm->uservisible) {
1831                     // Hold mail notification for assignments the user cannot access until later.
1832                     continue;
1833                 }
1835                 // Need to send this to the student.
1836                 $messagetype = 'feedbackavailable';
1837                 $eventtype = 'assign_notification';
1838                 $updatetime = $submission->lastmodified;
1839                 $modulename = get_string('modulename', 'assign');
1841                 $uniqueid = 0;
1842                 if ($submission->blindmarking && !$submission->revealidentities) {
1843                     $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1844                 }
1845                 $showusers = $submission->blindmarking && !$submission->revealidentities;
1846                 self::send_assignment_notification($grader,
1847                                                    $user,
1848                                                    $messagetype,
1849                                                    $eventtype,
1850                                                    $updatetime,
1851                                                    $cm,
1852                                                    $contextmodule,
1853                                                    $course,
1854                                                    $modulename,
1855                                                    $submission->name,
1856                                                    $showusers,
1857                                                    $uniqueid);
1859                 $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
1860                 if ($flags) {
1861                     $flags->mailed = 1;
1862                     $DB->update_record('assign_user_flags', $flags);
1863                 } else {
1864                     $flags = new stdClass();
1865                     $flags->userid = $user->id;
1866                     $flags->assignment = $submission->assignment;
1867                     $flags->mailed = 1;
1868                     $DB->insert_record('assign_user_flags', $flags);
1869                 }
1871                 mtrace('Done');
1872             }
1873             mtrace('Done processing ' . count($submissions) . ' assignment submissions');
1875             cron_setup_user();
1877             // Free up memory just to be sure.
1878             unset($courses);
1879         }
1881         // Update calendar events to provide a description.
1882         $sql = 'SELECT id
1883                     FROM {assign}
1884                     WHERE
1885                         allowsubmissionsfromdate >= :lastcron AND
1886                         allowsubmissionsfromdate <= :timenow AND
1887                         alwaysshowdescription = 0';
1888         $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
1889         $newlyavailable = $DB->get_records_sql($sql, $params);
1890         foreach ($newlyavailable as $record) {
1891             $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
1892             $context = context_module::instance($cm->id);
1894             $assignment = new assign($context, null, null);
1895             $assignment->update_calendar($cm->id);
1896         }
1898         return true;
1899     }
1901     /**
1902      * Mark in the database that this grade record should have an update notification sent by cron.
1903      *
1904      * @param stdClass $grade a grade record keyed on id
1905      * @param bool $mailedoverride when true, flag notification to be sent again.
1906      * @return bool true for success
1907      */
1908     public function notify_grade_modified($grade, $mailedoverride = false) {
1909         global $DB;
1911         $flags = $this->get_user_flags($grade->userid, true);
1912         if ($flags->mailed != 1 || $mailedoverride) {
1913             $flags->mailed = 0;
1914         }
1916         return $this->update_user_flags($flags);
1917     }
1919     /**
1920      * Update user flags for this user in this assignment.
1921      *
1922      * @param stdClass $flags a flags record keyed on id
1923      * @return bool true for success
1924      */
1925     public function update_user_flags($flags) {
1926         global $DB;
1927         if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
1928             return false;
1929         }
1931         $result = $DB->update_record('assign_user_flags', $flags);
1932         return $result;
1933     }
1935     /**
1936      * Update a grade in the grade table for the assignment and in the gradebook.
1937      *
1938      * @param stdClass $grade a grade record keyed on id
1939      * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment.
1940      * @return bool true for success
1941      */
1942     public function update_grade($grade, $reopenattempt = false) {
1943         global $DB;
1945         $grade->timemodified = time();
1947         if (!empty($grade->workflowstate)) {
1948             $validstates = $this->get_marking_workflow_states_for_current_user();
1949             if (!array_key_exists($grade->workflowstate, $validstates)) {
1950                 return false;
1951             }
1952         }
1954         if ($grade->grade && $grade->grade != -1) {
1955             if ($this->get_instance()->grade > 0) {
1956                 if (!is_numeric($grade->grade)) {
1957                     return false;
1958                 } else if ($grade->grade > $this->get_instance()->grade) {
1959                     return false;
1960                 } else if ($grade->grade < 0) {
1961                     return false;
1962                 }
1963             } else {
1964                 // This is a scale.
1965                 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1966                     $scaleoptions = make_menu_from_list($scale->scale);
1967                     if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1968                         return false;
1969                     }
1970                 }
1971             }
1972         }
1974         if (empty($grade->attemptnumber)) {
1975             // Set it to the default.
1976             $grade->attemptnumber = 0;
1977         }
1978         $DB->update_record('assign_grades', $grade);
1980         $submission = null;
1981         if ($this->get_instance()->teamsubmission) {
1982             $submission = $this->get_group_submission($grade->userid, 0, false);
1983         } else {
1984             $submission = $this->get_user_submission($grade->userid, false);
1985         }
1987         // Only push to gradebook if the update is for the latest attempt.
1988         // Not the latest attempt.
1989         if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
1990             return true;
1991         }
1993         if ($this->gradebook_item_update(null, $grade)) {
1994             \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
1995         }
1997         // If the conditions are met, allow another attempt.
1998         if ($submission) {
1999             $this->reopen_submission_if_required($grade->userid,
2000                     $submission,
2001                     $reopenattempt);
2002         }
2004         return true;
2005     }
2007     /**
2008      * View the grant extension date page.
2009      *
2010      * Uses url parameters 'userid'
2011      * or from parameter 'selectedusers'
2012      *
2013      * @param moodleform $mform - Used for validation of the submitted data
2014      * @return string
2015      */
2016     protected function view_grant_extension($mform) {
2017         global $DB, $CFG;
2018         require_once($CFG->dirroot . '/mod/assign/extensionform.php');
2020         $o = '';
2022         $data = new stdClass();
2023         $data->id = $this->get_course_module()->id;
2025         $formparams = array(
2026             'instance' => $this->get_instance()
2027         );
2029         $extrauserfields = get_extra_user_fields($this->get_context());
2031         if ($mform) {
2032             $submitteddata = $mform->get_data();
2033             $users = $submitteddata->selectedusers;
2034             $userlist = explode(',', $users);
2036             $data->selectedusers = $users;
2037             $data->userid = 0;
2039             $usershtml = '';
2040             $usercount = 0;
2041             foreach ($userlist as $userid) {
2042                 if ($usercount >= 5) {
2043                     $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
2044                     break;
2045                 }
2046                 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2048                 $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
2049                                                                     $this->get_course()->id,
2050                                                                     has_capability('moodle/site:viewfullnames',
2051                                                                     $this->get_course_context()),
2052                                                                     $this->is_blind_marking(),
2053                                                                     $this->get_uniqueid_for_user($user->id),
2054                                                                     $extrauserfields,
2055                                                                     !$this->is_active_user($userid)));
2056                 $usercount += 1;
2057             }
2059             $formparams['userscount'] = count($userlist);
2060             $formparams['usershtml'] = $usershtml;
2062         } else {
2063             $userid = required_param('userid', PARAM_INT);
2064             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
2065             $flags = $this->get_user_flags($userid, false);
2067             $data->userid = $user->id;
2068             if ($flags) {
2069                 $data->extensionduedate = $flags->extensionduedate;
2070             }
2072             $usershtml = $this->get_renderer()->render(new assign_user_summary($user,
2073                                                                 $this->get_course()->id,
2074                                                                 has_capability('moodle/site:viewfullnames',
2075                                                                 $this->get_course_context()),
2076                                                                 $this->is_blind_marking(),
2077                                                                 $this->get_uniqueid_for_user($user->id),
2078                                                                 $extrauserfields,
2079                                                                 !$this->is_active_user($userid)));
2080             $formparams['usershtml'] = $usershtml;
2081         }
2083         $mform = new mod_assign_extension_form(null, $formparams);
2084         $mform->set_data($data);
2085         $header = new assign_header($this->get_instance(),
2086                                     $this->get_context(),
2087                                     $this->show_intro(),
2088                                     $this->get_course_module()->id,
2089                                     get_string('grantextension', 'assign'));
2090         $o .= $this->get_renderer()->render($header);
2091         $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
2092         $o .= $this->view_footer();
2093         return $o;
2094     }
2096     /**
2097      * Get a list of the users in the same group as this user.
2098      *
2099      * @param int $groupid The id of the group whose members we want or 0 for the default group
2100      * @param bool $onlyids Whether to retrieve only the user id's
2101      * @param bool $excludesuspended Whether to exclude suspended users
2102      * @return array The users (possibly id's only)
2103      */
2104     public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) {
2105         $members = array();
2106         if ($groupid != 0) {
2107             if ($onlyids) {
2108                 $allusers = groups_get_members($groupid, 'u.id');
2109             } else {
2110                 $allusers = groups_get_members($groupid);
2111             }
2112             foreach ($allusers as $user) {
2113                 if ($this->get_submission_group($user->id)) {
2114                     $members[] = $user;
2115                 }
2116             }
2117         } else {
2118             $allusers = $this->list_participants(null, $onlyids);
2119             foreach ($allusers as $user) {
2120                 if ($this->get_submission_group($user->id) == null) {
2121                     $members[] = $user;
2122                 }
2123             }
2124         }
2125         // Exclude suspended users, if user can't see them.
2126         if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
2127             foreach ($members as $key => $member) {
2128                 if (!$this->is_active_user($member->id)) {
2129                     unset($members[$key]);
2130                 }
2131             }
2132         }
2134         return $members;
2135     }
2137     /**
2138      * Get a list of the users in the same group as this user that have not submitted the assignment.
2139      *
2140      * @param int $groupid The id of the group whose members we want or 0 for the default group
2141      * @param bool $onlyids Whether to retrieve only the user id's
2142      * @return array The users (possibly id's only)
2143      */
2144     public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
2145         $instance = $this->get_instance();
2146         if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
2147             return array();
2148         }
2149         $members = $this->get_submission_group_members($groupid, $onlyids);
2151         foreach ($members as $id => $member) {
2152             $submission = $this->get_user_submission($member->id, false);
2153             if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2154                 unset($members[$id]);
2155             } else {
2156                 if ($this->is_blind_marking()) {
2157                     $members[$id]->alias = get_string('hiddenuser', 'assign') .
2158                                            $this->get_uniqueid_for_user($id);
2159                 }
2160             }
2161         }
2162         return $members;
2163     }
2165     /**
2166      * Load the group submission object for a particular user, optionally creating it if required.
2167      *
2168      * @param int $userid The id of the user whose submission we want
2169      * @param int $groupid The id of the group for this user - may be 0 in which
2170      *                     case it is determined from the userid.
2171      * @param bool $create If set to true a new submission object will be created in the database
2172      *                     with the status set to "new".
2173      * @param int $attemptnumber - -1 means the latest attempt
2174      * @return stdClass The submission
2175      */
2176     public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
2177         global $DB;
2179         if ($groupid == 0) {
2180             $group = $this->get_submission_group($userid);
2181             if ($group) {
2182                 $groupid = $group->id;
2183             }
2184         }
2186         // Now get the group submission.
2187         $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2188         if ($attemptnumber >= 0) {
2189             $params['attemptnumber'] = $attemptnumber;
2190         }
2192         // Only return the row with the highest attemptnumber.
2193         $submission = null;
2194         $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2195         if ($submissions) {
2196             $submission = reset($submissions);
2197         }
2199         if ($submission) {
2200             return $submission;
2201         }
2202         if ($create) {
2203             $submission = new stdClass();
2204             $submission->assignment = $this->get_instance()->id;
2205             $submission->userid = 0;
2206             $submission->groupid = $groupid;
2207             $submission->timecreated = time();
2208             $submission->timemodified = $submission->timecreated;
2209             if ($attemptnumber >= 0) {
2210                 $submission->attemptnumber = $attemptnumber;
2211             } else {
2212                 $submission->attemptnumber = 0;
2213             }
2214             // Work out if this is the latest submission.
2215             $submission->latest = 0;
2216             $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2217             if ($attemptnumber == -1) {
2218                 // This is a new submission so it must be the latest.
2219                 $submission->latest = 1;
2220             } else {
2221                 // We need to work this out.
2222                 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2223                 if ($result) {
2224                     $latestsubmission = reset($result);
2225                 }
2226                 if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2227                     $submission->latest = 1;
2228                 }
2229             }
2230             if ($submission->latest) {
2231                 // This is the case when we need to set latest to 0 for all the other attempts.
2232                 $DB->set_field('assign_submission', 'latest', 0, $params);
2233             }
2234             $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
2235             $sid = $DB->insert_record('assign_submission', $submission);
2236             return $DB->get_record('assign_submission', array('id' => $sid));
2237         }
2238         return false;
2239     }
2241     /**
2242      * View a summary listing of all assignments in the current course.
2243      *
2244      * @return string
2245      */
2246     private function view_course_index() {
2247         global $USER;
2249         $o = '';
2251         $course = $this->get_course();
2252         $strplural = get_string('modulenameplural', 'assign');
2254         if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2255             $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2256             $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2257             return $o;
2258         }
2260         $strsectionname = '';
2261         $usesections = course_format_uses_sections($course->format);
2262         $modinfo = get_fast_modinfo($course);
2264         if ($usesections) {
2265             $strsectionname = get_string('sectionname', 'format_'.$course->format);
2266             $sections = $modinfo->get_section_info_all();
2267         }
2268         $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2270         $timenow = time();
2272         $currentsection = '';
2273         foreach ($modinfo->instances['assign'] as $cm) {
2274             if (!$cm->uservisible) {
2275                 continue;
2276             }
2278             $timedue = $cms[$cm->id]->duedate;
2280             $sectionname = '';
2281             if ($usesections && $cm->sectionnum) {
2282                 $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2283             }
2285             $submitted = '';
2286             $context = context_module::instance($cm->id);
2288             $assignment = new assign($context, $cm, $course);
2290             if (has_capability('mod/assign:grade', $context)) {
2291                 $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2293             } else if (has_capability('mod/assign:submit', $context)) {
2294                 $usersubmission = $assignment->get_user_submission($USER->id, false);
2296                 if (!empty($usersubmission->status)) {
2297                     $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2298                 } else {
2299                     $submitted = get_string('submissionstatus_', 'assign');
2300                 }
2301             }
2302             $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2303             if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2304                     !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2305                 $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
2306             } else {
2307                 $grade = '-';
2308             }
2310             $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2312         }
2314         $o .= $this->get_renderer()->render($courseindexsummary);
2315         $o .= $this->view_footer();
2317         return $o;
2318     }
2320     /**
2321      * View a page rendered by a plugin.
2322      *
2323      * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
2324      *
2325      * @return string
2326      */
2327     protected function view_plugin_page() {
2328         global $USER;
2330         $o = '';
2332         $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2333         $plugintype = required_param('plugin', PARAM_TEXT);
2334         $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2336         $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2337         if (!$plugin) {
2338             print_error('invalidformdata', '');
2339             return;
2340         }
2342         $o .= $plugin->view_page($pluginaction);
2344         return $o;
2345     }
2348     /**
2349      * This is used for team assignments to get the group for the specified user.
2350      * If the user is a member of multiple or no groups this will return false
2351      *
2352      * @param int $userid The id of the user whose submission we want
2353      * @return mixed The group or false
2354      */
2355     public function get_submission_group($userid) {
2357         if (isset($this->usersubmissiongroups[$userid])) {
2358             return $this->usersubmissiongroups[$userid];
2359         }
2361         $groups = $this->get_all_groups($userid);
2362         if (count($groups) != 1) {
2363             $return = false;
2364         } else {
2365             $return = array_pop($groups);
2366         }
2368         // Cache the user submission group.
2369         $this->usersubmissiongroups[$userid] = $return;
2371         return $return;
2372     }
2374     /**
2375      * Gets all groups the user is a member of.
2376      *
2377      * @param int $userid Teh id of the user who's groups we are checking
2378      * @return array The group objects
2379      */
2380     public function get_all_groups($userid) {
2381         if (isset($this->usergroups[$userid])) {
2382             return $this->usergroups[$userid];
2383         }
2385         $grouping = $this->get_instance()->teamsubmissiongroupingid;
2386         $return = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
2388         $this->usergroups[$userid] = $return;
2390         return $return;
2391     }
2394     /**
2395      * Display the submission that is used by a plugin.
2396      *
2397      * Uses url parameters 'sid', 'gid' and 'plugin'.
2398      *
2399      * @param string $pluginsubtype
2400      * @return string
2401      */
2402     protected function view_plugin_content($pluginsubtype) {
2403         $o = '';
2405         $submissionid = optional_param('sid', 0, PARAM_INT);
2406         $gradeid = optional_param('gid', 0, PARAM_INT);
2407         $plugintype = required_param('plugin', PARAM_TEXT);
2408         $item = null;
2409         if ($pluginsubtype == 'assignsubmission') {
2410             $plugin = $this->get_submission_plugin_by_type($plugintype);
2411             if ($submissionid <= 0) {
2412                 throw new coding_exception('Submission id should not be 0');
2413             }
2414             $item = $this->get_submission($submissionid);
2416             // Check permissions.
2417             $this->require_view_submission($item->userid);
2418             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2419                                                               $this->get_context(),
2420                                                               $this->show_intro(),
2421                                                               $this->get_course_module()->id,
2422                                                               $plugin->get_name()));
2423             $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
2424                                                               $item,
2425                                                               assign_submission_plugin_submission::FULL,
2426                                                               $this->get_course_module()->id,
2427                                                               $this->get_return_action(),
2428                                                               $this->get_return_params()));
2430             // Trigger event for viewing a submission.
2431             \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2433         } else {
2434             $plugin = $this->get_feedback_plugin_by_type($plugintype);
2435             if ($gradeid <= 0) {
2436                 throw new coding_exception('Grade id should not be 0');
2437             }
2438             $item = $this->get_grade($gradeid);
2439             // Check permissions.
2440             $this->require_view_submission($item->userid);
2441             $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2442                                                               $this->get_context(),
2443                                                               $this->show_intro(),
2444                                                               $this->get_course_module()->id,
2445                                                               $plugin->get_name()));
2446             $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
2447                                                               $item,
2448                                                               assign_feedback_plugin_feedback::FULL,
2449                                                               $this->get_course_module()->id,
2450                                                               $this->get_return_action(),
2451                                                               $this->get_return_params()));
2453             // Trigger event for viewing feedback.
2454             \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
2455         }
2457         $o .= $this->view_return_links();
2459         $o .= $this->view_footer();
2461         return $o;
2462     }
2464     /**
2465      * Rewrite plugin file urls so they resolve correctly in an exported zip.
2466      *
2467      * @param string $text - The replacement text
2468      * @param stdClass $user - The user record
2469      * @param assign_plugin $plugin - The assignment plugin
2470      */
2471     public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2472         $groupmode = groups_get_activity_groupmode($this->get_course_module());
2473         $groupname = '';
2474         if ($groupmode) {
2475             $groupid = groups_get_activity_group($this->get_course_module(), true);
2476             $groupname = groups_get_group_name($groupid).'-';
2477         }
2479         if ($this->is_blind_marking()) {
2480             $prefix = $groupname . get_string('participant', 'assign');
2481             $prefix = str_replace('_', ' ', $prefix);
2482             $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2483         } else {
2484             $prefix = $groupname . fullname($user);
2485             $prefix = str_replace('_', ' ', $prefix);
2486             $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2487         }
2489         $subtype = $plugin->get_subtype();
2490         $type = $plugin->get_type();
2491         $prefix = $prefix . $subtype . '_' . $type . '_';
2493         $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2495         return $result;
2496     }
2498     /**
2499      * Render the content in editor that is often used by plugin.
2500      *
2501      * @param string $filearea
2502      * @param int  $submissionid
2503      * @param string $plugintype
2504      * @param string $editor
2505      * @param string $component
2506      * @return string
2507      */
2508     public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2509         global $CFG;
2511         $result = '';
2513         $plugin = $this->get_submission_plugin_by_type($plugintype);
2515         $text = $plugin->get_editor_text($editor, $submissionid);
2516         $format = $plugin->get_editor_format($editor, $submissionid);
2518         $finaltext = file_rewrite_pluginfile_urls($text,
2519                                                   'pluginfile.php',
2520                                                   $this->get_context()->id,
2521                                                   $component,
2522                                                   $filearea,
2523                                                   $submissionid);
2524         $params = array('overflowdiv' => true, 'context' => $this->get_context());
2525         $result .= format_text($finaltext, $format, $params);
2527         if ($CFG->enableportfolios && has_capability('mod/assign:exportownsubmission', $this->context)) {
2528             require_once($CFG->libdir . '/portfoliolib.php');
2530             $button = new portfolio_add_button();
2531             $portfolioparams = array('cmid' => $this->get_course_module()->id,
2532                                      'sid' => $submissionid,
2533                                      'plugin' => $plugintype,
2534                                      'editor' => $editor,
2535                                      'area'=>$filearea);
2536             $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
2537             $fs = get_file_storage();
2539             if ($files = $fs->get_area_files($this->context->id,
2540                                              $component,
2541                                              $filearea,
2542                                              $submissionid,
2543                                              'timemodified',
2544                                              false)) {
2545                 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2546             } else {
2547                 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2548             }
2549             $result .= $button->to_html();
2550         }
2551         return $result;
2552     }
2554     /**
2555      * Display a continue page after grading.
2556      *
2557      * @param string $message - The message to display.
2558      * @return string
2559      */
2560     protected function view_savegrading_result($message) {
2561         $o = '';
2562         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2563                                                       $this->get_context(),
2564                                                       $this->show_intro(),
2565                                                       $this->get_course_module()->id,
2566                                                       get_string('savegradingresult', 'assign')));
2567         $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2568                                                    $message,
2569                                                    $this->get_course_module()->id);
2570         $o .= $this->get_renderer()->render($gradingresult);
2571         $o .= $this->view_footer();
2572         return $o;
2573     }
2574     /**
2575      * Display a continue page after quickgrading.
2576      *
2577      * @param string $message - The message to display.
2578      * @return string
2579      */
2580     protected function view_quickgrading_result($message) {
2581         $o = '';
2582         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2583                                                       $this->get_context(),
2584                                                       $this->show_intro(),
2585                                                       $this->get_course_module()->id,
2586                                                       get_string('quickgradingresult', 'assign')));
2587         $lastpage = optional_param('lastpage', null, PARAM_INT);
2588         $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2589                                                    $message,
2590                                                    $this->get_course_module()->id,
2591                                                    false,
2592                                                    $lastpage);
2593         $o .= $this->get_renderer()->render($gradingresult);
2594         $o .= $this->view_footer();
2595         return $o;
2596     }
2598     /**
2599      * Display the page footer.
2600      *
2601      * @return string
2602      */
2603     protected function view_footer() {
2604         // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2605         if (!PHPUNIT_TEST) {
2606             return $this->get_renderer()->render_footer();
2607         }
2609         return '';
2610     }
2612     /**
2613      * Throw an error if the permissions to view this users submission are missing.
2614      *
2615      * @throws required_capability_exception
2616      * @return none
2617      */
2618     public function require_view_submission($userid) {
2619         if (!$this->can_view_submission($userid)) {
2620             throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2621         }
2622     }
2624     /**
2625      * Throw an error if the permissions to view grades in this assignment are missing.
2626      *
2627      * @throws required_capability_exception
2628      * @return none
2629      */
2630     public function require_view_grades() {
2631         if (!$this->can_view_grades()) {
2632             throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2633         }
2634     }
2636     /**
2637      * Does this user have view grade or grade permission for this assignment?
2638      *
2639      * @return bool
2640      */
2641     public function can_view_grades() {
2642         // Permissions check.
2643         if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2644             return false;
2645         }
2647         return true;
2648     }
2650     /**
2651      * Does this user have grade permission for this assignment?
2652      *
2653      * @return bool
2654      */
2655     public function can_grade() {
2656         // Permissions check.
2657         if (!has_capability('mod/assign:grade', $this->context)) {
2658             return false;
2659         }
2661         return true;
2662     }
2664     /**
2665      * Download a zip file of all assignment submissions.
2666      *
2667      * @return string - If an error occurs, this will contain the error page.
2668      */
2669     protected function download_submissions() {
2670         global $CFG, $DB;
2672         // More efficient to load this here.
2673         require_once($CFG->libdir.'/filelib.php');
2675         // Increase the server timeout to handle the creation and sending of large zip files.
2676         core_php_time_limit::raise();
2678         $this->require_view_grades();
2680         // Load all users with submit.
2681         $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
2682                         $this->show_only_active_users());
2684         // Build a list of files to zip.
2685         $filesforzipping = array();
2686         $fs = get_file_storage();
2688         $groupmode = groups_get_activity_groupmode($this->get_course_module());
2689         // All users.
2690         $groupid = 0;
2691         $groupname = '';
2692         if ($groupmode) {
2693             $groupid = groups_get_activity_group($this->get_course_module(), true);
2694             $groupname = groups_get_group_name($groupid).'-';
2695         }
2697         // Construct the zip file name.
2698         $filename = clean_filename($this->get_course()->shortname . '-' .
2699                                    $this->get_instance()->name . '-' .
2700                                    $groupname.$this->get_course_module()->id . '.zip');
2702         // Get all the files for each student.
2703         foreach ($students as $student) {
2704             $userid = $student->id;
2706             if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
2707                 // Get the plugins to add their own files to the zip.
2709                 $submissiongroup = false;
2710                 $groupname = '';
2711                 if ($this->get_instance()->teamsubmission) {
2712                     $submission = $this->get_group_submission($userid, 0, false);
2713                     $submissiongroup = $this->get_submission_group($userid);
2714                     if ($submissiongroup) {
2715                         $groupname = $submissiongroup->name . '-';
2716                     } else {
2717                         $groupname = get_string('defaultteam', 'assign') . '-';
2718                     }
2719                 } else {
2720                     $submission = $this->get_user_submission($userid, false);
2721                 }
2723                 if ($this->is_blind_marking()) {
2724                     $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2725                     $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2726                 } else {
2727                     $prefix = str_replace('_', ' ', $groupname . fullname($student));
2728                     $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2729                 }
2731                 if ($submission) {
2732                     foreach ($this->submissionplugins as $plugin) {
2733                         if ($plugin->is_enabled() && $plugin->is_visible()) {
2734                             $pluginfiles = $plugin->get_files($submission, $student);
2735                             foreach ($pluginfiles as $zipfilename => $file) {
2736                                 $subtype = $plugin->get_subtype();
2737                                 $type = $plugin->get_type();
2738                                 $prefixedfilename = clean_filename($prefix .
2739                                                                    $subtype .
2740                                                                    '_' .
2741                                                                    $type .
2742                                                                    '_' .
2743                                                                    $zipfilename);
2744                                 $filesforzipping[$prefixedfilename] = $file;
2745                             }
2746                         }
2747                     }
2748                 }
2749             }
2750         }
2751         $result = '';
2752         if (count($filesforzipping) == 0) {
2753             $header = new assign_header($this->get_instance(),
2754                                         $this->get_context(),
2755                                         '',
2756                                         $this->get_course_module()->id,
2757                                         get_string('downloadall', 'assign'));
2758             $result .= $this->get_renderer()->render($header);
2759             $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
2760             $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2761                                                                     'action'=>'grading'));
2762             $result .= $this->get_renderer()->continue_button($url);
2763             $result .= $this->view_footer();
2764         } else if ($zipfile = $this->pack_files($filesforzipping)) {
2765             \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
2766             // Send file and delete after sending.
2767             send_temp_file($zipfile, $filename);
2768             // We will not get here - send_temp_file calls exit.
2769         }
2770         return $result;
2771     }
2773     /**
2774      * Util function to add a message to the log.
2775      *
2776      * @deprecated since 2.7 - Use new events system instead.
2777      *             (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2778      *
2779      * @param string $action The current action
2780      * @param string $info A detailed description of the change. But no more than 255 characters.
2781      * @param string $url The url to the assign module instance.
2782      * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2783      *                     retrieve the arguments to use them with the new event system (Event 2).
2784      * @return void|array
2785      */
2786     public function add_to_log($action = '', $info = '', $url='', $return = false) {
2787         global $USER;
2789         $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2790         if ($url != '') {
2791             $fullurl .= '&' . $url;
2792         }
2794         $args = array(
2795             $this->get_course()->id,
2796             'assign',
2797             $action,
2798             $fullurl,
2799             $info,
2800             $this->get_course_module()->id
2801         );
2803         if ($return) {
2804             // We only need to call debugging when returning a value. This is because the call to
2805             // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2806             debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
2807             return $args;
2808         }
2809         call_user_func_array('add_to_log', $args);
2810     }
2812     /**
2813      * Lazy load the page renderer and expose the renderer to plugins.
2814      *
2815      * @return assign_renderer
2816      */
2817     public function get_renderer() {
2818         global $PAGE;
2819         if ($this->output) {
2820             return $this->output;
2821         }
2822         $this->output = $PAGE->get_renderer('mod_assign');
2823         return $this->output;
2824     }
2826     /**
2827      * Load the submission object for a particular user, optionally creating it if required.
2828      *
2829      * For team assignments there are 2 submissions - the student submission and the team submission
2830      * All files are associated with the team submission but the status of the students contribution is
2831      * recorded separately.
2832      *
2833      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
2834      * @param bool $create If set to true a new submission object will be created in the database with the status set to "new".
2835      * @param int $attemptnumber - -1 means the latest attempt
2836      * @return stdClass The submission
2837      */
2838     public function get_user_submission($userid, $create, $attemptnumber=-1) {
2839         global $DB, $USER;
2841         if (!$userid) {
2842             $userid = $USER->id;
2843         }
2844         // If the userid is not null then use userid.
2845         $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2846         if ($attemptnumber >= 0) {
2847             $params['attemptnumber'] = $attemptnumber;
2848         }
2850         // Only return the row with the highest attemptnumber.
2851         $submission = null;
2852         $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2853         if ($submissions) {
2854             $submission = reset($submissions);
2855         }
2857         if ($submission) {
2858             return $submission;
2859         }
2860         if ($create) {
2861             $submission = new stdClass();
2862             $submission->assignment   = $this->get_instance()->id;
2863             $submission->userid       = $userid;
2864             $submission->timecreated = time();
2865             $submission->timemodified = $submission->timecreated;
2866             $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
2867             if ($attemptnumber >= 0) {
2868                 $submission->attemptnumber = $attemptnumber;
2869             } else {
2870                 $submission->attemptnumber = 0;
2871             }
2872             // Work out if this is the latest submission.
2873             $submission->latest = 0;
2874             $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2875             if ($attemptnumber == -1) {
2876                 // This is a new submission so it must be the latest.
2877                 $submission->latest = 1;
2878             } else {
2879                 // We need to work this out.
2880                 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2881                 $latestsubmission = null;
2882                 if ($result) {
2883                     $latestsubmission = reset($result);
2884                 }
2885                 if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
2886                     $submission->latest = 1;
2887                 }
2888             }
2889             if ($submission->latest) {
2890                 // This is the case when we need to set latest to 0 for all the other attempts.
2891                 $DB->set_field('assign_submission', 'latest', 0, $params);
2892             }
2893             $sid = $DB->insert_record('assign_submission', $submission);
2894             return $DB->get_record('assign_submission', array('id' => $sid));
2895         }
2896         return false;
2897     }
2899     /**
2900      * Load the submission object from it's id.
2901      *
2902      * @param int $submissionid The id of the submission we want
2903      * @return stdClass The submission
2904      */
2905     protected function get_submission($submissionid) {
2906         global $DB;
2908         $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2909         return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
2910     }
2912     /**
2913      * This will retrieve a user flags object from the db optionally creating it if required.
2914      * The user flags was split from the user_grades table in 2.5.
2915      *
2916      * @param int $userid The user we are getting the flags for.
2917      * @param bool $create If true the flags record will be created if it does not exist
2918      * @return stdClass The flags record
2919      */
2920     public function get_user_flags($userid, $create) {
2921         global $DB, $USER;
2923         // If the userid is not null then use userid.
2924         if (!$userid) {
2925             $userid = $USER->id;
2926         }
2928         $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2930         $flags = $DB->get_record('assign_user_flags', $params);
2932         if ($flags) {
2933             return $flags;
2934         }
2935         if ($create) {
2936             $flags = new stdClass();
2937             $flags->assignment = $this->get_instance()->id;
2938             $flags->userid = $userid;
2939             $flags->locked = 0;
2940             $flags->extensionduedate = 0;
2941             $flags->workflowstate = '';
2942             $flags->allocatedmarker = 0;
2944             // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2945             // This is because students only want to be notified about certain types of update (grades and feedback).
2946             $flags->mailed = 2;
2948             $fid = $DB->insert_record('assign_user_flags', $flags);
2949             $flags->id = $fid;
2950             return $flags;
2951         }
2952         return false;
2953     }
2955     /**
2956      * This will retrieve a grade object from the db, optionally creating it if required.
2957      *
2958      * @param int $userid The user we are grading
2959      * @param bool $create If true the grade will be created if it does not exist
2960      * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
2961      * @return stdClass The grade record
2962      */
2963     public function get_user_grade($userid, $create, $attemptnumber=-1) {
2964         global $DB, $USER;
2966         // If the userid is not null then use userid.
2967         if (!$userid) {
2968             $userid = $USER->id;
2969         }
2970         $submission = null;
2972         $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2973         if ($attemptnumber < 0 || $create) {
2974             // Make sure this grade matches the latest submission attempt.
2975             if ($this->get_instance()->teamsubmission) {
2976                 $submission = $this->get_group_submission($userid, 0, true);
2977             } else {
2978                 $submission = $this->get_user_submission($userid, true);
2979             }
2980             if ($submission) {
2981                 $attemptnumber = $submission->attemptnumber;
2982             }
2983         }
2985         if ($attemptnumber >= 0) {
2986             $params['attemptnumber'] = $attemptnumber;
2987         }
2989         $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
2991         if ($grades) {
2992             return reset($grades);
2993         }
2994         if ($create) {
2995             $grade = new stdClass();
2996             $grade->assignment   = $this->get_instance()->id;
2997             $grade->userid       = $userid;
2998             $grade->timecreated = time();
2999             // If we are "auto-creating" a grade - and there is a submission
3000             // the new grade should not have a more recent timemodified value
3001             // than the submission.
3002             if ($submission) {
3003                 $grade->timemodified = $submission->timemodified;
3004             } else {
3005                 $grade->timemodified = $grade->timecreated;
3006             }
3007             $grade->grade = -1;
3008             $grade->grader = $USER->id;
3009             if ($attemptnumber >= 0) {
3010                 $grade->attemptnumber = $attemptnumber;
3011             }
3013             $gid = $DB->insert_record('assign_grades', $grade);
3014             $grade->id = $gid;
3015             return $grade;
3016         }
3017         return false;
3018     }
3020     /**
3021      * This will retrieve a grade object from the db.
3022      *
3023      * @param int $gradeid The id of the grade
3024      * @return stdClass The grade record
3025      */
3026     protected function get_grade($gradeid) {
3027         global $DB;
3029         $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
3030         return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
3031     }
3033     /**
3034      * Print the grading page for a single user submission.
3035      *
3036      * @param moodleform $mform
3037      * @return string
3038      */
3039     protected function view_single_grade_page($mform) {
3040         global $DB, $CFG, $SESSION;
3042         $o = '';
3043         $instance = $this->get_instance();
3045         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3047         // Need submit permission to submit an assignment.
3048         require_capability('mod/assign:grade', $this->context);
3050         $header = new assign_header($instance,
3051                                     $this->get_context(),
3052                                     false,
3053                                     $this->get_course_module()->id,
3054                                     get_string('grading', 'assign'));
3055         $o .= $this->get_renderer()->render($header);
3057         // If userid is passed - we are only grading a single student.
3058         $rownum = required_param('rownum', PARAM_INT);
3059         $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
3060         $userid = optional_param('userid', 0, PARAM_INT);
3061         $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
3063         if (!$userid) {
3064             $useridlistkey = $this->get_useridlist_key($useridlistid);
3065             if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
3066                 $SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list();
3067             }
3068             $useridlist = $SESSION->mod_assign_useridlist[$useridlistkey];
3069         } else {
3070             $rownum = 0;
3071             $useridlist = array($userid);
3072         }
3074         if ($rownum < 0 || $rownum > count($useridlist)) {
3075             throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
3076         }
3078         $last = false;
3079         $userid = $useridlist[$rownum];
3080         if ($rownum == count($useridlist) - 1) {
3081             $last = true;
3082         }
3083         // This variation on the url will link direct to this student, with no next/previous links.
3084         // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3085         $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3086         $this->register_return_link('grade', $returnparams);
3088         $user = $DB->get_record('user', array('id' => $userid));
3089         if ($user) {
3090             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3091             $usersummary = new assign_user_summary($user,
3092                                                    $this->get_course()->id,
3093                                                    $viewfullnames,
3094                                                    $this->is_blind_marking(),
3095                                                    $this->get_uniqueid_for_user($user->id),
3096                                                    get_extra_user_fields($this->get_context()),
3097                                                    !$this->is_active_user($userid));
3098             $o .= $this->get_renderer()->render($usersummary);
3099         }
3100         $submission = $this->get_user_submission($userid, false, $attemptnumber);
3101         $submissiongroup = null;
3102         $teamsubmission = null;
3103         $notsubmitted = array();
3104         if ($instance->teamsubmission) {
3105             $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
3106             $submissiongroup = $this->get_submission_group($userid);
3107             $groupid = 0;
3108             if ($submissiongroup) {
3109                 $groupid = $submissiongroup->id;
3110             }
3111             $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3113         }
3115         // Get the requested grade.
3116         $grade = $this->get_user_grade($userid, false, $attemptnumber);
3117         $flags = $this->get_user_flags($userid, false);
3118         if ($this->can_view_submission($userid)) {
3119             $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
3120             $extensionduedate = null;
3121             if ($flags) {
3122                 $extensionduedate = $flags->extensionduedate;
3123             }
3124             $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
3125             $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3126             $usergroups = $this->get_all_groups($user->id);
3128             $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
3129                                                              $instance->alwaysshowdescription,
3130                                                              $submission,
3131                                                              $instance->teamsubmission,
3132                                                              $teamsubmission,
3133                                                              $submissiongroup,
3134                                                              $notsubmitted,
3135                                                              $this->is_any_submission_plugin_enabled(),
3136                                                              $gradelocked,
3137                                                              $this->is_graded($userid),
3138                                                              $instance->duedate,
3139                                                              $instance->cutoffdate,
3140                                                              $this->get_submission_plugins(),
3141                                                              $this->get_return_action(),
3142                                                              $this->get_return_params(),
3143                                                              $this->get_course_module()->id,
3144                                                              $this->get_course()->id,
3145                                                              assign_submission_status::GRADER_VIEW,
3146                                                              $showedit,
3147                                                              false,
3148                                                              $viewfullnames,
3149                                                              $extensionduedate,
3150                                                              $this->get_context(),
3151                                                              $this->is_blind_marking(),
3152                                                              '',
3153                                                              $instance->attemptreopenmethod,
3154                                                              $instance->maxattempts,
3155                                                              $this->get_grading_status($userid),
3156                                                              $instance->preventsubmissionnotingroup,
3157                                                              $usergroups);
3158             $o .= $this->get_renderer()->render($submissionstatus);
3159         }
3161         if ($grade) {
3162             $data = new stdClass();
3163             if ($grade->grade !== null && $grade->grade >= 0) {
3164                 $data->grade = format_float($grade->grade, 2);
3165             }
3166         } else {
3167             $data = new stdClass();
3168             $data->grade = '';
3169         }
3171         if (!empty($flags->workflowstate)) {
3172             $data->workflowstate = $flags->workflowstate;
3173         }
3174         if (!empty($flags->allocatedmarker)) {
3175             $data->allocatedmarker = $flags->allocatedmarker;
3176         }
3178         // Warning if required.
3179         $allsubmissions = $this->get_all_submissions($userid);
3181         if ($attemptnumber != -1) {
3182             $params = array('attemptnumber'=>$attemptnumber + 1,
3183                             'totalattempts'=>count($allsubmissions));
3184             $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
3185             $o .= $this->get_renderer()->notification($message);
3186         }
3188         // Now show the grading form.
3189         if (!$mform) {
3190             $pagination = array('rownum'=>$rownum,
3191                                 'useridlistid'=>$useridlistid,
3192                                 'last'=>$last,
3193                                 'userid'=>optional_param('userid', 0, PARAM_INT),
3194                                 'attemptnumber'=>$attemptnumber);
3195             $formparams = array($this, $data, $pagination);
3196             $mform = new mod_assign_grade_form(null,
3197                                                $formparams,
3198                                                'post',
3199                                                '',
3200                                                array('class'=>'gradeform'));
3201         }
3202         $o .= $this->get_renderer()->heading(get_string('grade'), 3);
3203         $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
3205         if (count($allsubmissions) > 1 && $attemptnumber == -1) {
3206             $allgrades = $this->get_all_grades($userid);
3207             $history = new assign_attempt_history($allsubmissions,
3208                                                   $allgrades,
3209                                                   $this->get_submission_plugins(),
3210                                                   $this->get_feedback_plugins(),
3211                                                   $this->get_course_module()->id,
3212                                                   $this->get_return_action(),
3213                                                   $this->get_return_params(),
3214                                                   true,
3215                                                   $useridlistid,
3216                                                   $rownum);
3218             $o .= $this->get_renderer()->render($history);
3219         }
3221         \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
3223         $o .= $this->view_footer();
3224         return $o;
3225     }
3227     /**
3228      * Show a confirmation page to make sure they want to release student identities.
3229      *
3230      * @return string
3231      */
3232     protected function view_reveal_identities_confirm() {
3233         require_capability('mod/assign:revealidentities', $this->get_context());
3235         $o = '';
3236         $header = new assign_header($this->get_instance(),
3237                                     $this->get_context(),
3238                                     false,
3239                                     $this->get_course_module()->id);
3240         $o .= $this->get_renderer()->render($header);
3242         $urlparams = array('id'=>$this->get_course_module()->id,
3243                            'action'=>'revealidentitiesconfirm',
3244                            'sesskey'=>sesskey());
3245         $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
3247         $urlparams = array('id'=>$this->get_course_module()->id,
3248                            'action'=>'grading');
3249         $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
3251         $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
3252                                              $confirmurl,
3253                                              $cancelurl);
3254         $o .= $this->view_footer();
3256         \mod_assign\event\reveal_identities_confirmation_page_viewed::create_from_assign($this)->trigger();
3258         return $o;
3259     }
3261     /**
3262      * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
3263      *
3264      * @return string
3265      */
3266     protected function view_return_links() {
3267         $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
3268         $returnparams = optional_param('returnparams', '', PARAM_TEXT);
3270         $params = array();
3271         $returnparams = str_replace('&amp;', '&', $returnparams);
3272         parse_str($returnparams, $params);
3273         $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
3274         $params = array_merge($newparams, $params);
3276         $url = new moodle_url('/mod/assign/view.php', $params);
3277         return $this->get_renderer()->single_button($url, get_string('back'), 'get');
3278     }
3280     /**
3281      * View the grading table of all submissions for this assignment.
3282      *
3283      * @return string
3284      */
3285     protected function view_grading_table() {
3286         global $USER, $CFG, $SESSION;
3288         // Include grading options form.
3289         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
3290         require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
3291         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3292         $o = '';
3293         $cmid = $this->get_course_module()->id;
3295         $links = array();
3296         if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
3297                 has_capability('moodle/grade:viewall', $this->get_course_context())) {
3298             $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
3299             $links[$gradebookurl] = get_string('viewgradebook', 'assign');
3300         }
3301         if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
3302             $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
3303             $links[$downloadurl] = get_string('downloadall', 'assign');
3304         }
3305         if ($this->is_blind_marking() &&
3306                 has_capability('mod/assign:revealidentities', $this->get_context())) {
3307             $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
3308             $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
3309         }
3310         foreach ($this->get_feedback_plugins() as $plugin) {
3311             if ($plugin->is_enabled() && $plugin->is_visible()) {
3312                 foreach ($plugin->get_grading_actions() as $action => $description) {
3313                     $url = '/mod/assign/view.php' .
3314                            '?id=' .  $cmid .
3315                            '&plugin=' . $plugin->get_type() .
3316                            '&pluginsubtype=assignfeedback' .
3317                            '&action=viewpluginpage&pluginaction=' . $action;
3318                     $links[$url] = $description;
3319                 }
3320             }
3321         }
3323         // Sort links alphabetically based on the link description.
3324         core_collator::asort($links);
3326         $gradingactions = new url_select($links);
3327         $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
3329         $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3331         $perpage = (int) get_user_preferences('assign_perpage', 10);
3332         $filter = get_user_preferences('assign_filter', '');
3333         $markerfilter = get_user_preferences('assign_markerfilter', '');
3334         $workflowfilter = get_user_preferences('assign_workflowfilter', '');
3335         $controller = $gradingmanager->get_active_controller();
3336         $showquickgrading = empty($controller) && $this->can_grade();
3337         $quickgrading = get_user_preferences('assign_quickgrading', false);
3338         $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
3340         $markingallocation = $this->get_instance()->markingworkflow &&
3341             $this->get_instance()->markingallocation &&
3342             has_capability('mod/assign:manageallocations', $this->context);
3343         // Get markers to use in drop lists.
3344         $markingallocationoptions = array();
3345         if ($markingallocation) {
3346             list($sort, $params) = users_order_by_sql();
3347             $markers = get_users_by_capability($this->context, 'mod/assign:grade', '', $sort);
3348             $markingallocationoptions[''] = get_string('filternone', 'assign');
3349             $markingallocationoptions[ASSIGN_MARKER_FILTER_NO_MARKER] = get_string('markerfilternomarker', 'assign');
3350             foreach ($markers as $marker) {
3351                 $markingallocationoptions[$marker->id] = fullname($marker);
3352             }
3353         }
3355         $markingworkflow = $this->get_instance()->markingworkflow;
3356         // Get marking states to show in form.
3357         $markingworkflowoptions = array();
3358         if ($markingworkflow) {
3359             $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
3360             $markingworkflowoptions[''] = get_string('filternone', 'assign');
3361             $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
3362             $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
3363         }
3365         // Print options for changing the filter and changing the number of results per page.
3366         $gradingoptionsformparams = array('cm'=>$cmid,
3367                                           'contextid'=>$this->context->id,
3368                                           'userid'=>$USER->id,
3369                                           'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
3370                                           'showquickgrading'=>$showquickgrading,
3371                                           'quickgrading'=>$quickgrading,
3372                                           'markingworkflowopt'=>$markingworkflowoptions,
3373                                           'markingallocationopt'=>$markingallocationoptions,
3374                                           'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
3375                                           'showonlyactiveenrol'=>$this->show_only_active_users());
3377         $classoptions = array('class'=>'gradingoptionsform');
3378         $gradingoptionsform = new mod_assign_grading_options_form(null,
3379                                                                   $gradingoptionsformparams,
3380                                                                   'post',
3381                                                                   '',
3382                                                                   $classoptions);
3384         $batchformparams = array('cm'=>$cmid,
3385                                  'submissiondrafts'=>$this->get_instance()->submissiondrafts,
3386                                  'duedate'=>$this->get_instance()->duedate,
3387                                  'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
3388                                  'feedbackplugins'=>$this->get_feedback_plugins(),
3389                                  'context'=>$this->get_context(),
3390                                  'markingworkflow'=>$markingworkflow,
3391                                  'markingallocation'=>$markingallocation);
3392         $classoptions = array('class'=>'gradingbatchoperationsform');
3394         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
3395                                                                                    $batchformparams,
3396                                                                                    'post',
3397                                                                                    '',
3398                                                                                    $classoptions);
3400         $gradingoptionsdata = new stdClass();
3401         $gradingoptionsdata->perpage = $perpage;
3402         $gradingoptionsdata->filter = $filter;
3403         $gradingoptionsdata->markerfilter = $markerfilter;
3404         $gradingoptionsdata->workflowfilter = $workflowfilter;
3405         $gradingoptionsform->set_data($gradingoptionsdata);
3407         $actionformtext = $this->get_renderer()->render($gradingactions);
3408         $header = new assign_header($this->get_instance(),
3409                                     $this->get_context(),
3410                                     false,
3411                                     $this->get_course_module()->id,
3412                                     get_string('grading', 'assign'),
3413                                     $actionformtext);
3414         $o .= $this->get_renderer()->render($header);
3416         $currenturl = $CFG->wwwroot .
3417                       '/mod/assign/view.php?id=' .
3418                       $this->get_course_module()->id .
3419                       '&action=grading';
3421         $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
3423         // Plagiarism update status apearring in the grading book.
3424         if (!empty($CFG->enableplagiarism)) {
3425             require_once($CFG->libdir . '/plagiarismlib.php');
3426             $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
3427         }
3429         if ($this->is_blind_marking() && has_capability('mod/assign:viewblinddetails', $this->get_context())) {
3430             $o .= $this->get_renderer()->notification(get_string('blindmarkingenabledwarning', 'assign'), 'notifymessage');
3431         }
3433         // Load and print the table of submissions.
3434         if ($showquickgrading && $quickgrading) {
3435             $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, true);
3436             $table = $this->get_renderer()->render($gradingtable);
3437             $page = optional_param('page', null, PARAM_INT);
3438             $quickformparams = array('cm'=>$this->get_course_module()->id,
3439                                      'gradingtable'=>$table,
3440                                      'sendstudentnotifications' => $this->get_instance()->sendstudentnotifications,
3441                                      'page' => $page);
3442             $quickgradingform = new mod_assign_quick_grading_form(null, $quickformparams);
3444             $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
3445         } else {
3446             $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, false);
3447             $o .= $this->get_renderer()->render($gradingtable);
3448         }
3450         if ($this->can_grade()) {
3451             // We need to store the order of uses in the table as the person may wish to grade them.
3452             // This is done based on the row number of the user.
3453             $useridlist = $gradingtable->get_column_data('userid');
3454             $SESSION->mod_assign_useridlist[$this->get_useridlist_key()] = $useridlist;
3455         }
3457         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
3458         $users = array_keys($this->list_participants($currentgroup, true));
3459         if (count($users) != 0 && $this->can_grade()) {
3460             // If no enrolled user in a course then don't display the batch operations feature.
3461             $assignform = new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform);
3462             $o .= $this->get_renderer()->render($assignform);
3463         }
3464         $assignform = new assign_form('gradingoptionsform',
3465                                       $gradingoptionsform,
3466                                       'M.mod_assign.init_grading_options');
3467         $o .= $this->get_renderer()->render($assignform);
3468         return $o;
3469     }
3471     /**
3472      * View entire grading page.
3473      *
3474      * @return string
3475      */
3476     protected function view_grading_page() {
3477         global $CFG;
3479         $o = '';
3480         // Need submit permission to submit an assignment.
3481         $this->require_view_grades();
3482         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3484         // Only load this if it is.
3485         $o .= $this->view_grading_table();
3487         $o .= $this->view_footer();
3489         \mod_assign\event\grading_table_viewed::create_from_assign($this)->trigger();
3491         return $o;
3492     }
3494     /**
3495      * Capture the output of the plagiarism plugins disclosures and return it as a string.
3496      *
3497      * @return string
3498      */
3499     protected function plagiarism_print_disclosure() {
3500         global $CFG;
3501         $o = '';
3503         if (!empty($CFG->enableplagiarism)) {
3504             require_once($CFG->libdir . '/plagiarismlib.php');
3506             $o .= plagiarism_print_disclosure($this->get_course_module()->id);
3507         }
3509         return $o;
3510     }
3512     /**
3513      * Message for students when assignment submissions have been closed.
3514      *
3515      * @param string $title The page title
3516      * @param array $notices The array of notices to show.
3517      * @return string
3518      */
3519     protected function view_notices($title, $notices) {
3520         global $CFG;
3522         $o = '';
3524         $header = new assign_header($this->get_instance(),
3525                                     $this->get_context(),
3526                                     $this->show_intro(),
3527                                     $this->get_course_module()->id,
3528                                     $title);
3529         $o .= $this->get_renderer()->render($header);
3531         foreach ($notices as $notice) {
3532             $o .= $this->get_renderer()->notification($notice);
3533         }
3535         $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id, 'action'=>'view'));
3536         $o .= $this->get_renderer()->continue_button($url);
3538         $o .= $this->view_footer();
3540         return $o;
3541     }
3543     /**
3544      * Get the name for a user - hiding their real name if blind marking is on.
3545      *
3546      * @param stdClass $user The user record as required by fullname()
3547      * @return string The name.
3548      */
3549     public function fullname($user) {
3550         if ($this->is_blind_marking()) {
3551             $hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context());
3552             if ($hasviewblind) {
3553                 return fullname($user);
3554             } else {
3555                 $uniqueid = $this->get_uniqueid_for_user($user->id);
3556                 return get_string('participant', 'assign') . ' ' . $uniqueid;
3557             }
3558         } else {
3559             return fullname($user);
3560         }
3561     }
3563     /**
3564      * View edit submissions page.
3565      *
3566      * @param moodleform $mform
3567      * @param array $notices A list of notices to display at the top of the
3568      *                       edit submission form (e.g. from plugins).
3569      * @return string The page output.
3570      */
3571     protected function view_edit_submission_page($mform, $notices) {
3572         global $CFG, $USER, $DB;
3574         $o = '';
3575         require_once($CFG->dirroot . '/mod/assign/submission_form.php');
3576         // Need submit permission to submit an assignment.
3577         $userid = optional_param('userid', $USER->id, PARAM_INT);
3578         $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
3579         if ($userid == $USER->id) {
3580             if (!$this->can_edit_submission($userid, $USER->id)) {
3581                 print_error('nopermission');
3582             }
3583             // User is editing their own submission.
3584             require_capability('mod/assign:submit', $this->context);
3585             $title = get_string('editsubmission', 'assign');
3586         } else {
3587             // User is editing another user's submission.
3588             if (!$this->can_edit_submission($userid, $USER->id)) {
3589                 print_error('nopermission');
3590             }
3592             $name = $this->fullname($user);
3593             $title = get_string('editsubmissionother', 'assign', $name);
3594         }
3596         if (!$this->submissions_open($userid)) {
3597             $message = array(get_string('submissionsclosed', 'assign'));
3598             return $this->view_notices($title, $message);
3599         }
3601         $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
3602                                                       $this->get_context(),
3603                                                       $this->show_intro(),
3604                                                       $this->get_course_module()->id,
3605                                                       $title));
3606         if ($userid == $USER->id) {
3607             // We only show this if it their submission.
3608             $o .= $this->plagiarism_print_disclosure();
3609         }
3610         $data = new stdClass();
3611         $data->userid = $userid;
3612         if (!$mform) {
3613             $mform = new mod_assign_submission_form(null, array($this, $data));
3614         }
3616         foreach ($notices as $notice) {
3617             $o .= $this->get_renderer()->notification($notice);
3618         }
3620         $o .= $this->get_renderer()->render(new assign_form('editsubmissionform', $mform));
3622         $o .= $this->view_footer();
3624         \mod_assign\event\submission_form_viewed::create_from_user($this, $user)->trigger();
3626         return $o;
3627     }
3629     /**
3630      * See if this assignment has a grade yet.
3631      *
3632      * @param int $userid
3633      * @return bool
3634      */
3635     protected function is_graded($userid) {
3636         $grade = $this->get_user_grade($userid, false);
3637         if ($grade) {
3638             return ($grade->grade !== null && $grade->grade >= 0);
3639         }
3640         return false;
3641     }
3643     /**
3644      * Perform an access check to see if the current $USER can view this group submission.
3645      *
3646      * @param int $groupid
3647      * @return bool
3648      */
3649     public function can_view_group_submission($groupid) {
3650         global $USER;
3652         if (has_capability('mod/assign:grade', $this->context)) {
3653             return true;
3654         }
3655         if (!is_enrolled($this->get_course_context(), $USER->id)) {
3656             return false;
3657         }
3658         $members = $this->get_submission_group_members($groupid, true);
3659         foreach ($members as $member) {
3660             if ($member->id == $USER->id) {
3661                 return true;
3662             }
3663         }
3664         return false;
3665     }
3667     /**
3668      * Perform an access check to see if the current $USER can view this users submission.
3669      *
3670      * @param int $userid
3671      * @return bool
3672      */
3673     public function can_view_submission($userid) {
3674         global $USER;
3676         if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
3677             return false;
3678         }
3679         if (has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
3680             return true;
3681         }
3682         if (!is_enrolled($this->get_course_context(), $userid)) {
3683             return false;
3684         }
3685         if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
3686             return true;
3687         }
3688         return false;
3689     }
3691     /**
3692      * Allows the plugin to show a batch grading operation page.
3693      *