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