Merge branch 'MDL-48235-assign-reopen-until-pass' of git://github.com/mpetrowi/moodle
[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 }
e77bacab 1885 $DB->update_record('assign_grades', $grade);
df211804 1886
df211804
DW
1887 $submission = null;
1888 if ($this->get_instance()->teamsubmission) {
1889 $submission = $this->get_group_submission($grade->userid, 0, false);
1890 } else {
1891 $submission = $this->get_user_submission($grade->userid, false);
1892 }
767fda9b
GF
1893
1894 // Only push to gradebook if the update is for the latest attempt.
df211804
DW
1895 // Not the latest attempt.
1896 if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
1897 return true;
1898 }
1899
e77bacab
MP
1900 $this->gradebook_item_update(null, $grade);
1901
1902 // If the conditions are met, allow another attempt.
1903 if ($submission) {
1904 $this->reopen_submission_if_required($grade->userid,
1905 $submission,
1906 $reopenattempt);
bbd0e548 1907 }
e77bacab
MP
1908 \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
1909 return true;
bbd0e548
DW
1910 }
1911
9e795179 1912 /**
e5403f8c 1913 * View the grant extension date page.
9e795179
DW
1914 *
1915 * Uses url parameters 'userid'
1916 * or from parameter 'selectedusers'
e5403f8c 1917 *
9e795179
DW
1918 * @param moodleform $mform - Used for validation of the submitted data
1919 * @return string
1920 */
47f48152 1921 protected function view_grant_extension($mform) {
9e795179
DW
1922 global $DB, $CFG;
1923 require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1924
1925 $o = '';
9231cd88 1926 $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
9e795179
DW
1927 $data = new stdClass();
1928 $data->extensionduedate = null;
1929 $userid = 0;
1930 if (!$batchusers) {
1931 $userid = required_param('userid', PARAM_INT);
1932
9f65f9c3 1933 $flags = $this->get_user_flags($userid, false);
9e795179
DW
1934
1935 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
1936
9f65f9c3
DW
1937 if ($flags) {
1938 $data->extensionduedate = $flags->extensionduedate;
9e795179
DW
1939 }
1940 $data->userid = $userid;
1941 } else {
1942 $data->batchusers = $batchusers;
1943 }
e5403f8c
DW
1944 $header = new assign_header($this->get_instance(),
1945 $this->get_context(),
1946 $this->show_intro(),
1947 $this->get_course_module()->id,
1948 get_string('grantextension', 'assign'));
1949 $o .= $this->get_renderer()->render($header);
9e795179
DW
1950
1951 if (!$mform) {
e5403f8c
DW
1952 $formparams = array($this->get_course_module()->id,
1953 $userid,
1954 $batchusers,
1955 $this->get_instance(),
1956 $data);
1957 $mform = new mod_assign_extension_form(null, $formparams);
9e795179 1958 }
49d83b9d 1959 $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
9e795179
DW
1960 $o .= $this->view_footer();
1961 return $o;
1962 }
1963
12a1a0da 1964 /**
e5403f8c 1965 * Get a list of the users in the same group as this user.
12a1a0da
DW
1966 *
1967 * @param int $groupid The id of the group whose members we want or 0 for the default group
1968 * @param bool $onlyids Whether to retrieve only the user id's
b2885704 1969 * @param bool $excludesuspended Whether to exclude suspended users
12a1a0da
DW
1970 * @return array The users (possibly id's only)
1971 */
b2885704 1972 public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) {
12a1a0da
DW
1973 $members = array();
1974 if ($groupid != 0) {
1975 if ($onlyids) {
1976 $allusers = groups_get_members($groupid, 'u.id');
1977 } else {
1978 $allusers = groups_get_members($groupid);
1979 }
1980 foreach ($allusers as $user) {
1981 if ($this->get_submission_group($user->id)) {
1982 $members[] = $user;
1983 }
1984 }
1985 } else {
1986 $allusers = $this->list_participants(null, $onlyids);
1987 foreach ($allusers as $user) {
1988 if ($this->get_submission_group($user->id) == null) {
1989 $members[] = $user;
1990 }
1991 }
1992 }
4c4c7b3f 1993 // Exclude suspended users, if user can't see them.
b2885704 1994 if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
4c4c7b3f 1995 foreach ($members as $key => $member) {
1ecb8044 1996 if (!$this->is_active_user($member->id)) {
4c4c7b3f
RT
1997 unset($members[$key]);
1998 }
1999 }
2000 }
12a1a0da
DW
2001 return $members;
2002 }
2003
2004 /**
e5403f8c 2005 * Get a list of the users in the same group as this user that have not submitted the assignment.
12a1a0da
DW
2006 *
2007 * @param int $groupid The id of the group whose members we want or 0 for the default group
2008 * @param bool $onlyids Whether to retrieve only the user id's
2009 * @return array The users (possibly id's only)
2010 */
2011 public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
e5403f8c
DW
2012 $instance = $this->get_instance();
2013 if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
12a1a0da
DW
2014 return array();
2015 }
2016 $members = $this->get_submission_group_members($groupid, $onlyids);
2017
2018 foreach ($members as $id => $member) {
2019 $submission = $this->get_user_submission($member->id, false);
df211804 2020 if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
12a1a0da 2021 unset($members[$id]);
88cfe469
DW
2022 } else {
2023 if ($this->is_blind_marking()) {
e5403f8c
DW
2024 $members[$id]->alias = get_string('hiddenuser', 'assign') .
2025 $this->get_uniqueid_for_user($id);
88cfe469 2026 }
12a1a0da
DW
2027 }
2028 }
2029 return $members;
2030 }
2031
2032 /**
e5403f8c 2033 * Load the group submission object for a particular user, optionally creating it if required.
12a1a0da
DW
2034 *
2035 * @param int $userid The id of the user whose submission we want
e5403f8c
DW
2036 * @param int $groupid The id of the group for this user - may be 0 in which
2037 * case it is determined from the userid.
12a1a0da 2038 * @param bool $create If set to true a new submission object will be created in the database
9e3eee67 2039 * with the status set to "new".
df211804 2040 * @param int $attemptnumber - -1 means the latest attempt
12a1a0da
DW
2041 * @return stdClass The submission
2042 */
df211804 2043 public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
12a1a0da
DW
2044 global $DB;
2045
2046 if ($groupid == 0) {
2047 $group = $this->get_submission_group($userid);
2048 if ($group) {
2049 $groupid = $group->id;
2050 }
2051 }
2052
12a1a0da
DW
2053 // Now get the group submission.
2054 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
df211804
DW
2055 if ($attemptnumber >= 0) {
2056 $params['attemptnumber'] = $attemptnumber;
2057 }
2058
2059 // Only return the row with the highest attemptnumber.
2060 $submission = null;
2061 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2062 if ($submissions) {
2063 $submission = reset($submissions);
2064 }
12a1a0da
DW
2065
2066 if ($submission) {
2067 return $submission;
2068 }
2069 if ($create) {
2070 $submission = new stdClass();
e5403f8c
DW
2071 $submission->assignment = $this->get_instance()->id;
2072 $submission->userid = 0;
2073 $submission->groupid = $groupid;
12a1a0da
DW
2074 $submission->timecreated = time();
2075 $submission->timemodified = $submission->timecreated;
df211804
DW
2076 if ($attemptnumber >= 0) {
2077 $submission->attemptnumber = $attemptnumber;
12a1a0da 2078 } else {
df211804 2079 $submission->attemptnumber = 0;
12a1a0da 2080 }
1523f9e0
DW
2081 // Work out if this is the latest submission.
2082 $submission->latest = 0;
2083 $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2084 if ($attemptnumber == -1) {
2085 // This is a new submission so it must be the latest.
2086 $submission->latest = 1;
2087 } else {
2088 // We need to work this out.
2089 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2090 if ($result) {
2091 $latestsubmission = reset($result);
2092 }
2093 if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2094 $submission->latest = 1;
2095 }
2096 }
2097 if ($submission->latest) {
2098 // This is the case when we need to set latest to 0 for all the other attempts.
2099 $DB->set_field('assign_submission', 'latest', 0, $params);
2100 }
9e3eee67 2101 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
12a1a0da 2102 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2103 return $DB->get_record('assign_submission', array('id' => $sid));
12a1a0da
DW
2104 }
2105 return false;
2106 }
2107
df47b77f 2108 /**
64220210
DW
2109 * View a summary listing of all assignments in the current course.
2110 *
2111 * @return string
2112 */
2113 private function view_course_index() {
2114 global $USER;
2115
2116 $o = '';
2117
2118 $course = $this->get_course();
2119 $strplural = get_string('modulenameplural', 'assign');
2120
2121 if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2122 $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2123 $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2124 return $o;
2125 }
2126
09af1e28 2127 $strsectionname = '';
64220210
DW
2128 $usesections = course_format_uses_sections($course->format);
2129 $modinfo = get_fast_modinfo($course);
2130
2131 if ($usesections) {
09af1e28 2132 $strsectionname = get_string('sectionname', 'format_'.$course->format);
64220210
DW
2133 $sections = $modinfo->get_section_info_all();
2134 }
2135 $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2136
2137 $timenow = time();
2138
2139 $currentsection = '';
2140 foreach ($modinfo->instances['assign'] as $cm) {
2141 if (!$cm->uservisible) {
2142 continue;
2143 }
2144
e5403f8c 2145 $timedue = $cms[$cm->id]->duedate;
64220210
DW
2146
2147 $sectionname = '';
2148 if ($usesections && $cm->sectionnum) {
2149 $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2150 }
2151
2152 $submitted = '';
2153 $context = context_module::instance($cm->id);
2154
2155 $assignment = new assign($context, $cm, $course);
2156
2157 if (has_capability('mod/assign:grade', $context)) {
2158 $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2159
2160 } else if (has_capability('mod/assign:submit', $context)) {
2161 $usersubmission = $assignment->get_user_submission($USER->id, false);
2162
2163 if (!empty($usersubmission->status)) {
2164 $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2165 } else {
2166 $submitted = get_string('submissionstatus_', 'assign');
2167 }
2168 }
539af602
DW
2169 $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2170 if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2171 !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2172 $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
64220210
DW
2173 } else {
2174 $grade = '-';
2175 }
2176
2177 $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2178
2179 }
2180
2181 $o .= $this->get_renderer()->render($courseindexsummary);
2182 $o .= $this->view_footer();
2183
2184 return $o;
2185 }
2186
2187 /**
2188 * View a page rendered by a plugin.
df47b77f 2189 *
e5403f8c 2190 * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
df47b77f
DW
2191 *
2192 * @return string
2193 */
47f48152 2194 protected function view_plugin_page() {
df47b77f
DW
2195 global $USER;
2196
2197 $o = '';
2198
2199 $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2200 $plugintype = required_param('plugin', PARAM_TEXT);
2201 $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2202
2203 $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2204 if (!$plugin) {
2205 print_error('invalidformdata', '');
2206 return;
2207 }
2208
2209 $o .= $plugin->view_page($pluginaction);
2210
2211 return $o;
2212 }
2213
2214
12a1a0da
DW
2215 /**
2216 * This is used for team assignments to get the group for the specified user.
2217 * If the user is a member of multiple or no groups this will return false
2218 *
2219 * @param int $userid The id of the user whose submission we want
2220 * @return mixed The group or false
2221 */
2222 public function get_submission_group($userid) {
e5403f8c
DW
2223 $grouping = $this->get_instance()->teamsubmissiongroupingid;
2224 $groups = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
12a1a0da
DW
2225 if (count($groups) != 1) {
2226 return false;
2227 }
2228 return array_pop($groups);
2229 }
2230
9e795179 2231
bbd0e548 2232 /**
e5403f8c
DW
2233 * Display the submission that is used by a plugin.
2234 *
2235 * Uses url parameters 'sid', 'gid' and 'plugin'.
2236 *
bbd0e548
DW
2237 * @param string $pluginsubtype
2238 * @return string
2239 */
47f48152 2240 protected function view_plugin_content($pluginsubtype) {
bbd0e548
DW
2241 $o = '';
2242
2243 $submissionid = optional_param('sid', 0, PARAM_INT);
2244 $gradeid = optional_param('gid', 0, PARAM_INT);
2245 $plugintype = required_param('plugin', PARAM_TEXT);
2246 $item = null;
2247 if ($pluginsubtype == 'assignsubmission') {
2248 $plugin = $this->get_submission_plugin_by_type($plugintype);
2249 if ($submissionid <= 0) {
2250 throw new coding_exception('Submission id should not be 0');
2251 }
2252 $item = $this->get_submission($submissionid);
2253
e5403f8c 2254 // Check permissions.
4a47008c 2255 $this->require_view_submission($item->userid);
49d83b9d 2256 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2257 $this->get_context(),
2258 $this->show_intro(),
2259 $this->get_course_module()->id,
2260 $plugin->get_name()));
49d83b9d 2261 $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
bbd0e548
DW
2262 $item,
2263 assign_submission_plugin_submission::FULL,
2264 $this->get_course_module()->id,
2265 $this->get_return_action(),
2266 $this->get_return_params()));
2267
1be7aef2 2268 // Trigger event for viewing a submission.
1b90858f
PS
2269 \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2270
bbd0e548
DW
2271 } else {
2272 $plugin = $this->get_feedback_plugin_by_type($plugintype);
2273 if ($gradeid <= 0) {
2274 throw new coding_exception('Grade id should not be 0');
2275 }
2276 $item = $this->get_grade($gradeid);
e5403f8c 2277 // Check permissions.
4a47008c 2278 $this->require_view_submission($item->userid);
49d83b9d 2279 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bbd0e548
DW
2280 $this->get_context(),
2281 $this->show_intro(),
2282 $this->get_course_module()->id,
2283 $plugin->get_name()));
49d83b9d 2284 $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
bbd0e548
DW
2285 $item,
2286 assign_feedback_plugin_feedback::FULL,
2287 $this->get_course_module()->id,
2288 $this->get_return_action(),
2289 $this->get_return_params()));
3290c01d
MN
2290
2291 // Trigger event for viewing feedback.
1b90858f 2292 \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
bbd0e548
DW
2293 }
2294
bbd0e548
DW
2295 $o .= $this->view_return_links();
2296
2297 $o .= $this->view_footer();
1be7aef2 2298
bbd0e548
DW
2299 return $o;
2300 }
2301
2406815b
DW
2302 /**
2303 * Rewrite plugin file urls so they resolve correctly in an exported zip.
2304 *
1561a37c 2305 * @param string $text - The replacement text
2406815b
DW
2306 * @param stdClass $user - The user record
2307 * @param assign_plugin $plugin - The assignment plugin
2308 */
2309 public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2310 $groupmode = groups_get_activity_groupmode($this->get_course_module());
2311 $groupname = '';
2312 if ($groupmode) {
2313 $groupid = groups_get_activity_group($this->get_course_module(), true);
2314 $groupname = groups_get_group_name($groupid).'-';
2315 }
2316
2317 if ($this->is_blind_marking()) {
2318 $prefix = $groupname . get_string('participant', 'assign');
2319 $prefix = str_replace('_', ' ', $prefix);
2320 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2321 } else {
2322 $prefix = $groupname . fullname($user);
2323 $prefix = str_replace('_', ' ', $prefix);
2324 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2325 }
2326
2327 $subtype = $plugin->get_subtype();
2328 $type = $plugin->get_type();
2329 $prefix = $prefix . $subtype . '_' . $type . '_';
2330
2331 $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2332
2333 return $result;
2334 }
2335
bbd0e548 2336 /**
e5403f8c 2337 * Render the content in editor that is often used by plugin.
bbd0e548
DW
2338 *
2339 * @param string $filearea
2340 * @param int $submissionid
2341 * @param string $plugintype
2342 * @param string $editor
2343 * @param string $component
2344 * @return string
2345 */
2346 public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2347 global $CFG;
2348
2349 $result = '';
2350
2351 $plugin = $this->get_submission_plugin_by_type($plugintype);
2352
2353 $text = $plugin->get_editor_text($editor, $submissionid);
2354 $format = $plugin->get_editor_format($editor, $submissionid);
2355
e5403f8c
DW
2356 $finaltext = file_rewrite_pluginfile_urls($text,
2357 'pluginfile.php',
2358 $this->get_context()->id,
2359 $component,
2360 $filearea,
2361 $submissionid);
2362 $params = array('overflowdiv' => true, 'context' => $this->get_context());
2363 $result .= format_text($finaltext, $format, $params);
bbd0e548
DW
2364
2365 if ($CFG->enableportfolios) {
2366 require_once($CFG->libdir . '/portfoliolib.php');
2367
2368 $button = new portfolio_add_button();
e5403f8c
DW
2369 $portfolioparams = array('cmid' => $this->get_course_module()->id,
2370 'sid' => $submissionid,
2371 'plugin' => $plugintype,
2372 'editor' => $editor,
2373 'area'=>$filearea);
2374 $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
bbd0e548
DW
2375 $fs = get_file_storage();
2376
e5403f8c
DW
2377 if ($files = $fs->get_area_files($this->context->id,
2378 $component,
2379 $filearea,
2380 $submissionid,
2381 'timemodified',
2382 false)) {
bbd0e548
DW
2383 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2384 } else {
2385 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2386 }
2387 $result .= $button->to_html();
2388 }
2389 return $result;
2390 }
2391
df211804 2392 /**
d9bfe3c5 2393 * Display a continue page after grading.
df211804 2394 *
d9bfe3c5 2395 * @param string $message - The message to display.
df211804
DW
2396 * @return string
2397 */
2398 protected function view_savegrading_result($message) {
2399 $o = '';
2400 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2401 $this->get_context(),
2402 $this->show_intro(),
2403 $this->get_course_module()->id,
2404 get_string('savegradingresult', 'assign')));
2405 $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2406 $message,
2407 $this->get_course_module()->id);
2408 $o .= $this->get_renderer()->render($gradingresult);
2409 $o .= $this->view_footer();
2410 return $o;
2411 }
bf78ebd6 2412 /**
d9bfe3c5 2413 * Display a continue page after quickgrading.
bf78ebd6 2414 *
d9bfe3c5 2415 * @param string $message - The message to display.
bf78ebd6
DW
2416 * @return string
2417 */
47f48152 2418 protected function view_quickgrading_result($message) {
bf78ebd6 2419 $o = '';
49d83b9d 2420 $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
bf78ebd6
DW
2421 $this->get_context(),
2422 $this->show_intro(),
2423 $this->get_course_module()->id,
2424 get_string('quickgradingresult', 'assign')));
cfdd3e5c 2425 $lastpage = optional_param('lastpage', null, PARAM_INT);
df211804
DW
2426 $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2427 $message,
cfdd3e5c
GF
2428 $this->get_course_module()->id,
2429 false,
2430 $lastpage);
e5403f8c 2431 $o .= $this->get_renderer()->render($gradingresult);
bf78ebd6
DW
2432 $o .= $this->view_footer();
2433 return $o;
2434 }
bbd0e548
DW
2435
2436 /**
e5403f8c 2437 * Display the page footer.
bbd0e548 2438 *
bf78ebd6 2439 * @return string
bbd0e548 2440 */
47f48152 2441 protected function view_footer() {
1be7aef2
MN
2442 // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2443 if (!PHPUNIT_TEST) {
2444 return $this->get_renderer()->render_footer();
2445 }
2446
2447 return '';
bbd0e548
DW
2448 }
2449
4a47008c
DW
2450 /**
2451 * Throw an error if the permissions to view this users submission are missing.
2452 *
2453 * @throws required_capability_exception
2454 * @return none
2455 */
2456 public function require_view_submission($userid) {
2457 if (!$this->can_view_submission($userid)) {
2458 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2459 }
2460 }
2461
2462 /**
2463 * Throw an error if the permissions to view grades in this assignment are missing.
2464 *
2465 * @throws required_capability_exception
2466 * @return none
2467 */
2468 public function require_view_grades() {
2469 if (!$this->can_view_grades()) {
2470 throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2471 }
2472 }
2473
2474 /**
2475 * Does this user have view grade or grade permission for this assignment?
2476 *
2477 * @return bool
2478 */
2479 public function can_view_grades() {
2480 // Permissions check.
2481 if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2482 return false;
2483 }
2484
2485 return true;
2486 }
2487
bbd0e548 2488 /**
e5403f8c 2489 * Does this user have grade permission for this assignment?
bbd0e548
DW
2490 *
2491 * @return bool
2492 */
5c386472 2493 public function can_grade() {
e5403f8c 2494 // Permissions check.
bbd0e548
DW
2495 if (!has_capability('mod/assign:grade', $this->context)) {
2496 return false;
2497 }
2498
2499 return true;
2500 }
2501
2502 /**
e5403f8c 2503 * Download a zip file of all assignment submissions.
bbd0e548 2504 *
df211804 2505 * @return string - If an error occurs, this will contain the error page.
bbd0e548 2506 */
47f48152 2507 protected function download_submissions() {
e5403f8c 2508 global $CFG, $DB;
bbd0e548 2509
d0d4796b 2510 // More efficient to load this here.
bbd0e548
DW
2511 require_once($CFG->libdir.'/filelib.php');
2512
4a47008c 2513 $this->require_view_grades();
76640b27 2514
d0d4796b 2515 // Load all users with submit.
4c4c7b3f 2516 $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
1ecb8044 2517 $this->show_only_active_users());
bbd0e548 2518
d0d4796b 2519 // Build a list of files to zip.
bbd0e548
DW
2520 $filesforzipping = array();
2521 $fs = get_file_storage();
2522
2523 $groupmode = groups_get_activity_groupmode($this->get_course_module());
d0d4796b
DW
2524 // All users.
2525 $groupid = 0;
bbd0e548
DW
2526 $groupname = '';
2527 if ($groupmode) {
2528 $groupid = groups_get_activity_group($this->get_course_module(), true);
2529 $groupname = groups_get_group_name($groupid).'-';
2530 }
2531
d0d4796b 2532 // Construct the zip file name.
e5403f8c
DW
2533 $filename = clean_filename($this->get_course()->shortname . '-' .
2534 $this->get_instance()->name . '-' .
2535 $groupname.$this->get_course_module()->id . '.zip');
bbd0e548 2536
d0d4796b
DW
2537 // Get all the files for each student.
2538 foreach ($students as $student) {
2539 $userid = $student->id;
bbd0e548 2540
7a2b911c 2541 if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
d0d4796b 2542 // Get the plugins to add their own files to the zip.
bbd0e548 2543
d0d4796b
DW
2544 $submissiongroup = false;
2545 $groupname = '';
2546 if ($this->get_instance()->teamsubmission) {
2547 $submission = $this->get_group_submission($userid, 0, false);
2548 $submissiongroup = $this->get_submission_group($userid);
21f77397
DW
2549 if ($submissiongroup) {
2550 $groupname = $submissiongroup->name . '-';
2551 } else {
2552 $groupname = get_string('defaultteam', 'assign') . '-';
2553 }
b473171a 2554 } else {
d0d4796b 2555 $submission = $this->get_user_submission($userid, false);
b473171a 2556 }
bbd0e548 2557
b473171a 2558 if ($this->is_blind_marking()) {
e5403f8c
DW
2559 $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2560 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2561 } else {
e5403f8c
DW
2562 $prefix = str_replace('_', ' ', $groupname . fullname($student));
2563 $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
b473171a 2564 }
bbd0e548 2565
d0d4796b
DW
2566 if ($submission) {
2567 foreach ($this->submissionplugins as $plugin) {
2568 if ($plugin->is_enabled() && $plugin->is_visible()) {
2406815b 2569 $pluginfiles = $plugin->get_files($submission, $student);
d0d4796b 2570 foreach ($pluginfiles as $zipfilename => $file) {
7a2b911c
DW
2571 $subtype = $plugin->get_subtype();
2572 $type = $plugin->get_type();
e5403f8c
DW
2573 $prefixedfilename = clean_filename($prefix .
2574 $subtype .
2575 '_' .
2576 $type .
2577 '_' .
2578 $zipfilename);
d0d4796b
DW
2579 $filesforzipping[$prefixedfilename] = $file;
2580 }
bbd0e548
DW
2581 }
2582 }
2583 }
bbd0e548 2584 }
d0d4796b 2585 }
afa3e637 2586 $result = '';
5c778358 2587 if (count($filesforzipping) == 0) {
afa3e637
DW
2588 $header = new assign_header($this->get_instance(),
2589 $this->get_context(),
2590 '',
2591 $this->get_course_module()->id,
2592 get_string('downloadall', 'assign'));
2593 $result .= $this->get_renderer()->render($header);
5c778358 2594 $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
afa3e637
DW
2595 $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2596 'action'=>'grading'));
2597 $result .= $this->get_renderer()->continue_button($url);
5c778358 2598 $result .= $this->view_footer();
5c778358 2599 } else if ($zipfile = $this->pack_files($filesforzipping)) {
1b90858f 2600 \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
d0d4796b
DW
2601 // Send file and delete after sending.
2602 send_temp_file($zipfile, $filename);
afa3e637 2603 // We will not get here - send_temp_file calls exit.
bbd0e548 2604 }
afa3e637 2605 return $result;
bbd0e548
DW
2606 }
2607
2608 /**
e5403f8c 2609 * Util function to add a message to the log.
bbd0e548 2610 *
c17e70e5
MN
2611 * @deprecated since 2.7 - Use new events system instead.
2612 * (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2613 *
bbd0e548
DW
2614 * @param string $action The current action
2615 * @param string $info A detailed description of the change. But no more than 255 characters.
2616 * @param string $url The url to the assign module instance.
caa06f4b
FM
2617 * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2618 * retrieve the arguments to use them with the new event system (Event 2).
2619 * @return void|array
bbd0e548 2620 */
caa06f4b 2621 public function add_to_log($action = '', $info = '', $url='', $return = false) {
bbd0e548
DW
2622 global $USER;
2623
2624 $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2625 if ($url != '') {
2626 $fullurl .= '&' . $url;
2627 }
2628
caa06f4b
FM
2629 $args = array(
2630 $this->get_course()->id,
2631 'assign',
2632 $action,
2633 $fullurl,
2634 $info,
666abe6e 2635 $this->get_course_module()->id
caa06f4b
FM
2636 );
2637
2638 if ($return) {
c17e70e5
MN
2639 // We only need to call debugging when returning a value. This is because the call to
2640 // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2641 debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
caa06f4b
FM
2642 return $args;
2643 }
2644 call_user_func_array('add_to_log', $args);
bbd0e548
DW
2645 }
2646
2cffef9f 2647 /**
e5403f8c 2648 * Lazy load the page renderer and expose the renderer to plugins.
49d83b9d 2649 *
2cffef9f
PC
2650 * @return assign_renderer
2651 */
23fffa2b 2652 public function get_renderer() {
2cffef9f
PC
2653 global $PAGE;
2654 if ($this->output) {
2655 return $this->output;
2656 }
2657 $this->output = $PAGE->get_renderer('mod_assign');
2658 return $this->output;
2659 }
bbd0e548
DW
2660
2661 /**
e5403f8c 2662 * Load the submission object for a particular user, optionally creating it if required.
bbd0e548 2663 *
12a1a0da
DW
2664 * For team assignments there are 2 submissions - the student submission and the team submission
2665 * All files are associated with the team submission but the status of the students contribution is
2666 * recorded separately.
2667 *
bbd0e548 2668 * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
e5403f8c 2669 * @param bool $create optional - defaults to false. If set to true a new submission object
9e3eee67 2670 * will be created in the database with the status set to "new".
df211804 2671 * @param int $attemptnumber - -1 means the latest attempt
bbd0e548
DW
2672 * @return stdClass The submission
2673 */
df211804 2674 public function get_user_submission($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2675 global $DB, $USER;
2676
2677 if (!$userid) {
2678 $userid = $USER->id;
2679 }
12a1a0da
DW
2680 // If the userid is not null then use userid.
2681 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
df211804
DW
2682 if ($attemptnumber >= 0) {
2683 $params['attemptnumber'] = $attemptnumber;
2684 }
2685
2686 // Only return the row with the highest attemptnumber.
2687 $submission = null;
2688 $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2689 if ($submissions) {
2690 $submission = reset($submissions);
2691 }
bbd0e548
DW
2692
2693 if ($submission) {
2694 return $submission;
2695 }
2696 if ($create) {
2697 $submission = new stdClass();
2698 $submission->assignment = $this->get_instance()->id;
2699 $submission->userid = $userid;
2700 $submission->timecreated = time();
2701 $submission->timemodified = $submission->timecreated;
9e3eee67 2702 $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
df211804
DW
2703 if ($attemptnumber >= 0) {
2704 $submission->attemptnumber = $attemptnumber;
2705 } else {
2706 $submission->attemptnumber = 0;
2707 }
1523f9e0
DW
2708 // Work out if this is the latest submission.
2709 $submission->latest = 0;
2710 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2711 if ($attemptnumber == -1) {
2712 // This is a new submission so it must be the latest.
2713 $submission->latest = 1;
2714 } else {
2715 // We need to work this out.
2716 $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
9e3eee67 2717 $latestsubmission = null;
1523f9e0
DW
2718 if ($result) {
2719 $latestsubmission = reset($result);
2720 }
9e3eee67 2721 if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
1523f9e0
DW
2722 $submission->latest = 1;
2723 }
2724 }
2725 if ($submission->latest) {
2726 // This is the case when we need to set latest to 0 for all the other attempts.
2727 $DB->set_field('assign_submission', 'latest', 0, $params);
2728 }
bbd0e548 2729 $sid = $DB->insert_record('assign_submission', $submission);
4781ff2e 2730 return $DB->get_record('assign_submission', array('id' => $sid));
bbd0e548
DW
2731 }
2732 return false;
2733 }
2734
2735 /**
e5403f8c 2736 * Load the submission object from it's id.
bbd0e548
DW
2737 *
2738 * @param int $submissionid The id of the submission we want
2739 * @return stdClass The submission
2740 */
47f48152 2741 protected function get_submission($submissionid) {
bbd0e548
DW
2742 global $DB;
2743
e5403f8c
DW
2744 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2745 return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
bbd0e548
DW
2746 }
2747
df211804
DW
2748 /**
2749 * This will retrieve a user flags object from the db optionally creating it if required.
2750 * The user flags was split from the user_grades table in 2.5.
2751 *
2752 * @param int $userid The user we are getting the flags for.
2753 * @param bool $create If true the flags record will be created if it does not exist
2754 * @return stdClass The flags record
2755 */
2756 public function get_user_flags($userid, $create) {
2757 global $DB, $USER;
2758
2759 // If the userid is not null then use userid.
2760 if (!$userid) {
2761 $userid = $USER->id;
2762 }
2763
2764 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2765
2766 $flags = $DB->get_record('assign_user_flags', $params);
2767
2768 if ($flags) {
2769 return $flags;
2770 }
2771 if ($create) {
2772 $flags = new stdClass();
2773 $flags->assignment = $this->get_instance()->id;
2774 $flags->userid = $userid;
2775 $flags->locked = 0;
2776 $flags->extensionduedate = 0;
f8d107b3
DM
2777 $flags->workflowstate = '';
2778 $flags->allocatedmarker = 0;
df211804
DW
2779
2780 // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2781 // This is because students only want to be notified about certain types of update (grades and feedback).
2782 $flags->mailed = 2;
2783
2784 $fid = $DB->insert_record('assign_user_flags', $flags);
2785 $flags->id = $fid;
2786 return $flags;
2787 }
2788 return false;
2789 }
2790
bbd0e548 2791 /**
e5403f8c 2792 * This will retrieve a grade object from the db, optionally creating it if required.
bbd0e548
DW
2793 *
2794 * @param int $userid The user we are grading
2795 * @param bool $create If true the grade will be created if it does not exist
df211804 2796 * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
bbd0e548
DW
2797 * @return stdClass The grade record
2798 */
df211804 2799 public function get_user_grade($userid, $create, $attemptnumber=-1) {
bbd0e548
DW
2800 global $DB, $USER;
2801
df211804 2802 // If the userid is not null then use userid.
bbd0e548
DW
2803 if (!$userid) {
2804 $userid = $USER->id;
2805 }
2806
df211804 2807 $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
e7af1926
DW
2808 if ($attemptnumber < 0) {
2809 // Make sure this grade matches the latest submission attempt.
2810 if ($this->get_instance()->teamsubmission) {
9e3eee67 2811 $submission = $this->get_group_submission($userid, 0, true);
e7af1926 2812 } else {
9e3eee67 2813 $submission = $this->get_user_submission($userid, true);
e7af1926
DW
2814 }
2815 if ($submission) {
2816 $attemptnumber = $submission->attemptnumber;
2817 }
2818 }
2819
df211804
DW
2820 if ($attemptnumber >= 0) {
2821 $params['attemptnumber'] = $attemptnumber;
2822 }
bbd0e548 2823
df211804
DW
2824 $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
2825
2826 if ($grades) {
2827 return reset($grades);
bbd0e548
DW
2828 }
2829 if ($create) {
2830 $grade = new stdClass();
2831 $grade->assignment = $this->get_instance()->id;
2832 $grade->userid = $userid;
2833 $grade->timecreated = time();
2834 $grade->timemodified = $grade->timecreated;
bbd0e548
DW
2835 $grade->grade = -1;
2836 $grade->grader = $USER->id;
df211804
DW
2837 if ($attemptnumber >= 0) {
2838 $grade->attemptnumber = $attemptnumber;
2839 }
d6c673ed 2840
bbd0e548
DW
2841 $gid = $DB->insert_record('assign_grades', $grade);
2842 $grade->id = $gid;
2843 return $grade;
2844 }
2845 return false;
2846 }
2847
2848 /**
e5403f8c 2849 * This will retrieve a grade object from the db.
bbd0e548
DW
2850 *
2851 * @param int $gradeid The id of the grade
2852 * @return stdClass The grade record
2853 */
47f48152 2854 protected function get_grade($gradeid) {
bbd0e548
DW
2855 global $DB;
2856
e5403f8c
DW
2857 $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
2858 return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
bbd0e548
DW
2859 }
2860
2861 /**
e5403f8c 2862 * Print the grading page for a single user submission.
bbd0e548
DW
2863 *
2864 * @param moodleform $mform
bbd0e548
DW
2865 * @return string
2866 */
d04557b3 2867 protected function view_single_grade_page($mform) {
bbd0e548
DW
2868 global $DB, $CFG;
2869
2870 $o = '';
e5403f8c 2871 $instance = $this->get_instance();
bbd0e548 2872
bbd0e548
DW
2873 require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2874
e5403f8c 2875 // Need submit permission to submit an assignment.
bbd0e548
DW
2876 require_capability('mod/assign:grade', $this->context);
2877
e5403f8c
DW
2878 $header = new assign_header($instance,
2879 $this->get_context(),
2880 false,
2881 $this->get_course_module()->id,
2882 get_string('grading', 'assign'));
2883 $o .= $this->get_renderer()->render($header);
bbd0e548 2884
df211804 2885 // If userid is passed - we are only grading a single student.
d04557b3
DW
2886 $rownum = required_param('rownum', PARAM_INT);
2887 $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
df211804
DW
2888 $userid = optional_param('userid', 0, PARAM_INT);
2889 $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
2890
d04557b3 2891 $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
df211804
DW
2892 if (!$userid) {
2893 if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
2894 $useridlist = $this->get_grading_userid_list();
2895 }
d04557b3 2896 $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
df211804
DW
2897 } else {
2898 $rownum = 0;
2899 $useridlist = array($userid);
bbd0e548 2900 }
d04557b3
DW
2901
2902 if ($rownum < 0 || $rownum > count($useridlist)) {
2903 throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
bbd0e548 2904 }
d04557b3 2905
bbd0e548
DW
2906 $last = false;
2907 $userid = $useridlist[$rownum];
2908 if ($rownum == count($useridlist) - 1) {
2909 $last = true;
2910 }
bbd0e548
DW
2911 $user = $DB->get_record('user', array('id' => $userid));
2912 if ($user) {
e5403f8c
DW
2913 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
2914 $usersummary = new assign_user_summary($user,
2915 $this->get_course()->id,
2916 $viewfullnames,
2917 $this->is_blind_marking(),
d08e6c31 2918 $this->get_uniqueid_for_user($user->id),
a69944eb 2919 get_extra_user_fields($this->get_context()),
1ecb8044 2920 !$this->is_active_user($userid));
e5403f8c 2921 $o .= $this->get_renderer()->render($usersummary);
bbd0e548 2922 }
df211804 2923 $submission = $this->get_user_submission($userid, false, $attemptnumber);
12a1a0da 2924 $submissiongroup = null;
12a1a0da
DW
2925 $teamsubmission = null;
2926 $notsubmitted = array();
e5403f8c 2927 if ($instance->teamsubmission) {
df211804 2928 $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
12a1a0da
DW
2929 $submissiongroup = $this->get_submission_group($userid);
2930 $groupid = 0;
2931 if ($submissiongroup) {
2932 $groupid = $submissiongroup->id;
2933 }
2934 $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
2935
2936 }
2937
df211804
DW
2938 // Get the requested grade.
2939 $grade = $this->get_user_grade($userid, false, $attemptnumber);
2940 $flags = $this->get_user_flags($userid, false);
bbd0e548 2941 if ($this->can_view_submission($userid)) {
df211804 2942 $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
9e795179 2943 $extensionduedate = null;
df211804
DW
2944 if ($flags) {
2945 $extensionduedate = $flags->extensionduedate;
9e795179 2946 }
88cfe469 2947 $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
12a1a0da 2948 $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
9e795179 2949
e5403f8c
DW
2950 $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
2951 $instance->alwaysshowdescription,
2952 $submission,
2953 $instance->teamsubmission,
2954 $teamsubmission,
2955 $submissiongroup,
2956 $notsubmitted,
2957 $this->is_any_submission_plugin_enabled(),
2958 $gradelocked,
2959 $this->is_graded($userid),
2960 $instance->duedate,
2961 $instance->cutoffdate,
2962 $this->get_submission_plugins(),
2963 $this->get_return_action(),
2964 $this->get_return_params(),
2965 $this->get_course_module()->id,
2966 $this->get_course()->id,
2967 assign_submission_status::GRADER_VIEW,
2968 $showedit,
19195b60 2969 false,
e5403f8c
DW
2970 $viewfullnames,
2971 $extensionduedate,
2972 $this->get_context(),
2973 $this->is_blind_marking(),
df211804
DW
2974 '',
2975 $instance->attemptreopenmethod,
2976 $instance->maxattempts);
e5403f8c 2977 $o .= $this->get_renderer()->render($submissionstatus);
bbd0e548 2978 }
df211804 2979
bbd0e548
DW
2980 if ($grade) {
2981 $data = new stdClass();
e5403f8c
DW
2982 if ($grade->grade !== null && $grade->grade >= 0) {
2983 $data->grade = format_float($grade->grade, 2);
bbd0e548 2984 }
f8d107b3
DM
2985 if (!empty($flags->workflowstate)) {
2986 $data->workflowstate = $flags->workflowstate;
2987 }
2988 if (!empty($flags->allocatedmarker)) {
2989 $data->allocatedmarker = $flags->allocatedmarker;
2990 }
bbd0e548
DW
2991 } else {
2992 $data = new stdClass();
2993 $data->grade = '';
2994 }
df211804
DW
2995 // Warning if required.
2996 $allsubmissions = $this->get_all_submissions($userid);
2997
2998 if ($attemptnumber != -1) {
2999 $params = array('attemptnumber'=>$attemptnumber + 1,
3000 'totalattempts'=>count($allsubmissions));
3001 $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
3002 $o .= $this->get_renderer()->notification($message);
3003 }
bbd0e548 3004
e5403f8c 3005 // Now show the grading form.
bbd0e548 3006 if (!$mform) {
df211804
DW
3007 $pagination = array('rownum'=>$rownum,
3008 'useridlistid'=>$useridlistid,
3009 'last'=>$last,
3010 'userid'=>optional_param('userid', 0, PARAM_INT),
3011 'attemptnumber'=>$attemptnumber);
12a1a0da
DW
3012 $formparams = array($this, $data, $pagination);
3013 $mform = new mod_assign_grade_form(null,
3014 $formparams,
3015 'post',
3016 '',
3017 array('class'=>'gradeform'));
bbd0e548 3018 }
df211804 3019 $o .= $this->get_renderer()->heading(get_string('grade'), 3);
e5403f8c 3020 $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
bbd0e548 3021
df211804
DW
3022 if (count($allsubmissions) > 1 && $attemptnumber == -1) {
3023 $allgrades = $this->get_all_grades($userid);
3024 $history = new assign_attempt_history($allsubmissions,
3025 $allgrades,
3026 $this->get_submission_plugins(),
3027 $this->get_feedback_plugins(),
3028 $this->get_course_module()->id,
3029 $this->get_return_action(),
3030 $this->get_return_params(),
99758819
DW
3031 true,
3032 $useridlistid,
3033 $rownum);
df211804
DW
3034
3035 $o .= $this->get_renderer()->render($history);
3036 }
3037
1b90858f 3038 \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
bbd0e548
DW
3039
3040 $o .= $this->view_footer();
3041 return $o;
3042 }
3043
b473171a 3044 /**
e5403f8c 3045 * Show a confirmation page to make sure they want to release student identities.
b473171a
DW
3046 *
3047 * @return string
3048 */
47f48152 3049 protected function view_reveal_identities_confirm() {
b473171a
DW
3050 require_capability('mod/assign:revealidentities', $this->get_context());
3051
3052 $o = '';
e5403f8c
DW
3053 $header = new assign_header($this->get_instance(),
3054 $this->get_context(),
3055 false,
3056 $this->get_course_module()->id);
3057 $o .= $this->get_renderer()->render($header);
3058
3059 $urlparams = array('id'=>$this->get_course_module()->id,
3060 'action'=>'revealidentitiesconfirm',
3061 'sesskey'=>sesskey());
3062 $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
3063
3064 $urlparams = array('id'=>$this->get_course_module()->id,
3065 'action'=>'grading');
3066 $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
3067
3068 $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
3069 $confirmurl,
3070 $cancelurl);
b473171a 3071 $o .= $this->view_footer();
b06decdd 3072
1b90858f 3073 \mod_assign\event\reveal_identities_confirmation_page_viewed::create_from_assign($this)->trigger();
b06decdd 3074
b473171a
DW
3075 return $o;
3076 }
3077
bbd0e548
DW
3078 /**
3079 * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
3080 *
3081 * @return string
3082 */
47f48152 3083 protected function view_return_links() {
e5403f8c
DW
3084 $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
3085 $returnparams = optional_param('returnparams', '', PARAM_TEXT);
bbd0e548
DW
3086
3087 $params = array();
d04557b3 3088 $returnparams = str_replace('&amp;', '&', $returnparams);
bbd0e548 3089 parse_str($returnparams, $params);
e5403f8c
DW
3090 $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
3091 $params = array_merge($newparams, $params);
bbd0e548 3092
e5403f8c
DW
3093 $url = new moodle_url('/mod/assign/view.php', $params);
3094 return $this->get_renderer()->single_button($url, get_string('back'), 'get');
bbd0e548
DW
3095 }
3096
3097 /**
e5403f8c 3098 * View the grading table of all submissions for this assignment.
bbd0e548
DW
3099 *
3100 * @return string
3101 */
47f48152 3102 protected function view_grading_table() {
bbd0e548 3103 global $USER, $CFG;
e5403f8c
DW
3104
3105 // Include grading options form.
bbd0e548 3106 require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
bf78ebd6 3107 require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
bbd0e548
DW
3108 require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3109 $o = '';
e5403f8c 3110 $cmid = $this->get_course_module()->id;
bbd0e548
DW
3111
3112 $links = array();
bbd0e548
DW
3113 if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
3114 has_capability('moodle/grade:viewall', $this->get_course_context())) {
a1e54f4d 3115 $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
bbd0e548
DW
3116 $links[$gradebookurl] = get_string('viewgradebook', 'assign');
3117 }
9b7a5f65 3118 if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
e5403f8c 3119 $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
bbd0e548
DW
3120 $links[$downloadurl] = get_string('downloadall', 'assign');
3121 }
e5403f8c
DW
3122 if ($this->is_blind_marking() &&
3123 has_capability('mod/assign:revealidentities', $this->get_context())) {
3124 $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
b473171a
DW
3125 $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
3126 }
df47b77f
DW
3127 foreach ($this->get_feedback_plugins() as $plugin) {
3128 if ($plugin->is_enabled() && $plugin->is_visible()) {
3129 foreach ($plugin->get_grading_actions() as $action => $description) {
3130 $url = '/mod/assign/view.php' .
e5403f8c 3131 '?id=' . $cmid .
df47b77f
DW
3132 '&plugin=' . $plugin->get_type() .
3133 '&pluginsubtype=assignfeedback' .
3134 '&action=viewpluginpage&pluginaction=' . $action;
3135 $links[$url] = $description;
3136 }
3137 }
3138 }
a1e54f4d 3139
0d416e9c
DW
3140 // Sort links alphabetically based on the link description.
3141 core_collator::asort($links);
3142
a1e54f4d 3143 $gradingactions = new url_select($links);
49f0c151 3144 $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
bbd0e548 3145
bf78ebd6
DW
3146 $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3147
79cf14b3 3148 $perpage = (int) get_user_preferences('assign_perpage', 10);
bbd0e548 3149 $filter = get_user_preferences('assign_filter', '');
f8d107b3
DM
3150 $markerfilter = get_user_preferences('assign_markerfilter', '');
3151 $workflowfilter = get_user_preferences('assign_workflowfilter', '');
bf78ebd6 3152 $controller = $gradingmanager->get_active_controller();
4a47008c 3153 $showquickgrading = empty($controller) && $this->can_grade();
bf78ebd6 3154 $quickgrading = get_user_preferences('assign_quickgrading', false);
4c4c7b3f 3155 $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
bf78ebd6 3156
5a96486b
DM
3157 $markingallocation = $this->get_instance()->markingworkflow &&
3158 $this->get_instance()->markingallocation &&
f8d107b3
DM
3159 has_capability('mod/assign:manageallocations', $this->context);
3160 // Get markers to use in drop lists.