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