MDL-52566 mod/assign: Fix group submission draft records
[moodle.git] / mod / assign / locallib.php
CommitLineData
bbd0e548
DW
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/>.
16
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 */
26
27defined('MOODLE_INTERNAL') || die();
28
e5403f8c 29// Assignment submission statuses.
9e3eee67 30define('ASSIGN_SUBMISSION_STATUS_NEW', 'new');
df211804 31define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
e5403f8c
DW
32define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
33define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
bbd0e548 34
e5403f8c 35// Search filters for grading page.
bbd0e548 36define('ASSIGN_FILTER_SUBMITTED', 'submitted');
c80d59f2 37define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted');
bbd0e548
DW
38define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
39define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
40
c80d59f2
JF
41// Marker filter for grading page.
42define('ASSIGN_MARKER_FILTER_NO_MARKER', -1);
43
df211804
DW
44// Reopen attempt methods.
45define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
46define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
47define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
48
49// Special value means allow unlimited attempts.
50define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
51
bd3ee807
MN
52// Grading states.
53define('ASSIGN_GRADING_STATUS_GRADED', 'graded');
54define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded');
55
f8d107b3
DM
56// Marking workflow states.
57define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
58define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
59define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
60define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
61define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
62define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
63
7faf78cb
HB
64// Name of file area for intro attachments.
65define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment');
66
e5403f8c
DW
67require_once($CFG->libdir . '/accesslib.php');
68require_once($CFG->libdir . '/formslib.php');
bbd0e548 69require_once($CFG->dirroot . '/repository/lib.php');
e5403f8c
DW
70require_once($CFG->dirroot . '/mod/assign/mod_form.php');
71require_once($CFG->libdir . '/gradelib.php');
72require_once($CFG->dirroot . '/grade/grading/lib.php');
73require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
74require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
75require_once($CFG->dirroot . '/mod/assign/renderable.php');
76require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
77require_once($CFG->libdir . '/eventslib.php');
37743241 78require_once($CFG->libdir . '/portfolio/caller.php');
bbd0e548
DW
79
80/**
81 * Standard base class for mod_assign (assignment types).
82 *
83 * @package mod_assign
84 * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
85 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
86 */
87class assign {
88
bbd0e548
DW
89 /** @var stdClass the assignment record that contains the global settings for this assign instance */
90 private $instance;
91
0d64c75f
PN
92 /** @var stdClass the grade_item record for this assign instance's primary grade item. */
93 private $gradeitem;
94
e5403f8c
DW
95 /** @var context the context of the course module for this assign instance
96 * (or just the course if we are creating a new one)
97 */
bbd0e548
DW
98 private $context;
99
100 /** @var stdClass the course this assign instance belongs to */
101 private $course;
bc5a657b 102
cfc81f03
DW
103 /** @var stdClass the admin config for all assign instances */
104 private $adminconfig;
105
bbd0e548
DW
106 /** @var assign_renderer the custom renderer for this module */
107 private $output;
108
c13ac85d 109 /** @var cm_info the course module for this assign instance */
bbd0e548
DW
110 private $coursemodule;
111
e5403f8c
DW
112 /** @var array cache for things like the coursemodule name or the scale menu -
113 * only lives for a single request.
114 */
bbd0e548
DW
115 private $cache;
116
117 /** @var array list of the installed submission plugins */
118 private $submissionplugins;
119
120 /** @var array list of the installed feedback plugins */
121 private $feedbackplugins;
122
e5403f8c
DW
123 /** @var string action to be used to return to this page
124 * (without repeating any form submissions etc).
125 */
bbd0e548
DW
126 private $returnaction = 'view';
127
128 /** @var array params to be used to return to this page */
129 private $returnparams = array();
130
131 /** @var string modulename prevents excessive calls to get_string */
f5b32abe 132 private static $modulename = null;
bbd0e548
DW
133
134 /** @var string modulenameplural prevents excessive calls to get_string */
f5b32abe 135 private static $modulenameplural = null;
bbd0e548 136
f8d107b3
DM
137 /** @var array of marking workflow states for the current user */
138 private $markingworkflowstates = null;
139
4c4c7b3f 140 /** @var bool whether to exclude users with inactive enrolment */
1ecb8044 141 private $showonlyactiveenrol = null;
4c4c7b3f 142
1a51661b 143 /** @var string A key used to identify userlists created by this object. */
3fca6937
NM
144 private $useridlistid = null;
145
c46db93c
DW
146 /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
147 private $participants = array();
148
bc006a66
DM
149 /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */
150 private $usersubmissiongroups = array();
151
ee5d4eef
AH
152 /** @var array cached list of user groups. The cache key will be the user. */
153 private $usergroups = array();
154
6d6e297b
DM
155 /** @var array cached list of IDs of users who share group membership with the user. The cache key will be the user. */
156 private $sharedgroupmembers = array();
157
bbd0e548 158 /**
e5403f8c 159 * Constructor for the base assign class.
bbd0e548 160 *
c13ac85d 161 * Note: For $coursemodule you can supply a stdclass if you like, but it
162 * will be more efficient to supply a cm_info object.
163 *
e5403f8c
DW
164 * @param mixed $coursemodulecontext context|null the course module context
165 * (or the course context if the coursemodule has not been
166 * created yet).
167 * @param mixed $coursemodule the current course module if it was already loaded,
168 * otherwise this class will load one from the context as required.
169 * @param mixed $course the current course if it was already loaded,
170 * otherwise this class will load one from the context as required.
bbd0e548
DW
171 */
172 public function __construct($coursemodulecontext, $coursemodule, $course) {
1a51661b
DM
173 global $SESSION;
174
bbd0e548 175 $this->context = $coursemodulecontext;
bbd0e548 176 $this->course = $course;
e5403f8c 177
c13ac85d 178 // Ensure that $this->coursemodule is a cm_info object (or null).
179 $this->coursemodule = cm_info::create($coursemodule);
180
e5403f8c
DW
181 // Temporary cache only lives for a single request - used to reduce db lookups.
182 $this->cache = array();
bbd0e548
DW
183
184 $this->submissionplugins = $this->load_plugins('assignsubmission');
185 $this->feedbackplugins = $this->load_plugins('assignfeedback');
3fca6937
NM
186
187 // Extra entropy is required for uniqid() to work on cygwin.
188 $this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM);
1a51661b
DM
189
190 if (!isset($SESSION->mod_assign_useridlist)) {
191 $SESSION->mod_assign_useridlist = [];
192 }
bbd0e548
DW
193 }
194
195 /**
e5403f8c 196 * Set the action and parameters that can be used to return to the current page.
bbd0e548
DW
197 *
198 * @param string $action The action for the current page
e5403f8c
DW
199 * @param array $params An array of name value pairs which form the parameters
200 * to return to the current page.
bbd0e548
DW
201 * @return void
202 */
203 public function register_return_link($action, $params) {
d04557b3
DW
204 global $PAGE;
205 $params['action'] = $action;
206 $currenturl = $PAGE->url;
207
208 $currenturl->params($params);
209 $PAGE->set_url($currenturl);
bbd0e548
DW
210 }
211
212 /**
e5403f8c
DW
213 * Return an action that can be used to get back to the current page.
214 *
bbd0e548
DW
215 * @return string action
216 */
217 public function get_return_action() {
d04557b3
DW
218 global $PAGE;
219
220 $params = $PAGE->url->params();
221
c2114099
DW
222 if (!empty($params['action'])) {
223 return $params['action'];
224 }
225 return '';
bbd0e548
DW
226 }
227
228 /**
e5403f8c
DW
229 * Based on the current assignment settings should we display the intro.
230 *
bbd0e548
DW
231 * @return bool showintro
232 */
7faf78cb 233 public function show_intro() {
bbd0e548
DW
234 if ($this->get_instance()->alwaysshowdescription ||
235 time() > $this->get_instance()->allowsubmissionsfromdate) {
236 return true;
237 }
238 return false;
239 }
240
241 /**
e5403f8c
DW
242 * Return a list of parameters that can be used to get back to the current page.
243 *
bbd0e548
DW
244 * @return array params
245 */
246 public function get_return_params() {
d04557b3
DW
247 global $PAGE;
248
c2114099
DW
249 $params = $PAGE->url->params();
250 unset($params['id']);
251 unset($params['action']);
252 return $params;
bbd0e548
DW
253 }
254
255 /**
e5403f8c
DW
256 * Set the submitted form data.
257 *
bbd0e548
DW
258 * @param stdClass $data The form data (instance)
259 */
260 public function set_instance(stdClass $data) {
261 $this->instance = $data;
262 }
263
264 /**
e5403f8c
DW
265 * Set the context.
266 *
bbd0e548
DW
267 * @param context $context The new context
268 */
269 public function set_context(context $context) {
270 $this->context = $context;
271 }
272
273 /**
e5403f8c
DW
274 * Set the course data.
275 *
bbd0e548
DW
276 * @param stdClass $course The course data
277 */
278 public function set_course(stdClass $course) {
279 $this->course = $course;
280 }
281
282 /**
e5403f8c
DW
283 * Get list of feedback plugins installed.
284 *
bbd0e548
DW
285 * @return array
286 */
287 public function get_feedback_plugins() {
288 return $this->feedbackplugins;
289 }
290
291 /**
e5403f8c
DW
292 * Get list of submission plugins installed.
293 *
bbd0e548
DW
294 * @return array
295 */
296 public function get_submission_plugins() {
297 return $this->submissionplugins;
298 }
299
b473171a
DW
300 /**
301 * Is blind marking enabled and reveal identities not set yet?
302 *
303 * @return bool
304 */
305 public function is_blind_marking() {
306 return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
307 }
308
309 /**
310 * Does an assignment have submission(s) or grade(s) already?
311 *
312 * @return bool
313 */
314 public function has_submissions_or_grades() {
315 $allgrades = $this->count_grades();
316 $allsubmissions = $this->count_submissions();
317 if (($allgrades == 0) && ($allsubmissions == 0)) {
318 return false;
319 }
320 return true;
321 }
bbd0e548
DW
322
323 /**
e5403f8c
DW
324 * Get a specific submission plugin by its type.
325 *
bbd0e548
DW
326 * @param string $subtype assignsubmission | assignfeedback
327 * @param string $type
328 * @return mixed assign_plugin|null
329 */
df47b77f 330 public function get_plugin_by_type($subtype, $type) {
bbd0e548
DW
331 $shortsubtype = substr($subtype, strlen('assign'));
332 $name = $shortsubtype . 'plugins';
d0d4796b
DW
333 if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
334 return null;
335 }
bbd0e548
DW
336 $pluginlist = $this->$name;
337 foreach ($pluginlist as $plugin) {
338 if ($plugin->get_type() == $type) {
339 return $plugin;
340 }
341 }
342 return null;
343 }
344
345 /**
e5403f8c
DW
346 * Get a feedback plugin by type.
347 *
bbd0e548
DW
348 * @param string $type - The type of plugin e.g comments
349 * @return mixed assign_feedback_plugin|null
350 */
351 public function get_feedback_plugin_by_type($type) {
352 return $this->get_plugin_by_type('assignfeedback', $type);
353 }
354
355 /**
e5403f8c
DW
356 * Get a submission plugin by type.
357 *
bbd0e548
DW
358 * @param string $type - The type of plugin e.g comments
359 * @return mixed assign_submission_plugin|null
360 */
361 public function get_submission_plugin_by_type($type) {
362 return $this->get_plugin_by_type('assignsubmission', $type);
363 }
364
365 /**
e5403f8c
DW
366 * Load the plugins from the sub folders under subtype.
367 *
bbd0e548
DW
368 * @param string $subtype - either submission or feedback
369 * @return array - The sorted list of plugins
370 */
47f48152 371 protected function load_plugins($subtype) {
bbd0e548
DW
372 global $CFG;
373 $result = array();
374
bd3b3bba 375 $names = core_component::get_plugin_list($subtype);
bbd0e548
DW
376
377 foreach ($names as $name => $path) {
378 if (file_exists($path . '/locallib.php')) {
379 require_once($path . '/locallib.php');
380
381 $shortsubtype = substr($subtype, strlen('assign'));
382 $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
383
384 $plugin = new $pluginclass($this, $name);
385
386 if ($plugin instanceof assign_plugin) {
387 $idx = $plugin->get_sort_order();
e5403f8c
DW
388 while (array_key_exists($idx, $result)) {
389 $idx +=1;
390 }
bbd0e548
DW
391 $result[$idx] = $plugin;
392 }
393 }
394 }
395 ksort($result);
396 return $result;
397 }
398
bbd0e548
DW
399 /**
400 * Display the assignment, used by view.php
401 *
402 * The assignment is displayed differently depending on your role,
403 * the settings for the assignment and the status of the assignment.
e5403f8c 404 *
bbd0e548 405 * @param string $action The current action if any.
df211804 406 * @return string - The page output.
bbd0e548
DW
407 */
408 public function view($action='') {
409
410 $o = '';
411 $mform = null;
34b8f3a8 412 $notices = array();
34e338a4 413 $nextpageparams = array();
bbd0e548 414
34e338a4
MN
415 if (!empty($this->get_course_module()->id)) {
416 $nextpageparams['id'] = $this->get_course_module()->id;
417 }
d04557b3 418
34b8f3a8 419 // Handle form submissions first.
bbd0e548
DW
420 if ($action == 'savesubmission') {
421 $action = 'editsubmission';
34b8f3a8 422 if ($this->process_save_submission($mform, $notices)) {
d04557b3
DW
423 $action = 'redirect';
424 $nextpageparams['action'] = 'view';
bbd0e548 425 }
df211804
DW
426 } else if ($action == 'editprevioussubmission') {
427 $action = 'editsubmission';
428 if ($this->process_copy_previous_attempt($notices)) {
429 $action = 'redirect';
430 $nextpageparams['action'] = 'editsubmission';
431 }
9e795179 432 } else if ($action == 'lock') {
05a6445a 433 $this->process_lock_submission();
d04557b3
DW
434 $action = 'redirect';
435 $nextpageparams['action'] = 'grading';
df211804
DW
436 } else if ($action == 'addattempt') {
437 $this->process_add_attempt(required_param('userid', PARAM_INT));
438 $action = 'redirect';
439 $nextpageparams['action'] = 'grading';
9e795179 440 } else if ($action == 'reverttodraft') {
bbd0e548 441 $this->process_revert_to_draft();
d04557b3
DW
442 $action = 'redirect';
443 $nextpageparams['action'] = 'grading';
9e795179 444 } else if ($action == 'unlock') {
05a6445a 445 $this->process_unlock_submission();
d04557b3
DW
446 $action = 'redirect';
447 $nextpageparams['action'] = 'grading';
f8d107b3
DM
448 } else if ($action == 'setbatchmarkingworkflowstate') {
449 $this->process_set_batch_marking_workflow_state();
450 $action = 'redirect';
451 $nextpageparams['action'] = 'grading';
452 } else if ($action == 'setbatchmarkingallocation') {
453 $this->process_set_batch_marking_allocation();
454 $action = 'redirect';
455 $nextpageparams['action'] = 'grading';
9e795179 456 } else if ($action == 'confirmsubmit') {
94f26900 457 $action = 'submit';
57fbd5f9 458 if ($this->process_submit_for_grading($mform, $notices)) {
d04557b3
DW
459 $action = 'redirect';
460 $nextpageparams['action'] = 'view';
57fbd5f9
DW
461 } else if ($notices) {
462 $action = 'viewsubmitforgradingerror';
463 }
464 } else if ($action == 'submitotherforgrading') {
465 if ($this->process_submit_other_for_grading($mform, $notices)) {
466 $action = 'redirect';
467 $nextpageparams['action'] = 'grading';
468 } else {
469 $action = 'viewsubmitforgradingerror';
94f26900 470 }
df47b77f
DW
471 } else if ($action == 'gradingbatchoperation') {
472 $action = $this->process_grading_batch_operation($mform);
d04557b3
DW
473 if ($action == 'grading') {
474 $action = 'redirect';
475 $nextpageparams['action'] = 'grading';
476 }
9e795179 477 } else if ($action == 'submitgrade') {
ba30fe35 478 if (optional_param('saveandshownext', null, PARAM_RAW)) {
e5403f8c 479 // Save and show next.
bbd0e548
DW
480 $action = 'grade';
481 if ($this->process_save_grade($mform)) {
d04557b3
DW
482 $action = 'redirect';
483 $nextpageparams['action'] = 'grade';
484 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
3fca6937 485 $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
bbd0e548 486 }
ba30fe35 487 } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
d04557b3
DW
488 $action = 'redirect';
489 $nextpageparams['action'] = 'grade';
490 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
3fca6937 491 $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
ba30fe35 492 } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
d04557b3
DW
493 $action = 'redirect';
494 $nextpageparams['action'] = 'grade';
495 $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
3fca6937 496 $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
ba30fe35 497 } else if (optional_param('savegrade', null, PARAM_RAW)) {
e5403f8c 498 // Save changes button.
bbd0e548
DW
499 $action = 'grade';
500 if ($this->process_save_grade($mform)) {
082ad257
JF
501 $action = 'redirect';
502 $nextpageparams['action'] = 'savegradingresult';
bbd0e548
DW
503 }
504 } else {
e5403f8c 505 // Cancel button.
d04557b3
DW
506 $action = 'redirect';
507 $nextpageparams['action'] = 'grading';
bbd0e548 508 }
9e795179 509 } else if ($action == 'quickgrade') {
bf78ebd6
DW
510 $message = $this->process_save_quick_grades();
511 $action = 'quickgradingresult';
9e795179 512 } else if ($action == 'saveoptions') {
bbd0e548 513 $this->process_save_grading_options();
d04557b3
DW
514 $action = 'redirect';
515 $nextpageparams['action'] = 'grading';
9e795179
DW
516 } else if ($action == 'saveextension') {
517 $action = 'grantextension';
518 if ($this->process_save_extension($mform)) {
d04557b3
DW
519 $action = 'redirect';
520 $nextpageparams['action'] = 'grading';
9e795179 521 }
b473171a
DW
522 } else if ($action == 'revealidentitiesconfirm') {
523 $this->process_reveal_identities();
d04557b3
DW
524 $action = 'redirect';
525 $nextpageparams['action'] = 'grading';
bbd0e548
DW
526 }
527
d04557b3 528 $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
3fca6937 529 'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM));
bbd0e548
DW
530 $this->register_return_link($action, $returnparams);
531
34b8f3a8 532 // Now show the right view page.
d04557b3
DW
533 if ($action == 'redirect') {
534 $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
535 redirect($nextpageurl);
536 return;
df211804 537 } else if ($action == 'savegradingresult') {
082ad257 538 $message = get_string('gradingchangessaved', 'assign');
df211804 539 $o .= $this->view_savegrading_result($message);
bf78ebd6
DW
540 } else if ($action == 'quickgradingresult') {
541 $mform = null;
542 $o .= $this->view_quickgrading_result($message);
bbd0e548
DW
543 } else if ($action == 'grade') {
544 $o .= $this->view_single_grade_page($mform);
545 } else if ($action == 'viewpluginassignfeedback') {
546 $o .= $this->view_plugin_content('assignfeedback');
547 } else if ($action == 'viewpluginassignsubmission') {
548 $o .= $this->view_plugin_content('assignsubmission');
549 } else if ($action == 'editsubmission') {
34b8f3a8 550 $o .= $this->view_edit_submission_page($mform, $notices);
bbd0e548
DW
551 } else if ($action == 'grading') {
552 $o .= $this->view_grading_page();
553 } else if ($action == 'downloadall') {
554 $o .= $this->download_submissions();
555 } else if ($action == 'submit') {
94f26900 556 $o .= $this->check_submit_for_grading($mform);
9e795179
DW
557 } else if ($action == 'grantextension') {
558 $o .= $this->view_grant_extension($mform);
b473171a
DW
559 } else if ($action == 'revealidentities') {
560 $o .= $this->view_reveal_identities_confirm($mform);
df47b77f
DW
561 } else if ($action == 'plugingradingbatchoperation') {
562 $o .= $this->view_plugin_grading_batch_operation($mform);
7a2b911c 563 } else if ($action == 'viewpluginpage') {
df47b77f 564 $o .= $this->view_plugin_page();
64220210
DW
565 } else if ($action == 'viewcourseindex') {
566 $o .= $this->view_course_index();
f8d107b3
DM
567 } else if ($action == 'viewbatchsetmarkingworkflowstate') {
568 $o .= $this->view_batch_set_workflow_state($mform);
569 } else if ($action == 'viewbatchmarkingallocation') {
570 $o .= $this->view_batch_markingallocation($mform);
57fbd5f9
DW
571 } else if ($action == 'viewsubmitforgradingerror') {
572 $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices);
bbd0e548
DW
573 } else {
574 $o .= $this->view_submission_page();
575 }
576
577 return $o;
578 }
579
bbd0e548 580 /**
e5403f8c 581 * Add this instance to the database.
bbd0e548
DW
582 *
583 * @param stdClass $formdata The data submitted from the form
584 * @param bool $callplugins This is used to skip the plugin code
585 * when upgrading an old assignment to a new one (the plugins get called manually)
586 * @return mixed false if an error occurs or the int id of the new instance
587 */
588 public function add_instance(stdClass $formdata, $callplugins) {
589 global $DB;
8e1266bf 590 $adminconfig = $this->get_admin_config();
bbd0e548
DW
591
592 $err = '';
593
e5403f8c 594 // Add the database record.
bbd0e548
DW
595 $update = new stdClass();
596 $update->name = $formdata->name;
597 $update->timemodified = time();
598 $update->timecreated = time();
599 $update->course = $formdata->course;
600 $update->courseid = $formdata->course;
601 $update->intro = $formdata->intro;
602 $update->introformat = $formdata->introformat;
18de52db 603 $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
bbd0e548 604 $update->submissiondrafts = $formdata->submissiondrafts;
94f26900 605 $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
bbd0e548 606 $update->sendnotifications = $formdata->sendnotifications;
75f87a57 607 $update->sendlatenotifications = $formdata->sendlatenotifications;
8e1266bf
DW
608 $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
609 if (isset($formdata->sendstudentnotifications)) {
610 $update->sendstudentnotifications = $formdata->sendstudentnotifications;
611 }
b618f2d9 612 $update->duedate = $formdata->duedate;
9e795179 613 $update->cutoffdate = $formdata->cutoffdate;
bbd0e548
DW
614 $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
615 $update->grade = $formdata->grade;
694b11ab 616 $update->completionsubmit = !empty($formdata->completionsubmit);
12a1a0da
DW
617 $update->teamsubmission = $formdata->teamsubmission;
618 $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
1fba9cf6
DW
619 if (isset($formdata->teamsubmissiongroupingid)) {
620 $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
621 }
b473171a 622 $update->blindmarking = $formdata->blindmarking;
e8490064
DW
623 $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
624 if (!empty($formdata->attemptreopenmethod)) {
625 $update->attemptreopenmethod = $formdata->attemptreopenmethod;
626 }
df211804
DW
627 if (!empty($formdata->maxattempts)) {
628 $update->maxattempts = $formdata->maxattempts;
629 }
e528997a
AH
630 if (isset($formdata->preventsubmissionnotingroup)) {
631 $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
632 }
f8d107b3
DM
633 $update->markingworkflow = $formdata->markingworkflow;
634 $update->markingallocation = $formdata->markingallocation;
5a96486b
DM
635 if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
636 $update->markingallocation = 0;
637 }
12a1a0da 638
bbd0e548
DW
639 $returnid = $DB->insert_record('assign', $update);
640 $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
e5403f8c 641 // Cache the course record.
bbd0e548
DW
642 $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
643
7faf78cb
HB
644 $this->save_intro_draft_files($formdata);
645
bbd0e548 646 if ($callplugins) {
e5403f8c 647 // Call save_settings hook for submission plugins.
bbd0e548
DW
648 foreach ($this->submissionplugins as $plugin) {
649 if (!$this->update_plugin_instance($plugin, $formdata)) {
650 print_error($plugin->get_error());
651 return false;
652 }
653 }
654 foreach ($this->feedbackplugins as $plugin) {
655 if (!$this->update_plugin_instance($plugin, $formdata)) {
656 print_error($plugin->get_error());
657 return false;
658 }
659 }
660
e5403f8c
DW
661 // In the case of upgrades the coursemodule has not been set,
662 // so we need to wait before calling these two.
bbd0e548 663 $this->update_calendar($formdata->coursemodule);
bbd0e548
DW
664 $this->update_gradebook(false, $formdata->coursemodule);
665
666 }
667
668 $update = new stdClass();
669 $update->id = $this->get_instance()->id;
670 $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
671 $DB->update_record('assign', $update);
672
673 return $returnid;
674 }
675
676 /**
e5403f8c 677 * Delete all grades from the gradebook for this assignment.
bbd0e548
DW
678 *
679 * @return bool
680 */
47f48152 681 protected function delete_grades() {
bbd0e548
DW
682 global $CFG;
683
e5403f8c
DW
684 $result = grade_update('mod/assign',
685 $this->get_course()->id,
686 'mod',
687 'assign',
688 $this->get_instance()->id,
689 0,
690 null,
691 array('deleted'=>1));
692 return $result == GRADE_UPDATE_OK;
bbd0e548
DW
693 }
694
695 /**
e5403f8c 696 * Delete this instance from the database.
bbd0e548
DW
697 *
698 * @return bool false if an error occurs
699 */
700 public function delete_instance() {
701 global $DB;
702 $result = true;
703
704 foreach ($this->submissionplugins as $plugin) {
705 if (!$plugin->delete_instance()) {
706 print_error($plugin->get_error());
707 $result = false;
708 }
709 }
710 foreach ($this->feedbackplugins as $plugin) {
711 if (!$plugin->delete_instance()) {
712 print_error($plugin->get_error());
713 $result = false;
714 }
715 }
716
e5403f8c 717 // Delete files associated with this assignment.
bbd0e548
DW
718 $fs = get_file_storage();
719 if (! $fs->delete_area_files($this->context->id) ) {
720 $result = false;
721 }
722
e5403f8c 723 // Delete_records will throw an exception if it fails - so no need for error checking here.
26ac0abc
MN
724 $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id));
725 $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id));
726 $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id));
727 $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id));
728 $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id));
bbd0e548 729
e5403f8c 730 // Delete items from the gradebook.
bbd0e548
DW
731 if (! $this->delete_grades()) {
732 $result = false;
733 }
734
e5403f8c 735 // Delete the instance.
bbd0e548
DW
736 $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
737
738 return $result;
739 }
740
d38dc52f 741 /**
e5403f8c
DW
742 * Actual implementation of the reset course functionality, delete all the
743 * assignment submissions for course $data->courseid.
744 *
1561a37c 745 * @param stdClass $data the data submitted from the reset course.
e5403f8c
DW
746 * @return array status array
747 */
d38dc52f 748 public function reset_userdata($data) {
e5403f8c 749 global $CFG, $DB;
d38dc52f
RW
750
751 $componentstr = get_string('modulenameplural', 'assign');
752 $status = array();
753
754 $fs = get_file_storage();
755 if (!empty($data->reset_assign_submissions)) {
756 // Delete files associated with this assignment.
757 foreach ($this->submissionplugins as $plugin) {
758 $fileareas = array();
759 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
760 $fileareas = $plugin->get_file_areas();
43fb9ced 761 foreach ($fileareas as $filearea => $notused) {
d38dc52f
RW
762 $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
763 }
764
765 if (!$plugin->delete_instance()) {
766 $status[] = array('component'=>$componentstr,
e5403f8c 767 'item'=>get_string('deleteallsubmissions', 'assign'),
d38dc52f
RW
768 'error'=>$plugin->get_error());
769 }
770 }
771
772 foreach ($this->feedbackplugins as $plugin) {
773 $fileareas = array();
774 $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
775 $fileareas = $plugin->get_file_areas();
43fb9ced 776 foreach ($fileareas as $filearea => $notused) {
d38dc52f
RW
777 $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
778 }
779
780 if (!$plugin->delete_instance()) {
781 $status[] = array('component'=>$componentstr,
e5403f8c 782 'item'=>get_string('deleteallsubmissions', 'assign'),
d38dc52f
RW
783 'error'=>$plugin->get_error());
784 }
785 }
786
26ac0abc
MN
787 $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id');
788 list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids));
d38dc52f 789
26ac0abc
MN
790 $DB->delete_records_select('assign_submission', "assignment $sql", $params);
791 $DB->delete_records_select('assign_user_flags', "assignment $sql", $params);
e5403f8c 792
d38dc52f 793 $status[] = array('component'=>$componentstr,
e5403f8c 794 'item'=>get_string('deleteallsubmissions', 'assign'),
d38dc52f
RW
795 'error'=>false);
796
e5403f8c 797 if (!empty($data->reset_gradebook_grades)) {
26ac0abc 798 $DB->delete_records_select('assign_grades', "assignment $sql", $params);
d38dc52f
RW
799 // Remove all grades from gradebook.
800 require_once($CFG->dirroot.'/mod/assign/lib.php');
801 assign_reset_gradebook($data->courseid);
48242bf3
MA
802
803 // Reset revealidentities if both submissions and grades have been reset.
48242bf3 804 if ($this->get_instance()->blindmarking && $this->get_instance()->revealidentities) {
225fa27f 805 $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id));
48242bf3 806 }
d38dc52f
RW
807 }
808 }
809 // Updating dates - shift may be negative too.
810 if ($data->timeshift) {
811 shift_course_mod_dates('assign',
e5403f8c 812 array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
d38dc52f 813 $data->timeshift,
e63515ba 814 $data->courseid, $this->get_instance()->id);
d38dc52f
RW
815 $status[] = array('component'=>$componentstr,
816 'item'=>get_string('datechanged'),
817 'error'=>false);
818 }
819
820 return $status;
821 }
822
bbd0e548 823 /**
e5403f8c 824 * Update the settings for a single plugin.
bbd0e548
DW
825 *
826 * @param assign_plugin $plugin The plugin to update
827 * @param stdClass $formdata The form data
828 * @return bool false if an error occurs
829 */
47f48152 830 protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
bbd0e548
DW
831 if ($plugin->is_visible()) {
832 $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
b0da618b 833 if (!empty($formdata->$enabledname)) {
bbd0e548
DW
834 $plugin->enable();
835 if (!$plugin->save_settings($formdata)) {
836 print_error($plugin->get_error());
837 return false;
838 }
839 } else {
840 $plugin->disable();
841 }
842 }
843 return true;
844 }
845
846 /**
e5403f8c 847 * Update the gradebook information for this assignment.
bbd0e548
DW
848 *
849 * @param bool $reset If true, will reset all grades in the gradbook for this assignment
850 * @param int $coursemoduleid This is required because it might not exist in the database yet
851 * @return bool
852 */
853 public function update_gradebook($reset, $coursemoduleid) {
e5403f8c
DW
854 global $CFG;
855
bbd0e548
DW
856 require_once($CFG->dirroot.'/mod/assign/lib.php');
857 $assign = clone $this->get_instance();
858 $assign->cmidnumber = $coursemoduleid;
456d7bc7
RT
859
860 // Set assign gradebook feedback plugin status (enabled and visible).
861 $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
862
bbd0e548
DW
863 $param = null;
864 if ($reset) {
865 $param = 'reset';
866 }
867
868 return assign_grade_item_update($assign, $param);
869 }
870
e5403f8c
DW
871 /**
872 * Load and cache the admin config for this module.
bc5a657b 873 *
cfc81f03
DW
874 * @return stdClass the plugin config
875 */
876 public function get_admin_config() {
877 if ($this->adminconfig) {
878 return $this->adminconfig;
879 }
b11808c7 880 $this->adminconfig = get_config('assign');
cfc81f03
DW
881 return $this->adminconfig;
882 }
883
bbd0e548 884 /**
e5403f8c 885 * Update the calendar entries for this assignment.
bbd0e548 886 *
e5403f8c
DW
887 * @param int $coursemoduleid - Required to pass this in because it might
888 * not exist in the database yet.
bbd0e548
DW
889 * @return bool
890 */
891 public function update_calendar($coursemoduleid) {
892 global $DB, $CFG;
893 require_once($CFG->dirroot.'/calendar/lib.php');
894
e5403f8c
DW
895 // Special case for add_instance as the coursemodule has not been set yet.
896 $instance = $this->get_instance();
bbd0e548 897
72797e68
MA
898 $eventtype = 'due';
899
e5403f8c 900 if ($instance->duedate) {
bbd0e548
DW
901 $event = new stdClass();
902
72797e68 903 $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype);
0cfc7d13
FM
904 $event->id = $DB->get_field('event', 'id', $params);
905 $event->name = $instance->name;
906 $event->timestart = $instance->duedate;
907
908 // Convert the links to pluginfile. It is a bit hacky but at this stage the files
909 // might not have been saved in the module area yet.
910 $intro = $instance->intro;
911 if ($draftid = file_get_submitted_draft_itemid('introeditor')) {
912 $intro = file_rewrite_urls_to_pluginfile($intro, $draftid);
913 }
bbd0e548 914
0cfc7d13
FM
915 // We need to remove the links to files as the calendar is not ready
916 // to support module events with file areas.
917 $intro = strip_pluginfile_content($intro);
ae7638f7
DW
918 if ($this->show_intro()) {
919 $event->description = array(
920 'text' => $intro,
921 'format' => $instance->introformat
922 );
923 } else {
924 $event->description = array(
925 'text' => '',
926 'format' => $instance->introformat
927 );
928 }
bbd0e548 929
0cfc7d13 930 if ($event->id) {
bbd0e548
DW
931 $calendarevent = calendar_event::load($event->id);
932 $calendarevent->update($event);
933 } else {
0cfc7d13 934 unset($event->id);
e5403f8c 935 $event->courseid = $instance->course;
bbd0e548
DW
936 $event->groupid = 0;
937 $event->userid = 0;
938 $event->modulename = 'assign';
e5403f8c 939 $event->instance = $instance->id;
72797e68 940 $event->eventtype = $eventtype;
bbd0e548 941 $event->timeduration = 0;
bbd0e548
DW
942 calendar_event::create($event);
943 }
944 } else {
72797e68 945 $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype));
bbd0e548
DW
946 }
947 }
948
949
950 /**
e5403f8c 951 * Update this instance in the database.
bbd0e548
DW
952 *
953 * @param stdClass $formdata - the data submitted from the form
954 * @return bool false if an error occurs
955 */
956 public function update_instance($formdata) {
957 global $DB;
8e1266bf 958 $adminconfig = $this->get_admin_config();
bbd0e548
DW
959
960 $update = new stdClass();
961 $update->id = $formdata->instance;
962 $update->name = $formdata->name;
963 $update->timemodified = time();
964 $update->course = $formdata->course;
965 $update->intro = $formdata->intro;
966 $update->introformat = $formdata->introformat;
18de52db 967 $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
bbd0e548 968 $update->submissiondrafts = $formdata->submissiondrafts;
94f26900 969 $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
bbd0e548 970 $update->sendnotifications = $formdata->sendnotifications;
75f87a57 971 $update->sendlatenotifications = $formdata->sendlatenotifications;
8e1266bf
DW
972 $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
973 if (isset($formdata->sendstudentnotifications)) {
974 $update->sendstudentnotifications = $formdata->sendstudentnotifications;
975 }
b618f2d9 976 $update->duedate = $formdata->duedate;
9e795179 977 $update->cutoffdate = $formdata->cutoffdate;
bbd0e548
DW
978 $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
979 $update->grade = $formdata->grade;
a9f5fc15 980 if (!empty($formdata->completionunlocked)) {
981 $update->completionsubmit = !empty($formdata->completionsubmit);
982 }
12a1a0da
DW
983 $update->teamsubmission = $formdata->teamsubmission;
984 $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
1fba9cf6
DW
985 if (isset($formdata->teamsubmissiongroupingid)) {
986 $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
987 }
b473171a 988 $update->blindmarking = $formdata->blindmarking;
e8490064
DW
989 $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
990 if (!empty($formdata->attemptreopenmethod)) {
991 $update->attemptreopenmethod = $formdata->attemptreopenmethod;
992 }
df211804
DW
993 if (!empty($formdata->maxattempts)) {
994 $update->maxattempts = $formdata->maxattempts;
995 }
e528997a
AH
996 if (isset($formdata->preventsubmissionnotingroup)) {
997 $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
998 }
f8d107b3
DM
999 $update->markingworkflow = $formdata->markingworkflow;
1000 $update->markingallocation = $formdata->markingallocation;
5a96486b
DM
1001 if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
1002 $update->markingallocation = 0;
1003 }
12a1a0da 1004
bbd0e548
DW
1005 $result = $DB->update_record('assign', $update);
1006 $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
1007
7faf78cb
HB
1008 $this->save_intro_draft_files($formdata);
1009
e5403f8c 1010 // Load the assignment so the plugins have access to it.
bbd0e548 1011
e5403f8c 1012 // Call save_settings hook for submission plugins.
bbd0e548
DW
1013 foreach ($this->submissionplugins as $plugin) {
1014 if (!$this->update_plugin_instance($plugin, $formdata)) {
1015 print_error($plugin->get_error());
1016 return false;
1017 }
1018 }
1019 foreach ($this->feedbackplugins as $plugin) {
1020 if (!$this->update_plugin_instance($plugin, $formdata)) {
1021 print_error($plugin->get_error());
1022 return false;
1023 }
1024 }
1025
bbd0e548 1026 $this->update_calendar($this->get_course_module()->id);
bbd0e548
DW
1027 $this->update_gradebook(false, $this->get_course_module()->id);
1028
1029 $update = new stdClass();
1030 $update->id = $this->get_instance()->id;
1031 $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
1032 $DB->update_record('assign', $update);
1033
bbd0e548
DW
1034 return $result;
1035 }
1036
7faf78cb
HB
1037 /**
1038 * Save the attachments in the draft areas.
1039 *
1040 * @param stdClass $formdata
1041 */
1042 protected function save_intro_draft_files($formdata) {
1043 if (isset($formdata->introattachments)) {
1044 file_save_draft_area_files($formdata->introattachments, $this->get_context()->id,
1045 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
1046 }
1047 }
1048
bbd0e548 1049 /**
e5403f8c 1050 * Add elements in grading plugin form.
bbd0e548
DW
1051 *
1052 * @param mixed $grade stdClass|null
1053 * @param MoodleQuickForm $mform
1054 * @param stdClass $data
fc7b7d52 1055 * @param int $userid - The userid we are grading
bbd0e548
DW
1056 * @return void
1057 */
47f48152 1058 protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
bbd0e548
DW
1059 foreach ($this->feedbackplugins as $plugin) {
1060 if ($plugin->is_enabled() && $plugin->is_visible()) {
3a5c3b3c 1061 $plugin->get_form_elements_for_user($grade, $mform, $data, $userid);
bbd0e548
DW
1062 }
1063 }
1064 }
1065
1066
1067
1068 /**
e5403f8c 1069 * Add one plugins settings to edit plugin form.
bbd0e548
DW
1070 *
1071 * @param assign_plugin $plugin The plugin to add the settings from
e5403f8c
DW
1072 * @param MoodleQuickForm $mform The form to add the configuration settings to.
1073 * This form is modified directly (not returned).
b0da618b
DW
1074 * @param array $pluginsenabled A list of form elements to be added to a group.
1075 * The new element is added to this array by this function.
bbd0e548
DW
1076 * @return void
1077 */
b0da618b 1078 protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
bbd0e548 1079 global $CFG;
f159ad73 1080 if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
1081 $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1082 $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
1083 $mform->setType($name, PARAM_BOOL);
1084 $plugin->get_settings($mform);
1085 } else if ($plugin->is_visible() && $plugin->is_configurable()) {
b0da618b
DW
1086 $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1087 $label = $plugin->get_name();
1088 $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
1089 $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
bbd0e548 1090
cfc81f03 1091 $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
bbd0e548
DW
1092 if ($plugin->get_config('enabled') !== false) {
1093 $default = $plugin->is_enabled();
1094 }
1095 $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
1096
1097 $plugin->get_settings($mform);
1098
1099 }
bbd0e548
DW
1100 }
1101
bbd0e548 1102 /**
e5403f8c 1103 * Add settings to edit plugin form.
bbd0e548 1104 *
e5403f8c
DW
1105 * @param MoodleQuickForm $mform The form to add the configuration settings to.
1106 * This form is modified directly (not returned).
bbd0e548
DW
1107 * @return void
1108 */
1109 public function add_all_plugin_settings(MoodleQuickForm $mform) {
df211804 1110 $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));
bbd0e548 1111
b0da618b
DW
1112 $submissionpluginsenabled = array();
1113 $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
bbd0e548 1114 foreach ($this->submissionplugins as $plugin) {
b0da618b 1115 $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
bbd0e548 1116 }
b0da618b
DW
1117 $group->setElements($submissionpluginsenabled);
1118
df211804 1119 $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign'));
b0da618b
DW
1120 $feedbackpluginsenabled = array();
1121 $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
bbd0e548 1122 foreach ($this->feedbackplugins as $plugin) {
b0da618b 1123 $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
bbd0e548 1124 }
b0da618b
DW
1125 $group->setElements($feedbackpluginsenabled);
1126 $mform->setExpanded('submissiontypes');
bbd0e548
DW
1127 }
1128
1129 /**
1130 * Allow each plugin an opportunity to update the defaultvalues
1131 * passed in to the settings form (needed to set up draft areas for
1132 * editor and filemanager elements)
e5403f8c 1133 *
bbd0e548
DW
1134 * @param array $defaultvalues
1135 */
1136 public function plugin_data_preprocessing(&$defaultvalues) {
1137 foreach ($this->submissionplugins as $plugin) {
1138 if ($plugin->is_visible()) {
1139 $plugin->data_preprocessing($defaultvalues);
1140 }
1141 }
1142 foreach ($this->feedbackplugins as $plugin) {
1143 if ($plugin->is_visible()) {
1144 $plugin->data_preprocessing($defaultvalues);
1145 }
1146 }
1147 }
1148
1149 /**
1150 * Get the name of the current module.
1151 *
1152 * @return string the module name (Assignment)
1153 */
1154 protected function get_module_name() {
1155 if (isset(self::$modulename)) {
1156 return self::$modulename;
1157 }
1158 self::$modulename = get_string('modulename', 'assign');
1159 return self::$modulename;
1160 }
1161
1162 /**
1163 * Get the plural name of the current module.
1164 *
1165 * @return string the module name plural (Assignments)
1166 */
1167 protected function get_module_name_plural() {
1168 if (isset(self::$modulenameplural)) {
1169 return self::$modulenameplural;
1170 }
1171 self::$modulenameplural = get_string('modulenameplural', 'assign');
1172 return self::$modulenameplural;
1173 }
1174
1175 /**
1176 * Has this assignment been constructed from an instance?
1177 *
1178 * @return bool
1179 */
1180 public function has_instance() {
1181 return $this->instance || $this->get_course_module();
1182 }
1183
1184 /**
1185 * Get the settings for the current instance of this assignment
1186 *
1187 * @return stdClass The settings
1188 */
1189 public function get_instance() {
1190 global $DB;
1191 if ($this->instance) {
1192 return $this->instance;
1193 }
1194 if ($this->get_course_module()) {
e5403f8c
DW
1195 $params = array('id' => $this->get_course_module()->instance);
1196 $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
bbd0e548
DW
1197 }
1198 if (!$this->instance) {
e5403f8c
DW
1199 throw new coding_exception('Improper use of the assignment class. ' .
1200 'Cannot load the assignment record.');
bbd0e548
DW
1201 }
1202 return $this->instance;
1203 }
1204
0d64c75f
PN
1205 /**
1206 * Get the primary grade item for this assign instance.
1207 *
1208 * @return stdClass The grade_item record
1209 */
1210 public function get_grade_item() {
1211 if ($this->gradeitem) {
1212 return $this->gradeitem;
1213 }
1214 $instance = $this->get_instance();
1215 $params = array('itemtype' => 'mod',
1216 'itemmodule' => 'assign',
1217 'iteminstance' => $instance->id,
1218 'courseid' => $instance->course,
1219 'itemnumber' => 0);
1220 $this->gradeitem = grade_item::fetch($params);
1221 if (!$this->gradeitem) {
1222 throw new coding_exception('Improper use of the assignment class. ' .
1223 'Cannot load the grade item.');
1224 }
1225 return $this->gradeitem;
1226 }
1227
bbd0e548 1228 /**
e5403f8c
DW
1229 * Get the context of the current course.
1230 *
bbd0e548
DW
1231 * @return mixed context|null The course context
1232 */
1233 public function get_course_context() {
1234 if (!$this->context && !$this->course) {
e5403f8c
DW
1235 throw new coding_exception('Improper use of the assignment class. ' .
1236 'Cannot load the course context.');
bbd0e548
DW
1237 }
1238 if ($this->context) {
1239 return $this->context->get_course_context();
1240 } else {
1241 return context_course::instance($this->course->id);
1242 }
1243 }
1244
1245
1246 /**
e5403f8c 1247 * Get the current course module.
bbd0e548 1248 *
c13ac85d 1249 * @return cm_info|null The course module or null if not known
bbd0e548
DW
1250 */
1251 public function get_course_module() {
1252 if ($this->coursemodule) {
1253 return $this->coursemodule;
1254 }
1255 if (!$this->context) {
1256 return null;
1257 }
1258
1259 if ($this->context->contextlevel == CONTEXT_MODULE) {
c13ac85d 1260 $modinfo = get_fast_modinfo($this->get_course());
1261 $this->coursemodule = $modinfo->get_cm($this->context->instanceid);
bbd0e548
DW
1262 return $this->coursemodule;
1263 }
1264 return null;
1265 }
1266
1267 /**
e5403f8c 1268 * Get context module.
bbd0e548
DW
1269 *
1270 * @return context
1271 */
1272 public function get_context() {
1273 return $this->context;
1274 }
1275
1276 /**
e5403f8c
DW
1277 * Get the current course.
1278 *
bbd0e548
DW
1279 * @return mixed stdClass|null The course
1280 */
1281 public function get_course() {
1282 global $DB;
e5403f8c 1283
bbd0e548
DW
1284 if ($this->course) {
1285 return $this->course;
1286 }
1287
1288 if (!$this->context) {
1289 return null;
1290 }
e5403f8c
DW
1291 $params = array('id' => $this->get_course_context()->instanceid);
1292 $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
1293
bbd0e548
DW
1294 return $this->course;
1295 }
1296
7faf78cb
HB
1297 /**
1298 * Count the number of intro attachments.
1299 *
1300 * @return int
1301 */
1302 protected function count_attachments() {
1303
1304 $fs = get_file_storage();
1305 $files = $fs->get_area_files($this->get_context()->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA,
1306 0, 'id', false);
1307
1308 return count($files);
1309 }
1310
1311 /**
1312 * Are there any intro attachments to display?
1313 *
1314 * @return boolean
1315 */
1316 protected function has_visible_attachments() {
1317 return ($this->count_attachments() > 0);
1318 }
1319
bbd0e548 1320 /**
e5403f8c 1321 * Return a grade in user-friendly form, whether it's a scale or not.
bbd0e548 1322 *
9682626e 1323 * @param mixed $grade int|null
bf78ebd6 1324 * @param boolean $editing Are we allowing changes to this grade?
2a4fbc32
SH
1325 * @param int $userid The user id the grade belongs to
1326 * @param int $modified Timestamp from when the grade was last modified
bbd0e548
DW
1327 * @return string User-friendly representation of grade
1328 */
bf78ebd6 1329 public function display_grade($grade, $editing, $userid=0, $modified=0) {
bbd0e548
DW
1330 global $DB;
1331
1332 static $scalegrades = array();
1333
be79d93f
DW
1334 $o = '';
1335
2a4fbc32 1336 if ($this->get_instance()->grade >= 0) {
e5403f8c 1337 // Normal number.
e7ade405 1338 if ($editing && $this->get_instance()->grade > 0) {
2d8a9ce9
DW
1339 if ($grade < 0) {
1340 $displaygrade = '';
1341 } else {
dd8ba800 1342 $displaygrade = format_float($grade, 2);
2d8a9ce9 1343 }
e5403f8c
DW
1344 $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
1345 get_string('usergrade', 'assign') .
1346 '</label>';
1347 $o .= '<input type="text"
1348 id="quickgrade_' . $userid . '"
1349 name="quickgrade_' . $userid . '"
1350 value="' . $displaygrade . '"
1351 size="6"
1352 maxlength="10"
1353 class="quickgrade"/>';
1354 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
bf78ebd6 1355 return $o;
bbd0e548 1356 } else {
a1e54f4d 1357 if ($grade == -1 || $grade === null) {
be79d93f 1358 $o .= '-';
a1e54f4d 1359 } else {
0d64c75f
PN
1360 $item = $this->get_grade_item();
1361 $o .= grade_format_gradevalue($grade, $item);
1362 if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
1363 // If displaying the raw grade, also display the total value.
1364 $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1365 }
a1e54f4d 1366 }
0d64c75f 1367 return $o;
bbd0e548
DW
1368 }
1369
2a4fbc32 1370 } else {
e5403f8c 1371 // Scale.
bbd0e548
DW
1372 if (empty($this->cache['scale'])) {
1373 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1374 $this->cache['scale'] = make_menu_from_list($scale->scale);
1375 } else {
be79d93f
DW
1376 $o .= '-';
1377 return $o;
bbd0e548
DW
1378 }
1379 }
bf78ebd6 1380 if ($editing) {
e5403f8c
DW
1381 $o .= '<label class="accesshide"
1382 for="quickgrade_' . $userid . '">' .
1383 get_string('usergrade', 'assign') .
1384 '</label>';
7400be1b 1385 $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
bf78ebd6
DW
1386 $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1387 foreach ($this->cache['scale'] as $optionid => $option) {
1388 $selected = '';
1389 if ($grade == $optionid) {
1390 $selected = 'selected="selected"';
1391 }
1392 $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1393 }
1394 $o .= '</select>';
bf78ebd6
DW
1395 return $o;
1396 } else {
1397 $scaleid = (int)$grade;
1398 if (isset($this->cache['scale'][$scaleid])) {
be79d93f
DW
1399 $o .= $this->cache['scale'][$scaleid];
1400 return $o;
bf78ebd6 1401 }
be79d93f
DW
1402 $o .= '-';
1403 return $o;
bbd0e548 1404 }
bbd0e548
DW
1405 }
1406 }
1407
1408 /**
e5403f8c
DW
1409 * Load a list of users enrolled in the current course with the specified permission and group.
1410 * 0 for no group.
bbd0e548
DW
1411 *
1412 * @param int $currentgroup
1413 * @param bool $idsonly
1414 * @return array List of user records
1415 */
1416 public function list_participants($currentgroup, $idsonly) {
bc006a66
DM
1417
1418 if (empty($currentgroup)) {
1419 $currentgroup = 0;
1420 }
1421
c46db93c
DW
1422 $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
1423 if (!isset($this->participants[$key])) {
a0e59f04 1424 $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
1ecb8044 1425 $this->show_only_active_users());
c46db93c
DW
1426
1427 $cm = $this->get_course_module();
c13ac85d 1428 $info = new \core_availability\info_module($cm);
1429 $users = $info->filter_user_list($users);
c46db93c
DW
1430
1431 $this->participants[$key] = $users;
bbd0e548 1432 }
a0e59f04 1433
c46db93c
DW
1434 if ($idsonly) {
1435 $idslist = array();
1436 foreach ($this->participants[$key] as $id => $user) {
1437 $idslist[$id] = new stdClass();
1438 $idslist[$id]->id = $id;
a0e59f04 1439 }
c46db93c 1440 return $idslist;
a0e59f04 1441 }
c46db93c 1442 return $this->participants[$key];
bbd0e548
DW
1443 }
1444
12a1a0da 1445 /**
e5403f8c 1446 * Load a count of valid teams for this assignment.
12a1a0da 1447 *
bc006a66 1448 * @param int $activitygroup Activity active group
12a1a0da
DW
1449 * @return int number of valid teams
1450 */
bc006a66
DM
1451 public function count_teams($activitygroup = 0) {
1452
1453 $count = 0;
1454
1455 $participants = $this->list_participants($activitygroup, true);
1456
1457 // If a team submission grouping id is provided all good as all returned groups
1458 // are the submission teams, but if no team submission grouping was specified
1459 // $groups will contain all participants groups.
1460 if ($this->get_instance()->teamsubmissiongroupingid) {
12a1a0da 1461
bc006a66
DM
1462 // We restrict the users to the selected group ones.
1463 $groups = groups_get_all_groups($this->get_course()->id,
1464 array_keys($participants),
1465 $this->get_instance()->teamsubmissiongroupingid,
1466 'DISTINCT g.id, g.name');
12a1a0da 1467
bc006a66
DM
1468 $count = count($groups);
1469
1470 // When a specific group is selected we don't count the default group users.
1471 if ($activitygroup == 0) {
e528997a
AH
1472 if (empty($this->get_instance()->preventsubmissionnotingroup)) {
1473 // See if there are any users in the default group.
1474 $defaultusers = $this->get_submission_group_members(0, true);
1475 if (count($defaultusers) > 0) {
1476 $count += 1;
1477 }
bc006a66
DM
1478 }
1479 }
1480 } else {
1481 // It is faster to loop around participants if no grouping was specified.
1482 $groups = array();
1483 foreach ($participants as $participant) {
1484 if ($group = $this->get_submission_group($participant->id)) {
1485 $groups[$group->id] = true;
e528997a 1486 } else if (empty($this->get_instance()->preventsubmissionnotingroup)) {
bc006a66
DM
1487 $groups[0] = true;
1488 }
1489 }
12a1a0da 1490
bc006a66 1491 $count = count($groups);
12a1a0da 1492 }
bc006a66 1493
12a1a0da
DW
1494 return $count;
1495 }
1496
bbd0e548 1497 /**
c494e18c 1498 * Load a count of active users enrolled in the current course with the specified permission and group.
e5403f8c 1499 * 0 for no group.
bbd0e548
DW
1500 *
1501 * @param int $currentgroup
1502 * @return int number of matching users
1503 */
1504 public function count_participants($currentgroup) {
a0e59f04 1505 return count($this->list_participants($currentgroup, true));
bbd0e548
DW
1506 }
1507
f70079b9 1508 /**
c494e18c 1509 * Load a count of active users submissions in the current module that require grading
f70079b9 1510 * This means the submission modification time is more recent than the
7a9fd6da 1511 * grading modification time and the status is SUBMITTED.
f70079b9
DW
1512 *
1513 * @return int number of matching submissions
1514 */
1515 public function count_submissions_need_grading() {
1516 global $DB;
1517
8f7e1c05
DW
1518 if ($this->get_instance()->teamsubmission) {
1519 // This does not make sense for group assignment because the submission is shared.
1520 return 0;
1521 }
1522
1523 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
c494e18c 1524 list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05
DW
1525
1526 $params['assignid'] = $this->get_instance()->id;
1527 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1528
1529 $sql = 'SELECT COUNT(s.userid)
1530 FROM {assign_submission} s
1531 LEFT JOIN {assign_grades} g ON
1532 s.assignment = g.assignment AND
df211804 1533 s.userid = g.userid AND
ad030ab4 1534 g.attemptnumber = s.attemptnumber
0c7b6910 1535 JOIN(' . $esql . ') e ON e.id = s.userid
8f7e1c05 1536 WHERE
ad030ab4 1537 s.latest = 1 AND
8f7e1c05
DW
1538 s.assignment = :assignid AND
1539 s.timemodified IS NOT NULL AND
1540 s.status = :submitted AND
ab848fea 1541 (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
f70079b9 1542
8f7e1c05 1543 return $DB->count_records_sql($sql, $params);
f70079b9
DW
1544 }
1545
bbd0e548 1546 /**
e5403f8c 1547 * Load a count of grades.
b473171a
DW
1548 *
1549 * @return int number of grades
1550 */
1551 public function count_grades() {
1552 global $DB;
1553
1554 if (!$this->has_instance()) {
1555 return 0;
1556 }
1557
8f7e1c05 1558 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
c494e18c 1559 list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05
DW
1560
1561 $params['assignid'] = $this->get_instance()->id;
1562
1563 $sql = 'SELECT COUNT(g.userid)
1564 FROM {assign_grades} g
0c7b6910 1565 JOIN(' . $esql . ') e ON e.id = g.userid
8f7e1c05 1566 WHERE g.assignment = :assignid';
b473171a
DW
1567
1568 return $DB->count_records_sql($sql, $params);
1569 }
1570
1571 /**
e5403f8c 1572 * Load a count of submissions.
b473171a 1573 *
ab14ab74 1574 * @param bool $includenew When true, also counts the submissions with status 'new'.
b473171a
DW
1575 * @return int number of submissions
1576 */
ab14ab74 1577 public function count_submissions($includenew = false) {
b473171a
DW
1578 global $DB;
1579
1580 if (!$this->has_instance()) {
1581 return 0;
1582 }
1583
8f7e1c05 1584 $params = array();
ab14ab74
FM
1585 $sqlnew = '';
1586
1587 if (!$includenew) {
1588 $sqlnew = ' AND s.status <> :status ';
1589 $params['status'] = ASSIGN_SUBMISSION_STATUS_NEW;
1590 }
b473171a
DW
1591
1592 if ($this->get_instance()->teamsubmission) {
8f7e1c05 1593 // We cannot join on the enrolment tables for group submissions (no userid).
df211804 1594 $sql = 'SELECT COUNT(DISTINCT s.groupid)
8f7e1c05
DW
1595 FROM {assign_submission} s
1596 WHERE
1597 s.assignment = :assignid AND
1598 s.timemodified IS NOT NULL AND
ab14ab74
FM
1599 s.userid = :groupuserid' .
1600 $sqlnew;
8f7e1c05
DW
1601
1602 $params['assignid'] = $this->get_instance()->id;
1603 $params['groupuserid'] = 0;
1604 } else {
1605 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
ab14ab74 1606 list($esql, $enrolparams) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05 1607
ab14ab74 1608 $params = array_merge($params, $enrolparams);
8f7e1c05
DW
1609 $params['assignid'] = $this->get_instance()->id;
1610
df211804 1611 $sql = 'SELECT COUNT(DISTINCT s.userid)
8f7e1c05 1612 FROM {assign_submission} s
0c7b6910 1613 JOIN(' . $esql . ') e ON e.id = s.userid
8f7e1c05
DW
1614 WHERE
1615 s.assignment = :assignid AND
ab14ab74
FM
1616 s.timemodified IS NOT NULL ' .
1617 $sqlnew;
4c4c7b3f 1618
b473171a 1619 }
8f7e1c05 1620
b473171a
DW
1621 return $DB->count_records_sql($sql, $params);
1622 }
1623
1624 /**
e5403f8c 1625 * Load a count of submissions with a specified status.
bbd0e548
DW
1626 *
1627 * @param string $status The submission status - should match one of the constants
1628 * @return int number of matching submissions
1629 */
1630 public function count_submissions_with_status($status) {
1631 global $DB;
8f7e1c05
DW
1632
1633 $currentgroup = groups_get_activity_group($this->get_course_module(), true);
c494e18c 1634 list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
8f7e1c05
DW
1635
1636 $params['assignid'] = $this->get_instance()->id;
df211804 1637 $params['assignid2'] = $this->get_instance()->id;
8f7e1c05 1638 $params['submissionstatus'] = $status;
12a1a0da
DW
1639
1640 if ($this->get_instance()->teamsubmission) {
bc006a66
DM
1641
1642 $groupsstr = '';
1643 if ($currentgroup != 0) {
1644 // If there is an active group we should only display the current group users groups.
1645 $participants = $this->list_participants($currentgroup, true);
1646 $groups = groups_get_all_groups($this->get_course()->id,
1647 array_keys($participants),
1648 $this->get_instance()->teamsubmissiongroupingid,
1649 'DISTINCT g.id, g.name');
1650 list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
1651 $groupsstr = 's.groupid ' . $groupssql . ' AND';
1652 $params = $params + $groupsparams;
1653 }
8f7e1c05
DW
1654 $sql = 'SELECT COUNT(s.groupid)
1655 FROM {assign_submission} s
1656 WHERE
ad030ab4 1657 s.latest = 1 AND
8f7e1c05
DW
1658 s.assignment = :assignid AND
1659 s.timemodified IS NOT NULL AND
bc006a66
DM
1660 s.userid = :groupuserid AND '
1661 . $groupsstr . '
8f7e1c05
DW
1662 s.status = :submissionstatus';
1663 $params['groupuserid'] = 0;
1664 } else {
1665 $sql = 'SELECT COUNT(s.userid)
1666 FROM {assign_submission} s
0c7b6910 1667 JOIN(' . $esql . ') e ON e.id = s.userid
8f7e1c05 1668 WHERE
ad030ab4 1669 s.latest = 1 AND
8f7e1c05
DW
1670 s.assignment = :assignid AND
1671 s.timemodified IS NOT NULL AND
1672 s.status = :submissionstatus';
4c4c7b3f 1673
12a1a0da 1674 }
8f7e1c05 1675
12a1a0da 1676 return $DB->count_records_sql($sql, $params);
bbd0e548
DW
1677 }
1678
1679 /**
1680 * Utility function to get the userid for every row in the grading table
e5403f8c 1681 * so the order can be frozen while we iterate it.
bbd0e548
DW
1682 *
1683 * @return array An array of userids
1684 */
47f48152 1685 protected function get_grading_userid_list() {
bbd0e548 1686 $filter = get_user_preferences('assign_filter', '');
bf78ebd6 1687 $table = new assign_grading_table($this, 0, $filter, 0, false);
bbd0e548
DW
1688
1689 $useridlist = $table->get_column_data('userid');
1690
1691 return $useridlist;
1692 }
1693
bbd0e548 1694 /**
e5403f8c 1695 * Generate zip file from array of given files.
bbd0e548 1696 *
e5403f8c
DW
1697 * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1698 * This array is indexed by the final file name and each
1699 * element in the array is an instance of a stored_file object.
1700 * @return path of temp file - note this returned file does
1701 * not have a .zip extension - it is a temp file.
bbd0e548 1702 */
47f48152 1703 protected function pack_files($filesforzipping) {
e5403f8c
DW
1704 global $CFG;
1705 // Create path for new zip file.
1706 $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1707 // Zip files.
1708 $zipper = new zip_packer();
1709 if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1710 return $tempzip;
bbd0e548 1711 }
e5403f8c 1712 return false;
bbd0e548
DW
1713 }
1714
bbd0e548 1715 /**
3f7b501e
SH
1716 * Finds all assignment notifications that have yet to be mailed out, and mails them.
1717 *
e5403f8c 1718 * Cron function to be run periodically according to the moodle cron.
bbd0e548
DW
1719 *
1720 * @return bool
1721 */
e5403f8c 1722 public static function cron() {
3f7b501e 1723 global $DB;
75f87a57 1724
e5403f8c 1725 // Only ever send a max of one days worth of updates.
75f87a57
DW
1726 $yesterday = time() - (24 * 3600);
1727 $timenow = time();
abd0f0ae 1728 $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign'));
75f87a57 1729
70cfc878
JF
1730 // Collect all submissions that require mailing.
1731 // Submissions are included if all are true:
1732 // - The assignment is visible in the gradebook.
1733 // - No previous notification has been sent.
1734 // - If marking workflow is not enabled, the grade was updated in the past 24 hours, or
1735 // if marking workflow is enabled, the workflow state is at 'released'.
86dced43
TB
1736 $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
1737 g.*, g.timemodified as lastmodified, cm.id as cmid
3f7b501e
SH
1738 FROM {assign} a
1739 JOIN {assign_grades} g ON g.assignment = a.id
fdd3ebc5 1740 LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
86dced43
TB
1741 JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
1742 JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
fdd3ebc5 1743 JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
70cfc878
JF
1744 WHERE ((a.markingworkflow = 0 AND g.timemodified >= :yesterday AND g.timemodified <= :today) OR
1745 (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND
f33be9eb
TB
1746 uf.mailed = 0 AND gri.hidden = 0
1747 ORDER BY a.course, cm.id";
e5403f8c 1748
70cfc878
JF
1749 $params = array(
1750 'yesterday' => $yesterday,
1751 'today' => $timenow,
1752 'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
1753 );
3f7b501e 1754 $submissions = $DB->get_records_sql($sql, $params);
75f87a57 1755
ae7638f7 1756 if (!empty($submissions)) {
c8314005 1757
ae7638f7 1758 mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
75f87a57 1759
ae7638f7
DW
1760 // Preload courses we are going to need those.
1761 $courseids = array();
1762 foreach ($submissions as $submission) {
1763 $courseids[] = $submission->course;
1764 }
e5403f8c 1765
ae7638f7
DW
1766 // Filter out duplicates.
1767 $courseids = array_unique($courseids);
1768 $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1769 list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1770 $sql = 'SELECT c.*, ' . $ctxselect .
1771 ' FROM {course} c
1772 LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1773 WHERE c.id ' . $courseidsql;
e5403f8c 1774
ae7638f7
DW
1775 $params['contextlevel'] = CONTEXT_COURSE;
1776 $courses = $DB->get_records_sql($sql, $params);
e5403f8c 1777
ae7638f7
DW
1778 // Clean up... this could go on for a while.
1779 unset($courseids);
1780 unset($ctxselect);
1781 unset($courseidsql);
1782 unset($params);
3f7b501e 1783
ae7638f7
DW
1784 // Message students about new feedback.
1785 foreach ($submissions as $submission) {
75f87a57 1786
ae7638f7 1787 mtrace("Processing assignment submission $submission->id ...");
75f87a57 1788
ae7638f7
DW
1789 // Do not cache user lookups - could be too many.
1790 if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1791 mtrace('Could not find user ' . $submission->userid);
1792 continue;
1793 }
75f87a57 1794
ae7638f7
DW
1795 // Use a cache to prevent the same DB queries happening over and over.
1796 if (!array_key_exists($submission->course, $courses)) {
1797 mtrace('Could not find course ' . $submission->course);
1798 continue;
1799 }
1800 $course = $courses[$submission->course];
1801 if (isset($course->ctxid)) {
1802 // Context has not yet been preloaded. Do so now.
1803 context_helper::preload_from_record($course);
1804 }
75f87a57 1805
ae7638f7
DW
1806 // Override the language and timezone of the "current" user, so that
1807 // mail is customised for the receiver.
1808 cron_setup_user($user, $course);
1809
1810 // Context lookups are already cached.
1811 $coursecontext = context_course::instance($course->id);
1812 if (!is_enrolled($coursecontext, $user->id)) {
1813 $courseshortname = format_string($course->shortname,
1814 true,
1815 array('context' => $coursecontext));
1816 mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1817 continue;
1818 }
75f87a57 1819
ae7638f7
DW
1820 if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1821 mtrace('Could not find grader ' . $submission->grader);
1822 continue;
1823 }
75f87a57 1824
ccd6839a 1825 $modinfo = get_fast_modinfo($course, $user->id);
86dced43 1826 $cm = $modinfo->get_cm($submission->cmid);
ae7638f7 1827 // Context lookups are already cached.
86dced43 1828 $contextmodule = context_module::instance($cm->id);
ae7638f7 1829
ccd6839a
TB
1830 if (!$cm->uservisible) {
1831 // Hold mail notification for assignments the user cannot access until later.
75f87a57
DW
1832 continue;
1833 }
bbd0e548 1834
ae7638f7
DW
1835 // Need to send this to the student.
1836 $messagetype = 'feedbackavailable';
1837 $eventtype = 'assign_notification';
1838 $updatetime = $submission->lastmodified;
1839 $modulename = get_string('modulename', 'assign');
75f87a57 1840
ae7638f7
DW
1841 $uniqueid = 0;
1842 if ($submission->blindmarking && !$submission->revealidentities) {
1843 $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1844 }
1845 $showusers = $submission->blindmarking && !$submission->revealidentities;
1846 self::send_assignment_notification($grader,
1847 $user,
1848 $messagetype,
1849 $eventtype,
1850 $updatetime,
86dced43 1851 $cm,
ae7638f7
DW
1852 $contextmodule,
1853 $course,
1854 $modulename,
1855 $submission->name,
1856 $showusers,
1857 $uniqueid);
1858
1859 $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
1860 if ($flags) {
1861 $flags->mailed = 1;
1862 $DB->update_record('assign_user_flags', $flags);
1863 } else {
1864 $flags = new stdClass();
1865 $flags->userid = $user->id;
1866 $flags->assignment = $submission->assignment;
1867 $flags->mailed = 1;
1868 $DB->insert_record('assign_user_flags', $flags);
1869 }
b473171a 1870
ae7638f7 1871 mtrace('Done');
df211804 1872 }
ae7638f7 1873 mtrace('Done processing ' . count($submissions) . ' assignment submissions');
75f87a57 1874
ae7638f7
DW
1875 cron_setup_user();
1876
1877 // Free up memory just to be sure.
1878 unset($courses);
75f87a57 1879 }
75f87a57 1880
ae7638f7
DW
1881 // Update calendar events to provide a description.
1882 $sql = 'SELECT id
1883 FROM {assign}
1884 WHERE
1885 allowsubmissionsfromdate >= :lastcron AND
1886 allowsubmissionsfromdate <= :timenow AND
1887 alwaysshowdescription = 0';
1888 $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
1889 $newlyavailable = $DB->get_records_sql($sql, $params);
1890 foreach ($newlyavailable as $record) {
1891 $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
1892 $context = context_module::instance($cm->id);
3f7b501e 1893
ae7638f7
DW
1894 $assignment = new assign($context, null, null);
1895 $assignment->update_calendar($cm->id);
1896 }
bbd0e548
DW
1897
1898 return true;
1899 }
1900
d6c673ed
DW
1901 /**
1902 * Mark in the database that this grade record should have an update notification sent by cron.
1903 *
1904 * @param stdClass $grade a grade record keyed on id
a4b10a52 1905 * @param bool $mailedoverride when true, flag notification to be sent again.
d6c673ed
DW
1906 * @return bool true for success
1907 */
a4b10a52 1908 public function notify_grade_modified($grade, $mailedoverride = false) {
d6c673ed
DW
1909 global $DB;
1910
df211804 1911 $flags = $this->get_user_flags($grade->userid, true);
a4b10a52 1912 if ($flags->mailed != 1 || $mailedoverride) {
df211804 1913 $flags->mailed = 0;
d6c673ed
DW
1914 }
1915
df211804
DW
1916 return $this->update_user_flags($flags);
1917 }
1918
1919 /**
1920 * Update user flags for this user in this assignment.
1921 *
1922 * @param stdClass $flags a flags record keyed on id
1923 * @return bool true for success
1924 */
1925 public function update_user_flags($flags) {
1926 global $DB;
1927 if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
1928 return false;
1929 }
1930
1931 $result = $DB->update_record('assign_user_flags', $flags);
1932 return $result;
d6c673ed
DW
1933 }
1934
bbd0e548 1935 /**
e5403f8c 1936 * Update a grade in the grade table for the assignment and in the gradebook.
bbd0e548
DW
1937 *
1938 * @param stdClass $grade a grade record keyed on id
ab38cb28 1939 * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment.
bbd0e548
DW
1940 * @return bool true for success
1941 */
ab38cb28 1942 public function update_grade($grade, $reopenattempt = false) {
bbd0e548
DW
1943 global $DB;
1944
1945 $grade->timemodified = time();
1946
f8d107b3
DM
1947 if (!empty($grade->workflowstate)) {
1948 $validstates = $this->get_marking_workflow_states_for_current_user();
1949 if (!array_key_exists($grade->workflowstate, $validstates)) {
1950 return false;
1951 }
1952 }
1953
bbd0e548
DW
1954 if ($grade->grade && $grade->grade != -1) {
1955 if ($this->get_instance()->grade > 0) {
1956 if (!is_numeric($grade->grade)) {
1957 return false;
1958 } else if ($grade->grade > $this->get_instance()->grade) {
1959 return false;
1960 } else if ($grade->grade < 0) {
1961 return false;
1962 }
1963 } else {
e5403f8c 1964 // This is a scale.
bbd0e548
DW
1965 if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1966 $scaleoptions = make_menu_from_list($scale->scale);
1967 if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1968 return false;
1969 }
1970 }
1971 }
1972 }
1973
a8fdb36c
DW
1974 if (empty($grade->attemptnumber)) {
1975 // Set it to the default.
1976 $grade->attemptnumber = 0;
1977 }
e77bacab 1978 $DB->update_record('assign_grades', $grade);
df211804 1979
df211804
DW
1980 $submission = null;
1981 if ($this->get_instance()->teamsubmission) {
1982 $submission = $this->get_group_submission($grade->userid, 0, false);
1983 } else {
1984 $submission = $this->get_user_submission($grade->userid, false);
1985 }
767fda9b
GF
1986
1987 // Only push to gradebook if the update is for the latest attempt.
df211804
DW
1988 // Not the latest attempt.
1989 if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
1990 return true;
1991 }
1992
bd3ee807
MN
1993 if ($this->gradebook_item_update(null, $grade)) {
1994 \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
1995 }
e77bacab
MP
1996
1997 // If the conditions are met, allow another attempt.
1998 if ($submission) {
1999 $this->reopen_submission_if_required($grade->userid,
2000 $submission,
2001 $reopenattempt);
bbd0e548 2002 }
bd3ee807 2003
e77bacab 2004 return true;
bbd0e548
DW
2005 }
2006
9e795179 2007 /**
e5403f8c 2008 * View the grant extension date page.
9e795179
DW
2009 *
2010 * Uses url parameters 'userid'
2011 * or from parameter 'selectedusers'
e5403f8c 2012 *
9e795179
DW
2013 * @param moodleform $mform - Used for validation of the submitted data
2014 * @return string
2015 */
47f48152 2016 protected function view_grant_extension($mform) {
9e795179
DW
2017 global $DB, $CFG;
2018 require_once($CFG->dirroot . '/mod/assign/extensionform.php');
2019
2020 $o = '';
26670f5e 2021
9e795179 2022 $data = new stdClass();
26670f5e 2023 $data->id = $this->get_course_module()->id;
9e795179 2024
26670f5e
JF
2025 $formparams = array(
2026 'instance' => $this->get_instance()
2027 );
2028
2029 $extrauserfields = get_extra_user_fields($this->get_context());
2030
2031 if ($mform) {
2032 $submitteddata = $mform->get_data();
2033 $users = $submitteddata->selectedusers;
2034 $userlist = explode(',', $users);
2035
2036 $data->selectedusers = $users;
2037 $data->userid = 0;
9e795179 2038
26670f5e
JF
2039 $usershtml = '';
2040 $usercount = 0;
2041 foreach ($userlist as $userid) {
2042 if ($usercount >= 5) {
2043 $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
2044 break;
2045 }
2046 $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2047
2048 $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
2049 $this->get_course()->id,
2050 has_capability('moodle/site:viewfullnames',
2051 $this->get_course_context()),
2052 $this->is_blind_marking(),
2053 $this->get_uniqueid_for_user($user->id),
2054 $extrauserfields,
2055 !$this->is_active_user($userid)));
2056 $usercount += 1;
2057 }
2058
2059 $formparams['userscount'] = count($userlist);
2060 $formparams['usershtml'] = $usershtml;
2061
2062 } else {
2063 $userid = required_param('userid', PARAM_INT);
9e795179 2064 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
26670f5e 2065 $flags = $this->get_user_flags($userid, false);
9e795179 2066
26670f5e 2067 $data->userid = $user->id;
9f65f9c3
DW
2068 if ($flags) {
2069 $data->extensionduedate = $flags->extensionduedate;
9e795179 2070 }
26670f5e
JF
2071
2072 $usershtml = $this->get_renderer()->render(new assign_user_summary($user,
2073 $this->get_course()->id,
2074 has_capability('moodle/site:viewfullnames',
2075 $this->get_course_context()),
2076 $this->is_blind_marking(),
2077 $this->get_uniqueid_for_user($user->id),
2078 $extrauserfields,
2079 !$this->is_active_user($userid)));
2080 $formparams['usershtml'] = $usershtml;
9e795179 2081 }
26670f5e
JF
2082
2083 $mform = new mod_assign_extension_form(null, $formparams);
2084 $mform->set_data($data);
e5403f8c
DW
2085 $header = new assign_header($this->get_instance(),
2086 $this->get_context(),
2087 $this->show_intro(),
2088 $this->get_course_module()->id,
2089 get_string('grantextension', 'assign'));
2090 $o .= $this->get_renderer()->render($header);
49d83b9d 2091 $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
9e795179
DW
2092 $o .= $this->view_footer();
2093 return $o;
2094 }
2095
12a1a0da 2096 /**
e5403f8c 2097 * Get a list of the users in the same group as this user.
12a1a0da
DW
2098 *
2099 * @param int $groupid The id of the group whose members we want or 0 for the default group
2100 * @param bool $onlyids Whether to retrieve only the user id's
b2885704 2101 * @param bool $excludesuspended Whether to exclude suspended users
12a1a0da
DW
2102 * @return array The users (possibly id's only)
2103 */
b2885704 2104 public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) {
12a1a0da
DW
2105 $members = array();
2106 if ($groupid != 0) {
2107 if ($onlyids) {
2108 $allusers = groups_get_members($groupid, 'u.id');
2109 } else {
2110 $allusers = groups_get_members($groupid);
2111 }
2112 foreach ($allusers as $user) {
2113 if ($this->get_submission_group($user->id)) {
2114 $members[] = $user;
2115 }
2116 }
2117 } else {
2118 $allusers = $this->list_participants(null, $onlyids);
2119 foreach ($allusers as $user) {
2120 if ($this->get_submission_group($user->id) == null) {
2121 $members[] = $user;
2122 }
2123 }
2124 }
4c4c7b3f 2125 // Exclude suspended users, if user can't see them.
b2885704 2126 if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
4c4c7b3f 2127 foreach ($members as $key => $member) {
1ecb8044 2128 if (!$this->is_active_user($member->id)) {
4c4c7b3f
RT
2129 unset($members[$key]);
2130 }
2131 }
2132 }
bc006a66 2133
12a1a0da
DW
2134 return $members;
2135 }
2136
2137 /**
e5403f8c 2138 * Get a list of the users in the same group as this user that have not submitted the assignment.
12a1a0da
DW
2139 *
2140 * @param int $groupid The id of the group whose members we want or 0 for the default group
2141 * @param bool $onlyids Whether to retrieve only the user id's
2142 * @return array The users (possibly id's only)
2143 */
2144 public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
e5403f8c
DW
2145 $instance = $this->get_instance();
2146 if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
12a1a0da
DW
2147 return array();
2148 }
2149 $members = $this->get_submission_group_members($groupid, $onlyids);
2150
2151 foreach ($members as $id => $member) {
2152 $submission = $this->get_user_submission($member->id, false);
df211804 2153 if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
12a1a0da 2154 unset($members[$id]);
88cfe469
DW
2155 } else {
2156 if ($this->is_blind_marking()) {
e5403f8c
DW
2157 $members[$id]->alias = get_string('hiddenuser', 'assign') .
2158 $this->get_uniqueid_for_user($id);
88cfe469 2159 }
12a1a0da
DW
2160 }
2161 }
2162 return $members;
2163 }
2164
2165 /**
e5403f8c 2166 * Load the group submission object for a particular user, optionally creating it if required.
12a1a0da
DW
2167 *
2168 * @param int $userid The id of the user whose submission we want
e5403f8c
DW
2169 * @param int $groupid The id of the group for this user - may be 0 in which
2170 * case it is determined from the userid.
12a1a0da 2171 * @param bool $create If set to true a new submission object will be created in the database
9e3eee67 2172 * with the status set to "new".
df211804 2173 * @param int $attemptnumber - -1 means the latest attempt
12a1a0da
DW
2174 * @return stdClass The submission
2175 */
df211804 2176 public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
12a1a0da
DW
2177 global $DB;
2178
2179 if ($groupid == 0) {
2180 $group = $this->get_submission_group($userid);
2181 if ($group) {
2182 $groupid = $group->id;
2183 }
2184 }
2185
12a1a0da
DW
2186 // Now get the group submission.
2187 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
df211804
DW
2188 if ($attemptnumber >= 0) {
2189 $params['attemptnumber'] = $attemptnumber;
2190 }
2191
2192 // Only return the row with the highest attemptnumber.
2193 $submission = null;
2194 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2195 if ($submissions) {
2196 $submission = reset($submissions);
2197 }
12a1a0da
DW
2198
2199 if ($submission) {
2200 return $submission;
2201 }
2202 if ($create) {
2203 $submission = new stdClass();
e5403f8c
DW
2204 $submission->assignment = $this->get_instance()->id;
2205 $submission->userid = 0;
2206 $submission->groupid = $groupid;
12a1a0da
DW
2207 $submission->timecreated = time();
2208 $submission->timemodified = $submission->timecreated;
df211804
DW
2209 if ($attemptnumber >= 0) {
2210 $submission->attemptnumber = $attemptnumber;
12a1a0da 2211 } else {
df211804 2212 $submission->attemptnumber = 0;
12a1a0da 2213 }
1523f9e0
DW
2214 // Work out if this is the latest submission.
2215 $submission->latest = 0;
2216 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2217 if ($attemptnumber == -1) {
2218 // This is a new submission so it must be the latest.
2219 $submission->latest = 1;
2220 } else {
2221 // We need to work this out.
2222 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2223 if ($result) {
2224 $latestsubmission = reset($result);
2225 }
2226 if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2227 $submission->latest = 1;
2228 }
2229 }
2230 if ($submission->latest) {
2231 // This is the case when we need to set latest to 0 for all the other attempts.
2232 $DB->set_field('assign_submission', 'latest', 0, $params);
2233 }
9e3eee67 2234 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
12a1a0da 2235 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2236 return $DB->get_record('assign_submission', array('id' => $sid));
12a1a0da
DW
2237 }
2238 return false;
2239 }
2240
df47b77f 2241 /**
64220210
DW
2242 * View a summary listing of all assignments in the current course.
2243 *
2244 * @return string
2245 */
2246 private function view_course_index() {
2247 global $USER;
2248
2249 $o = '';
2250
2251 $course = $this->get_course();
2252 $strplural = get_string('modulenameplural', 'assign');
2253
2254 if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2255 $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2256 $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2257 return $o;
2258 }
2259
09af1e28 2260 $strsectionname = '';
64220210
DW
2261 $usesections = course_format_uses_sections($course->format);
2262 $modinfo = get_fast_modinfo($course);
2263
2264 if ($usesections) {
09af1e28 2265 $strsectionname = get_string('sectionname', 'format_'.$course->format);
64220210
DW
2266 $sections = $modinfo->get_section_info_all();
2267 }
2268 $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2269
2270 $timenow = time();
2271
2272 $currentsection = '';
2273 foreach ($modinfo->instances['assign'] as $cm) {
2274 if (!$cm->uservisible) {
2275 continue;
2276 }
2277
e5403f8c 2278 $timedue = $cms[$cm->id]->duedate;
64220210
DW
2279
2280 $sectionname = '';
2281 if ($usesections && $cm->sectionnum) {
2282 $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2283 }
2284
2285 $submitted = '';
2286 $context = context_module::instance($cm->id);
2287
2288 $assignment = new assign($context, $cm, $course);
2289
2290 if (has_capability('mod/assign:grade', $context)) {
2291 $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2292
2293 } else if (has_capability('mod/assign:submit', $context)) {
2294 $usersubmission = $assignment->get_user_submission($USER->id, false);
2295
2296 if (!empty($usersubmission->status)) {
2297 $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2298 } else {
2299 $submitted = get_string('submissionstatus_', 'assign');
2300 }
2301 }
539af602
DW
2302 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2303 if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2304 !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2305 $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
64220210
DW
2306 } else {
2307 $grade = '-';
2308 }
2309
2310 $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2311
2312 }
2313
2314 $o .= $this->get_renderer()->render($courseindexsummary);
2315 $o .= $this->view_footer();
2316
2317 return $o;
2318 }
2319
2320 /**
2321 * View a page rendered by a plugin.
df47b77f 2322 *
e5403f8c 2323 * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
df47b77f
DW
2324 *
2325 * @return string
2326 */
47f48152 2327 protected function view_plugin_page() {
df47b77f
DW
2328 global $USER;
2329
2330 $o = '';
2331
2332 $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2333 $plugintype = required_param('plugin', PARAM_TEXT);
2334 $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2335
2336 $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2337 if (!$plugin) {
2338 print_error('invalidformdata', '');
2339 return;
2340 }
2341
2342 $o .= $plugin->view_page($pluginaction);
2343
2344 return $o;
2345 }
2346
2347
12a1a0da
DW
2348 /**
2349 * This is used for team assignments to get the group for the specified user.
2350 * If the user is a member of multiple or no groups this will return false
2351 *
2352 * @param int $userid The id of the user whose submission we want
2353 * @return mixed The group or false
2354 */
2355 public function get_submission_group($userid) {
bc006a66
DM
2356
2357 if (isset($this->usersubmissiongroups[$userid])) {
2358 return $this->usersubmissiongroups[$userid];
2359 }
2360
ee5d4eef 2361 $groups = $this->get_all_groups($userid);
12a1a0da 2362 if (count($groups) != 1) {
bc006a66
DM
2363 $return = false;
2364 } else {
2365 $return = array_pop($groups);
12a1a0da 2366 }
bc006a66
DM
2367
2368 // Cache the user submission group.
2369 $this->usersubmissiongroups[$userid] = $return;
2370
2371 return $return;
12a1a0da
DW
2372 }
2373
a059ef2e
DP
2374 /**
2375 * Gets all groups the user is a member of.
2376 *
2377 * @param int $userid Teh id of the user who's groups we are checking
2378 * @return array The group objects
2379 */
ce449f63 2380 public function get_all_groups($userid) {
ee5d4eef
AH
2381 if (isset($this->usergroups[$userid])) {
2382 return $this->usergroups[$userid];
2383 }
2384
2385 $grouping = $this->get_instance()->teamsubmissiongroupingid;
2386 $return = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
2387
2388 $this->usergroups[$userid] = $return;
2389
2390 return $return;
2391 }
2392
9e795179 2393
bbd0e548 2394 /**
e5403f8c
DW
2395 * Display the submission that is used by a plugin.
2396 *
2397 * Uses url parameters 'sid', 'gid' and 'plugin'.
2398 *
bbd0e548
DW
2399 * @param string $pluginsubtype
2400 * @return string
2401 */
47f48152 2402 protected function view_plugin_content($pluginsubtype) {
bbd0e548
DW
2403 $o = '';
2404
2405 $submissionid = optional_param('sid', 0, PARAM_INT);
2406 $gradeid = optional_param('gid', 0, PARAM_INT);
2407 $plugintype = required_param('plugin', PARAM_TEXT);
2408 $item = null;
2409 if ($pluginsubtype == 'assignsubmission') {
2410 $plugin = $this->get_submission_plugin_by_type($plugintype);
2411 if ($submissionid <= 0) {
2412 throw new coding_exception('Submission id should not be 0');
2413 }
2414 $item = $this->get_submission($submissionid);
2415
e5403f8c 2416 // Check permissions.
4a47008c 2417 $this->require_view_submission($item->userid);
49d83b9d 2418 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2419 $this->get_context(),
2420 $this->show_intro(),
2421 $this->get_course_module()->id,
2422 $plugin->get_name()));
49d83b9d 2423 $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
bbd0e548
DW
2424 $item,
2425 assign_submission_plugin_submission::FULL,
2426 $this->get_course_module()->id,
2427 $this->get_return_action(),
2428 $this->get_return_params()));
2429
1be7aef2 2430 // Trigger event for viewing a submission.
1b90858f
PS
2431 \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2432
bbd0e548
DW
2433 } else {
2434 $plugin = $this->get_feedback_plugin_by_type($plugintype);
2435 if ($gradeid <= 0) {
2436 throw new coding_exception('Grade id should not be 0');
2437 }
2438 $item = $this->get_grade($gradeid);
e5403f8c 2439 // Check permissions.
4a47008c 2440 $this->require_view_submission($item->userid);
49d83b9d 2441 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2442 $this->get_context(),
2443 $this->show_intro(),
2444 $this->get_course_module()->id,
2445 $plugin->get_name()));
49d83b9d 2446 $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
bbd0e548
DW
2447 $item,
2448 assign_feedback_plugin_feedback::FULL,
2449 $this->get_course_module()->id,
2450 $this->get_return_action(),
2451 $this->get_return_params()));
3290c01d
MN
2452
2453 // Trigger event for viewing feedback.
1b90858f 2454 \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
bbd0e548
DW
2455 }
2456
bbd0e548
DW
2457 $o .= $this->view_return_links();
2458
2459 $o .= $this->view_footer();
1be7aef2 2460
bbd0e548
DW
2461 return $o;
2462 }
2463
2406815b
DW
2464 /**
2465 * Rewrite plugin file urls so they resolve correctly in an exported zip.
2466 *
1561a37c 2467 * @param string $text - The replacement text
2406815b
DW
2468 * @param stdClass $user - The user record
2469 * @param assign_plugin $plugin - The assignment plugin
2470 */
2471 public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2472 $groupmode = groups_get_activity_groupmode($this->get_course_module());
2473 $groupname = '';
2474 if ($groupmode) {
2475 $groupid = groups_get_activity_group($this->get_course_module(), true);
2476 $groupname = groups_get_group_name($groupid).'-';
2477 }
2478
2479 if ($this->is_blind_marking()) {
2480 $prefix = $groupname . get_string('participant', 'assign');
2481 $prefix = str_replace('_', ' ', $prefix);
2482 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2483 } else {
2484 $prefix = $groupname . fullname($user);
2485 $prefix = str_replace('_', ' ', $prefix);
2486 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2487 }
2488
2489 $subtype = $plugin->get_subtype();
2490 $type = $plugin->get_type();
2491 $prefix = $prefix . $subtype . '_' . $type . '_';
2492
2493 $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2494
2495 return $result;
2496 }
2497
bbd0e548 2498 /**
e5403f8c 2499 * Render the content in editor that is often used by plugin.
bbd0e548
DW
2500 *
2501 * @param string $filearea
2502 * @param int $submissionid
2503 * @param string $plugintype
2504 * @param string $editor
2505 * @param string $component
2506 * @return string
2507 */
2508 public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2509 global $CFG;
2510
2511 $result = '';
2512
2513 $plugin = $this->get_submission_plugin_by_type($plugintype);
2514
2515 $text = $plugin->get_editor_text($editor, $submissionid);
2516 $format = $plugin->get_editor_format($editor, $submissionid);
2517
e5403f8c
DW
2518 $finaltext = file_rewrite_pluginfile_urls($text,
2519 'pluginfile.php',
2520 $this->get_context()->id,
2521 $component,
2522 $filearea,
2523 $submissionid);
2524 $params = array('overflowdiv' => true, 'context' => $this->get_context());
2525 $result .= format_text($finaltext, $format, $params);
bbd0e548 2526
418bd23f 2527 if ($CFG->enableportfolios && has_capability('mod/assign:exportownsubmission', $this->context)) {
bbd0e548
DW
2528 require_once($CFG->libdir . '/portfoliolib.php');
2529
2530 $button = new portfolio_add_button();
e5403f8c
DW
2531 $portfolioparams = array('cmid' => $this->get_course_module()->id,
2532 'sid' => $submissionid,
2533 'plugin' => $plugintype,
2534 'editor' => $editor,
2535 'area'=>$filearea);
2536 $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
bbd0e548
DW
2537 $fs = get_file_storage();
2538
e5403f8c
DW
2539 if ($files = $fs->get_area_files($this->context->id,
2540 $component,
2541 $filearea,
2542 $submissionid,
2543 'timemodified',
2544 false)) {
bbd0e548
DW
2545 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2546 } else {
2547 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2548 }
2549 $result .= $button->to_html();
2550 }
2551 return $result;
2552 }
2553
df211804 2554 /**
d9bfe3c5 2555 * Display a continue page after grading.
df211804 2556 *
d9bfe3c5 2557 * @param string $message - The message to display.
df211804
DW
2558 * @return string
2559 */
2560 protected function view_savegrading_result($message) {
2561 $o = '';
2562 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2563 $this->get_context(),
2564 $this->show_intro(),
2565 $this->get_course_module()->id,
2566 get_string('savegradingresult', 'assign')));
2567 $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2568 $message,
2569 $this->get_course_module()->id);
2570 $o .= $this->get_renderer()->render($gradingresult);
2571 $o .= $this->view_footer();
2572 return $o;
2573 }
bf78ebd6 2574 /**
d9bfe3c5 2575 * Display a continue page after quickgrading.
bf78ebd6 2576 *
d9bfe3c5 2577 * @param string $message - The message to display.
bf78ebd6
DW
2578 * @return string
2579 */
47f48152 2580 protected function view_quickgrading_result($message) {
bf78ebd6 2581 $o = '';
49d83b9d 2582 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bf78ebd6
DW
2583 $this->get_context(),
2584 $this->show_intro(),
2585 $this->get_course_module()->id,
2586 get_string('quickgradingresult', 'assign')));
cfdd3e5c 2587 $lastpage = optional_param('lastpage', null, PARAM_INT);
df211804
DW
2588 $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2589 $message,
cfdd3e5c
GF
2590 $this->get_course_module()->id,
2591 false,
2592 $lastpage);
e5403f8c 2593 $o .= $this->get_renderer()->render($gradingresult);
bf78ebd6
DW
2594 $o .= $this->view_footer();
2595 return $o;
2596 }
bbd0e548
DW
2597
2598 /**
e5403f8c 2599 * Display the page footer.
bbd0e548 2600 *
bf78ebd6 2601 * @return string
bbd0e548 2602 */
47f48152 2603 protected function view_footer() {
1be7aef2
MN
2604 // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2605 if (!PHPUNIT_TEST) {
2606 return $this->get_renderer()->render_footer();
2607 }
2608
2609 return '';
bbd0e548
DW
2610 }
2611
4a47008c
DW
2612 /**
2613 * Throw an error if the permissions to view this users submission are missing.
2614 *
2615 * @throws required_capability_exception
2616 * @return none
2617 */
2618 public function require_view_submission($userid) {
2619 if (!$this->can_view_submission($userid)) {
2620 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2621 }
2622 }
2623
2624 /**
2625 * Throw an error if the permissions to view grades in this assignment are missing.
2626 *
2627 * @throws required_capability_exception
2628 * @return none
2629 */
2630 public function require_view_grades() {
2631 if (!$this->can_view_grades()) {
2632 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2633 }
2634 }
2635
2636 /**
2637 * Does this user have view grade or grade permission for this assignment?
2638 *
2639 * @return bool
2640 */
2641 public function can_view_grades() {
2642 // Permissions check.
2643 if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2644 return false;
2645 }
2646
2647 return true;
2648 }
2649
bbd0e548 2650 /**
e5403f8c 2651 * Does this user have grade permission for this assignment?
bbd0e548
DW
2652 *
2653 * @return bool
2654 */
5c386472 2655 public function can_grade() {
e5403f8c 2656 // Permissions check.
bbd0e548
DW
2657 if (!has_capability('mod/assign:grade', $this->context)) {
2658 return false;
2659 }
2660
2661 return true;
2662 }
2663
2664 /**
e5403f8c 2665 * Download a zip file of all assignment submissions.
bbd0e548 2666 *
df211804 2667 * @return string - If an error occurs, this will contain the error page.
bbd0e548 2668 */
47f48152 2669 protected function download_submissions() {
e5403f8c 2670 global $CFG, $DB;
bbd0e548 2671
d0d4796b 2672 // More efficient to load this here.
bbd0e548
DW
2673 require_once($CFG->libdir.'/filelib.php');
2674
08334e06
MS
2675 // Increase the server timeout to handle the creation and sending of large zip files.
2676 core_php_time_limit::raise();
2677
4a47008c 2678 $this->require_view_grades();
76640b27 2679
d0d4796b 2680 // Load all users with submit.
4c4c7b3f 2681 $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
1ecb8044 2682 $this->show_only_active_users());
bbd0e548 2683
d0d4796b 2684 // Build a list of files to zip.
bbd0e548
DW
2685 $filesforzipping = array();
2686 $fs = get_file_storage();
2687
2688 $groupmode = groups_get_activity_groupmode($this->get_course_module());
d0d4796b
DW
2689 // All users.
2690 $groupid = 0;
bbd0e548
DW
2691 $groupname = '';
2692 if ($groupmode) {
2693 $groupid = groups_get_activity_group($this->get_course_module(), true);
2694 $groupname = groups_get_group_name($groupid).'-';
2695 }
2696
d0d4796b 2697 // Construct the zip file name.
e5403f8c
DW
2698 $filename = clean_filename($this->get_course()->shortname . '-' .
2699 $this->get_instance()->name . '-' .
2700 $groupname.$this->get_course_module()->id . '.zip');
bbd0e548 2701
d0d4796b
DW
2702 // Get all the files for each student.
2703 foreach ($students as $student) {
2704 $userid = $student->id;
bbd0e548 2705
7a2b911c 2706 if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
d0d4796b 2707 // Get the plugins to add their own files to the zip.
bbd0e548 2708
d0d4796b
DW
2709 $submissiongroup = false;
2710 $groupname = '';
2711 if ($this->get_instance()->teamsubmission) {
2712 $submission = $this->get_group_submission($userid, 0, false);
2713 $submissiongroup = $this->get_submission_group($userid);
21f77397
DW
2714 if ($submissiongroup) {
2715 $groupname = $submissiongroup->name . '-';
2716 } else {
2717 $groupname = get_string('defaultteam', 'assign') . '-';
2718 }
b473171a 2719 } else {
d0d4796b 2720 $submission = $this->get_user_submission($userid, false);
b473171a 2721 }
bbd0e548 2722
b473171a 2723 if ($this->is_blind_marking()) {
e5403f8c
DW
2724 $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2725 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2726 } else {
e5403f8c
DW
2727 $prefix = str_replace('_', ' ', $groupname . fullname($student));
2728 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2729 }
bbd0e548 2730
d0d4796b
DW
2731 if ($submission) {
2732 foreach ($this->submissionplugins as $plugin) {
2733 if ($plugin->is_enabled() && $plugin->is_visible()) {
2406815b 2734 $pluginfiles = $plugin->get_files($submission, $student);
d0d4796b 2735 foreach ($pluginfiles as $zipfilename => $file) {
7a2b911c
DW
2736 $subtype = $plugin->get_subtype();
2737 $type = $plugin->get_type();
e5403f8c
DW
2738 $prefixedfilename = clean_filename($prefix .
2739 $subtype .
2740 '_' .
2741 $type .
2742 '_' .
2743 $zipfilename);
d0d4796b
DW
2744 $filesforzipping[$prefixedfilename] = $file;
2745 }
bbd0e548
DW
2746 }
2747 }
2748 }
bbd0e548 2749 }
d0d4796b 2750 }
afa3e637 2751 $result = '';
5c778358 2752 if (count($filesforzipping) == 0) {
afa3e637
DW
2753 $header = new assign_header($this->get_instance(),
2754 $this->get_context(),
2755 '',
2756 $this->get_course_module()->id,
2757 get_string('downloadall', 'assign'));
2758 $result .= $this->get_renderer()->render($header);
5c778358 2759 $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
afa3e637
DW
2760 $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2761 'action'=>'grading'));
2762 $result .= $this->get_renderer()->continue_button($url);
5c778358 2763 $result .= $this->view_footer();
5c778358 2764 } else if ($zipfile = $this->pack_files($filesforzipping)) {
1b90858f 2765 \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
d0d4796b
DW
2766 // Send file and delete after sending.
2767 send_temp_file($zipfile, $filename);
afa3e637 2768 // We will not get here - send_temp_file calls exit.
bbd0e548 2769 }
afa3e637 2770 return $result;
bbd0e548
DW
2771 }
2772
2773 /**
e5403f8c 2774 * Util function to add a message to the log.
bbd0e548 2775 *
c17e70e5
MN
2776 * @deprecated since 2.7 - Use new events system instead.
2777 * (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2778 *
bbd0e548
DW
2779 * @param string $action The current action
2780 * @param string $info A detailed description of the change. But no more than 255 characters.
2781 * @param string $url The url to the assign module instance.
caa06f4b
FM
2782 * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2783 * retrieve the arguments to use them with the new event system (Event 2).
2784 * @return void|array
bbd0e548 2785 */
caa06f4b 2786 public function add_to_log($action = '', $info = '', $url='', $return = false) {
bbd0e548
DW
2787 global $USER;
2788
2789 $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2790 if ($url != '') {
2791 $fullurl .= '&' . $url;
2792 }
2793
caa06f4b
FM
2794 $args = array(
2795 $this->get_course()->id,
2796 'assign',
2797 $action,
2798 $fullurl,
2799 $info,
666abe6e 2800 $this->get_course_module()->id
caa06f4b
FM
2801 );
2802
2803 if ($return) {
c17e70e5
MN
2804 // We only need to call debugging when returning a value. This is because the call to
2805 // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2806 debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
caa06f4b
FM
2807 return $args;
2808 }
2809 call_user_func_array('add_to_log', $args);
bbd0e548
DW
2810 }
2811
2cffef9f 2812 /**
e5403f8c 2813 * Lazy load the page renderer and expose the renderer to plugins.
49d83b9d 2814 *
2cffef9f
PC
2815 * @return assign_renderer
2816 */
23fffa2b 2817 public function get_renderer() {
2cffef9f
PC
2818 global $PAGE;
2819 if ($this->output) {
2820 return $this->output;
2821 }
2822 $this->output = $PAGE->get_renderer('mod_assign');
2823 return $this->output;
2824 }
bbd0e548
DW
2825
2826 /**
e5403f8c 2827 * Load the submission object for a particular user, optionally creating it if required.
bbd0e548 2828 *
12a1a0da
DW
2829 * For team assignments there are 2 submissions - the student submission and the team submission
2830 * All files are associated with the team submission but the status of the students contribution is
2831 * recorded separately.
2832 *
bbd0e548 2833 * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
f6b4fe38 2834 * @param bool $create If set to true a new submission object will be created in the database with the status set to "new".
df211804 2835 * @param int $attemptnumber - -1 means the latest attempt
bbd0e548
DW
2836 * @return stdClass The submission
2837 */
df211804 2838 public function get_user_submission($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2839 global $DB, $USER;
2840
2841 if (!$userid) {
2842 $userid = $USER->id;
2843 }
12a1a0da
DW
2844 // If the userid is not null then use userid.
2845 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
df211804
DW
2846 if ($attemptnumber >= 0) {
2847 $params['attemptnumber'] = $attemptnumber;
2848 }
2849
2850 // Only return the row with the highest attemptnumber.
2851 $submission = null;
2852 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2853 if ($submissions) {
2854 $submission = reset($submissions);
2855 }
bbd0e548
DW
2856
2857 if ($submission) {
2858 return $submission;
2859 }
2860 if ($create) {
2861 $submission = new stdClass();
2862 $submission->assignment = $this->get_instance()->id;
2863 $submission->userid = $userid;
2864 $submission->timecreated = time();
2865 $submission->timemodified = $submission->timecreated;
9e3eee67 2866 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
df211804
DW
2867 if ($attemptnumber >= 0) {
2868 $submission->attemptnumber = $attemptnumber;
2869 } else {
2870 $submission->attemptnumber = 0;
2871 }
1523f9e0
DW
2872 // Work out if this is the latest submission.
2873 $submission->latest = 0;
2874 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2875 if ($attemptnumber == -1) {
2876 // This is a new submission so it must be the latest.
2877 $submission->latest = 1;
2878 } else {
2879 // We need to work this out.
2880 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
9e3eee67 2881 $latestsubmission = null;
1523f9e0
DW
2882 if ($result) {
2883 $latestsubmission = reset($result);
2884 }
9e3eee67 2885 if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
1523f9e0
DW
2886 $submission->latest = 1;
2887 }
2888 }
2889 if ($submission->latest) {
2890 // This is the case when we need to set latest to 0 for all the other attempts.
2891 $DB->set_field('assign_submission', 'latest', 0, $params);
2892 }
bbd0e548 2893 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2894 return $DB->get_record('assign_submission', array('id' => $sid));
bbd0e548
DW
2895 }
2896 return false;
2897 }
2898
2899 /**
e5403f8c 2900 * Load the submission object from it's id.
bbd0e548
DW
2901 *
2902 * @param int $submissionid The id of the submission we want
2903 * @return stdClass The submission
2904 */
47f48152 2905 protected function get_submission($submissionid) {
bbd0e548
DW
2906 global $DB;
2907
e5403f8c
DW
2908 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2909 return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
bbd0e548
DW
2910 }
2911
df211804
DW
2912 /**
2913 * This will retrieve a user flags object from the db optionally creating it if required.
2914 * The user flags was split from the user_grades table in 2.5.
2915 *
2916 * @param int $userid The user we are getting the flags for.
2917 * @param bool $create If true the flags record will be created if it does not exist
2918 * @return stdClass The flags record
2919 */
2920 public function get_user_flags($userid, $create) {
2921 global $DB, $USER;
2922
2923 // If the userid is not null then use userid.
2924 if (!$userid) {
2925 $userid = $USER->id;
2926 }
2927
2928 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2929
2930 $flags = $DB->get_record('assign_user_flags', $params);
2931
2932 if ($flags) {
2933 return $flags;
2934 }
2935 if ($create) {
2936 $flags = new stdClass();
2937 $flags->assignment = $this->get_instance()->id;
2938 $flags->userid = $userid;
2939 $flags->locked = 0;
2940 $flags->extensionduedate = 0;
f8d107b3
DM
2941 $flags->workflowstate = '';
2942 $flags->allocatedmarker = 0;
df211804
DW
2943
2944 // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2945 // This is because students only want to be notified about certain types of update (grades and feedback).
2946 $flags->mailed = 2;
2947
2948 $fid = $DB->insert_record('assign_user_flags', $flags);
2949 $flags->id = $fid;
2950 return $flags;
2951 }
2952 return false;
2953 }
2954
bbd0e548 2955 /**
e5403f8c 2956 * This will retrieve a grade object from the db, optionally creating it if required.
bbd0e548
DW
2957 *
2958 * @param int $userid The user we are grading
2959 * @param bool $create If true the grade will be created if it does not exist
df211804 2960 * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
bbd0e548
DW
2961 * @return stdClass The grade record
2962 */
df211804 2963 public function get_user_grade($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2964 global $DB, $USER;
2965
df211804 2966 // If the userid is not null then use userid.
bbd0e548
DW
2967 if (!$userid) {
2968 $userid = $USER->id;
2969 }
9d3c6b95 2970 $submission = null;
bbd0e548 2971
df211804 2972 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
9d3c6b95 2973 if ($attemptnumber < 0 || $create) {
e7af1926
DW
2974 // Make sure this grade matches the latest submission attempt.
2975 if ($this->get_instance()->teamsubmission) {
9e3eee67 2976 $submission = $this->get_group_submission($userid, 0, true);
e7af1926 2977 } else {
9e3eee67 2978 $submission = $this->get_user_submission($userid, true);
e7af1926
DW
2979 }
2980 if ($submission) {
2981 $attemptnumber = $submission->attemptnumber;
2982 }
2983 }
2984
df211804
DW
2985 if ($attemptnumber >= 0) {
2986 $params['attemptnumber'] = $attemptnumber;
2987 }
bbd0e548 2988
df211804
DW
2989 $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
2990
2991 if ($grades) {
2992 return reset($grades);
bbd0e548
DW
2993 }
2994 if ($create) {
2995 $grade = new stdClass();
2996 $grade->assignment = $this->get_instance()->id;
2997 $grade->userid = $userid;
2998 $grade->timecreated = time();
9d3c6b95
DW
2999 // If we are "auto-creating" a grade - and there is a submission
3000 // the new grade should not have a more recent timemodified value
3001 // than the submission.
3002 if ($submission) {
3003 $grade->timemodified = $submission->timemodified;
3004 } else {
3005 $grade->timemodified = $grade->timecreated;
3006 }
bbd0e548
DW
3007 $grade->grade = -1;
3008 $grade->grader = $USER->id;
df211804
DW
3009 if ($attemptnumber >= 0) {
3010 $grade->attemptnumber = $attemptnumber;
3011 }
d6c673ed 3012
bbd0e548
DW
3013 $gid = $DB->insert_record('assign_grades', $grade);
3014 $grade->id = $gid;
3015 return $grade;
3016 }
3017 return false;
3018 }
3019
3020 /**
e5403f8c 3021 * This will retrieve a grade object from the db.
bbd0e548
DW
3022 *
3023 * @param int $gradeid The id of the grade
3024 * @return stdClass The grade record
3025 */
47f48152 3026 protected function get_grade($gradeid) {
bbd0e548
DW
3027 global $DB;
3028
e5403f8c
DW
3029 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
3030 return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
bbd0e548
DW
3031 }
3032
3033 /**
e5403f8c 3034 * Print the grading page for a single user submission.
bbd0e548
DW
3035 *
3036 * @param moodleform $mform
bbd0e548
DW
3037 * @return string
3038 */
d04557b3 3039 protected function view_single_grade_page($mform) {
1a51661b 3040 global $DB, $CFG, $SESSION;
bbd0e548
DW
3041
3042 $o = '';
e5403f8c 3043 $instance = $this->get_instance();
bbd0e548 3044
bbd0e548
DW
3045 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3046
e5403f8c 3047 // Need submit permission to submit an assignment.
bbd0e548
DW
3048 require_capability('mod/assign:grade', $this->context);
3049
e5403f8c
DW
3050 $header = new assign_header($instance,
3051 $this->get_context(),
3052 false,
3053 $this->get_course_module()->id,
3054 get_string('grading', 'assign'));
3055 $o .= $this->get_renderer()->render($header);
bbd0e548 3056
df211804 3057 // If userid is passed - we are only grading a single student.
d04557b3 3058 $rownum = required_param('rownum', PARAM_INT);
3fca6937 3059 $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
df211804
DW
3060 $userid = optional_param('userid', 0, PARAM_INT);
3061 $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
3062
df211804 3063 if (!$userid) {
1a51661b
DM
3064 $useridlistkey = $this->get_useridlist_key($useridlistid);
3065 if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
3066 $SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list();
df211804 3067 }
1a51661b 3068 $useridlist = $SESSION->mod_assign_useridlist[$useridlistkey];
df211804
DW
3069 } else {
3070 $rownum = 0;
3071 $useridlist = array($userid);
bbd0e548 3072 }
d04557b3
DW
3073
3074 if ($rownum < 0 || $rownum > count($useridlist)) {
3075 throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
bbd0e548 3076 }
d04557b3 3077
bbd0e548
DW
3078 $last = false;
3079 $userid = $useridlist[$rownum];
3080 if ($rownum == count($useridlist) - 1) {
3081 $last = true;
3082 }
cfca338d
DW
3083 // This variation on the url will link direct to this student, with no next/previous links.
3084 // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3085 $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3086 $this->register_return_link('grade', $returnparams);
3087
bbd0e548
DW
3088 $user = $DB->get_record('user', array('id' => $userid));
3089 if ($user) {
e5403f8c
DW
3090 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3091 $usersummary = new assign_user_summary($user,
3092 $this->get_course()->id,
3093 $viewfullnames,
3094 $this->is_blind_marking(),
d08e6c31 3095 $this->get_uniqueid_for_user($user->id),
a69944eb 3096 get_extra_user_fields($this->get_context()),
1ecb8044 3097 !$this->is_active_user($userid));
e5403f8c 3098 $o .= $this->get_renderer()->render($usersummary);
bbd0e548 3099 }
df211804 3100 $submission = $this->get_user_submission($userid, false, $attemptnumber);
12a1a0da 3101 $submissiongroup = null;
12a1a0da
DW
3102 $teamsubmission = null;
3103 $notsubmitted = array();
e5403f8c 3104 if ($instance->teamsubmission) {
df211804 3105 $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
12a1a0da
DW
3106 $submissiongroup = $this->get_submission_group($userid);
3107 $groupid = 0;
3108 if ($submissiongroup) {
3109 $groupid = $submissiongroup->id;
3110 }
3111 $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3112
3113 }
3114
df211804
DW
3115 // Get the requested grade.
3116 $grade = $this->get_user_grade($userid, false, $attemptnumber);
3117 $flags = $this->get_user_flags($userid, false);
bbd0e548 3118 if ($this->can_view_submission($userid)) {
df211804 3119 $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
9e795179 3120 $extensionduedate = null;
df211804
DW
3121 if ($flags) {
3122 $extensionduedate = $flags->extensionduedate;
9e795179 3123 }
88cfe469 3124 $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
12a1a0da 3125 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
ee5d4eef 3126 $usergroups = $this->get_all_groups($user->id);
9e795179 3127
e5403f8c
DW
3128 $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
3129 $instance->alwaysshowdescription,
3130 $submission,
3131 $instance->teamsubmission,
3132 $teamsubmission,
3133 $submissiongroup,
3134 $notsubmitted,
3135 $this->is_any_submission_plugin_enabled(),
3136 $gradelocked,
3137 $this->is_graded($userid),
3138 $instance->duedate,
3139 $instance->cutoffdate,
3140 $this->get_submission_plugins(),
3141 $this->get_return_action(),
3142 $this->get_return_params(),
3143 $this->get_course_module()->id,
3144 $this->get_course()->id,
3145 assign_submission_status::GRADER_VIEW,
3146 $showedit,
19195b60 3147 false,
e5403f8c
DW
3148 $viewfullnames,
3149 $extensionduedate,
3150 $this->get_context(),
3151 $this->is_blind_marking(),
df211804
DW
3152 '',
3153 $instance->attemptreopenmethod,
bd3ee807 3154 $instance->maxattempts,
e528997a 3155 $this->get_grading_status($userid),
ee5d4eef
AH
3156 $instance->preventsubmissionnotingroup,
3157 $usergroups);
e5403f8c 3158 $o .= $this->get_renderer()->render($submissionstatus);
bbd0e548 3159 }
df211804 3160
bbd0e548
DW
3161 if ($grade) {
3162 $data = new stdClass();
e5403f8c
DW
3163 if ($grade->grade !== null && $grade->grade >= 0) {
3164 $data->grade = format_float($grade->grade, 2);